@atlaskit/editor-plugin-code-block
Version:
Code block plugin for @atlaskit/editor-core
139 lines (136 loc) • 6.7 kB
JavaScript
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;