@atlaskit/editor-plugin-block-type
Version:
BlockType plugin for @atlaskit/editor-core
166 lines • 9.1 kB
JavaScript
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;
}
}
});
};