UNPKG

@atlaskit/editor-plugin-code-block

Version:

Code block plugin for @atlaskit/editor-core

139 lines (136 loc) 6.7 kB
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { filterCommand as filter } from '@atlaskit/editor-common/utils'; import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap'; import { TextSelection } from '@atlaskit/editor-prosemirror/state'; import { setTextSelection } from '@atlaskit/editor-prosemirror/utils'; import { getAutoClosingBracketInfo, shouldAutoCloseBracket } from './ide-ux/bracket-handling'; import { indent, insertIndent, insertNewlineWithIndent, outdent } from './ide-ux/commands'; import { getEndOfCurrentLine, getLineInfo, getStartOfCurrentLine, isCursorInsideCodeBlock, isSelectionEntirelyInsideCodeBlock } from './ide-ux/line-handling'; import { isClosingCharacter, isCursorBeforeClosingCharacter } from './ide-ux/paired-character-handling'; import { getAutoClosingQuoteInfo, shouldAutoCloseQuote } from './ide-ux/quote-handling'; import { getCursor } from './utils'; const ideUX = pluginInjectionApi => { var _pluginInjectionApi$a; const editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions; return new SafePlugin({ props: { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params handleTextInput(view, from, to, text) { var _pluginInjectionApi$c; const { state, dispatch } = view; const compositionPluginState = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.composition) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.sharedState.currentState(); if (isCursorInsideCodeBlock(state) && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing)) { const beforeText = getStartOfCurrentLine(state).text; const afterText = getEndOfCurrentLine(state).text; // If text is a closing bracket/quote and we've already inserted it, move the selection after if (isCursorBeforeClosingCharacter(afterText) && isClosingCharacter(text) && afterText.startsWith(text)) { dispatch(setTextSelection(to + text.length)(state.tr)); return true; } // Automatically add right-hand side bracket when user types the left bracket if (shouldAutoCloseBracket(beforeText, afterText)) { const { left, right } = getAutoClosingBracketInfo(beforeText + text, afterText); if (left && right) { const bracketPair = state.schema.text(text + right); const tr = state.tr.replaceWith(from, to, bracketPair); dispatch(setTextSelection(from + text.length)(tr)); return true; } } // Automatically add closing quote when user types a starting quote if (shouldAutoCloseQuote(beforeText, afterText)) { const { left: leftQuote, right: rightQuote } = getAutoClosingQuoteInfo(beforeText + text, afterText); if (leftQuote && rightQuote) { const quotePair = state.schema.text(text + rightQuote); const tr = state.tr.replaceWith(from, to, quotePair); dispatch(setTextSelection(from + text.length)(tr)); return true; } } } return false; }, handleKeyDown: keydownHandler({ Backspace: (state, dispatch) => { if (isCursorInsideCodeBlock(state)) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const $cursor = getCursor(state.selection); const beforeText = getStartOfCurrentLine(state).text; const afterText = getEndOfCurrentLine(state).text; const { left: leftBracket, right: rightBracket, hasTrailingMatchingBracket } = getAutoClosingBracketInfo(beforeText, afterText); if (leftBracket && rightBracket && hasTrailingMatchingBracket && dispatch) { dispatch(state.tr.delete($cursor.pos - leftBracket.length, $cursor.pos + rightBracket.length)); return true; } const { left: leftQuote, right: rightQuote, hasTrailingMatchingQuote } = getAutoClosingQuoteInfo(beforeText, afterText); if (leftQuote && rightQuote && hasTrailingMatchingQuote && dispatch) { dispatch(state.tr.delete($cursor.pos - leftQuote.length, $cursor.pos + rightQuote.length)); return true; } const { indentToken: { size, token }, indentText } = getLineInfo(beforeText); if (beforeText === indentText) { if (indentText.endsWith(token.repeat(size)) && dispatch) { dispatch(state.tr.delete($cursor.pos - (size - indentText.length % size || size), $cursor.pos)); return true; } } } return false; }, Enter: filter(isSelectionEntirelyInsideCodeBlock, insertNewlineWithIndent), 'Mod-]': filter(isSelectionEntirelyInsideCodeBlock, indent(editorAnalyticsAPI)), 'Mod-[': filter(isSelectionEntirelyInsideCodeBlock, outdent(editorAnalyticsAPI)), Tab: filter(isSelectionEntirelyInsideCodeBlock, (state, dispatch) => { if (!dispatch) { return false; } if (isCursorInsideCodeBlock(state)) { return insertIndent(state, dispatch); } return indent(editorAnalyticsAPI)(state, dispatch); }), 'Shift-Tab': filter(isSelectionEntirelyInsideCodeBlock, outdent(editorAnalyticsAPI)), 'Mod-a': (state, dispatch) => { if (isSelectionEntirelyInsideCodeBlock(state)) { const { $from, $to } = state.selection; const isFullCodeBlockSelection = $from.parentOffset === 0 && $to.parentOffset === $to.parent.nodeSize - 2; if (!isFullCodeBlockSelection && dispatch) { dispatch(state.tr.setSelection(TextSelection.create(state.doc, $from.start(), $to.end()))); return true; } } return false; } }) } }); }; export default ideUX;