UNPKG

@atlaskit/editor-plugin-block-type

Version:

BlockType plugin for @atlaskit/editor-core

166 lines 9.1 kB
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { BLOCK_QUOTE, CODE_BLOCK, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6, HEADINGS_BY_LEVEL, NORMAL_TEXT, OTHER, PANEL, TEXT_BLOCK_TYPES, WRAPPER_BLOCK_TYPES, getBlockTypesInDropdown, SMALL_TEXT } from './block-types'; import { setHeadingWithAnalytics, setNormalTextWithAnalytics, setSmallTextWithAnalytics } from './commands/block-type'; import { HEADING_KEYS, HEADING_NUMPAD_KEYS, KEY_7 } from './consts'; import { areBlockTypesDisabled, checkFormattingIsPresent, hasBlockQuoteInOptions } from './utils'; const blockTypeForNode = (node, schema) => { if (node.type === schema.nodes.heading) { const maybeNode = HEADINGS_BY_LEVEL[node.attrs['level']]; if (maybeNode) { return maybeNode; } } else if (node.marks.some(m => m.type.name === 'fontSize' && m.attrs.fontSize === 'small') && expValEquals('platform_editor_small_font_size', 'isEnabled', true)) { return SMALL_TEXT; } else if (node.type === schema.nodes.paragraph) { return NORMAL_TEXT; } else if (node.type === schema.nodes.blockquote) { return BLOCK_QUOTE; } return OTHER; }; const isBlockTypeSchemaSupported = (blockType, state) => { switch (blockType) { case NORMAL_TEXT: return !!state.schema.nodes.paragraph; case HEADING_1: case HEADING_2: case HEADING_3: case HEADING_4: case HEADING_5: case HEADING_6: return !!state.schema.nodes.heading; case SMALL_TEXT: return !!state.schema.marks.fontSize && expValEquals('platform_editor_small_font_size', 'isEnabled', true); case BLOCK_QUOTE: return !!state.schema.nodes.blockquote; case CODE_BLOCK: return !!state.schema.nodes.codeBlock; case PANEL: return !!state.schema.nodes.panel; } return; }; const detectBlockType = (availableBlockTypes, state) => { // Before a document is loaded, there is no selection. if (!state.selection) { return NORMAL_TEXT; } let blockType; const { $from, $to } = state.selection; state.doc.nodesBetween($from.pos, $to.pos, node => { const nodeBlockType = availableBlockTypes.filter(blockType => blockType === blockTypeForNode(node, state.schema)); if (nodeBlockType.length > 0) { if (!blockType) { blockType = nodeBlockType[0]; } else if (blockType !== OTHER && blockType !== nodeBlockType[0]) { blockType = OTHER; } return false; } }); return blockType || OTHER; }; const autoformatHeading = (headingLevel, editorAnalyticsApi) => { if (headingLevel === 0) { return setNormalTextWithAnalytics(INPUT_METHOD.FORMATTING, editorAnalyticsApi); } return setHeadingWithAnalytics(headingLevel, INPUT_METHOD.FORMATTING, editorAnalyticsApi); }; export const pluginKey = new PluginKey('blockTypePlugin'); export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph, includeBlockQuoteAsTextstyleOption, allowFontSize) => { var _editorAPI$analytics; const editorAnalyticsApi = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$analytics = editorAPI.analytics) === null || _editorAPI$analytics === void 0 ? void 0 : _editorAPI$analytics.actions; let altKeyLocation = 0; return new SafePlugin({ appendTransaction(_transactions, _oldState, newState) { if (lastNodeMustBeParagraph) { const pos = newState.doc.resolve(newState.doc.content.size - 1); const lastNode = pos.node(1); const { paragraph } = newState.schema.nodes; if (lastNode && lastNode.isBlock && lastNode.type !== paragraph) { return newState.tr.insert(newState.doc.content.size, newState.schema.nodes.paragraph.create()); } } }, state: { init(_config, state) { const availableBlockTypes = TEXT_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state)); const availableWrapperBlockTypes = WRAPPER_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state)); const BLOCK_TYPES_IN_DROPDOWN = getBlockTypesInDropdown(includeBlockQuoteAsTextstyleOption); const availableBlockTypesInDropdown = BLOCK_TYPES_IN_DROPDOWN.filter(blockType => isBlockTypeSchemaSupported(blockType, state)); const formattingIsPresent = hasBlockQuoteInOptions(availableBlockTypesInDropdown) ? checkFormattingIsPresent(state) : undefined; return { currentBlockType: detectBlockType(availableBlockTypesInDropdown, state), blockTypesDisabled: areBlockTypesDisabled(state, allowFontSize), availableBlockTypes, availableWrapperBlockTypes, availableBlockTypesInDropdown, formattingIsPresent }; }, apply(_tr, oldPluginState, _oldState, newState) { const newPluginState = { ...oldPluginState, currentBlockType: detectBlockType(oldPluginState.availableBlockTypesInDropdown, newState), blockTypesDisabled: areBlockTypesDisabled(newState, allowFontSize), formattingIsPresent: hasBlockQuoteInOptions(oldPluginState.availableBlockTypesInDropdown) ? checkFormattingIsPresent(newState) : undefined }; if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled || newPluginState.formattingIsPresent !== oldPluginState.formattingIsPresent) { dispatch(pluginKey, newPluginState); } return newPluginState; } }, key: pluginKey, props: { /** * As we only want the left alt key to work for headings shortcuts on Windows * we can't use prosemirror-keymap and need to handle these shortcuts specially * Shortcut on Mac: Cmd-Opt-{heading level} * Shortcut on Windows: Ctrl-LeftAlt-{heading level} */ handleKeyDown: (view, event) => { let headingLevel = HEADING_KEYS.indexOf(event.keyCode); if (headingLevel === -1) { // Check for numpad keys if not found in digits row headingLevel = HEADING_NUMPAD_KEYS.indexOf(event.keyCode); } const browser = getBrowserInfo(); if (headingLevel > -1 && event.altKey) { if (browser.mac && event.metaKey) { var _editorAPI$core$actio, _editorAPI$core; return (_editorAPI$core$actio = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$core = editorAPI.core) === null || _editorAPI$core === void 0 ? void 0 : _editorAPI$core.actions.execute(autoformatHeading(headingLevel, editorAnalyticsApi))) !== null && _editorAPI$core$actio !== void 0 ? _editorAPI$core$actio : false; } else if (!browser.mac && event.ctrlKey && altKeyLocation !== event.DOM_KEY_LOCATION_RIGHT) { var _editorAPI$core$actio2, _editorAPI$core2; return (_editorAPI$core$actio2 = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$core2 = editorAPI.core) === null || _editorAPI$core2 === void 0 ? void 0 : _editorAPI$core2.actions.execute(autoformatHeading(headingLevel, editorAnalyticsApi))) !== null && _editorAPI$core$actio2 !== void 0 ? _editorAPI$core$actio2 : false; } } else if (event.keyCode === KEY_7 && event.altKey && expValEquals('platform_editor_small_font_size', 'isEnabled', true)) { if (browser.mac && event.metaKey) { var _editorAPI$core$actio3, _editorAPI$core3; return (_editorAPI$core$actio3 = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$core3 = editorAPI.core) === null || _editorAPI$core3 === void 0 ? void 0 : _editorAPI$core3.actions.execute(setSmallTextWithAnalytics(INPUT_METHOD.SHORTCUT, editorAnalyticsApi))) !== null && _editorAPI$core$actio3 !== void 0 ? _editorAPI$core$actio3 : false; } else if (!browser.mac && event.ctrlKey && altKeyLocation !== event.DOM_KEY_LOCATION_RIGHT) { var _editorAPI$core$actio4, _editorAPI$core4; return (_editorAPI$core$actio4 = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$core4 = editorAPI.core) === null || _editorAPI$core4 === void 0 ? void 0 : _editorAPI$core4.actions.execute(setSmallTextWithAnalytics(INPUT_METHOD.SHORTCUT, editorAnalyticsApi))) !== null && _editorAPI$core$actio4 !== void 0 ? _editorAPI$core$actio4 : false; } } else if (event.key === 'Alt') { // event.location is for the current key only; when a user hits Ctrl-Alt-1 the // location refers to the location of the '1' key // We store the location of the Alt key when it is hit to check against later altKeyLocation = event.location; } else if (!event.altKey) { altKeyLocation = 0; } return false; } } }); };