UNPKG

@atlaskit/editor-plugin-text-formatting

Version:

Text-formatting plugin for @atlaskit/editor-core

160 lines (159 loc) 6.61 kB
// TODO: ED-26962 - Ideally this should use the custom toggleMark function from @atlaskit/editor-common so we also disable the options when selecting inline nodes but it disables the marks when the selection is empty at this point in time which is undesirable // import { toggleMark } from '@atlaskit/editor-common/mark'; import { moveLeft as keymapMoveLeft, moveRight as keymapMoveRight } from '@atlaskit/editor-common/keymaps'; import { anyMarkActive, wholeSelectionHasMarks } from '@atlaskit/editor-common/mark'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { shallowEqual } from '@atlaskit/editor-common/utils'; import { toggleMark } from '@atlaskit/editor-prosemirror/commands'; import { MarkType } from '@atlaskit/editor-prosemirror/model'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { createInlineCodeFromTextInputWithAnalytics } from '../editor-commands/text-formatting'; // Ignored via go/ees005 // eslint-disable-next-line import/no-namespace import * as commands from '../editor-commands/text-formatting'; import { pluginKey } from './plugin-key'; const isSelectionInlineCursor = selection => { if (selection instanceof NodeSelection) { return true; } return false; }; const checkNodeSelection = (mark, editorState, type) => { const selection = editorState.selection; if (isSelectionInlineCursor(selection)) { return false; } return toggleMark(mark, { type: type })(editorState); }; const getTextFormattingState = (editorState, _editorAnalyticsAPI) => { const { em, code, strike, strong, subsup, underline } = editorState.schema.marks; const state = { isInitialised: true }; const showOnlyCommonMarks = expValEquals('platform_editor_controls', 'cohort', 'variant1'); if (showOnlyCommonMarks) { // Code marks will disable all other formatting options when they are included in a // selection but (for now) we do not want to make it behave differently in regards to which // toolbar items are highlighted on selection. We need to track code in selection seperately // to ensure all other formatting options are disabled appropriately. if (code) { state.codeInSelection = anyMarkActive(editorState, code.create()); } const marks = [[code, 'code'], [em, 'em'], [strike, 'strike'], [strong, 'strong'], [underline, 'underline'], [subsup === null || subsup === void 0 ? void 0 : subsup.create({ type: 'sub' }), 'subscript'], [subsup === null || subsup === void 0 ? void 0 : subsup.create({ type: 'sup' }), 'superscript']].filter(([mark]) => mark); const marksToName = new Map(marks); const activeMarks = wholeSelectionHasMarks(editorState, Array.from(marksToName.keys())); for (const [mark, markName] of marks) { const active = activeMarks.get(mark); if (active !== undefined) { state[`${markName}Active`] = active; } state[`${markName}Disabled`] = // Disable when code is active, except for code itself which should not be disabled // when code is in selection 😅 state.codeInSelection && markName !== 'code' ? true : !checkNodeSelection(mark instanceof MarkType ? mark : mark.type, editorState); } return state; } if (code) { state.codeActive = anyMarkActive(editorState, code.create()); state.codeDisabled = !checkNodeSelection(code, editorState); } if (em) { state.emActive = anyMarkActive(editorState, em); state.emDisabled = state.codeActive ? true : !checkNodeSelection(em, editorState); } if (strike) { state.strikeActive = anyMarkActive(editorState, strike); state.strikeDisabled = state.codeActive ? true : !checkNodeSelection(strike, editorState); } if (strong) { state.strongActive = anyMarkActive(editorState, strong); state.strongDisabled = state.codeActive ? true : !checkNodeSelection(strong, editorState); } if (subsup) { const subMark = subsup.create({ type: 'sub' }); const supMark = subsup.create({ type: 'sup' }); state.subscriptActive = anyMarkActive(editorState, subMark); state.superscriptActive = anyMarkActive(editorState, supMark); state.subscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sub'); state.superscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sup'); } if (underline) { state.underlineActive = anyMarkActive(editorState, underline); state.underlineDisabled = state.codeActive ? true : !checkNodeSelection(underline, editorState); } return state; }; export const plugin = (dispatch, editorAnalyticsAPI) => new SafePlugin({ state: { init(_config, state) { return getTextFormattingState(state, editorAnalyticsAPI); }, apply(_tr, pluginState, _oldState, newState) { const state = getTextFormattingState(newState, editorAnalyticsAPI); if (!shallowEqual(pluginState, state)) { dispatch(pluginKey, state); return state; } return pluginState; } }, key: pluginKey, props: { handleKeyDown(view, event) { var _pluginKey$getState; const { state, dispatch } = view; if (event.key === keymapMoveRight.common && !event.metaKey) { return commands.moveRight()(state, dispatch); } else if (event.key === keymapMoveLeft.common && !event.metaKey) { return commands.moveLeft()(state, dispatch); } else if (event.key === 'u' && event.metaKey && (_pluginKey$getState = pluginKey.getState(state)) !== null && _pluginKey$getState !== void 0 && _pluginKey$getState.underlineDisabled && editorExperiment('platform_editor_controls', 'variant1')) { // This is a workaround for browser behaviour with cmd+u (in Chrome only) where the underline mark being applied around the selection event.preventDefault(); } return false; }, handleTextInput(view, from, to, text) { const { state, dispatch } = view; const { schema, selection: { $from: { parent: { type: parentNodeType } } } } = state; if (parentNodeType.allowsMarkType(schema.marks.code)) { return createInlineCodeFromTextInputWithAnalytics(editorAnalyticsAPI)(from, to, text)(state, dispatch); } return false; } } });