UNPKG

@atlaskit/editor-plugin-code-block

Version:

Code block plugin for @atlaskit/editor-core

166 lines (164 loc) 9.13 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { updateCodeBlockWrappedStateNodeKeys } from '@atlaskit/editor-common/code-block'; import { blockTypeMessages } from '@atlaskit/editor-common/messages'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { createSelectionClickHandler } from '@atlaskit/editor-common/selection'; import { findCodeBlock } from '@atlaskit/editor-common/transforms'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { ignoreFollowingMutations, resetShouldIgnoreFollowingMutations } from '../editor-commands'; import { codeBlockNodeView } from '../nodeviews/code-block'; import { codeBlockClassNames } from '../ui/class-names'; import { ACTIONS } from './actions'; import { generateInitialDecorations, updateCodeBlockDecorations, updateDecorationSetWithWordWrappedDecorator } from './decorators'; import { pluginKey } from './plugin-key'; import { getAllChangedCodeBlocksInTransaction } from './utils'; export var createPlugin = function createPlugin(_ref) { var _ref$useLongPressSele = _ref.useLongPressSelection, useLongPressSelection = _ref$useLongPressSele === void 0 ? false : _ref$useLongPressSele, getIntl = _ref.getIntl, _ref$allowComposition = _ref.allowCompositionInputOverride, allowCompositionInputOverride = _ref$allowComposition === void 0 ? false : _ref$allowComposition, api = _ref.api; var handleDOMEvents = { click: function click() { var _api$core, _api$interaction; // Set hasHadInteraction to true on any click of code blocks, as clicks // on code blocks to not propagate to editor-level click handlers api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$interaction = api.interaction) === null || _api$interaction === void 0 ? void 0 : _api$interaction.commands.handleInteraction); } }; // ME-1599: Composition on mobile was causing the DOM observer to mutate the code block // incorrecly and lose content when pressing enter in the middle of a code block line. if (allowCompositionInputOverride) { handleDOMEvents.beforeinput = function (view, event) { var keyEvent = event; var eventInputType = keyEvent.inputType; var eventText = keyEvent.data; var browser = getBrowserInfo(); if (browser.ios && event.composed && // insertParagraph will be the input type when the enter key is pressed. eventInputType === 'insertParagraph' && findCodeBlock(view.state, view.state.selection)) { event.preventDefault(); return true; } else if (browser.android && event.composed && eventInputType === 'insertCompositionText' && eventText[(eventText === null || eventText === void 0 ? void 0 : eventText.length) - 1] === '\n' && findCodeBlock(view.state, view.state.selection)) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var resultingText = event.target.outerText + '\n'; if (resultingText.endsWith(eventText)) { // End of paragraph setTimeout(function () { view.someProp('handleKeyDown', function (f) { return f(view, new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter' })); }); }, 0); } else { // Middle of paragraph, end of line ignoreFollowingMutations(view.state, view.dispatch); } return true; } if (browser.android) { resetShouldIgnoreFollowingMutations(view.state, view.dispatch); } return false; }; } return new SafePlugin({ state: { init: function init(_, state) { var node = findCodeBlock(state, state.selection); var initialDecorations = // Disabled when using advanced code block advanced since it interfers with styling // Long term we will deprecate the code block plugin and move all the related logic to // the advanced plugin // @ts-expect-error Code block advanced cannot depend on code block (api === null || api === void 0 ? void 0 : api.codeBlockAdvanced) !== undefined && expValEquals('platform_editor_code_block_fold_gutter', 'isEnabled', true) ? [] : generateInitialDecorations(state); return { pos: node ? node.pos : null, contentCopied: false, isNodeSelected: false, shouldIgnoreFollowingMutations: false, decorations: DecorationSet.create(state.doc, initialDecorations) }; }, apply: function apply(tr, pluginState, _oldState, newState) { var meta = tr.getMeta(pluginKey); if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_IS_WRAPPED) { var node = findCodeBlock(newState, tr.selection); return _objectSpread(_objectSpread({}, pluginState), {}, { decorations: updateDecorationSetWithWordWrappedDecorator(pluginState.decorations, tr, node) }); } if (tr.docChanged) { var _node = findCodeBlock(newState, tr.selection); // Updates mapping position of all existing decorations to new positions // specifically used for updating word wrap node decorators (does not cover drag & drop, validateWordWrappedDecorators does). var updatedDecorationSet = pluginState.decorations.map(tr.mapping, tr.doc); var codeBlockNodes = getAllChangedCodeBlocksInTransaction(tr); if (codeBlockNodes) { updateCodeBlockWrappedStateNodeKeys(codeBlockNodes, _oldState); // Disabled when using advanced code block for performance reasons // @ts-expect-error Code block advanced cannot depend on code block if ((api === null || api === void 0 ? void 0 : api.codeBlockAdvanced) === undefined) { updatedDecorationSet = updateCodeBlockDecorations(tr, codeBlockNodes, updatedDecorationSet); } } var newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, { pos: _node ? _node.pos : null, isNodeSelected: tr.selection instanceof NodeSelection, decorations: updatedDecorationSet }); return newPluginState; } if (tr.selectionSet) { var _node2 = findCodeBlock(newState, tr.selection); var _newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, { pos: _node2 ? _node2.pos : null, isNodeSelected: tr.selection instanceof NodeSelection }); return _newPluginState; } if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_COPIED_TO_CLIPBOARD) { return _objectSpread(_objectSpread({}, pluginState), {}, { contentCopied: meta.data }); } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS) { return _objectSpread(_objectSpread({}, pluginState), {}, { shouldIgnoreFollowingMutations: meta.data }); } return pluginState; } }, key: pluginKey, props: { decorations: function decorations(state) { return pluginKey.getState(state).decorations || DecorationSet.empty; }, nodeViews: { codeBlock: function codeBlock(node, view, getPos) { var _getIntl = getIntl(), formatMessage = _getIntl.formatMessage; var formattedAriaLabel = formatMessage(blockTypeMessages.codeblock); return codeBlockNodeView(node, view, getPos, formattedAriaLabel, api); } }, handleClickOn: createSelectionClickHandler(['codeBlock'], function (target) { return !!(target.classList.contains(codeBlockClassNames.lineNumberWidget) || target.classList.contains(codeBlockClassNames.gutter)); }, { useLongPressSelection: useLongPressSelection }), handleDOMEvents: handleDOMEvents } }); };