UNPKG

@lobehub/editor

Version:

A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.

736 lines (702 loc) 29.9 kB
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import { $getEndOfCodeInLine, $getFirstCodeNodeOfLine, $getLastCodeNodeOfLine, $getStartOfCodeInLine, $isCodeHighlightNode, $isCodeNode, CodeHighlightNode, CodeNode, DEFAULT_CODE_LANGUAGE } from '@lexical/code'; import { mergeRegister } from '@lexical/utils'; import { $createLineBreakNode, $createPoint, $createTabNode, $createTextNode, $getCaretRange, $getCaretRangeInDirection, $getNodeByKey, $getSelection, $getSiblingCaret, $getTextPointCaret, $insertNodes, $isLineBreakNode, $isRangeSelection, $isTabNode, $isTextNode, $normalizeCaret, $setSelectionFromCaretRange, COMMAND_PRIORITY_LOW, INDENT_CONTENT_COMMAND, INSERT_TAB_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_TAB_COMMAND, MOVE_TO_END, MOVE_TO_START, OUTDENT_CONTENT_COMMAND, TextNode } from 'lexical'; import { $getHighlightNodes, getHighlightSerializeNode, isCodeLanguageLoaded, isCodeThemeLoaded, loadCodeLanguage, loadCodeTheme } from "./FacadeShiki"; import invariant from "./invariant"; var DEFAULT_CODE_THEME = 'lobe-theme'; export var ShikiTokenizer = { $tokenize: function $tokenize(codeNode, language) { return $getHighlightNodes(codeNode, language || this.defaultLanguage); }, $tokenizeSerialized: function $tokenizeSerialized(code, language, theme) { return getHighlightSerializeNode(code, language || this.defaultLanguage, // eslint-disable-next-line @typescript-eslint/no-use-before-define toCodeTheme({ defaultTheme: theme || this.defaultTheme })); }, defaultLanguage: DEFAULT_CODE_LANGUAGE, defaultTheme: DEFAULT_CODE_THEME }; export function toCodeTheme(tokenizer) { if (typeof tokenizer.defaultTheme === 'string') { return tokenizer.defaultTheme; } return tokenizer.defaultTheme.light + ' ' + tokenizer.defaultTheme.dark; } function $textNodeTransform(node, editor, tokenizer) { // Since CodeNode has flat children structure we only need to check // if node's parent is a code node and run highlighting if so var parentNode = node.getParent(); if ($isCodeNode(parentNode)) { // eslint-disable-next-line @typescript-eslint/no-use-before-define codeNodeTransform(parentNode, editor, tokenizer); } else if ($isCodeHighlightNode(node)) { // When code block converted into paragraph or other element // code highlight nodes converted back to normal text node.replace($createTextNode(node.__text)); } } function updateCodeGutter(node, editor) { var codeElement = editor.getElementByKey(node.getKey()); if (codeElement === null) { return; } var children = node.getChildren(); var childrenLength = children.length; // @ts-ignore: internal field if (childrenLength === codeElement.__cachedChildrenLength) { // Avoid updating the attribute if the children length hasn't changed. return; } // @ts-ignore:: internal field codeElement.__cachedChildrenLength = childrenLength; var gutter = '1'; var count = 1; for (var i = 0; i < childrenLength; i++) { if ($isLineBreakNode(children[i])) { gutter += '\n' + ++count; } } codeElement.dataset.gutter = gutter; } // Using `skipTransforms` to prevent extra transforms since reformatting the code // will not affect code block content itself. // // Using extra cache (`nodesCurrentlyHighlighting`) since both CodeNode and CodeHighlightNode // transforms might be called at the same time (e.g. new CodeHighlight node inserted) and // in both cases we'll rerun whole reformatting over CodeNode, which is redundant. // Especially when pasting code into CodeBlock. var MAX_CONCURRENT_HIGHLIGHTING = 2; var nodesCurrentlyHighlighting = new Set(); var waitingNodesCurrentlyHighlighting = new Set(); function codeNodeTransform(node, editor, tokenizer) { var nodeKey = node.getKey(); // When new code block inserted it might not have language selected var language = node.getLanguage(); if (!language) { language = tokenizer.defaultLanguage; node.setLanguage(language); } var theme = node.getTheme(); if (!theme) { theme = toCodeTheme(tokenizer); node.setTheme(theme); } // dynamic import of themes var inFlight = false; if (!isCodeThemeLoaded(theme)) { loadCodeTheme(theme, editor, nodeKey); inFlight = true; } // dynamic import of languages if (isCodeLanguageLoaded(language)) { if (!node.getIsSyntaxHighlightSupported()) { node.setIsSyntaxHighlightSupported(true); } } else { if (node.getIsSyntaxHighlightSupported()) { node.setIsSyntaxHighlightSupported(false); } loadCodeLanguage(language, editor, nodeKey); inFlight = true; } if (inFlight) { return; } if (nodesCurrentlyHighlighting.has(nodeKey)) { return; } if (nodesCurrentlyHighlighting.size > MAX_CONCURRENT_HIGHLIGHTING) { waitingNodesCurrentlyHighlighting.add(nodeKey); return; } nodesCurrentlyHighlighting.add(nodeKey); // Using nested update call to pass `skipTransforms` since we don't want // each individual CodeHighlightNode to be transformed again as it's already // in its final state editor.update(function () { // eslint-disable-next-line @typescript-eslint/no-use-before-define $updateAndRetainSelection(nodeKey, function () { var currentNode = $getNodeByKey(nodeKey); if (!$isCodeNode(currentNode) || !currentNode.isAttached()) { return false; } var lang = currentNode.getLanguage() || tokenizer.defaultLanguage; var highlightNodes = tokenizer.$tokenize(currentNode, lang); // eslint-disable-next-line @typescript-eslint/no-use-before-define var diffRange = getDiffRange(currentNode.getChildren(), highlightNodes); var from = diffRange.from, to = diffRange.to, nodesForReplacement = diffRange.nodesForReplacement; if (from !== to || nodesForReplacement.length) { node.splice(from, to - from, nodesForReplacement); return true; } return false; }); }, { onUpdate: function onUpdate() { nodesCurrentlyHighlighting.delete(nodeKey); if (nodesCurrentlyHighlighting.size < MAX_CONCURRENT_HIGHLIGHTING && waitingNodesCurrentlyHighlighting.size) { var next = waitingNodesCurrentlyHighlighting.values().next().value; if (!next) return; waitingNodesCurrentlyHighlighting.delete(next); requestAnimationFrame(function () { editor.getEditorState().read(function () { codeNodeTransform($getNodeByKey(next), editor, tokenizer); }); }); } }, skipTransforms: true }); } // Wrapping update function into selection retainer, that tries to keep cursor at the same // position as before. function $updateAndRetainSelection(nodeKey, updateFn) { var node = $getNodeByKey(nodeKey); if (!$isCodeNode(node) || !node.isAttached()) { return; } var selection = $getSelection(); // If it's not range selection (or null selection) there's no need to change it, // but we can still run highlighting logic if (!$isRangeSelection(selection)) { updateFn(); return; } var anchor = selection.anchor; var anchorOffset = anchor.offset; var isNewLineAnchor = anchor.type === 'element' && $isLineBreakNode(node.getChildAtIndex(anchor.offset - 1)); var textOffset = 0; // Calculating previous text offset (all text node prior to anchor + anchor own text offset) if (!isNewLineAnchor) { var anchorNode = anchor.getNode(); textOffset = anchorOffset + anchorNode.getPreviousSiblings().reduce(function (offset, _node) { return offset + _node.getTextContentSize(); }, 0); } var hasChanges = updateFn(); if (!hasChanges) { return; } // Non-text anchors only happen for line breaks, otherwise // selection will be within text node (code highlight node) if (isNewLineAnchor) { anchor.getNode().select(anchorOffset, anchorOffset); return; } // If it was non-element anchor then we walk through child nodes // and looking for a position of original text offset node.getChildren().some(function (_node) { var isText = $isTextNode(_node); if (isText || $isLineBreakNode(_node)) { var textContentSize = _node.getTextContentSize(); if (isText && textContentSize >= textOffset) { _node.select(textOffset, textOffset); return true; } textOffset -= textContentSize; } return false; }); } // Finds minimal diff range between two nodes lists. It returns from/to range boundaries of prevNodes // that needs to be replaced with `nodes` (subset of nextNodes) to make prevNodes equal to nextNodes. function getDiffRange(prevNodes, nextNodes) { var leadingMatch = 0; while (leadingMatch < prevNodes.length) { // eslint-disable-next-line @typescript-eslint/no-use-before-define if (!isEqual(prevNodes[leadingMatch], nextNodes[leadingMatch])) { break; } leadingMatch++; } var prevNodesLength = prevNodes.length; var nextNodesLength = nextNodes.length; var maxTrailingMatch = Math.min(prevNodesLength, nextNodesLength) - leadingMatch; var trailingMatch = 0; while (trailingMatch < maxTrailingMatch) { trailingMatch++; if ( // eslint-disable-next-line @typescript-eslint/no-use-before-define !isEqual(prevNodes[prevNodesLength - trailingMatch], nextNodes[nextNodesLength - trailingMatch])) { trailingMatch--; break; } } var from = leadingMatch; var to = prevNodesLength - trailingMatch; var nodesForReplacement = nextNodes.slice(leadingMatch, nextNodesLength - trailingMatch); return { from: from, nodesForReplacement: nodesForReplacement, to: to }; } function isEqual(nodeA, nodeB) { // Only checking for code highlight nodes, tabs and linebreaks. If it's regular text node // returning false so that it's transformed into code highlight node return $isCodeHighlightNode(nodeA) && $isCodeHighlightNode(nodeB) && nodeA.__text === nodeB.__text && nodeA.__highlightType === nodeB.__highlightType && nodeA.__style === nodeB.__style || $isTabNode(nodeA) && $isTabNode(nodeB) || $isLineBreakNode(nodeA) && $isLineBreakNode(nodeB); } /** * Returns a boolean. * Check that the selection span is within a single CodeNode. * This is used to guard against executing handlers that can only be * applied in a single CodeNode context */ function $isSelectionInCode(selection) { if (!$isRangeSelection(selection)) { return false; } var anchorNode = selection.anchor.getNode(); var maybeAnchorCodeNode = $isCodeNode(anchorNode) ? anchorNode : anchorNode.getParent(); var focusNode = selection.focus.getNode(); var maybeFocusCodeNode = $isCodeNode(focusNode) ? focusNode : focusNode.getParent(); return $isCodeNode(maybeAnchorCodeNode) && maybeAnchorCodeNode.is(maybeFocusCodeNode); } /** * Returns an Array of code lines * Take the sequence of LineBreakNode | TabNode | CodeHighlightNode forming * the selection and split it by LineBreakNode. * If the selection ends at the start of the last line, it is considered empty. * Empty lines are discarded. */ function $getCodeLines(selection) { var nodes = selection.getNodes(); var lines = []; if (nodes.length === 1 && $isCodeNode(nodes[0])) { return lines; } var lastLine = []; var _iterator = _createForOfIteratorHelper(nodes), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var node = _step.value; invariant($isCodeHighlightNode(node) || $isTabNode(node) || $isLineBreakNode(node), 'Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, TabNode and LineBreakNode'); if ($isLineBreakNode(node)) { if (lastLine.length > 0) { lines.push(lastLine); lastLine = []; } } else { lastLine.push(node); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } if (lastLine.length > 0) { var selectionEnd = selection.isBackward() ? selection.anchor : selection.focus; // Discard the last line if the selection ends exactly at the // start of the line (no real selection) var lastPoint = $createPoint(lastLine[0].getKey(), 0, 'text'); if (!selectionEnd.is(lastPoint)) { lines.push(lastLine); } } return lines; } function $handleTab(shiftKey) { var selection = $getSelection(); if (!$isRangeSelection(selection) || !$isSelectionInCode(selection)) { return null; } var indentOrOutdent = !shiftKey ? INDENT_CONTENT_COMMAND : OUTDENT_CONTENT_COMMAND; var tabOrOutdent = !shiftKey ? INSERT_TAB_COMMAND : OUTDENT_CONTENT_COMMAND; var anchor = selection.anchor; var focus = selection.focus; // 1. early decision when there is no real selection if (anchor.is(focus)) { return tabOrOutdent; } // 2. If only empty lines or multiple non-empty lines are selected: indent/outdent var codeLines = $getCodeLines(selection); if (codeLines.length !== 1) { return indentOrOutdent; } var codeLine = codeLines[0]; var codeLineLength = codeLine.length; invariant(codeLineLength !== 0, '$getCodeLines only extracts non-empty lines'); // Take into account the direction of the selection var selectionFirst; var selectionLast; if (selection.isBackward()) { selectionFirst = focus; selectionLast = anchor; } else { selectionFirst = anchor; selectionLast = focus; } // find boundary elements of the line // since codeLine only contains TabNode | CodeHighlightNode // the result of these functions should is of Type TabNode | CodeHighlightNode var firstOfLine = $getFirstCodeNodeOfLine(codeLine[0]); var lastOfLine = $getLastCodeNodeOfLine(codeLine[0]); var anchorOfLine = $createPoint(firstOfLine.getKey(), 0, 'text'); var focusOfLine = $createPoint(lastOfLine.getKey(), lastOfLine.getTextContentSize(), 'text'); // 3. multiline because selection started strictly before the line if (selectionFirst.isBefore(anchorOfLine)) { return indentOrOutdent; } // 4. multiline because the selection stops strictly after the line if (focusOfLine.isBefore(selectionLast)) { return indentOrOutdent; } // The selection if within the line. // 4. If it does not touch both borders, it needs a tab if (anchorOfLine.isBefore(selectionFirst) || selectionLast.isBefore(focusOfLine)) { return tabOrOutdent; } // 5. Selection is matching a full line on non-empty code return indentOrOutdent; } function $handleMultilineIndent(type) { var selection = $getSelection(); if (!$isRangeSelection(selection) || !$isSelectionInCode(selection)) { return false; } var codeLines = $getCodeLines(selection); var codeLinesLength = codeLines.length; // Special Indent case // Selection is collapsed at the beginning of a line if (codeLinesLength === 0 && selection.isCollapsed()) { if (type === INDENT_CONTENT_COMMAND) { selection.insertNodes([$createTabNode()]); } return true; } // Special Indent case // Selection is matching only one LineBreak if (codeLinesLength === 0 && type === INDENT_CONTENT_COMMAND && selection.getTextContent() === '\n') { var tabNode = $createTabNode(); var lineBreakNode = $createLineBreakNode(); var direction = selection.isBackward() ? 'previous' : 'next'; selection.insertNodes([tabNode, lineBreakNode]); $setSelectionFromCaretRange($getCaretRangeInDirection($getCaretRange($getTextPointCaret(tabNode, 'next', 0), $normalizeCaret($getSiblingCaret(lineBreakNode, 'next'))), direction)); return true; } // Indent Non Empty Lines for (var i = 0; i < codeLinesLength; i++) { var line = codeLines[i]; // a line here is never empty if (line.length > 0) { var firstOfLine = line[0]; // make sure to consider the first node on the first line // because the line might not be fully selected if (i === 0) { firstOfLine = $getFirstCodeNodeOfLine(firstOfLine); } if (type === INDENT_CONTENT_COMMAND) { var _tabNode = $createTabNode(); firstOfLine.insertBefore(_tabNode); // First real code line may need selection adjustment // when firstOfLine is at the selection boundary if (i === 0) { var anchorKey = selection.isBackward() ? 'focus' : 'anchor'; var anchorLine = $createPoint(firstOfLine.getKey(), 0, 'text'); if (selection[anchorKey].is(anchorLine)) { selection[anchorKey].set(_tabNode.getKey(), 0, 'text'); } } } else if ($isTabNode(firstOfLine)) { firstOfLine.remove(); } } } return true; } function $handleShiftLines(type, event) { // We only care about the alt+arrow keys var selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } // I'm not quite sure why, but it seems like calling anchor.getNode() collapses the selection here // So first, get the anchor and the focus, then get their nodes var anchor = selection.anchor, focus = selection.focus; var anchorOffset = anchor.offset; var focusOffset = focus.offset; var anchorNode = anchor.getNode(); var focusNode = focus.getNode(); var arrowIsUp = type === KEY_ARROW_UP_COMMAND; // Ensure the selection is within the codeblock if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))) { return false; } if (!event.altKey) { // Handle moving selection out of the code block, given there are no // siblings that can natively take the selection. if (selection.isCollapsed()) { var _codeNode = anchorNode.getParentOrThrow(); if (arrowIsUp && anchorOffset === 0 && anchorNode.getPreviousSibling() === null) { var codeNodeSibling = _codeNode.getPreviousSibling(); if (codeNodeSibling === null) { _codeNode.selectPrevious(); event.preventDefault(); return true; } } else if (!arrowIsUp && anchorOffset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null) { var _codeNodeSibling = _codeNode.getNextSibling(); if (_codeNodeSibling === null) { _codeNode.selectNext(); event.preventDefault(); return true; } } } return false; } var start; var end; if (anchorNode.isBefore(focusNode)) { start = $getFirstCodeNodeOfLine(anchorNode); end = $getLastCodeNodeOfLine(focusNode); } else { start = $getFirstCodeNodeOfLine(focusNode); end = $getLastCodeNodeOfLine(anchorNode); } if (start === null || end === null) { return false; } var range = start.getNodesBetween(end); var _iterator2 = _createForOfIteratorHelper(range), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var node = _step2.value; if (!$isCodeHighlightNode(node) && !$isTabNode(node) && !$isLineBreakNode(node)) { return false; } } // After this point, we know the selection is within the codeblock. We may not be able to // actually move the lines around, but we want to return true either way to prevent // the event's default behavior } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } event.preventDefault(); event.stopPropagation(); // required to stop cursor movement under Firefox var linebreak = arrowIsUp ? start.getPreviousSibling() : end.getNextSibling(); if (!$isLineBreakNode(linebreak)) { return true; } var sibling = arrowIsUp ? linebreak.getPreviousSibling() : linebreak.getNextSibling(); if (sibling === null) { return true; } var maybeInsertionPoint = $isCodeHighlightNode(sibling) || $isTabNode(sibling) || $isLineBreakNode(sibling) ? arrowIsUp ? $getFirstCodeNodeOfLine(sibling) : $getLastCodeNodeOfLine(sibling) : null; var insertionPoint = maybeInsertionPoint !== null ? maybeInsertionPoint : sibling; linebreak.remove(); range.forEach(function (node) { return node.remove(); }); if (type === KEY_ARROW_UP_COMMAND) { range.forEach(function (node) { return insertionPoint.insertBefore(node); }); insertionPoint.insertBefore(linebreak); } else { insertionPoint.insertAfter(linebreak); insertionPoint = linebreak; range.forEach(function (node) { insertionPoint.insertAfter(node); insertionPoint = node; }); } selection.setTextNodeRange(anchorNode, anchorOffset, focusNode, focusOffset); return true; } function $handleMoveTo(type, event) { var selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } var anchor = selection.anchor, focus = selection.focus; var anchorNode = anchor.getNode(); var focusNode = focus.getNode(); var isMoveToStart = type === MOVE_TO_START; // Ensure the selection is within the codeblock if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))) { return false; } if (isMoveToStart) { var start = $getStartOfCodeInLine(focusNode, focus.offset); if (start !== null) { var node = start.node, offset = start.offset; if ($isLineBreakNode(node)) { node.selectNext(0, 0); } else { selection.setTextNodeRange(node, offset, node, offset); } } else { focusNode.getParentOrThrow().selectStart(); } } else { var _node2 = $getEndOfCodeInLine(focusNode); _node2.select(); } event.preventDefault(); event.stopPropagation(); return true; } export function registerCodeHighlighting(editor, tokenizer) { if (!editor.hasNodes([CodeNode, CodeHighlightNode])) { throw new Error('CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor'); } // eslint-disable-next-line eqeqeq if (tokenizer == null) { // eslint-disable-next-line no-param-reassign tokenizer = ShikiTokenizer; } var registrations = []; // Only register the mutation listener if not in headless mode if (editor._headless !== true) { registrations.push(editor.registerMutationListener(CodeNode, function (mutations) { editor.update(function () { var _iterator3 = _createForOfIteratorHelper(mutations), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var _step3$value = _slicedToArray(_step3.value, 2), key = _step3$value[0], type = _step3$value[1]; if (type !== 'destroyed') { var node = $getNodeByKey(key); if (node !== null) { updateCodeGutter(node, editor); } } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } }); }, { skipInitialization: false })); } // Add the rest of the registrations registrations.push(editor.registerMutationListener(CodeNode, function (mutations) { editor.update(function () { var _iterator4 = _createForOfIteratorHelper(mutations), _step4; try { var _loop = function _loop() { var _step4$value = _slicedToArray(_step4.value, 2), key = _step4$value[0], type = _step4$value[1]; if (type !== 'destroyed') { var node = $getNodeByKey(key); if (node !== null) { var _codeNode2$getTheme; var _codeNode2 = node; if ((_codeNode2$getTheme = _codeNode2.getTheme()) !== null && _codeNode2$getTheme !== void 0 && _codeNode2$getTheme.endsWith(' needUpdate')) { editor.update(function () { var _codeNode2$getTheme2; _codeNode2.setTheme(((_codeNode2$getTheme2 = _codeNode2.getTheme()) === null || _codeNode2$getTheme2 === void 0 ? void 0 : _codeNode2$getTheme2.replace(' needUpdate', '')) || ''); }); } } } }; for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { _loop(); } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } }); }, { skipInitialization: false }), editor.registerNodeTransform(CodeNode, function (node) { return codeNodeTransform(node, editor, tokenizer); }), editor.registerNodeTransform(TextNode, function (node) { return $textNodeTransform(node, editor, tokenizer); }), editor.registerNodeTransform(CodeHighlightNode, function (node) { return $textNodeTransform(node, editor, tokenizer); }), editor.registerCommand(KEY_TAB_COMMAND, function (event) { var command = $handleTab(event.shiftKey); if (command === null) { return false; } event.preventDefault(); editor.dispatchCommand(command, undefined); return true; }, COMMAND_PRIORITY_LOW), editor.registerCommand(INSERT_TAB_COMMAND, function () { var selection = $getSelection(); if (!$isSelectionInCode(selection)) { return false; } $insertNodes([$createTabNode()]); return true; }, COMMAND_PRIORITY_LOW), editor.registerCommand(INDENT_CONTENT_COMMAND, function () { return $handleMultilineIndent(INDENT_CONTENT_COMMAND); }, COMMAND_PRIORITY_LOW), editor.registerCommand(OUTDENT_CONTENT_COMMAND, function () { return $handleMultilineIndent(OUTDENT_CONTENT_COMMAND); }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_UP_COMMAND, function (event) { var selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } var anchor = selection.anchor; var anchorNode = anchor.getNode(); if (!$isSelectionInCode(selection)) { return false; } // If at the start of a code block, prevent selection from moving out if (selection.isCollapsed() && anchor.offset === 0 && anchorNode.getPreviousSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) { event.preventDefault(); return true; } return $handleShiftLines(KEY_ARROW_UP_COMMAND, event); }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_DOWN_COMMAND, function (event) { var selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } var anchor = selection.anchor; var anchorNode = anchor.getNode(); if (!$isSelectionInCode(selection)) { return false; } // If at the end of a code block, prevent selection from moving out if (selection.isCollapsed() && anchor.offset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) { event.preventDefault(); return true; } return $handleShiftLines(KEY_ARROW_DOWN_COMMAND, event); }, COMMAND_PRIORITY_LOW), editor.registerCommand(MOVE_TO_START, function (event) { return $handleMoveTo(MOVE_TO_START, event); }, COMMAND_PRIORITY_LOW), editor.registerCommand(MOVE_TO_END, function (event) { return $handleMoveTo(MOVE_TO_END, event); }, COMMAND_PRIORITY_LOW)); return mergeRegister.apply(void 0, registrations); }