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.

489 lines (485 loc) 19.8 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 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; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 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; } /* eslint-disable @typescript-eslint/no-use-before-define */ import { $isListItemNode } from '@lexical/list'; import { mergeRegister } from '@lexical/utils'; import { $createParagraphNode, $getNodeByKey, $insertNodes, $isElementNode, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'; import { $closest } from "../../../editor-kernel"; import { createDebugLogger } from "../../../utils/debug"; import { $createDiffNode, DiffNode } from "../node/DiffNode"; import { $cloneNode, $parseSerializedNodeImpl, charToId } from "../utils"; var logger = createDebugLogger('plugin', 'litexml'); // Helpers to reduce duplication and improve readability function toArrayXml(litexml) { return Array.isArray(litexml) ? litexml : [litexml]; } function tryParseChild(child, editor) { try { var oldNode = $getNodeByKey(child.id); var newNode = $parseSerializedNodeImpl(child, editor); return { newNode: newNode, oldNode: oldNode }; } catch (error) { logger.error('❌ Error parsing child node:', error); return { newNode: null, oldNode: null }; } } function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor) { var oldBlock = $closest(oldNode, function (node) { return node.isInline() === false; }); if (!oldBlock) { throw new Error('Old block node not found for diffing.'); } var originDiffNode = $closest(oldNode, function (node) { return node.getType() === DiffNode.getType(); }); if (originDiffNode) { oldNode.replace(newNode, false); return; } if (oldNode === oldBlock) { var diffNode = $createDiffNode('modify'); diffNode.append($cloneNode(oldBlock, editor), newNode); oldNode.replace(diffNode, false); } else { if (!modifyBlockNodes.has(oldBlock.getKey())) { modifyBlockNodes.add(oldBlock.getKey()); var _diffNode = $createDiffNode('modify'); _diffNode.append($cloneNode(oldBlock, editor)); diffNodeMap.set(oldBlock.getKey(), _diffNode); } oldNode.replace(newNode, false); } } function finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor) { var _iterator = _createForOfIteratorHelper(modifyBlockNodes), _step; try { var _loop = function _loop() { var blockNodeKey = _step.value; var blockNode = $getNodeByKey(blockNodeKey); var diffNode = diffNodeMap.get(blockNodeKey); if (diffNode && blockNode) { // 如果是列表项,可能需要特殊处理 if (blockNode.getType() === 'listitem' && $isElementNode(blockNode)) { var newDiffNode = $createDiffNode('listItemModify'); var firstChild = diffNode.getFirstChild(); if (firstChild && $isElementNode(firstChild)) { newDiffNode.append(firstChild); } var children = blockNode.getChildren(); var p = $createParagraphNode(); children.forEach(function (child) { child.remove(); p.append(child); }); newDiffNode.append(p); blockNode.append(newDiffNode); return 1; // continue } else { diffNode.append($cloneNode(blockNode, editor)); blockNode.replace(diffNode, false); } } }; for (_iterator.s(); !(_step = _iterator.n()).done;) { if (_loop()) continue; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } /** * Wrap a block-level change with a `modify` diff: clone the old block, run the * provided changeFn (which should mutate nodes inside the block), then clone * the new block and replace it with the diff node. Useful for inline->block * transitions where we want to show a modify diff. */ function wrapBlockModify(oldBlock, editor, changeFn) { if ($isListItemNode(oldBlock)) { var _diffNode2 = $createDiffNode('listItemModify'); var p = $createParagraphNode(); oldBlock.getChildren().forEach(function (child) { p.append($cloneNode(child, editor)); }); changeFn(); _diffNode2.append(p); var pNew = $createParagraphNode(); oldBlock.getChildren().forEach(function (child) { pNew.append(child); }); _diffNode2.append(pNew); oldBlock.append(_diffNode2); return; } var diffNode = $createDiffNode('modify'); diffNode.append($cloneNode(oldBlock, editor)); changeFn(); var newBlock = $getNodeByKey(oldBlock.getKey()); if (!newBlock) { throw new Error('New block node not found for modify wrapper.'); } diffNode.append($cloneNode(newBlock, editor)); newBlock.replace(diffNode, false); } export var LITEXML_MODIFY_COMMAND = createCommand('LITEXML_MODIFY_COMMAND'); export var LITEXML_APPLY_COMMAND = createCommand('LITEXML_APPLY_COMMAND'); export var LITEXML_REMOVE_COMMAND = createCommand('LITEXML_REMOVE_COMMAND'); export var LITEXML_INSERT_COMMAND = createCommand('LITEXML_INSERT_COMMAND'); export function registerLiteXMLCommand(editor, dataSource) { return mergeRegister(editor.registerCommand(LITEXML_MODIFY_COMMAND, function (payload) { var resultPayload = payload.reduce(function (acc, cur) { if (cur.action === 'insert') { acc.unshift(cur); } else { acc.push(cur); } return acc; }, []); try { resultPayload.forEach(function (item) { var action = item.action; switch (action) { case 'modify': { var litexml = item.litexml; var arrayXml = toArrayXml(litexml); // handle modfy action handleModify(editor, dataSource, arrayXml, true); break; } case 'remove': { var id = item.id; var key = charToId(id); // handle remove action handleRemove(editor, key, true); break; } case 'insert': { handleInsert(editor, _objectSpread(_objectSpread({}, item), {}, { delay: true }), dataSource); break; } default: { logger.warn("\u26A0\uFE0F Unknown action type: ".concat(action)); } } }); return false; } catch (error) { logger.error('❌ Error processing LITEXML_MODIFY_COMMAND:', error); return false; } }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, function (payload) { var litexml = payload.litexml, delay = payload.delay; var arrayXml = toArrayXml(litexml); handleModify(editor, dataSource, arrayXml, delay); return false; }, COMMAND_PRIORITY_EDITOR // Priority ), editor.registerCommand(LITEXML_REMOVE_COMMAND, function (payload) { var id = payload.id, delay = payload.delay; var key = charToId(id); handleRemove(editor, key, delay); return false; }, COMMAND_PRIORITY_EDITOR // Priority ), editor.registerCommand(LITEXML_INSERT_COMMAND, function (payload) { handleInsert(editor, payload, dataSource); return false; }, COMMAND_PRIORITY_EDITOR // Priority )); } function handleModify(editor, dataSource, arrayXml, delay) { if (delay) { editor.update(function () { var modifyBlockNodes = new Set(); var diffNodeMap = new Map(); arrayXml.forEach(function (xml) { var inode = dataSource.readLiteXMLToInode(xml); inode.root.children.forEach(function (child) { try { var _tryParseChild = tryParseChild(child, editor), oldNode = _tryParseChild.oldNode, newNode = _tryParseChild.newNode; if (oldNode && newNode) { handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor); } else { logger.warn("\u26A0\uFE0F Node with key ".concat(child.id, " not found for diffing.")); } } catch (error) { logger.error('❌ Error replacing node:', error); } }); }); // replace modified block nodes with diff nodes finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor); }); } else { editor.update(function () { arrayXml.forEach(function (xml) { var inode = dataSource.readLiteXMLToInode(xml); var prevNode = null; inode.root.children.forEach(function (child) { try { var _tryParseChild2 = tryParseChild(child, editor), oldNode = _tryParseChild2.oldNode, newNode = _tryParseChild2.newNode; if (oldNode && newNode) { prevNode = oldNode.replace(newNode, false); } else if (newNode) { if (prevNode) { if (!newNode.isInline()) { var prevBlock = $closest(prevNode, function (node) { return node.isInline() === false; }); if (prevBlock) { prevNode = prevBlock.insertAfter(newNode); } else { $insertNodes([newNode]); prevNode = newNode; } } else { prevNode = prevNode.insertAfter(newNode); } } else { $insertNodes([newNode]); } } } catch (error) { logger.error('❌ Error replacing node:', error); } }); }); }); } } function handleRemove(editor, key, delay) { editor.update(function () { var node = $getNodeByKey(key); if (!node) return; if (!delay) { node.remove(); return; } // delay removal: show a diff if (node.isInline() === false) { var originDiffNode = $closest(node, function (node) { return node.getType() === DiffNode.getType(); }); if (originDiffNode) { switch (originDiffNode.diffType) { case 'add': { originDiffNode.remove(); return; } case 'modify': { var children = originDiffNode.getChildren(); var newDiff = $createDiffNode('remove'); newDiff.append(children[0]); originDiffNode.replace(newDiff, false); return; } case 'listItemModify': { var _children = originDiffNode.getChildren(); originDiffNode.replace(_children[0], false).selectEnd(); return; } case 'remove': case 'unchanged': { // do nothing special break; } } return; } if ($isListItemNode(node)) { var diffNode = $createDiffNode('listItemRemove'); node.getChildren().forEach(function (child) { diffNode.append($cloneNode(child, editor)); }); node.clear(); node.append(diffNode); } else { var _diffNode3 = $createDiffNode('remove'); _diffNode3.append($cloneNode(node, editor)); node.replace(_diffNode3, false); } } else { var oldBlock = $closest(node, function (node) { return node.isInline() === false; }); if (!oldBlock) { throw new Error('Old block node not found for removal.'); } var _originDiffNode = $closest(node, function (node) { return node.getType() === DiffNode.getType(); }); if (_originDiffNode) { node.remove(); return; } // wrap changes inside a modify diff wrapBlockModify(oldBlock, editor, function () { node.remove(); }); } }); } function handleInsert(editor, payload, dataSource) { var litexml = payload.litexml, delay = payload.delay; var isBefore = ('beforeId' in payload); var inode = dataSource.readLiteXMLToInode(litexml); editor.update(function () { try { var referenceNode = null; if (isBefore) { referenceNode = $getNodeByKey(charToId(payload.beforeId)); } else { referenceNode = $getNodeByKey(charToId(payload.afterId)); } if (!referenceNode) { throw new Error('Reference node not found for insertion.'); } var newNodes = inode.root.children.map(function (child) { return $parseSerializedNodeImpl(child, editor); }); if (!delay) { if (isBefore) { referenceNode = referenceNode.insertBefore(newNodes); } else { newNodes.forEach(function (node) { if (referenceNode) { referenceNode = referenceNode.insertAfter(node); } }); } return; } // delay insertion: show diffs or wrap block modifications if (isBefore) { if (referenceNode.isInline() === false) { var originDiffNode = $closest(referenceNode, function (node) { return node.getType() === DiffNode.getType(); }); if (originDiffNode) { referenceNode = originDiffNode; } var diffNodes = newNodes.map(function (node) { var diffNode = $createDiffNode('add'); diffNode.append(node); return diffNode; }); diffNodes.forEach(function (diffNode) { if (referenceNode) { referenceNode = referenceNode.insertBefore(diffNode); } }); } else { var refBlock = $closest(referenceNode, function (node) { return node.isInline() === false; }); if (!refBlock) { throw new Error('Reference block node not found for insertion.'); } var _originDiffNode2 = $closest(referenceNode, function (node) { return node.getType() === DiffNode.getType(); }); if (_originDiffNode2) { // 可能是 modify / add,那么直接修改就好了 newNodes.forEach(function (node) { if (referenceNode) { referenceNode = referenceNode.insertBefore(node); } }); } else { wrapBlockModify(refBlock, editor, function () { newNodes.forEach(function (node) { if (referenceNode) { referenceNode = referenceNode.insertBefore(node); } }); }); } } } else { if (referenceNode.isInline() === false) { var _originDiffNode3 = $closest(referenceNode, function (node) { return node.getType() === DiffNode.getType(); }); if (_originDiffNode3) { referenceNode = _originDiffNode3; } newNodes.forEach(function (node) { if (referenceNode) { if ($isListItemNode(node)) { var diffNode = $createDiffNode('listItemAdd'); node.getChildren().forEach(function (child) { diffNode.append(child); }); node.append(diffNode); referenceNode = referenceNode.insertAfter(node); } else { var _diffNode4 = $createDiffNode('add'); _diffNode4.append(node); referenceNode = referenceNode.insertAfter(_diffNode4); } } }); } else { var _refBlock = $closest(referenceNode, function (node) { return node.isInline() === false; }); if (!_refBlock) { throw new Error('Reference block node not found for insertion.'); } var _originDiffNode4 = $closest(referenceNode, function (node) { return node.getType() === DiffNode.getType(); }); if (_originDiffNode4) { // 可能是 modify / add,那么直接修改就好了 newNodes.forEach(function (node) { if (referenceNode) { referenceNode = referenceNode.insertAfter(node); } }); } else { wrapBlockModify(_refBlock, editor, function () { newNodes.forEach(function (node) { if (referenceNode) { referenceNode = referenceNode.insertAfter(node); } }); }); } } } } catch (error) { logger.error('❌ Error inserting node:', error); } }); }