@lobehub/editor 4.17.1 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/es/headless/index.js +12 -2
  2. package/es/headless.js +3119 -1224
  3. package/es/index.d.ts +1 -1
  4. package/es/index.js +13 -13
  5. package/es/plugins/block/react/ReactBlockPlugin.js +3 -1
  6. package/es/plugins/codeblock/command/index.d.ts +2 -8
  7. package/es/plugins/codeblock/command/index.js +3 -3
  8. package/es/plugins/codeblock/command/symbols.d.ts +8 -0
  9. package/es/plugins/codeblock/command/symbols.js +5 -0
  10. package/es/plugins/codeblock/index.d.ts +1 -1
  11. package/es/plugins/codeblock/index.js +1 -1
  12. package/es/plugins/codeblock/plugin/FacadeShiki.js +1 -1
  13. package/es/plugins/codeblock/plugin/index.js +1 -1
  14. package/es/plugins/codemirror-block/command/index.js +1 -1
  15. package/es/plugins/codemirror-block/node/CodeMirrorNode.js +10 -1
  16. package/es/plugins/codemirror-block/plugin/index.d.ts +1 -1
  17. package/es/plugins/file/plugin/index.d.ts +2 -2
  18. package/es/plugins/file/plugin/index.js +26 -23
  19. package/es/plugins/image/node/block-image-node.d.ts +1 -0
  20. package/es/plugins/image/node/block-image-node.js +6 -0
  21. package/es/plugins/image/node/image-node.d.ts +1 -0
  22. package/es/plugins/image/node/image-node.js +6 -0
  23. package/es/plugins/image/plugin/index.d.ts +3 -3
  24. package/es/plugins/image/plugin/index.js +5 -23
  25. package/es/plugins/image/react/ReactImagePlugin.js +18 -0
  26. package/es/plugins/image/react/components/Image.js +1 -1
  27. package/es/plugins/math/plugin/index.d.ts +1 -1
  28. package/es/plugins/math/react/components/MathEditor.js +1 -1
  29. package/es/plugins/math/react/components/MathInline.js +1 -1
  30. package/es/plugins/mention/plugin/index.d.ts +1 -1
  31. package/es/plugins/mention/plugin/index.js +1 -1
  32. package/es/react/hooks/useEditorState/index.js +2 -2
  33. package/es/renderer/engine/shiki.js +1 -1
  34. package/es/renderer/nodes/index.js +4 -4
  35. package/es/renderer/renderers/codeblock.js +1 -0
  36. package/package.json +1 -1
  37. package/es/headless/plugins/codeblock.js +0 -82
package/es/headless.js CHANGED
@@ -4,10 +4,10 @@ import { createEmptyHistoryState, registerHistory } from "@lexical/history";
4
4
  import { $computeTableMapSkipCellCheck, $createTableNodeWithDimensions, $createTableSelection, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $findTableNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isSimpleTable, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, TableCellNode, TableNode, TableNode as TableNode$1, TableRowNode, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive } from "@lexical/table";
5
5
  import { get, merge, template, templateSettings } from "es-toolkit/compat";
6
6
  import EventEmitter from "eventemitter3";
7
- import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, TEXT_TYPE_TO_FORMAT, TabNode, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
7
+ import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
8
8
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
9
9
  import createDebug from "debug";
10
- import { $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertNodeToNearestRoot, CAN_USE_DOM, IS_APPLE as isApple, addClassNamesToElement, calculateZoomLevel, isHTMLAnchorElement, isHTMLElement as isHTMLElement$1, mergeRegister } from "@lexical/utils";
10
+ import { $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertNodeToNearestRoot, $wrapNodeInElement, CAN_USE_DOM, IS_APPLE as isApple, addClassNamesToElement, calculateZoomLevel, isHTMLAnchorElement, isHTMLElement as isHTMLElement$1, mergeRegister } from "@lexical/utils";
11
11
  import { $isAtNodeEnd, $setBlocksType } from "@lexical/selection";
12
12
  import { registerDragonSupport } from "@lexical/dragon";
13
13
  import { $createListItemNode, $createListNode, $insertList, $isListItemNode, $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode, registerList, registerListStrictIndentTransform } from "@lexical/list";
@@ -17,8 +17,7 @@ import remarkCjkFriendly from "remark-cjk-friendly";
17
17
  import remarkGfm from "remark-gfm";
18
18
  import remarkMath from "remark-math";
19
19
  import { $getClipboardDataFromSelection, setLexicalClipboardDataTransfer } from "@lexical/clipboard";
20
- import { $isCodeHighlightNode, $isCodeNode, CodeHighlightNode, CodeNode } from "@lexical/code-core";
21
- import { bundledLanguagesInfo } from "shiki";
20
+ import { $isCodeHighlightNode, $isCodeNode } from "@lexical/code-core";
22
21
  //#region src/locale/index.ts
23
22
  var locale_default = {
24
23
  block: {
@@ -594,7 +593,7 @@ const isHotkeyMatch = (event, hotkey) => {
594
593
  };
595
594
  //#endregion
596
595
  //#region src/utils/hotkey/registerHotkey.ts
597
- const logger$7 = createDebugLogger("hotkey");
596
+ const logger$9 = createDebugLogger("hotkey");
598
597
  const registerHotkey = (hotkey, callback, options = {}) => {
599
598
  return (e) => {
600
599
  if (!isHotkeyMatch(e, hotkey.keys)) return false;
@@ -606,7 +605,7 @@ const registerHotkey = (hotkey, callback, options = {}) => {
606
605
  ...keys,
607
606
  scopes: hotkey.scopes
608
607
  });
609
- logger$7.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
608
+ logger$9.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
610
609
  return true;
611
610
  };
612
611
  };
@@ -1423,7 +1422,7 @@ function registerCommands(editor) {
1423
1422
  }
1424
1423
  //#endregion
1425
1424
  //#region src/plugins/common/node/cursor.ts
1426
- const logger$6 = createDebugLogger("common", "cursor");
1425
+ const logger$8 = createDebugLogger("common", "cursor");
1427
1426
  var CardLikeElementNode = class extends ElementNode {
1428
1427
  isCardLike() {
1429
1428
  return true;
@@ -1462,7 +1461,7 @@ function registerCursorNode(editor) {
1462
1461
  const needAddCursor = [];
1463
1462
  for (const [kClass, nodeMaps] of mutatedNodes) if (DecoratorNode.prototype.isPrototypeOf(kClass.prototype)) for (const [key, mutation] of nodeMaps) {
1464
1463
  const node = $getNodeByKey(key);
1465
- logger$6.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1464
+ logger$8.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1466
1465
  if (mutation === "created" && node?.isInline() && node.getNextSibling() === null) needAddCursor.push(node);
1467
1466
  }
1468
1467
  if (needAddCursor.length > 0) editor.update(() => {
@@ -1564,7 +1563,7 @@ function registerCursorNode(editor) {
1564
1563
  $setSelection(sel);
1565
1564
  return true;
1566
1565
  } catch (error) {
1567
- logger$6.error("❌ Cursor selection error:", error);
1566
+ logger$8.error("❌ Cursor selection error:", error);
1568
1567
  }
1569
1568
  else if ($isCursorNode(focusNode)) try {
1570
1569
  const { key: anchorKey, offset: anchorOffset, type: anchorType } = selection.anchor;
@@ -1575,7 +1574,7 @@ function registerCursorNode(editor) {
1575
1574
  $setSelection(sel);
1576
1575
  return true;
1577
1576
  } catch (error) {
1578
- logger$6.error("❌ Cursor navigation error:", error);
1577
+ logger$8.error("❌ Cursor navigation error:", error);
1579
1578
  }
1580
1579
  return false;
1581
1580
  }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, (event) => {
@@ -1754,7 +1753,7 @@ function charToId(char) {
1754
1753
  }
1755
1754
  //#endregion
1756
1755
  //#region src/plugins/litexml/command/index.ts
1757
- const logger$5 = createDebugLogger("plugin", "litexml");
1756
+ const logger$7 = createDebugLogger("plugin", "litexml");
1758
1757
  function toArrayXml(litexml) {
1759
1758
  return Array.isArray(litexml) ? litexml : [litexml];
1760
1759
  }
@@ -1766,7 +1765,7 @@ function tryParseChild(child, editor) {
1766
1765
  oldNode
1767
1766
  };
1768
1767
  } catch (error) {
1769
- logger$5.error("❌ Error parsing child node:", error);
1768
+ logger$7.error("❌ Error parsing child node:", error);
1770
1769
  return {
1771
1770
  newNode: null,
1772
1771
  oldNode: null
@@ -1875,12 +1874,12 @@ function registerLiteXMLCommand(editor, dataSource) {
1875
1874
  delay: true
1876
1875
  }, dataSource);
1877
1876
  break;
1878
- default: logger$5.warn(`⚠️ Unknown action type: ${action}`);
1877
+ default: logger$7.warn(`⚠️ Unknown action type: ${action}`);
1879
1878
  }
1880
1879
  });
1881
1880
  return false;
1882
1881
  } catch (error) {
1883
- logger$5.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
1882
+ logger$7.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
1884
1883
  return false;
1885
1884
  }
1886
1885
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
@@ -1905,9 +1904,9 @@ function handleModify(editor, dataSource, arrayXml, delay) {
1905
1904
  try {
1906
1905
  const { oldNode, newNode } = tryParseChild(child, editor);
1907
1906
  if (oldNode && newNode) handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor);
1908
- else logger$5.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
1907
+ else logger$7.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
1909
1908
  } catch (error) {
1910
- logger$5.error("❌ Error replacing node:", error);
1909
+ logger$7.error("❌ Error replacing node:", error);
1911
1910
  }
1912
1911
  });
1913
1912
  });
@@ -1931,7 +1930,7 @@ function handleModify(editor, dataSource, arrayXml, delay) {
1931
1930
  } else prevNode = prevNode.insertAfter(newNode);
1932
1931
  else $insertNodes([newNode]);
1933
1932
  } catch (error) {
1934
- logger$5.error("❌ Error replacing node:", error);
1933
+ logger$7.error("❌ Error replacing node:", error);
1935
1934
  }
1936
1935
  });
1937
1936
  });
@@ -2068,7 +2067,7 @@ function handleInsert(editor, payload, dataSource) {
2068
2067
  });
2069
2068
  }
2070
2069
  } catch (error) {
2071
- logger$5.error("❌ Error inserting node:", error);
2070
+ logger$7.error("❌ Error inserting node:", error);
2072
2071
  }
2073
2072
  });
2074
2073
  }
@@ -2164,6 +2163,39 @@ function registerLiteXMLDiffCommand(editor) {
2164
2163
  * Service ID for Node service
2165
2164
  */
2166
2165
  const INodeService = genServiceId("INodeService");
2166
+ /**
2167
+ * Default implementation of INodeService
2168
+ */
2169
+ var NodeService = class {
2170
+ constructor() {
2171
+ this.processNodeTreeHandlers = [];
2172
+ }
2173
+ registerProcessNodeTree(process) {
2174
+ this.processNodeTreeHandlers.push(process);
2175
+ }
2176
+ processNodeTree(inode) {
2177
+ for (const handler of this.processNodeTreeHandlers) handler(inode);
2178
+ }
2179
+ };
2180
+ //#endregion
2181
+ //#region src/plugins/inode/plugin/index.ts
2182
+ /**
2183
+ * LitexmlPlugin - A plugin that provides XML-based data source support
2184
+ * Allows converting between Lexical editor state and XML format
2185
+ */
2186
+ const INodePlugin = class extends KernelPlugin {
2187
+ static {
2188
+ this.pluginName = "INodePlugin";
2189
+ }
2190
+ constructor(kernel, config) {
2191
+ super();
2192
+ this.kernel = kernel;
2193
+ this.config = config;
2194
+ const nodeService = new NodeService();
2195
+ kernel.registerService(INodeService, nodeService);
2196
+ }
2197
+ onInit() {}
2198
+ };
2167
2199
  //#endregion
2168
2200
  //#region src/plugins/litexml/service/litexml-service.ts
2169
2201
  /**
@@ -2206,7 +2238,7 @@ var LitexmlService = class {
2206
2238
  };
2207
2239
  //#endregion
2208
2240
  //#region src/plugins/litexml/data-source/litexml-data-source.ts
2209
- const logger$4 = createDebugLogger("plugin", "litexml");
2241
+ const logger$6 = createDebugLogger("plugin", "litexml");
2210
2242
  var IXmlWriterContext = class {
2211
2243
  createXmlNode(tagName, attributes, textContent) {
2212
2244
  return {
@@ -2234,7 +2266,7 @@ var LitexmlDataSource = class extends DataSource {
2234
2266
  const xml = this.parseXMLString(litexml);
2235
2267
  const inode = this.xmlToLexical(xml);
2236
2268
  this.getService?.(INodeService)?.processNodeTree(inode);
2237
- logger$4.debug("Parsed XML to Lexical State:", inode);
2269
+ logger$6.debug("Parsed XML to Lexical State:", inode);
2238
2270
  return inode;
2239
2271
  }
2240
2272
  /**
@@ -2255,7 +2287,7 @@ var LitexmlDataSource = class extends DataSource {
2255
2287
  });
2256
2288
  editor.setEditorState(newState);
2257
2289
  } catch (error) {
2258
- logger$4.error("Failed to parse XML:", error);
2290
+ logger$6.error("Failed to parse XML:", error);
2259
2291
  throw error;
2260
2292
  }
2261
2293
  }
@@ -2290,7 +2322,7 @@ var LitexmlDataSource = class extends DataSource {
2290
2322
  return this.lexicalToXML(rootNode);
2291
2323
  });
2292
2324
  } catch (error) {
2293
- logger$4.error("Failed to export to XML:", error);
2325
+ logger$6.error("Failed to export to XML:", error);
2294
2326
  throw error;
2295
2327
  }
2296
2328
  }
@@ -2481,7 +2513,7 @@ var LitexmlDataSource = class extends DataSource {
2481
2513
  };
2482
2514
  //#endregion
2483
2515
  //#region src/plugins/markdown/utils/logger.ts
2484
- const logger$3 = createDebugLogger("plugin", "markdown");
2516
+ const logger$5 = createDebugLogger("plugin", "markdown");
2485
2517
  //#endregion
2486
2518
  //#region src/plugins/markdown/data-source/markdown/parse.ts
2487
2519
  const selfClosingHtmlTags = new Set([
@@ -2574,7 +2606,7 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
2574
2606
  const top = ctx.pop();
2575
2607
  htmlStack.pop();
2576
2608
  if (top?.tag !== tag) {
2577
- logger$3.warn("HTML tag mismatch:", tag);
2609
+ logger$5.warn("HTML tag mismatch:", tag);
2578
2610
  ret.push(...top?.children || []);
2579
2611
  return ret;
2580
2612
  }
@@ -2664,7 +2696,7 @@ function registerDefaultReaders(markdownReaders) {
2664
2696
  }
2665
2697
  function parseMarkdownToLexical(markdown, markdownReaders = {}) {
2666
2698
  const ast = remark().use(remarkCjkFriendly).use(remarkMath).use([[remarkGfm, { singleTilde: false }]]).parse(markdown);
2667
- logger$3.debug("Parsed MDAST:", ast);
2699
+ logger$5.debug("Parsed MDAST:", ast);
2668
2700
  const ctx = new MarkdownContext(ast, markdown);
2669
2701
  registerDefaultReaders(markdownReaders);
2670
2702
  return convertMdastToLexical(ast, 0, ctx, markdownReaders);
@@ -2812,7 +2844,7 @@ function insertIRootNode(editor, root, selection) {
2812
2844
  }
2813
2845
  //#endregion
2814
2846
  //#region src/plugins/markdown/command/index.ts
2815
- const logger$2 = createDebugLogger("plugin", "markdown");
2847
+ const logger$4 = createDebugLogger("plugin", "markdown");
2816
2848
  const INSERT_MARKDOWN_COMMAND = createCommand("INSERT_MARKDOWN_COMMAND");
2817
2849
  const GET_MARKDOWN_SELECTION_COMMAND = createCommand("GET_MARKDOWN_SELECTION_COMMAND");
2818
2850
  function restoreToEntry(editor, entry) {
@@ -2826,7 +2858,7 @@ const getLineNumber = (content, charIndex) => {
2826
2858
  function registerMarkdownCommand(editor, kernel, service) {
2827
2859
  return mergeRegister(editor.registerCommand(INSERT_MARKDOWN_COMMAND, (payload) => {
2828
2860
  const { markdown } = payload;
2829
- logger$2.debug("INSERT_MARKDOWN_COMMAND payload:", payload);
2861
+ logger$4.debug("INSERT_MARKDOWN_COMMAND payload:", payload);
2830
2862
  restoreToEntry(editor, payload.historyState);
2831
2863
  setTimeout(() => {
2832
2864
  editor.update(() => {
@@ -2834,11 +2866,11 @@ function registerMarkdownCommand(editor, kernel, service) {
2834
2866
  const root = parseMarkdownToLexical(markdown, service.markdownReaders);
2835
2867
  const selection = $getSelection();
2836
2868
  const nodes = $generateNodesFromSerializedNodes(root.children);
2837
- logger$2.debug("INSERT_MARKDOWN_COMMAND nodes:", nodes);
2869
+ logger$4.debug("INSERT_MARKDOWN_COMMAND nodes:", nodes);
2838
2870
  $insertGeneratedNodes(editor, nodes, selection);
2839
2871
  return true;
2840
2872
  } catch (error) {
2841
- logger$2.error("Failed to handle markdown paste:", error);
2873
+ logger$4.error("Failed to handle markdown paste:", error);
2842
2874
  }
2843
2875
  }, { tag: HISTORY_PUSH_TAG });
2844
2876
  }, 0);
@@ -2869,8 +2901,8 @@ function registerMarkdownCommand(editor, kernel, service) {
2869
2901
  const startLine = getLineNumber(markdownContent, startIndex);
2870
2902
  const endLine = getLineNumber(markdownContent, endIndex);
2871
2903
  payload.onResult(startLine, endLine);
2872
- logger$2.debug("GET_MARKDOWN_SELECTION_COMMAND markdownContent:", markdownContent);
2873
- logger$2.debug("GET_MARKDOWN_SELECTION_COMMAND startLine:", startLine, "endLine:", endLine);
2904
+ logger$4.debug("GET_MARKDOWN_SELECTION_COMMAND markdownContent:", markdownContent);
2905
+ logger$4.debug("GET_MARKDOWN_SELECTION_COMMAND startLine:", startLine, "endLine:", endLine);
2874
2906
  return markdownContent;
2875
2907
  } });
2876
2908
  }
@@ -2930,7 +2962,7 @@ var MarkdownDataSource = class extends DataSource {
2930
2962
  }).processSync(markdown);
2931
2963
  return String(result);
2932
2964
  } catch (error) {
2933
- logger$3.error("Failed to format markdown:", error);
2965
+ logger$5.error("Failed to format markdown:", error);
2934
2966
  return markdown;
2935
2967
  }
2936
2968
  }
@@ -2943,7 +2975,7 @@ var MarkdownDataSource = class extends DataSource {
2943
2975
  read(editor, data) {
2944
2976
  const inode = { root: parseMarkdownToLexical(data, this.markdownService.markdownReaders) };
2945
2977
  this.getService?.(INodeService)?.processNodeTree(inode);
2946
- logger$3.debug("Parsed Lexical State:", inode);
2978
+ logger$5.debug("Parsed Lexical State:", inode);
2947
2979
  editor.setEditorState(editor.parseEditorState(inode));
2948
2980
  }
2949
2981
  write(editor, options) {
@@ -4116,6 +4148,15 @@ const DEFAULT_OPTIONS = {
4116
4148
  lineNumbers: false,
4117
4149
  tabSize: 2
4118
4150
  };
4151
+ const extractSerializedCodeText$1 = (children) => children.map((child) => {
4152
+ if (!child || typeof child !== "object") return "";
4153
+ const record = child;
4154
+ if (record.type === "linebreak") return "\n";
4155
+ if (record.type === "tab") return " ";
4156
+ if (typeof record.text === "string") return record.text;
4157
+ if (Array.isArray(record.children)) return extractSerializedCodeText$1(record.children);
4158
+ return "";
4159
+ }).join("");
4119
4160
  function hasChildDOMNodeTag(node, tagName) {
4120
4161
  for (const child of node.childNodes) {
4121
4162
  if (isHTMLElement(child) && child.tagName === tagName) return true;
@@ -4138,7 +4179,7 @@ var CodeMirrorNode = class CodeMirrorNode extends DecoratorNode {
4138
4179
  }
4139
4180
  static importJSON(serializedNode) {
4140
4181
  let code = serializedNode.code;
4141
- if ("children" in serializedNode) code = serializedNode.children?.map((child) => child.text).join("") || "";
4182
+ if ("children" in serializedNode && Array.isArray(serializedNode.children)) code = extractSerializedCodeText$1(serializedNode.children);
4142
4183
  return $createCodeMirrorNode(serializedNode.language, code, serializedNode.codeTheme, serializedNode.options).updateFromJSON(serializedNode);
4143
4184
  }
4144
4185
  static importDOM() {
@@ -4301,6 +4342,9 @@ function $convertPreElement(domNode) {
4301
4342
  const codeTheme = domNode.getAttribute(THEME_DATA_ATTRIBUTE) || "";
4302
4343
  return { node: $createCodeMirrorNode(language || "plain", domNode.textContent || "", codeTheme) };
4303
4344
  }
4345
+ function $isCodeMirrorNode(node) {
4346
+ return node.getType() === CodeMirrorNode.getType();
4347
+ }
4304
4348
  function convertCodeNoop() {
4305
4349
  return { node: null };
4306
4350
  }
@@ -4364,7 +4408,7 @@ function runPasteHandlers(ctx, handlers) {
4364
4408
  }
4365
4409
  //#endregion
4366
4410
  //#region src/plugins/common/plugin/register.ts
4367
- const logger$1 = createDebugLogger("plugin", "common");
4411
+ const logger$3 = createDebugLogger("plugin", "common");
4368
4412
  function resolveElement(element, isBackward, focusOffset) {
4369
4413
  const parent = element.getParent();
4370
4414
  let offset = focusOffset;
@@ -4465,7 +4509,7 @@ function registerRichKeydown(editor, kernel, options) {
4465
4509
  selection.insertText(text);
4466
4510
  });
4467
4511
  } catch (error) {
4468
- logger$1.error("❌ Failed to paste as plain text:", error);
4512
+ logger$3.error("❌ Failed to paste as plain text:", error);
4469
4513
  }
4470
4514
  }, {
4471
4515
  enabled: enableHotkey,
@@ -4974,12 +5018,12 @@ const CommonPlugin = class extends KernelPlugin {
4974
5018
  };
4975
5019
  //#endregion
4976
5020
  //#region src/plugins/code/node/code.ts
4977
- var CodeNode$1 = class CodeNode$1 extends CardLikeElementNode {
5021
+ var CodeNode = class CodeNode extends CardLikeElementNode {
4978
5022
  static getType() {
4979
5023
  return "codeInline";
4980
5024
  }
4981
5025
  static clone(node) {
4982
- return new CodeNode$1(node.__key);
5026
+ return new CodeNode(node.__key);
4983
5027
  }
4984
5028
  static importJSON(serializedNode) {
4985
5029
  return $createCodeNode().updateFromJSON(serializedNode);
@@ -5021,14 +5065,14 @@ var CodeNode$1 = class CodeNode$1 extends CardLikeElementNode {
5021
5065
  }
5022
5066
  };
5023
5067
  function $createCodeNode(textContent) {
5024
- const codeNode = $applyNodeReplacement(new CodeNode$1());
5068
+ const codeNode = $applyNodeReplacement(new CodeNode());
5025
5069
  const cursorNode = $createCursorNode();
5026
5070
  codeNode.append(cursorNode);
5027
5071
  if (textContent) codeNode.append($createTextNode(textContent));
5028
5072
  return codeNode;
5029
5073
  }
5030
5074
  function $isCodeInlineNode(node) {
5031
- return node instanceof CodeNode$1;
5075
+ return node instanceof CodeNode;
5032
5076
  }
5033
5077
  function getCodeInlineNode(node) {
5034
5078
  if ($isCursorNode(node)) {
@@ -5071,7 +5115,7 @@ function registerCodeInlineCommand(editor) {
5071
5115
  function registerCodeInline(editor, kernel, options) {
5072
5116
  const { enableHotkey = true } = options || {};
5073
5117
  return mergeRegister(editor.registerUpdateListener(({ mutatedNodes }) => {
5074
- const keys = (mutatedNodes?.get(CodeNode$1))?.keys() || [];
5118
+ const keys = (mutatedNodes?.get(CodeNode))?.keys() || [];
5075
5119
  const needAddBefore = /* @__PURE__ */ new Set();
5076
5120
  editor.getEditorState().read(() => {
5077
5121
  for (const key of keys) {
@@ -5101,7 +5145,7 @@ const CodePlugin = class extends KernelPlugin {
5101
5145
  super();
5102
5146
  this.kernel = kernel;
5103
5147
  this.config = config;
5104
- kernel.registerNodes([CodeNode$1]);
5148
+ kernel.registerNodes([CodeNode]);
5105
5149
  kernel.registerThemes({ codeInline: config?.theme || "editor-code" });
5106
5150
  }
5107
5151
  onInit(editor) {
@@ -5116,23 +5160,23 @@ const CodePlugin = class extends KernelPlugin {
5116
5160
  registerLiteXml() {
5117
5161
  const litexmlService = this.kernel.requireService(ILitexmlService);
5118
5162
  if (!litexmlService) return;
5119
- litexmlService.registerXMLWriter(CodeNode$1.getType(), (node, ctx) => {
5163
+ litexmlService.registerXMLWriter(CodeNode.getType(), (node, ctx) => {
5120
5164
  return ctx.createXmlNode("codeInline", {});
5121
5165
  });
5122
5166
  litexmlService.registerXMLReader("codeInline", (xmlElement, children) => {
5123
- return INodeHelper.createElementNode(CodeNode$1.getType(), { children });
5167
+ return INodeHelper.createElementNode(CodeNode.getType(), { children });
5124
5168
  });
5125
5169
  }
5126
5170
  registerMarkdown() {
5127
5171
  const markdownService = this.kernel.requireService(IMarkdownShortCutService);
5128
5172
  if (!markdownService) return;
5129
- markdownService.registerMarkdownWriter(CodeNode$1.getType(), (ctx, node) => {
5173
+ markdownService.registerMarkdownWriter(CodeNode.getType(), (ctx, node) => {
5130
5174
  ctx.appendLine(`\`${node.getTextContent().replaceAll("", "")}\``);
5131
5175
  return true;
5132
5176
  });
5133
5177
  markdownService.registerMarkdownShortCuts([{
5134
5178
  process: (selection) => {
5135
- if (selection.getNodes().some((node) => node.getType() === CodeNode$1.getType())) return false;
5179
+ if (selection.getNodes().some((node) => node.getType() === CodeNode.getType())) return false;
5136
5180
  const text = selection.getTextContent();
5137
5181
  selection.removeText();
5138
5182
  selection.insertNodes([$createCodeNode(text), $createCursorNode()]);
@@ -5154,1249 +5198,3174 @@ const CodePlugin = class extends KernelPlugin {
5154
5198
  }
5155
5199
  };
5156
5200
  //#endregion
5157
- //#region src/plugins/hr/node/HorizontalRuleNode.ts
5158
- var HorizontalRuleNode = class HorizontalRuleNode extends DecoratorNode {
5159
- static getType() {
5160
- return "horizontalrule";
5161
- }
5162
- static clone(node) {
5163
- return new HorizontalRuleNode(node.__key);
5164
- }
5165
- static importJSON(serializedNode) {
5166
- return $createHorizontalRuleNode().updateFromJSON(serializedNode);
5167
- }
5168
- static importDOM() {
5169
- return { hr: () => ({
5170
- conversion: $convertHorizontalRuleElement,
5171
- priority: 0
5172
- }) };
5173
- }
5174
- exportDOM() {
5175
- return { element: document.createElement("hr") };
5176
- }
5177
- createDOM(config) {
5178
- const element = document.createElement("div");
5179
- addClassNamesToElement(element, config.theme.hr);
5180
- return element;
5181
- }
5182
- getTextContent() {
5183
- return "\n";
5184
- }
5185
- isInline() {
5186
- return false;
5187
- }
5188
- updateDOM() {
5189
- return false;
5190
- }
5191
- decorate(editor) {
5192
- const decorator = getKernelFromEditor(editor)?.getDecorator("horizontalrule");
5193
- if (!decorator) return null;
5194
- if (typeof decorator === "function") return decorator(this, editor);
5195
- return {
5196
- queryDOM: decorator.queryDOM,
5197
- render: decorator.render(this, editor)
5198
- };
5199
- }
5200
- };
5201
- function $createHorizontalRuleNode() {
5202
- return $applyNodeReplacement(new HorizontalRuleNode());
5203
- }
5204
- function $convertHorizontalRuleElement() {
5205
- return { node: $createHorizontalRuleNode() };
5206
- }
5207
- function $isHorizontalRuleNode(node) {
5208
- return node.getType() === HorizontalRuleNode.getType();
5209
- }
5201
+ //#region src/plugins/codeblock/command/symbols.ts
5202
+ const UPDATE_CODEBLOCK_LANG = createCommand("UPDATE_CODEBLOCK_LANG");
5210
5203
  //#endregion
5211
- //#region src/plugins/hr/command/index.ts
5212
- const INSERT_HORIZONTAL_RULE_COMMAND = createCommand("INSERT_HORIZONTAL_RULE_COMMAND");
5213
- function registerHorizontalRuleCommand(editor) {
5214
- return editor.registerCommand(INSERT_HORIZONTAL_RULE_COMMAND, () => {
5204
+ //#region src/plugins/codemirror-block/command/index.ts
5205
+ const INSERT_CODEMIRROR_COMMAND = createCommand("INSERT_CODEMIRROR_COMMAND");
5206
+ const SELECT_BEFORE_CODEMIRROR_COMMAND = createCommand("SELECT_BEFORE_CODEMIRROR_COMMAND");
5207
+ const SELECT_AFTER_CODEMIRROR_COMMAND = createCommand("SELECT_AFTER_CODEMIRROR_COMMAND");
5208
+ function registerCodeMirrorCommand(editor) {
5209
+ return mergeRegister(editor.registerCommand(INSERT_CODEMIRROR_COMMAND, () => {
5215
5210
  editor.update(() => {
5216
- $insertNodes([$createHorizontalRuleNode()]);
5211
+ const codeMirrorNode = $createCodeMirrorNode("", "");
5212
+ $insertNodes([codeMirrorNode]);
5213
+ const selection = $createNodeSelection();
5214
+ selection.add(codeMirrorNode.getKey());
5215
+ $setSelection(selection);
5217
5216
  });
5218
5217
  return true;
5219
- }, COMMAND_PRIORITY_EDITOR);
5220
- }
5221
- //#endregion
5222
- //#region src/plugins/hr/plugin/index.ts
5223
- const HRPlugin = class extends KernelPlugin {
5224
- static {
5225
- this.pluginName = "HRPlugin";
5226
- }
5227
- constructor(kernel, config) {
5228
- super();
5229
- this.kernel = kernel;
5230
- kernel.registerNodes([HorizontalRuleNode]);
5231
- kernel.registerThemes({ hr: config?.theme || "" });
5232
- this.registerDecorator(kernel, "horizontalrule", (node, editor) => {
5233
- return config?.decorator ? config.decorator(node, editor) : null;
5234
- });
5235
- }
5236
- onInit(editor) {
5237
- this.register(registerHorizontalRuleCommand(editor));
5238
- this.registerMarkdown();
5239
- this.registerLiteXml();
5240
- }
5241
- registerLiteXml() {
5242
- const litexmlService = this.kernel.requireService(ILitexmlService);
5243
- if (!litexmlService) return;
5244
- litexmlService.registerXMLWriter(HorizontalRuleNode.getType(), (node, ctx) => {
5245
- if ($isHorizontalRuleNode(node)) return ctx.createXmlNode("hr", {});
5218
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_CODEBLOCK_LANG, (payload) => {
5219
+ const codeMirrorNode = editor.getEditorState().read(() => {
5220
+ const selection = $getSelection();
5221
+ if ($isRangeSelection(selection)) if (selection.isCollapsed()) return $findMatchingParent(selection.anchor.getNode(), $isCodeMirrorNode);
5222
+ else {
5223
+ const anchor = $findMatchingParent(selection.anchor.getNode(), $isCodeMirrorNode);
5224
+ const focus = $findMatchingParent(selection.focus.getNode(), $isCodeMirrorNode);
5225
+ if (anchor && focus && anchor === focus) return anchor;
5226
+ return null;
5227
+ }
5246
5228
  return false;
5247
5229
  });
5248
- litexmlService.registerXMLReader("hr", () => {
5249
- return INodeHelper.createElementNode(HorizontalRuleNode.getType(), {});
5250
- });
5251
- }
5252
- registerMarkdown() {
5253
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
5254
- regExp: /^(---|\*\*\*|___)$/,
5255
- replace: (parentNode, _1, _2, isImport) => {
5256
- const line = $createHorizontalRuleNode();
5257
- if (isImport || parentNode.getNextSibling()) parentNode.replace(line);
5258
- else parentNode.insertBefore(line);
5259
- line.selectNext();
5260
- },
5261
- trigger: "enter",
5262
- type: "element"
5263
- });
5264
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(HorizontalRuleNode.getType(), (ctx, node) => {
5265
- if ($isHorizontalRuleNode(node)) ctx.appendLine("---\n\n");
5266
- });
5267
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("thematicBreak", () => {
5268
- return INodeHelper.createElementNode("horizontalrule", {});
5230
+ if (!codeMirrorNode) return false;
5231
+ queueMicrotask(() => {
5232
+ editor.update(() => {
5233
+ if ($isCodeMirrorNode(codeMirrorNode)) codeMirrorNode.setLang(payload.lang);
5234
+ });
5269
5235
  });
5270
- }
5271
- };
5272
- //#endregion
5273
- //#region src/plugins/link/node/LinkNode.ts
5274
- const logger = createDebugLogger("plugin", "link");
5275
- const SUPPORTED_URL_PROTOCOLS$1 = new Set([
5276
- "http:",
5277
- "https:",
5278
- "mailto:",
5279
- "sms:",
5280
- "tel:"
5281
- ]);
5282
- const HOVER_LINK_COMMAND = createCommand("HOVER_LINK_COMMAND");
5283
- const HOVER_OUT_LINK_COMMAND = createCommand("HOVER_OUT_LINK_COMMAND");
5284
- /** @noInheritDoc */
5285
- var LinkNode = class LinkNode extends ElementNode {
5286
- static getType() {
5287
- return "link";
5288
- }
5289
- static clone(node) {
5290
- return new LinkNode(node.__url, {
5291
- rel: node.__rel,
5292
- target: node.__target,
5293
- title: node.__title
5294
- }, node.__key);
5295
- }
5296
- constructor(url = "", attributes = {}, key) {
5297
- super(key);
5298
- const { target = null, rel = null, title = null } = attributes;
5299
- this.__url = url;
5300
- this.__target = target;
5301
- this.__rel = rel;
5302
- this.__title = title;
5303
- }
5304
- createDOM(config, editor) {
5305
- logger.debug("🔍 config", config);
5306
- const element = document.createElement("a");
5307
- this.updateLinkDOM(null, element, config);
5308
- addClassNamesToElement(element, config.theme.link);
5309
- element.addEventListener("mouseenter", (event) => {
5310
- if (event.target instanceof HTMLElement) {
5311
- event.target.classList.add("hover");
5312
- editor.dispatchCommand(HOVER_LINK_COMMAND, {
5313
- event,
5314
- linkNode: this
5315
- });
5316
- }
5236
+ return true;
5237
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECT_BEFORE_CODEMIRROR_COMMAND, (payload) => {
5238
+ editor.update(() => {
5239
+ const node = $getNodeByKey(payload.key);
5240
+ if (!node) return;
5241
+ const prevNode = node.getPreviousSibling();
5242
+ const sel = prevNode?.selectEnd();
5243
+ console.info("SELECT_BEFORE_CODEMIRROR_COMMAND", prevNode, sel);
5244
+ if (sel) $setSelection(sel);
5245
+ editor.focus();
5317
5246
  });
5318
- element.addEventListener("mouseleave", (event) => {
5319
- if (event.target instanceof HTMLElement) {
5320
- event.target.classList.remove("hover");
5321
- editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
5247
+ return false;
5248
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECT_AFTER_CODEMIRROR_COMMAND, (payload) => {
5249
+ editor.update(() => {
5250
+ const node = $getNodeByKey(payload.key);
5251
+ if (!node) return;
5252
+ const selection = node.getNextSibling()?.selectStart();
5253
+ if (selection) $setSelection(selection);
5254
+ else {
5255
+ const paragraph = $createParagraphNode();
5256
+ node.insertAfter(paragraph);
5257
+ const paragraphSelection = paragraph.selectStart();
5258
+ if (paragraphSelection) $setSelection(paragraphSelection);
5322
5259
  }
5260
+ editor.focus();
5323
5261
  });
5324
- return element;
5325
- }
5326
- updateLinkDOM(prevNode, anchor, _config) {
5327
- if (isHTMLAnchorElement(anchor)) {
5328
- if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url);
5329
- for (const attr of [
5330
- "target",
5331
- "rel",
5332
- "title"
5333
- ]) {
5334
- const key = `__${attr}`;
5335
- const value = this[key];
5336
- if (!prevNode || prevNode[key] !== value) if (value) anchor[attr] = value;
5337
- else anchor.removeAttribute(attr);
5338
- }
5339
- }
5340
- }
5341
- updateDOM(prevNode, anchor, config) {
5342
- this.updateLinkDOM(prevNode, anchor, config);
5343
5262
  return false;
5263
+ }, COMMAND_PRIORITY_EDITOR));
5264
+ }
5265
+ //#endregion
5266
+ //#region src/codemirror/constants.ts
5267
+ const LANGUAGES = [
5268
+ {
5269
+ ext: ["agda"],
5270
+ name: "Agda",
5271
+ syntax: "text/x-agda",
5272
+ value: "agda"
5273
+ },
5274
+ {
5275
+ ext: ["ets", "arkts"],
5276
+ name: "ArkTS",
5277
+ syntax: "text/x-arkts",
5278
+ value: "arkts"
5279
+ },
5280
+ {
5281
+ ext: ["bash"],
5282
+ name: "Bash",
5283
+ syntax: "shell",
5284
+ value: "bash"
5285
+ },
5286
+ {
5287
+ ext: ["vbs"],
5288
+ name: "Basic",
5289
+ syntax: "vbscript",
5290
+ value: "basic"
5291
+ },
5292
+ {
5293
+ ext: [
5294
+ "c",
5295
+ "h",
5296
+ "ino"
5297
+ ],
5298
+ name: "C",
5299
+ syntax: "text/x-csrc",
5300
+ value: "c"
5301
+ },
5302
+ {
5303
+ ext: [
5304
+ "cpp",
5305
+ "c++",
5306
+ "cc",
5307
+ "cxx",
5308
+ "hpp",
5309
+ "h++",
5310
+ "hh",
5311
+ "hxx"
5312
+ ],
5313
+ name: "C++",
5314
+ syntax: "text/x-c++src",
5315
+ value: "cpp"
5316
+ },
5317
+ {
5318
+ ext: ["cs"],
5319
+ name: "C#",
5320
+ syntax: "text/x-csharp",
5321
+ value: "csharp"
5322
+ },
5323
+ {
5324
+ ext: ["css"],
5325
+ name: "CSS",
5326
+ syntax: "css",
5327
+ value: "css"
5328
+ },
5329
+ {
5330
+ ext: ["dart"],
5331
+ name: "Dart",
5332
+ syntax: "dart",
5333
+ value: "dart"
5334
+ },
5335
+ {
5336
+ ext: ["diff", "patch"],
5337
+ name: "Diff",
5338
+ syntax: "diff",
5339
+ value: "diff"
5340
+ },
5341
+ {
5342
+ name: "Dockerfile",
5343
+ syntax: "dockerfile",
5344
+ value: "dockerfile"
5345
+ },
5346
+ {
5347
+ ext: ["erl"],
5348
+ name: "Erlang",
5349
+ syntax: "erlang",
5350
+ value: "erlang"
5351
+ },
5352
+ {
5353
+ ext: ["glsl"],
5354
+ name: "Glsl",
5355
+ syntax: "x-shader/x-vertex",
5356
+ value: "glsl"
5357
+ },
5358
+ {
5359
+ name: "Git",
5360
+ syntax: "shell",
5361
+ value: "git"
5362
+ },
5363
+ {
5364
+ ext: ["go"],
5365
+ name: "Go",
5366
+ syntax: "go",
5367
+ value: "go"
5368
+ },
5369
+ {
5370
+ name: "GraphQL",
5371
+ syntax: "graphql",
5372
+ value: "graphql"
5373
+ },
5374
+ {
5375
+ ext: ["groovy", "gradle"],
5376
+ name: "Groovy",
5377
+ syntax: "groovy",
5378
+ value: "groovy"
5379
+ },
5380
+ {
5381
+ ext: [
5382
+ "html",
5383
+ "htm",
5384
+ "handlebars",
5385
+ "hbs"
5386
+ ],
5387
+ name: "HTML",
5388
+ syntax: "htmlmixed",
5389
+ value: "html"
5390
+ },
5391
+ {
5392
+ name: "HTTP",
5393
+ syntax: "http",
5394
+ value: "http"
5395
+ },
5396
+ {
5397
+ ext: ["java"],
5398
+ name: "Java",
5399
+ syntax: "text/x-java",
5400
+ value: "java"
5401
+ },
5402
+ {
5403
+ ext: ["js"],
5404
+ name: "JavaScript",
5405
+ syntax: "text/javascript",
5406
+ value: "javascript"
5407
+ },
5408
+ {
5409
+ ext: ["json", "map"],
5410
+ name: "JSON",
5411
+ syntax: "application/json",
5412
+ value: "json"
5413
+ },
5414
+ {
5415
+ ext: ["jsx"],
5416
+ name: "JSX",
5417
+ syntax: "jsx",
5418
+ value: "jsx"
5419
+ },
5420
+ {
5421
+ name: "KaTeX",
5422
+ syntax: "simplemode",
5423
+ value: "katex"
5424
+ },
5425
+ {
5426
+ ext: ["kt"],
5427
+ name: "Kotlin",
5428
+ syntax: "text/x-kotlin",
5429
+ value: "kotlin"
5430
+ },
5431
+ {
5432
+ ext: ["less"],
5433
+ name: "Less",
5434
+ syntax: "css",
5435
+ value: "less"
5436
+ },
5437
+ {
5438
+ name: "Makefile",
5439
+ syntax: "cmake",
5440
+ value: "makefile"
5441
+ },
5442
+ {
5443
+ ext: [
5444
+ "markdown",
5445
+ "md",
5446
+ "mkd"
5447
+ ],
5448
+ name: "Markdown",
5449
+ syntax: "markdown",
5450
+ value: "markdown"
5451
+ },
5452
+ {
5453
+ name: "MATLAB",
5454
+ syntax: "octave",
5455
+ value: "matlab"
5456
+ },
5457
+ {
5458
+ ext: ["conf"],
5459
+ name: "Nginx",
5460
+ syntax: "nginx",
5461
+ value: "nginx"
5462
+ },
5463
+ {
5464
+ ext: ["m"],
5465
+ name: "Objective-C",
5466
+ syntax: "text/x-objectivec",
5467
+ value: "objectivec"
5468
+ },
5469
+ {
5470
+ ext: ["p", "pas"],
5471
+ name: "Pascal",
5472
+ syntax: "pascal",
5473
+ value: "pascal"
5474
+ },
5475
+ {
5476
+ ext: ["pl", "pm"],
5477
+ name: "Perl",
5478
+ syntax: "perl",
5479
+ value: "perl"
5480
+ },
5481
+ {
5482
+ ext: [
5483
+ "php",
5484
+ "php3",
5485
+ "php4",
5486
+ "php5",
5487
+ "php7",
5488
+ "phtml"
5489
+ ],
5490
+ name: "PHP",
5491
+ syntax: "text/x-php",
5492
+ value: "php"
5493
+ },
5494
+ {
5495
+ ext: [
5496
+ "ps1",
5497
+ "psd1",
5498
+ "psm1"
5499
+ ],
5500
+ name: "PowerShell",
5501
+ syntax: "powershell",
5502
+ value: "powershell"
5503
+ },
5504
+ {
5505
+ ext: ["proto"],
5506
+ name: "Protobuf",
5507
+ syntax: "protobuf",
5508
+ value: "protobuf"
5509
+ },
5510
+ {
5511
+ ext: [
5512
+ "build",
5513
+ "bzl",
5514
+ "py",
5515
+ "pyw"
5516
+ ],
5517
+ name: "Python",
5518
+ syntax: "python",
5519
+ value: "python"
5520
+ },
5521
+ {
5522
+ ext: ["r", "R"],
5523
+ name: "R",
5524
+ syntax: "r",
5525
+ value: "r"
5526
+ },
5527
+ {
5528
+ ext: ["rb"],
5529
+ name: "Ruby",
5530
+ syntax: "ruby",
5531
+ value: "ruby"
5532
+ },
5533
+ {
5534
+ ext: ["rs"],
5535
+ name: "Rust",
5536
+ syntax: "rust",
5537
+ value: "rust"
5538
+ },
5539
+ {
5540
+ ext: ["scala"],
5541
+ name: "Scala",
5542
+ syntax: "text/x-scala",
5543
+ value: "scala"
5544
+ },
5545
+ {
5546
+ ext: ["sh", "ksh"],
5547
+ name: "Shell",
5548
+ syntax: "shell",
5549
+ value: "shell"
5550
+ },
5551
+ {
5552
+ ext: ["sql"],
5553
+ name: "SQL",
5554
+ syntax: "text/x-sql",
5555
+ value: "sql"
5556
+ },
5557
+ {
5558
+ name: "PL/SQL",
5559
+ syntax: "text/x-plsql",
5560
+ value: "plsql"
5561
+ },
5562
+ {
5563
+ ext: ["swift"],
5564
+ name: "Swift",
5565
+ syntax: "swift",
5566
+ value: "swift"
5567
+ },
5568
+ {
5569
+ ext: ["ts"],
5570
+ name: "TypeScript",
5571
+ syntax: "text/typescript",
5572
+ value: "typescript"
5573
+ },
5574
+ {
5575
+ ext: ["vb"],
5576
+ name: "VB.net",
5577
+ syntax: "vb",
5578
+ value: "vbnet"
5579
+ },
5580
+ {
5581
+ ext: ["vtl"],
5582
+ name: "Velocity",
5583
+ syntax: "velocity",
5584
+ value: "velocity"
5585
+ },
5586
+ {
5587
+ ext: [
5588
+ "xml",
5589
+ "xsl",
5590
+ "xsd",
5591
+ "svg"
5592
+ ],
5593
+ name: "XML",
5594
+ syntax: "xml",
5595
+ value: "xml"
5596
+ },
5597
+ {
5598
+ ext: ["yaml", "yml"],
5599
+ name: "YAML",
5600
+ syntax: "yaml",
5601
+ value: "yaml"
5602
+ },
5603
+ {
5604
+ name: "sTeX",
5605
+ syntax: "text/x-stex",
5606
+ value: "stex"
5607
+ },
5608
+ {
5609
+ ext: [
5610
+ "text",
5611
+ "ltx",
5612
+ "tex"
5613
+ ],
5614
+ name: "LaTeX",
5615
+ syntax: "text/x-latex",
5616
+ value: "latex"
5617
+ },
5618
+ {
5619
+ ext: ["sv", "svh"],
5620
+ name: "SystemVerilog",
5621
+ syntax: "text/x-systemverilog",
5622
+ value: "systemverilog"
5623
+ },
5624
+ {
5625
+ ext: ["sass", "scss"],
5626
+ name: "Sass",
5627
+ syntax: "text/x-sass",
5628
+ value: "sass"
5629
+ },
5630
+ {
5631
+ ext: ["tcl"],
5632
+ name: "Tcl",
5633
+ syntax: "text/x-tcl",
5634
+ value: "tcl"
5635
+ },
5636
+ {
5637
+ ext: ["v"],
5638
+ name: "Verilog",
5639
+ syntax: "text/x-verilog",
5640
+ value: "verilog"
5641
+ },
5642
+ {
5643
+ name: "Vue",
5644
+ syntax: "text/x-vue",
5645
+ value: "vue"
5646
+ },
5647
+ {
5648
+ ext: ["lua"],
5649
+ name: "Lua",
5650
+ syntax: "text/x-lua",
5651
+ value: "lua"
5652
+ },
5653
+ {
5654
+ ext: ["hs"],
5655
+ name: "Haskell",
5656
+ syntax: "haskell",
5657
+ value: "haskell"
5658
+ },
5659
+ {
5660
+ ext: [
5661
+ "properties",
5662
+ "ini",
5663
+ "in"
5664
+ ],
5665
+ name: "Properties",
5666
+ syntax: "properties",
5667
+ value: "properties"
5668
+ },
5669
+ {
5670
+ ext: ["toml"],
5671
+ name: "TOML",
5672
+ syntax: "toml",
5673
+ value: "toml"
5674
+ },
5675
+ {
5676
+ ext: ["cyp", "cypher"],
5677
+ name: "Cypher",
5678
+ syntax: "cypher",
5679
+ value: "cypher"
5680
+ },
5681
+ {
5682
+ ext: ["tsx"],
5683
+ name: "TSX",
5684
+ syntax: "jsx",
5685
+ value: "tsx"
5686
+ },
5687
+ {
5688
+ ext: ["fs"],
5689
+ name: "F#",
5690
+ syntax: "mllike",
5691
+ value: "f#"
5692
+ },
5693
+ {
5694
+ ext: [
5695
+ "ml",
5696
+ "mli",
5697
+ "mll",
5698
+ "mly"
5699
+ ],
5700
+ name: "OCaml",
5701
+ syntax: "mllike",
5702
+ value: "ocaml"
5703
+ },
5704
+ {
5705
+ ext: [
5706
+ "clj",
5707
+ "cljc",
5708
+ "cljx"
5709
+ ],
5710
+ name: "Clojure",
5711
+ syntax: "clojure",
5712
+ value: "clojure"
5713
+ },
5714
+ {
5715
+ name: "ABAP",
5716
+ syntax: "abap",
5717
+ value: "abap"
5718
+ },
5719
+ {
5720
+ ext: ["jl"],
5721
+ name: "Julia",
5722
+ syntax: "julia",
5723
+ value: "julia"
5724
+ },
5725
+ {
5726
+ ext: ["cmake"],
5727
+ name: "CMake",
5728
+ syntax: "cmake",
5729
+ value: "cmake"
5730
+ },
5731
+ {
5732
+ ext: ["scm", "ss"],
5733
+ name: "Scheme",
5734
+ syntax: "scheme",
5735
+ value: "scheme"
5736
+ },
5737
+ {
5738
+ ext: [
5739
+ "cl",
5740
+ "lisp",
5741
+ "el"
5742
+ ],
5743
+ name: "Lisp",
5744
+ syntax: "commonlisp",
5745
+ value: "commonlisp"
5746
+ },
5747
+ {
5748
+ ext: [
5749
+ "f90",
5750
+ "f95",
5751
+ "f03"
5752
+ ],
5753
+ name: "Fortran",
5754
+ syntax: "fortran",
5755
+ value: "fortran"
5756
+ },
5757
+ {
5758
+ ext: ["sol"],
5759
+ name: "Solidity",
5760
+ syntax: "solidity",
5761
+ value: "solidity"
5344
5762
  }
5345
- static importDOM() {
5346
- return { a: () => ({
5347
- conversion: $convertAnchorElement,
5348
- priority: 1
5349
- }) };
5350
- }
5351
- static importJSON(serializedNode) {
5352
- return $createLinkNode().updateFromJSON(serializedNode);
5353
- }
5354
- updateFromJSON(serializedNode) {
5355
- return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
5356
- }
5357
- sanitizeUrl(url) {
5358
- url = formatUrl(url);
5359
- try {
5360
- const parsedUrl = new URL(formatUrl(url));
5361
- if (!SUPPORTED_URL_PROTOCOLS$1.has(parsedUrl.protocol)) return "about:blank";
5362
- } catch {
5363
- return url;
5364
- }
5365
- return url;
5366
- }
5367
- exportJSON() {
5368
- return {
5369
- ...super.exportJSON(),
5370
- rel: this.getRel(),
5371
- target: this.getTarget(),
5372
- title: this.getTitle(),
5373
- url: this.getURL()
5374
- };
5375
- }
5376
- getURL() {
5377
- return this.getLatest().__url;
5378
- }
5379
- setURL(url) {
5380
- const writable = this.getWritable();
5381
- writable.__url = url;
5382
- return writable;
5383
- }
5384
- getTarget() {
5385
- return this.getLatest().__target;
5763
+ ];
5764
+ LANGUAGES.sort((modeA, modeB) => {
5765
+ const nameA = modeA.name.toLowerCase();
5766
+ const nameB = modeB.name.toLowerCase();
5767
+ if (nameA === nameB) return 0;
5768
+ if (nameA < nameB) return -1;
5769
+ return 1;
5770
+ });
5771
+ LANGUAGES.unshift({
5772
+ name: "Plain Text",
5773
+ syntax: "simplemode",
5774
+ value: "plain"
5775
+ });
5776
+ //#endregion
5777
+ //#region src/plugins/codemirror-block/lib/mode.ts
5778
+ function modeMatch(mode = "") {
5779
+ mode = mode.toLocaleLowerCase() || "plain";
5780
+ return LANGUAGES.find((m) => m.value === mode || m.ext?.includes(mode))?.value || "plain";
5781
+ }
5782
+ //#endregion
5783
+ //#region src/plugins/codemirror-block/plugin/index.ts
5784
+ const CodemirrorPlugin = class extends KernelPlugin {
5785
+ static {
5786
+ this.pluginName = "CodemirrorPlugin";
5386
5787
  }
5387
- setTarget(target) {
5388
- const writable = this.getWritable();
5389
- writable.__target = target;
5390
- return writable;
5788
+ constructor(kernel, config) {
5789
+ super();
5790
+ this.kernel = kernel;
5791
+ kernel.registerNodes([CodeMirrorNode]);
5792
+ kernel.registerThemes({ hr: config?.theme || "" });
5793
+ this.registerDecorator(kernel, "codemirror", (node, editor) => {
5794
+ return config?.decorator ? config.decorator(node, editor) : null;
5795
+ });
5391
5796
  }
5392
- getRel() {
5393
- return this.getLatest().__rel;
5797
+ onInit(editor) {
5798
+ this.register(registerCodeMirrorCommand(editor));
5799
+ this.registerMarkdown();
5800
+ this.registerLiteXml();
5394
5801
  }
5395
- setRel(rel) {
5396
- const writable = this.getWritable();
5397
- writable.__rel = rel;
5398
- return writable;
5802
+ registerLiteXml() {
5803
+ const litexmlService = this.kernel.requireService(ILitexmlService);
5804
+ if (!litexmlService) return;
5805
+ litexmlService.registerXMLWriter(CodeMirrorNode.getType(), (node, ctx) => {
5806
+ const codeMirrorNode = node;
5807
+ return ctx.createXmlNode("code", { lang: codeMirrorNode.lang || "plain" }, codeMirrorNode.code);
5808
+ });
5809
+ litexmlService.registerXMLReader("code", (xmlElement, children) => {
5810
+ const text = children.map((v) => v.text || "").join("");
5811
+ const language = xmlElement.getAttribute("lang") || "plain";
5812
+ return INodeHelper.createTypeNode("code", {
5813
+ code: text || xmlElement.textContent || "",
5814
+ language: modeMatch(language),
5815
+ version: 1
5816
+ });
5817
+ });
5399
5818
  }
5400
- getTitle() {
5401
- return this.getLatest().__title;
5819
+ registerMarkdown() {
5820
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
5821
+ markdownService?.registerMarkdownShortCut({
5822
+ regExp: /^(```|···)(.+)?$/,
5823
+ replace: (parentNode, _, match) => {
5824
+ const code = $createCodeMirrorNode(modeMatch(match[2]), "");
5825
+ parentNode.replace(code);
5826
+ const sel = $createNodeSelection();
5827
+ sel.add(code.getKey());
5828
+ $setSelection(sel);
5829
+ },
5830
+ trigger: "enter",
5831
+ type: "element"
5832
+ });
5833
+ markdownService?.registerMarkdownWriter(CodeMirrorNode.getType(), (ctx, node) => {
5834
+ if (node instanceof CodeMirrorNode) {
5835
+ ctx.appendLine("```" + node.lang);
5836
+ ctx.appendLine("\n");
5837
+ ctx.appendLine(node.code);
5838
+ ctx.appendLine("\n```\n");
5839
+ }
5840
+ });
5841
+ markdownService?.registerMarkdownReader("code", (node) => {
5842
+ const language = node.lang ? modeMatch(node.lang) : "plain";
5843
+ return INodeHelper.createTypeNode("code", {
5844
+ code: node.value,
5845
+ language,
5846
+ version: 1
5847
+ });
5848
+ });
5402
5849
  }
5403
- setTitle(title) {
5404
- const writable = this.getWritable();
5405
- writable.__title = title;
5406
- return writable;
5850
+ };
5851
+ //#endregion
5852
+ //#region src/plugins/upload/service/i-upload-service.ts
5853
+ const IUploadService = genServiceId("UploadService");
5854
+ //#endregion
5855
+ //#region src/plugins/file/node/FileNode.ts
5856
+ var FileNode = class FileNode extends DecoratorNode {
5857
+ static getType() {
5858
+ return "file";
5407
5859
  }
5408
- insertNewAfter(_, restoreSelection = true) {
5409
- const linkNode = $createLinkNode(this.__url, {
5410
- rel: this.__rel,
5411
- target: this.__target,
5412
- title: this.__title
5413
- });
5414
- this.insertAfter(linkNode, restoreSelection);
5415
- return linkNode;
5860
+ static clone(node) {
5861
+ return new FileNode(node.__name, node.__fileUrl, node.__size, node.__status, node.__message, node.__key);
5416
5862
  }
5417
- canInsertTextBefore() {
5418
- return false;
5863
+ static importJSON(serializedNode) {
5864
+ return new FileNode(serializedNode.name, serializedNode.fileUrl, serializedNode.size, serializedNode.status, serializedNode.message);
5419
5865
  }
5420
- canInsertTextAfter() {
5421
- return false;
5866
+ static importDOM() {
5867
+ return { span: (node) => {
5868
+ if (node.classList.contains("file")) return {
5869
+ conversion: $convertFileElement,
5870
+ priority: 0
5871
+ };
5872
+ return null;
5873
+ } };
5422
5874
  }
5423
- canBeEmpty() {
5424
- return false;
5875
+ get name() {
5876
+ return this.__name;
5425
5877
  }
5426
- isInline() {
5427
- return true;
5878
+ get fileUrl() {
5879
+ return this.__fileUrl;
5428
5880
  }
5429
- extractWithChild(child, selection) {
5430
- if (!$isRangeSelection(selection)) return false;
5431
- const anchorNode = selection.anchor.getNode();
5432
- const focusNode = selection.focus.getNode();
5433
- return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && selection.getTextContent().length > 0;
5881
+ get size() {
5882
+ return this.__size;
5434
5883
  }
5435
- isEmailURI() {
5436
- return this.__url.startsWith("mailto:");
5884
+ get status() {
5885
+ return this.__status;
5437
5886
  }
5438
- isWebSiteURI() {
5439
- return this.__url.startsWith("https://") || this.__url.startsWith("http://");
5887
+ get message() {
5888
+ return this.__message;
5440
5889
  }
5441
- };
5442
- function $convertAnchorElement(domNode) {
5443
- let node = null;
5444
- if (isHTMLAnchorElement(domNode)) {
5445
- const content = domNode.textContent;
5446
- if (content !== null && content !== "" || domNode.children.length > 0) node = $createLinkNode(domNode.getAttribute("href") || "", {
5447
- rel: domNode.getAttribute("rel"),
5448
- target: domNode.getAttribute("target"),
5449
- title: domNode.getAttribute("title")
5450
- });
5451
- }
5452
- return { node };
5453
- }
5454
- /**
5455
- * Takes a URL and creates a LinkNode.
5456
- * @param url - The URL the LinkNode should direct to.
5457
- * @param attributes - Optional HTML a tag attributes \\{ target, rel, title \\}
5458
- * @returns The LinkNode.
5459
- */
5460
- function $createLinkNode(url = "", attributes) {
5461
- return $applyNodeReplacement(new LinkNode(url, attributes));
5462
- }
5463
- /**
5464
- * Determines if node is a LinkNode.
5465
- * @param node - The node to be checked.
5466
- * @returns true if node is a LinkNode, false otherwise.
5467
- */
5468
- function $isLinkNode(node) {
5469
- return node instanceof LinkNode;
5470
- }
5471
- var AutoLinkNode = class AutoLinkNode extends LinkNode {
5472
- constructor(url = "", attributes = {}, key) {
5473
- super(url, attributes, key);
5474
- this.__isUnlinked = attributes.isUnlinked !== void 0 && attributes.isUnlinked !== null ? attributes.isUnlinked : false;
5475
- }
5476
- static getType() {
5477
- return "autolink";
5478
- }
5479
- static clone(node) {
5480
- return new AutoLinkNode(node.__url, {
5481
- isUnlinked: node.__isUnlinked,
5482
- rel: node.__rel,
5483
- target: node.__target,
5484
- title: node.__title
5485
- }, node.__key);
5890
+ constructor(name, fileUrl, size, status, message, key) {
5891
+ super(key);
5892
+ this.__name = name;
5893
+ this.__fileUrl = fileUrl;
5894
+ this.__size = size;
5895
+ this.__status = status || "pending";
5896
+ this.__message = message;
5486
5897
  }
5487
- getIsUnlinked() {
5488
- return this.__isUnlinked;
5898
+ setUploaded(url) {
5899
+ const writable = this.getWritable();
5900
+ writable.__fileUrl = url;
5901
+ writable.__status = "uploaded";
5902
+ writable.__message = void 0;
5489
5903
  }
5490
- setIsUnlinked(value) {
5491
- const self = this.getWritable();
5492
- self.__isUnlinked = value;
5493
- return self;
5904
+ setError(message) {
5905
+ const writable = this.getWritable();
5906
+ writable.__status = "error";
5907
+ writable.__message = message;
5494
5908
  }
5495
- createDOM(config, editor) {
5496
- logger.debug("🔍 config", config);
5497
- if (this.__isUnlinked) return document.createElement("span");
5498
- else return super.createDOM(config, editor);
5909
+ exportDOM() {
5910
+ return { element: document.createElement("span") };
5499
5911
  }
5500
- updateDOM(prevNode, anchor, config) {
5501
- return super.updateDOM(prevNode, anchor, config) || prevNode.__isUnlinked !== this.__isUnlinked;
5912
+ createDOM(config) {
5913
+ const element = document.createElement("span");
5914
+ addClassNamesToElement(element, config.theme.file);
5915
+ return element;
5502
5916
  }
5503
- static importJSON(serializedNode) {
5504
- return $createAutoLinkNode().updateFromJSON(serializedNode);
5917
+ exportJSON() {
5918
+ return {
5919
+ ...super.exportJSON(),
5920
+ fileUrl: this.fileUrl,
5921
+ message: this.message,
5922
+ name: this.name,
5923
+ size: this.size,
5924
+ status: this.status
5925
+ };
5505
5926
  }
5506
5927
  updateFromJSON(serializedNode) {
5507
- return super.updateFromJSON(serializedNode).setIsUnlinked(serializedNode.isUnlinked || false);
5928
+ return super.updateFromJSON(serializedNode);
5508
5929
  }
5509
- static importDOM() {
5510
- return null;
5930
+ getTextContent() {
5931
+ return "\n";
5511
5932
  }
5512
- exportJSON() {
5933
+ updateDOM() {
5934
+ return false;
5935
+ }
5936
+ decorate(editor) {
5937
+ const decorator = getKernelFromEditor(editor)?.getDecorator("file");
5938
+ if (!decorator) return null;
5939
+ if (typeof decorator === "function") return decorator(this, editor);
5513
5940
  return {
5514
- ...super.exportJSON(),
5515
- isUnlinked: this.__isUnlinked
5941
+ queryDOM: decorator.queryDOM,
5942
+ render: decorator.render(this, editor)
5516
5943
  };
5517
5944
  }
5518
- insertNewAfter(selection, restoreSelection = true) {
5519
- const element = this.getParentOrThrow().insertNewAfter(selection, restoreSelection);
5520
- if ($isElementNode(element)) {
5521
- const linkNode = $createAutoLinkNode(this.__url, {
5522
- isUnlinked: this.__isUnlinked,
5523
- rel: this.__rel,
5524
- target: this.__target,
5525
- title: this.__title
5526
- });
5527
- element.append(linkNode);
5528
- return linkNode;
5529
- }
5530
- return null;
5531
- }
5532
5945
  };
5533
- /**
5534
- * Takes a URL and creates an AutoLinkNode. AutoLinkNodes are generally automatically generated
5535
- * during typing, which is especially useful when a button to generate a LinkNode is not practical.
5536
- * @param url - The URL the LinkNode should direct to.
5537
- * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
5538
- * @returns The LinkNode.
5539
- */
5540
- function $createAutoLinkNode(url = "", attributes) {
5541
- return $applyNodeReplacement(new AutoLinkNode(url, attributes));
5946
+ function $createFileNode(name = "unknown", fileUrl, size, status, message) {
5947
+ return $applyNodeReplacement(new FileNode(name, fileUrl, size, status, message));
5542
5948
  }
5543
- /**
5544
- * Determines if node is an AutoLinkNode.
5545
- * @param node - The node to be checked.
5546
- * @returns true if node is an AutoLinkNode, false otherwise.
5547
- */
5548
- function $isAutoLinkNode(node) {
5549
- return node instanceof AutoLinkNode;
5949
+ function $convertFileElement() {
5950
+ return { node: $createFileNode() };
5550
5951
  }
5551
- const TOGGLE_LINK_COMMAND = createCommand("TOGGLE_LINK_COMMAND");
5552
- function $getPointNode(point, offset) {
5553
- if (point.type === "element") {
5554
- const node = point.getNode();
5555
- assert($isElementNode(node), "$getPointNode: element point is not an ElementNode");
5556
- return node.getChildren()[point.offset + offset] || null;
5557
- }
5558
- return null;
5952
+ function $isFileNode(node) {
5953
+ return node.getType() === FileNode.getType();
5559
5954
  }
5560
- /**
5561
- * Preserve the logical start/end of a RangeSelection in situations where
5562
- * the point is an element that may be reparented in the callback.
5563
- *
5564
- * @param $fn The function to run
5565
- * @returns The result of the callback
5566
- */
5567
- function $withSelectedNodes($fn) {
5568
- const initialSelection = $getSelection();
5569
- if (!$isRangeSelection(initialSelection)) return $fn();
5570
- const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection);
5571
- const isBackwards = normalized.isBackward();
5572
- const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0);
5573
- const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1);
5574
- const rval = $fn();
5575
- if (anchorNode || focusNode) {
5576
- const updatedSelection = $getSelection();
5577
- if ($isRangeSelection(updatedSelection)) {
5578
- const finalSelection = updatedSelection.clone();
5579
- if (anchorNode) {
5580
- const anchorParent = anchorNode.getParent();
5581
- if (anchorParent) finalSelection.anchor.set(anchorParent.getKey(), anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), "element");
5582
- }
5583
- if (focusNode) {
5584
- const focusParent = focusNode.getParent();
5585
- if (focusParent) finalSelection.focus.set(focusParent.getKey(), focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), "element");
5586
- }
5587
- $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection));
5588
- }
5589
- }
5590
- return rval;
5955
+ //#endregion
5956
+ //#region src/plugins/file/command/index.ts
5957
+ const logger$2 = createDebugLogger("plugin", "file");
5958
+ const INSERT_FILE_COMMAND = createCommand("INSERT_FILE_COMMAND");
5959
+ function registerFileCommand(editor, handleUpload) {
5960
+ return editor.registerCommand(INSERT_FILE_COMMAND, (payload) => {
5961
+ const { file } = payload;
5962
+ editor.update(() => {
5963
+ const fileNode = $createFileNode(file.name);
5964
+ $insertNodes([fileNode]);
5965
+ if ($isRootOrShadowRoot(fileNode.getParentOrThrow())) $wrapNodeInElement(fileNode, $createParagraphNode).selectEnd();
5966
+ handleUpload(file).then((url) => {
5967
+ editor.update(() => {
5968
+ fileNode.setUploaded(url.url);
5969
+ });
5970
+ }).catch((error) => {
5971
+ logger$2.error("❌ File upload failed:", error);
5972
+ editor.update(() => {
5973
+ fileNode.setError("File upload failed : " + error.message);
5974
+ });
5975
+ });
5976
+ });
5977
+ return false;
5978
+ }, COMMAND_PRIORITY_HIGH);
5591
5979
  }
5592
- /**
5593
- * Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null,
5594
- * but saves any children and brings them up to the parent node.
5595
- * @param url - The URL the link directs to.
5596
- * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
5597
- */
5598
- function $toggleLink(url, attributes = {}) {
5599
- const { target, title } = attributes;
5600
- const rel = attributes.rel === void 0 ? "noreferrer" : attributes.rel;
5601
- const selection = $getSelection();
5602
- if (selection === null || !$isRangeSelection(selection) && !$isNodeSelection(selection)) return;
5603
- if ($isNodeSelection(selection)) {
5604
- const nodes = selection.getNodes();
5605
- if (nodes.length === 0) return;
5606
- nodes.forEach((node) => {
5607
- if (url === null) {
5608
- const linkParent = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
5609
- if (linkParent) {
5610
- linkParent.insertBefore(node);
5611
- if (linkParent.getChildren().length === 0) linkParent.remove();
5612
- }
5613
- } else {
5614
- const existingLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
5615
- if (existingLink) {
5616
- existingLink.setURL(url);
5617
- if (target !== void 0) existingLink.setTarget(target);
5618
- if (rel !== void 0) existingLink.setRel(rel);
5619
- } else {
5620
- const linkNode = $createLinkNode(url, {
5621
- rel,
5622
- target
5980
+ //#endregion
5981
+ //#region src/plugins/file/utils/index.ts
5982
+ function registerFileNodeSelectionObserver(editor) {
5983
+ const selectFileKeys = [];
5984
+ return editor.registerUpdateListener(({ editorState }) => {
5985
+ const selection = editorState.read(() => $getSelection());
5986
+ const newSelectFileKeys = [];
5987
+ if ($isNodeSelection(selection)) editorState.read(() => selection.getNodes()).forEach((node) => {
5988
+ if (node.getType() === "file") newSelectFileKeys.push(node.getKey());
5989
+ });
5990
+ else if ($isRangeSelection(selection) && !selection.isCollapsed()) editorState.read(() => {
5991
+ selection.getNodes().forEach((node) => {
5992
+ if (node.getType() === "file") newSelectFileKeys.push(node.getKey());
5993
+ });
5994
+ });
5995
+ const removeKeys = selectFileKeys.filter((key) => !newSelectFileKeys.includes(key));
5996
+ const addKeys = newSelectFileKeys.filter((key) => !selectFileKeys.includes(key));
5997
+ selectFileKeys.length = 0;
5998
+ selectFileKeys.push(...newSelectFileKeys);
5999
+ removeKeys.forEach((key) => {
6000
+ editor.getElementByKey(key)?.classList.remove("selected");
6001
+ });
6002
+ addKeys.forEach((key) => {
6003
+ editor.getElementByKey(key)?.classList.add("selected");
6004
+ });
6005
+ });
6006
+ }
6007
+ //#endregion
6008
+ //#region src/plugins/file/plugin/index.ts
6009
+ const FilePlugin = class extends KernelPlugin {
6010
+ static {
6011
+ this.pluginName = "FilePlugin";
6012
+ }
6013
+ constructor(kernel, config) {
6014
+ super();
6015
+ this.kernel = kernel;
6016
+ this.config = config;
6017
+ this.logger = createDebugLogger("plugin", "file");
6018
+ kernel.registerNodes([FileNode]);
6019
+ if (config?.theme) kernel.registerThemes(config?.theme);
6020
+ this.registerDecorator(kernel, FileNode.getType(), (node, editor) => {
6021
+ return config?.decorator ? config.decorator(node, editor) : null;
6022
+ });
6023
+ }
6024
+ onInit(editor) {
6025
+ const handleUpload = this.config?.handleUpload;
6026
+ if (handleUpload) {
6027
+ this.kernel.requireService(IUploadService)?.registerUpload(async (file, from, range) => {
6028
+ editor.update(() => {
6029
+ if (range) {
6030
+ const rangeSelection = $createRangeSelection();
6031
+ if (range !== null && range !== void 0) rangeSelection.applyDOMRange(range);
6032
+ $setSelection(rangeSelection);
6033
+ }
6034
+ const fileNode = $createFileNode(file.name);
6035
+ $insertNodes([fileNode]);
6036
+ if ($isRootOrShadowRoot(fileNode.getParentOrThrow())) $wrapNodeInElement(fileNode, $createParagraphNode).selectEnd();
6037
+ handleUpload(file).then((url) => {
6038
+ editor.update(() => {
6039
+ fileNode.setUploaded(url.url);
6040
+ });
6041
+ }).catch((error) => {
6042
+ this.logger.error("File upload failed:", error);
6043
+ editor.update(() => {
6044
+ fileNode.setError("File upload failed : " + error.message);
6045
+ });
5623
6046
  });
5624
- node.insertBefore(linkNode);
5625
- linkNode.append(node);
5626
- }
5627
- }
6047
+ });
6048
+ return null;
6049
+ });
6050
+ this.register(registerFileCommand(editor, handleUpload));
6051
+ }
6052
+ if (this.config?.decorator) this.register(registerFileNodeSelectionObserver(editor));
6053
+ this.registerLiteXml();
6054
+ this.registerMarkdownWriter();
6055
+ }
6056
+ registerLiteXml() {
6057
+ const litexmlService = this.kernel.requireService(ILitexmlService);
6058
+ if (!litexmlService) return;
6059
+ litexmlService.registerXMLWriter(FileNode.getType(), (node, ctx) => {
6060
+ if ($isFileNode(node)) return ctx.createXmlNode("file", {
6061
+ fileUrl: node.fileUrl || "",
6062
+ message: node.message || "",
6063
+ name: node.name,
6064
+ size: node.size?.toString() || "0",
6065
+ status: node.status
6066
+ });
6067
+ return false;
6068
+ });
6069
+ litexmlService.registerXMLReader("file", (xmlElement) => {
6070
+ const name = xmlElement.getAttribute("name") || "unknown";
6071
+ const fileUrl = xmlElement.getAttribute("fileUrl") || "";
6072
+ const status = xmlElement.getAttribute("status");
6073
+ return INodeHelper.createTypeNode(FileNode.getType(), {
6074
+ fileUrl,
6075
+ message: xmlElement.getAttribute("message") || "",
6076
+ name,
6077
+ size: parseInt(xmlElement.getAttribute("size") || "0", 10),
6078
+ status
6079
+ });
5628
6080
  });
5629
- return;
5630
6081
  }
5631
- const nodes = selection.extract();
5632
- if (url === null) {
5633
- nodes.forEach((node) => {
5634
- const parentLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
5635
- if (parentLink) {
5636
- const children = parentLink.getChildren();
5637
- for (const child of children) parentLink.insertBefore(child);
5638
- parentLink.remove();
6082
+ registerMarkdownWriter() {
6083
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
6084
+ if (!markdownService) return;
6085
+ markdownService.registerMarkdownWriter(FileNode.getType(), (ctx, node) => {
6086
+ if ($isFileNode(node)) {
6087
+ if (this.config?.markdownWriter) {
6088
+ ctx.appendLine(this.config.markdownWriter(node));
6089
+ return;
6090
+ }
6091
+ if (node.status === "pending") ctx.appendLine(`Uploading ${node.name}...`);
6092
+ else if (node.status === "error") ctx.appendLine(`Failed to upload ${node.name}: ${node.message}`);
6093
+ else ctx.appendLine(`[${node.name}](${node.fileUrl})`);
5639
6094
  }
5640
6095
  });
5641
- return;
5642
6096
  }
5643
- const updatedNodes = /* @__PURE__ */ new Set();
5644
- const updateLinkNode = (linkNode) => {
5645
- if (updatedNodes.has(linkNode.getKey())) return;
5646
- updatedNodes.add(linkNode.getKey());
5647
- linkNode.setURL(url);
5648
- if (target !== void 0) linkNode.setTarget(target);
5649
- if (rel !== void 0) linkNode.setRel(rel);
5650
- if (title !== void 0) linkNode.setTitle(title);
5651
- };
5652
- if (nodes.length === 1) {
5653
- const firstNode = nodes[0];
5654
- const linkNode = $getAncestor(firstNode, $isLinkNode);
5655
- if (linkNode !== null) return updateLinkNode(linkNode);
6097
+ };
6098
+ //#endregion
6099
+ //#region src/plugins/hr/node/HorizontalRuleNode.ts
6100
+ var HorizontalRuleNode = class HorizontalRuleNode extends DecoratorNode {
6101
+ static getType() {
6102
+ return "horizontalrule";
5656
6103
  }
5657
- $withSelectedNodes(() => {
5658
- let linkNode = null;
5659
- for (const node of nodes) {
5660
- if (!node.isAttached()) continue;
5661
- const parentLinkNode = $getAncestor(node, $isLinkNode);
5662
- if (parentLinkNode) {
5663
- updateLinkNode(parentLinkNode);
5664
- continue;
5665
- }
5666
- if ($isElementNode(node)) {
5667
- if (!node.isInline()) continue;
5668
- if ($isLinkNode(node)) {
5669
- if (!$isAutoLinkNode(node) && (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))) {
5670
- updateLinkNode(node);
5671
- linkNode = node;
5672
- continue;
5673
- }
5674
- for (const child of node.getChildren()) node.insertBefore(child);
5675
- node.remove();
5676
- continue;
5677
- }
5678
- }
5679
- const prevLinkNode = node.getPreviousSibling();
5680
- if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) {
5681
- prevLinkNode.append(node);
5682
- continue;
5683
- }
5684
- linkNode = $createLinkNode(url, {
5685
- rel,
5686
- target,
5687
- title
5688
- });
5689
- node.insertAfter(linkNode);
5690
- linkNode.append(node);
5691
- }
5692
- });
6104
+ static clone(node) {
6105
+ return new HorizontalRuleNode(node.__key);
6106
+ }
6107
+ static importJSON(serializedNode) {
6108
+ return $createHorizontalRuleNode().updateFromJSON(serializedNode);
6109
+ }
6110
+ static importDOM() {
6111
+ return { hr: () => ({
6112
+ conversion: $convertHorizontalRuleElement,
6113
+ priority: 0
6114
+ }) };
6115
+ }
6116
+ exportDOM() {
6117
+ return { element: document.createElement("hr") };
6118
+ }
6119
+ createDOM(config) {
6120
+ const element = document.createElement("div");
6121
+ addClassNamesToElement(element, config.theme.hr);
6122
+ return element;
6123
+ }
6124
+ getTextContent() {
6125
+ return "\n";
6126
+ }
6127
+ isInline() {
6128
+ return false;
6129
+ }
6130
+ updateDOM() {
6131
+ return false;
6132
+ }
6133
+ decorate(editor) {
6134
+ const decorator = getKernelFromEditor(editor)?.getDecorator("horizontalrule");
6135
+ if (!decorator) return null;
6136
+ if (typeof decorator === "function") return decorator(this, editor);
6137
+ return {
6138
+ queryDOM: decorator.queryDOM,
6139
+ render: decorator.render(this, editor)
6140
+ };
6141
+ }
6142
+ };
6143
+ function $createHorizontalRuleNode() {
6144
+ return $applyNodeReplacement(new HorizontalRuleNode());
5693
6145
  }
5694
- function $getAncestor(node, predicate) {
5695
- let parent = node;
5696
- while (parent !== null && parent.getParent() !== null && !predicate(parent)) parent = parent.getParentOrThrow();
5697
- return predicate(parent) ? parent : null;
6146
+ function $convertHorizontalRuleElement() {
6147
+ return { node: $createHorizontalRuleNode() };
5698
6148
  }
5699
- const PHONE_NUMBER_REGEX = /^\+?[\d\s()-]{5,}$/;
5700
- /**
5701
- * Formats a URL string by adding appropriate protocol if missing
5702
- *
5703
- * @param url - URL to format
5704
- * @returns Formatted URL with appropriate protocol
5705
- */
5706
- function formatUrl(url) {
5707
- if (/^[a-z][\d+.a-z-]*:/i.test(url)) return url;
5708
- else if (/^[#./]/.test(url)) return url;
5709
- else if (url.includes("@")) return `mailto:${url}`;
5710
- else if (PHONE_NUMBER_REGEX.test(url)) return `tel:${url}`;
5711
- return url;
6149
+ function $isHorizontalRuleNode(node) {
6150
+ return node.getType() === HorizontalRuleNode.getType();
5712
6151
  }
5713
6152
  //#endregion
5714
- //#region src/plugins/link/command/index.ts
5715
- const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
5716
- const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
5717
- function registerLinkCommand(editor) {
5718
- return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
5719
- const { url, title = url } = payload;
5720
- editor.update(() => {
5721
- const linkNode = $createLinkNode(url, { title });
5722
- const textNode = $createTextNode(title);
5723
- linkNode.append(textNode);
5724
- $insertNodes([linkNode]);
5725
- });
5726
- return false;
5727
- }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
5728
- const { key, text } = payload;
6153
+ //#region src/plugins/hr/command/index.ts
6154
+ const INSERT_HORIZONTAL_RULE_COMMAND = createCommand("INSERT_HORIZONTAL_RULE_COMMAND");
6155
+ function registerHorizontalRuleCommand(editor) {
6156
+ return editor.registerCommand(INSERT_HORIZONTAL_RULE_COMMAND, () => {
5729
6157
  editor.update(() => {
5730
- const linkNode = $getNodeByKey(key);
5731
- if (linkNode) {
5732
- const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
5733
- const textNode = $createTextNode(text);
5734
- newLinkNode.append(textNode);
5735
- linkNode?.replace(newLinkNode);
5736
- newLinkNode.select(1);
5737
- }
6158
+ $insertNodes([$createHorizontalRuleNode()]);
5738
6159
  });
5739
- return false;
5740
- }, COMMAND_PRIORITY_EDITOR));
6160
+ return true;
6161
+ }, COMMAND_PRIORITY_EDITOR);
5741
6162
  }
5742
6163
  //#endregion
5743
- //#region src/plugins/link/service/i-link-service.ts
5744
- const ILinkService = genServiceId("LinkService");
5745
- var LinkService = class extends EventEmitter {
5746
- constructor(..._args) {
5747
- super(..._args);
5748
- this._enableLinkToolbar = true;
6164
+ //#region src/plugins/hr/plugin/index.ts
6165
+ const HRPlugin = class extends KernelPlugin {
6166
+ static {
6167
+ this.pluginName = "HRPlugin";
5749
6168
  }
5750
- get enableLinkToolbar() {
5751
- return this._enableLinkToolbar;
6169
+ constructor(kernel, config) {
6170
+ super();
6171
+ this.kernel = kernel;
6172
+ kernel.registerNodes([HorizontalRuleNode]);
6173
+ kernel.registerThemes({ hr: config?.theme || "" });
6174
+ this.registerDecorator(kernel, "horizontalrule", (node, editor) => {
6175
+ return config?.decorator ? config.decorator(node, editor) : null;
6176
+ });
5752
6177
  }
5753
- setLinkToolbar(enable) {
5754
- this._enableLinkToolbar = enable;
5755
- this.emit("linkToolbarChange", enable);
6178
+ onInit(editor) {
6179
+ this.register(registerHorizontalRuleCommand(editor));
6180
+ this.registerMarkdown();
6181
+ this.registerLiteXml();
6182
+ }
6183
+ registerLiteXml() {
6184
+ const litexmlService = this.kernel.requireService(ILitexmlService);
6185
+ if (!litexmlService) return;
6186
+ litexmlService.registerXMLWriter(HorizontalRuleNode.getType(), (node, ctx) => {
6187
+ if ($isHorizontalRuleNode(node)) return ctx.createXmlNode("hr", {});
6188
+ return false;
6189
+ });
6190
+ litexmlService.registerXMLReader("hr", () => {
6191
+ return INodeHelper.createElementNode(HorizontalRuleNode.getType(), {});
6192
+ });
6193
+ }
6194
+ registerMarkdown() {
6195
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
6196
+ regExp: /^(---|\*\*\*|___)$/,
6197
+ replace: (parentNode, _1, _2, isImport) => {
6198
+ const line = $createHorizontalRuleNode();
6199
+ if (isImport || parentNode.getNextSibling()) parentNode.replace(line);
6200
+ else parentNode.insertBefore(line);
6201
+ line.selectNext();
6202
+ },
6203
+ trigger: "enter",
6204
+ type: "element"
6205
+ });
6206
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(HorizontalRuleNode.getType(), (ctx, node) => {
6207
+ if ($isHorizontalRuleNode(node)) ctx.appendLine("---\n\n");
6208
+ });
6209
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("thematicBreak", () => {
6210
+ return INodeHelper.createElementNode("horizontalrule", {});
6211
+ });
5756
6212
  }
5757
6213
  };
5758
6214
  //#endregion
5759
- //#region src/plugins/link/utils/index.ts
5760
- const SUPPORTED_URL_PROTOCOLS = new Set([
5761
- "http:",
5762
- "https:",
5763
- "mailto:",
5764
- "sms:",
5765
- "tel:"
5766
- ]);
5767
- function sanitizeUrl(url) {
5768
- try {
5769
- const parsedUrl = new URL(url);
5770
- if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) return "about:blank";
5771
- } catch {
5772
- return url;
6215
+ //#region src/plugins/image/node/basie-image-node.ts
6216
+ var BaseImageNode = class BaseImageNode extends DecoratorNode {
6217
+ static clone(node) {
6218
+ return new BaseImageNode(node.__src, node.__altText, node.__maxWidth, node.__width, node.__height, node.__key);
5773
6219
  }
5774
- return url;
5775
- }
5776
- const urlRegExp = /* @__PURE__ */ new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\w$&+,:;=-]+@)?[\d.A-Za-z-]+|(?:www.|[\w$&+,:;=-]+@)[\d.A-Za-z-]+)((?:\/[%+./~\w-_]*)?\??[\w%&+.;=@-]*#?\w*)?)/);
5777
- function extractUrlFromText(text) {
5778
- const match = urlRegExp.exec(text);
5779
- if (!match) return null;
5780
- const raw = match[0];
5781
- const start = match.index ?? text.indexOf(raw);
5782
- const trimmed = raw.replace(/[)\],.;:]+$/u, "");
5783
- return {
5784
- index: start,
5785
- length: trimmed.length,
5786
- url: trimmed
5787
- };
5788
- }
5789
- function getSelectedNode(selection) {
5790
- const anchor = selection.anchor;
5791
- const focus = selection.focus;
5792
- const anchorNode = selection.anchor.getNode();
5793
- const focusNode = selection.focus.getNode();
5794
- if (anchorNode === focusNode) return anchorNode;
5795
- if (selection.isBackward()) return $isAtNodeEnd(focus) ? anchorNode : focusNode;
5796
- else return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
5797
- }
5798
- //#endregion
5799
- //#region src/plugins/link/plugin/registry.ts
5800
- function registerLinkCommands(editor, kernel, options) {
5801
- const { validateUrl, attributes, enableHotkey = true } = options || {};
5802
- const state = { isLink: false };
5803
- const registrations = [editor.registerUpdateListener(() => {
5804
- const selection = editor.getEditorState().read(() => $getSelection());
5805
- if (!selection) return;
5806
- if ($isRangeSelection(selection)) editor.getEditorState().read(() => {
5807
- const node = getSelectedNode(selection);
5808
- state.isLink = $isLinkNode(node.getParent()) || $isLinkNode(node);
5809
- });
5810
- else state.isLink = false;
5811
- }), editor.registerCommand(TOGGLE_LINK_COMMAND, (payload) => {
5812
- if (payload === null) {
5813
- $toggleLink(payload);
5814
- return true;
5815
- } else if (typeof payload === "string") {
5816
- if (validateUrl === void 0 || validateUrl(payload)) {
5817
- $toggleLink(payload, attributes);
5818
- return true;
5819
- }
5820
- return false;
5821
- } else {
5822
- const { url, target, rel, title } = payload;
5823
- $toggleLink(url, {
5824
- ...attributes,
5825
- rel,
5826
- target,
5827
- title
5828
- });
5829
- return true;
5830
- }
5831
- }, COMMAND_PRIORITY_LOW)];
5832
- registrations.push(kernel.registerHotkey(HotkeyEnum.Link, () => {
5833
- if (state.isLink) {
5834
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
5835
- return;
5836
- }
5837
- let nextUrl = sanitizeUrl("https://");
5838
- let expandTo = null;
5839
- editor.getEditorState().read(() => {
5840
- const selection = $getSelection();
5841
- if ($isRangeSelection(selection)) if (!selection.isCollapsed()) {
5842
- const maybeUrl = formatUrl(selection.getTextContent().trim());
5843
- if (validateUrl?.(maybeUrl)) nextUrl = maybeUrl;
5844
- } else {
5845
- const found = extractUrlFromText(selection.anchor.getNode().getTextContent());
5846
- if (found && validateUrl?.(formatUrl(found.url))) {
5847
- nextUrl = formatUrl(found.url);
5848
- expandTo = {
5849
- index: found.index,
5850
- length: found.length
5851
- };
5852
- }
5853
- }
6220
+ static importJSON(serializedNode) {
6221
+ const { altText, height, width, maxWidth, src } = serializedNode;
6222
+ return new BaseImageNode(src, altText, maxWidth, width, height);
6223
+ }
6224
+ static getType() {
6225
+ return "image";
6226
+ }
6227
+ updateFromJSON(serializedNode) {
6228
+ return super.updateFromJSON(serializedNode);
6229
+ }
6230
+ exportDOM() {
6231
+ const element = document.createElement("img");
6232
+ element.setAttribute("src", this.__src);
6233
+ element.setAttribute("alt", this.__altText);
6234
+ element.setAttribute("width", this.__width.toString());
6235
+ element.setAttribute("height", this.__height.toString());
6236
+ return { element };
6237
+ }
6238
+ constructor(src, altText, maxWidth, width, height, key) {
6239
+ super(key);
6240
+ this.__src = src;
6241
+ this.__altText = altText;
6242
+ this.__maxWidth = maxWidth;
6243
+ this.__width = width || "inherit";
6244
+ this.__height = height || "inherit";
6245
+ }
6246
+ exportJSON() {
6247
+ return {
6248
+ ...super.exportJSON(),
6249
+ altText: this.getAltText(),
6250
+ height: this.__height === "inherit" ? 0 : this.__height,
6251
+ maxWidth: this.__maxWidth,
6252
+ src: this.getSrc(),
6253
+ width: this.__width === "inherit" ? 0 : this.__width
6254
+ };
6255
+ }
6256
+ setWidthAndHeight(width, height) {
6257
+ const writable = this.getWritable();
6258
+ writable.__width = width;
6259
+ writable.__height = height;
6260
+ }
6261
+ isInline() {
6262
+ return true;
6263
+ }
6264
+ createDOM(config) {
6265
+ const span = document.createElement("span");
6266
+ const className = config.theme.image;
6267
+ if (className !== void 0) span.className = className;
6268
+ return span;
6269
+ }
6270
+ updateDOM() {
6271
+ return false;
6272
+ }
6273
+ getSrc() {
6274
+ return this.__src;
6275
+ }
6276
+ getAltText() {
6277
+ return this.__altText;
6278
+ }
6279
+ };
6280
+ //#endregion
6281
+ //#region src/plugins/image/node/block-image-node.tsx
6282
+ var BlockImageNode = class BlockImageNode extends BaseImageNode {
6283
+ static {
6284
+ this._decorate = () => null;
6285
+ }
6286
+ static setDecorate(decorate) {
6287
+ BlockImageNode._decorate = decorate;
6288
+ }
6289
+ static getType() {
6290
+ return "block-image";
6291
+ }
6292
+ get isLoading() {
6293
+ return this.__loading;
6294
+ }
6295
+ get status() {
6296
+ return this.__status;
6297
+ }
6298
+ get message() {
6299
+ return this.__message;
6300
+ }
6301
+ get src() {
6302
+ return this.__src;
6303
+ }
6304
+ get altText() {
6305
+ return this.__altText;
6306
+ }
6307
+ get maxWidth() {
6308
+ return this.__maxWidth;
6309
+ }
6310
+ get width() {
6311
+ return this.__width;
6312
+ }
6313
+ get height() {
6314
+ return this.__height;
6315
+ }
6316
+ constructor(opt) {
6317
+ super(opt.src, opt.altText, opt.maxWidth, opt.width, opt.height, opt.key);
6318
+ this.__loading = true;
6319
+ this.__status = "uploaded";
6320
+ this.__message = null;
6321
+ this.__extra = null;
6322
+ this.__status = opt.status ?? "uploaded";
6323
+ }
6324
+ isInline() {
6325
+ return false;
6326
+ }
6327
+ setMaxWidth(maxWidth) {
6328
+ const writable = this.getWritable();
6329
+ writable.__maxWidth = maxWidth;
6330
+ }
6331
+ setWidth(width) {
6332
+ const writable = this.getWritable();
6333
+ writable.__width = width;
6334
+ }
6335
+ setStatus(status) {
6336
+ const writable = this.getWritable();
6337
+ writable.__status = status;
6338
+ }
6339
+ setUploaded(url) {
6340
+ const writable = this.getWritable();
6341
+ writable.__loading = false;
6342
+ writable.__src = url;
6343
+ writable.__status = "uploaded";
6344
+ }
6345
+ setError(message) {
6346
+ const writable = this.getWritable();
6347
+ writable.__loading = false;
6348
+ writable.__status = "error";
6349
+ writable.__message = message;
6350
+ }
6351
+ static clone(node) {
6352
+ return new BlockImageNode({
6353
+ altText: node.__altText,
6354
+ height: node.__height,
6355
+ key: node.__key,
6356
+ maxWidth: node.__maxWidth,
6357
+ src: node.__src,
6358
+ status: node.__status,
6359
+ width: node.__width
6360
+ });
6361
+ }
6362
+ static importJSON(serializedNode) {
6363
+ const { altText, height, width, maxWidth, src, status } = serializedNode;
6364
+ return $createBlockImageNode({
6365
+ altText,
6366
+ height,
6367
+ maxWidth,
6368
+ src,
6369
+ status,
6370
+ width
6371
+ }).updateFromJSON(serializedNode);
6372
+ }
6373
+ static importDOM() {
6374
+ return { img: () => ({
6375
+ conversion: $convertImageElement$1,
6376
+ priority: 0
6377
+ }) };
6378
+ }
6379
+ decorate() {
6380
+ return BlockImageNode._decorate(this);
6381
+ }
6382
+ exportJSON() {
6383
+ return {
6384
+ ...super.exportJSON(),
6385
+ status: this.__status
6386
+ };
6387
+ }
6388
+ createDOM(config) {
6389
+ const span = document.createElement("div");
6390
+ const className = config.theme.blockImage;
6391
+ if (className !== void 0) span.className = className;
6392
+ return span;
6393
+ }
6394
+ };
6395
+ function $createBlockImageNode({ altText, height, maxWidth = 4200, src, width, key, status }) {
6396
+ return $applyNodeReplacement(new BlockImageNode({
6397
+ altText,
6398
+ height,
6399
+ key,
6400
+ maxWidth,
6401
+ src,
6402
+ status,
6403
+ width
6404
+ }));
6405
+ }
6406
+ function $convertImageElement$1(domNode) {
6407
+ const img = domNode;
6408
+ if (img.src.startsWith("file:///")) return null;
6409
+ const { alt: altText, src, width, height } = img;
6410
+ return { node: $createBlockImageNode({
6411
+ altText,
6412
+ height,
6413
+ src,
6414
+ width
6415
+ }) };
6416
+ }
6417
+ function $isBlockImageNode(node) {
6418
+ return node.getType() === BlockImageNode.getType();
6419
+ }
6420
+ //#endregion
6421
+ //#region src/plugins/image/node/image-node.tsx
6422
+ var ImageNode = class ImageNode extends BaseImageNode {
6423
+ static {
6424
+ this._decorate = () => null;
6425
+ }
6426
+ static setDecorate(decorate) {
6427
+ ImageNode._decorate = decorate;
6428
+ }
6429
+ static getType() {
6430
+ return "image";
6431
+ }
6432
+ constructor(opt) {
6433
+ super(opt.src, opt.altText, opt.maxWidth, opt.width, opt.height, opt.key);
6434
+ this.__loading = true;
6435
+ this.__status = "uploaded";
6436
+ this.__message = null;
6437
+ this.__extra = null;
6438
+ this.__status = opt.status ?? "uploaded";
6439
+ }
6440
+ get isLoading() {
6441
+ return this.__loading;
6442
+ }
6443
+ get status() {
6444
+ return this.__status;
6445
+ }
6446
+ get message() {
6447
+ return this.__message;
6448
+ }
6449
+ get src() {
6450
+ return this.__src;
6451
+ }
6452
+ get altText() {
6453
+ return this.__altText;
6454
+ }
6455
+ get maxWidth() {
6456
+ return this.__maxWidth;
6457
+ }
6458
+ get width() {
6459
+ return this.__width;
6460
+ }
6461
+ get height() {
6462
+ return this.__height;
6463
+ }
6464
+ setMaxWidth(maxWidth) {
6465
+ const writable = this.getWritable();
6466
+ writable.__maxWidth = maxWidth;
6467
+ }
6468
+ setStatus(status) {
6469
+ const writable = this.getWritable();
6470
+ writable.__status = status;
6471
+ }
6472
+ setWidth(width) {
6473
+ const writable = this.getWritable();
6474
+ writable.__width = width;
6475
+ }
6476
+ setUploaded(url) {
6477
+ const writable = this.getWritable();
6478
+ writable.__loading = false;
6479
+ writable.__src = url;
6480
+ writable.__status = "uploaded";
6481
+ }
6482
+ setError(message) {
6483
+ const writable = this.getWritable();
6484
+ writable.__loading = false;
6485
+ writable.__status = "error";
6486
+ writable.__message = message;
6487
+ }
6488
+ static clone(node) {
6489
+ return new ImageNode({
6490
+ altText: node.__altText,
6491
+ height: node.__height,
6492
+ key: node.__key,
6493
+ maxWidth: node.__maxWidth,
6494
+ src: node.__src,
6495
+ status: node.__status,
6496
+ width: node.__width
5854
6497
  });
6498
+ }
6499
+ static importJSON(serializedNode) {
6500
+ const { altText, height, width, maxWidth, src, status } = serializedNode;
6501
+ return $createImageNode({
6502
+ altText,
6503
+ height,
6504
+ maxWidth,
6505
+ src,
6506
+ status,
6507
+ width
6508
+ }).updateFromJSON(serializedNode);
6509
+ }
6510
+ static importDOM() {
6511
+ return { img: () => ({
6512
+ conversion: $convertImageElement,
6513
+ priority: 0
6514
+ }) };
6515
+ }
6516
+ decorate() {
6517
+ return ImageNode._decorate(this);
6518
+ }
6519
+ exportJSON() {
6520
+ return {
6521
+ ...super.exportJSON(),
6522
+ status: this.__status
6523
+ };
6524
+ }
6525
+ };
6526
+ function $createImageNode({ altText, height, maxWidth = 500, src, width, key, status }) {
6527
+ return $applyNodeReplacement(new ImageNode({
6528
+ altText,
6529
+ height,
6530
+ key,
6531
+ maxWidth,
6532
+ src,
6533
+ status,
6534
+ width
6535
+ }));
6536
+ }
6537
+ function $convertImageElement(domNode) {
6538
+ const img = domNode;
6539
+ if (img.src.startsWith("file:///")) return null;
6540
+ const { alt: altText, src, width, height } = img;
6541
+ return { node: $createImageNode({
6542
+ altText,
6543
+ height,
6544
+ src,
6545
+ width
6546
+ }) };
6547
+ }
6548
+ function $isImageNode(node) {
6549
+ return node.getType() === ImageNode.getType();
6550
+ }
6551
+ //#endregion
6552
+ //#region src/plugins/image/command/index.ts
6553
+ const logger$1 = createDebugLogger("plugin", "image");
6554
+ const INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
6555
+ function isImageFile(file) {
6556
+ return file.type.startsWith("image/");
6557
+ }
6558
+ function registerImageCommand(editor, handleUpload, defaultBlockImage = false) {
6559
+ return editor.registerCommand(INSERT_IMAGE_COMMAND, (payload) => {
6560
+ const { file, range, block, maxWidth } = payload;
6561
+ const isBlock = block ?? defaultBlockImage;
6562
+ if (!isImageFile(file)) return false;
6563
+ const placeholderURL = URL.createObjectURL(file);
5855
6564
  editor.update(() => {
5856
- if (expandTo) {
5857
- const selection = $getSelection();
5858
- if ($isRangeSelection(selection)) {
5859
- const anchorNode = selection.anchor.getNode();
5860
- selection.anchor.set(anchorNode.getKey(), expandTo.index, "text");
5861
- selection.focus.set(anchorNode.getKey(), expandTo.index + expandTo.length, "text");
5862
- }
6565
+ if (range) {
6566
+ const rangeSelection = $createRangeSelection();
6567
+ if (range !== null && range !== void 0) rangeSelection.applyDOMRange(range);
6568
+ $setSelection(rangeSelection);
5863
6569
  }
5864
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, nextUrl);
6570
+ const imageNode = isBlock ? $createBlockImageNode({
6571
+ altText: file.name,
6572
+ maxWidth: maxWidth || 800,
6573
+ src: placeholderURL,
6574
+ status: "loading"
6575
+ }) : $createImageNode({
6576
+ altText: file.name,
6577
+ maxWidth: maxWidth || 800,
6578
+ src: placeholderURL,
6579
+ status: "loading"
6580
+ });
6581
+ $insertNodes([imageNode]);
6582
+ if (!isBlock && $isRootOrShadowRoot(imageNode.getParentOrThrow())) $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
6583
+ handleUpload(file).then((res) => {
6584
+ editor.update(() => {
6585
+ imageNode.setUploaded(res.url);
6586
+ });
6587
+ }).catch((error) => {
6588
+ logger$1.error("❌ Image upload failed:", error);
6589
+ editor.update(() => {
6590
+ imageNode.setError("Image upload failed : " + error.message);
6591
+ });
6592
+ });
5865
6593
  });
5866
- }, {
5867
- enabled: enableHotkey,
5868
- preventDefault: true,
5869
- stopPropagation: true
5870
- }));
5871
- return mergeRegister(...registrations);
6594
+ return true;
6595
+ }, COMMAND_PRIORITY_EDITOR);
5872
6596
  }
5873
6597
  //#endregion
5874
- //#region src/plugins/link/plugin/index.ts
5875
- const LinkPlugin = class extends KernelPlugin {
6598
+ //#region src/plugins/image/plugin/index.ts
6599
+ const ImagePlugin = class extends KernelPlugin {
5876
6600
  static {
5877
- this.pluginName = "LinkPlugin";
6601
+ this.pluginName = "ImagePlugin";
5878
6602
  }
5879
6603
  constructor(kernel, config) {
5880
6604
  super();
5881
6605
  this.kernel = kernel;
5882
6606
  this.config = config;
5883
- this.linkRegex = /^https?:\/\/\S+$/;
5884
- this.service = new LinkService();
5885
- kernel.registerNodes([LinkNode, AutoLinkNode]);
5886
- kernel.registerService(ILinkService, this.service);
6607
+ kernel.registerNodes([ImageNode, BlockImageNode]);
6608
+ ImageNode.setDecorate(config?.renderImage ?? (() => null));
6609
+ BlockImageNode.setDecorate(config?.renderImage ?? (() => null));
5887
6610
  if (config?.theme) kernel.registerThemes(config.theme);
5888
- if (config?.linkRegex) this.linkRegex = config.linkRegex;
5889
6611
  }
5890
6612
  onInit(editor) {
5891
- this.register(registerLinkCommand(editor));
5892
- this.register(registerLinkCommands(editor, this.kernel, {
5893
- attributes: this.config?.attributes,
5894
- enableHotkey: this.config?.enableHotkey,
5895
- validateUrl: this.config?.validateUrl
5896
- }));
5897
- this.register(editor.registerCommand(PASTE_COMMAND, (payload) => {
5898
- const { clipboardData } = payload;
5899
- if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
5900
- const data = clipboardData.getData("text/plain").trim();
5901
- if (this.linkRegex.test(data)) {
5902
- payload.stopImmediatePropagation();
5903
- payload.preventDefault();
5904
- editor.dispatchCommand(INSERT_LINK_COMMAND, { url: data });
5905
- return true;
5906
- }
5907
- }
5908
- return false;
5909
- }, COMMAND_PRIORITY_NORMAL));
6613
+ if (this.config?.handleUpload) this.register(registerImageCommand(editor, this.config.handleUpload, this.config?.defaultBlockImage !== false));
5910
6614
  this.registerMarkdown();
5911
6615
  this.registerLiteXml();
6616
+ this.registerINode();
6617
+ this.registerUpload(editor);
6618
+ if (this.config?.needRehost && this.config?.handleRehost) {
6619
+ const needRehost = this.config.needRehost;
6620
+ const handleRehost = this.config.handleRehost;
6621
+ this.register(editor.registerNodeTransform(ImageNode, (node) => {
6622
+ if (node.status === "uploaded" && needRehost(node.src)) {
6623
+ node.setStatus("loading");
6624
+ handleRehost(node.src).then(({ url }) => {
6625
+ editor.update(() => {
6626
+ node.setUploaded(url);
6627
+ });
6628
+ }).catch(() => {
6629
+ editor.update(() => {
6630
+ node.setError("Rehost failed");
6631
+ });
6632
+ });
6633
+ }
6634
+ }));
6635
+ this.register(editor.registerNodeTransform(BlockImageNode, (node) => {
6636
+ if (node.status === "uploaded" && needRehost(node.src)) {
6637
+ node.setStatus("loading");
6638
+ handleRehost(node.src).then(({ url }) => {
6639
+ editor.update(() => {
6640
+ node.setUploaded(url);
6641
+ });
6642
+ }).catch(() => {
6643
+ editor.update(() => {
6644
+ node.setError("Rehost failed");
6645
+ });
6646
+ });
6647
+ }
6648
+ }));
6649
+ }
6650
+ }
6651
+ registerUpload(editor) {
6652
+ const uploadService = this.kernel.requireService(IUploadService);
6653
+ if (!uploadService) return;
6654
+ if (!this.config?.handleUpload) return;
6655
+ uploadService.registerUpload(async (file, from, range) => {
6656
+ const imageWidth = await this.config?.getImageWidth?.(file);
6657
+ return editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
6658
+ block: this.config?.defaultBlockImage !== false,
6659
+ file,
6660
+ maxWidth: imageWidth,
6661
+ range
6662
+ });
6663
+ }, 0);
5912
6664
  }
5913
6665
  registerLiteXml() {
5914
6666
  const litexmlService = this.kernel.requireService(ILitexmlService);
5915
6667
  if (!litexmlService) return;
5916
- litexmlService.registerXMLWriter(LinkNode.getType(), (node, ctx) => {
5917
- if ($isLinkNode(node)) {
5918
- const attributes = { href: node.getURL() };
5919
- return ctx.createXmlNode("a", attributes);
6668
+ litexmlService.registerXMLWriter(ImageNode.getType(), (node, ctx) => {
6669
+ if ($isImageNode(node)) {
6670
+ const attributes = { src: node.src };
6671
+ if (node.altText) attributes.alt = node.altText;
6672
+ return ctx.createXmlNode("img", attributes);
5920
6673
  }
5921
6674
  return false;
5922
6675
  });
5923
- litexmlService.registerXMLReader("a", (xmlNode, children) => {
5924
- return [INodeHelper.createElementNode("link", {
5925
- children,
5926
- direction: "ltr",
5927
- format: "",
5928
- indent: 0,
5929
- type: "link",
5930
- url: xmlNode.getAttribute("href") || "",
5931
- version: 1
5932
- })];
6676
+ litexmlService.registerXMLWriter(BlockImageNode.getType(), (node, ctx) => {
6677
+ if ($isBlockImageNode(node)) {
6678
+ const attributes = {
6679
+ block: "true",
6680
+ src: node.src
6681
+ };
6682
+ if (node.altText) attributes.alt = node.altText;
6683
+ if (node.width) attributes.width = String(node.width);
6684
+ if (node.maxWidth) attributes["max-width"] = String(node.maxWidth);
6685
+ return ctx.createXmlNode("img", attributes);
6686
+ }
6687
+ return false;
6688
+ });
6689
+ litexmlService.registerXMLReader("img", (xmlNode) => {
6690
+ if (this.config?.defaultBlockImage !== false) return INodeHelper.createElementNode(BlockImageNode.getType(), {
6691
+ altText: xmlNode.getAttribute("alt") || "",
6692
+ maxWidth: xmlNode.getAttribute("max-width") ? parseInt(xmlNode.getAttribute("max-width"), 10) : void 0,
6693
+ src: xmlNode.getAttribute("src") || "",
6694
+ width: xmlNode.getAttribute("width") ? parseInt(xmlNode.getAttribute("width"), 10) : void 0
6695
+ });
6696
+ if (xmlNode.getAttribute("block") === "true") return INodeHelper.createElementNode(BlockImageNode.getType(), {
6697
+ altText: xmlNode.getAttribute("alt") || "",
6698
+ maxWidth: xmlNode.getAttribute("max-width") ? parseInt(xmlNode.getAttribute("max-width"), 10) : void 0,
6699
+ src: xmlNode.getAttribute("src") || "",
6700
+ width: xmlNode.getAttribute("width") ? parseInt(xmlNode.getAttribute("width"), 10) : void 0
6701
+ });
6702
+ else return INodeHelper.createElementNode(ImageNode.getType(), {
6703
+ altText: xmlNode.getAttribute("alt") || "",
6704
+ maxWidth: xmlNode.getAttribute("max-width") ? parseInt(xmlNode.getAttribute("max-width"), 10) : void 0,
6705
+ src: xmlNode.getAttribute("src") || "",
6706
+ width: xmlNode.getAttribute("width") ? parseInt(xmlNode.getAttribute("width"), 10) : void 0
6707
+ });
5933
6708
  });
5934
6709
  }
5935
6710
  registerMarkdown() {
5936
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
5937
- regExp: /\[([^[]+)]\(([^\s()]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)\s?$/,
5938
- replace: (textNode, match) => {
5939
- const [, linkText, linkUrl, linkTitle] = match;
5940
- const linkNode = $createLinkNode(linkUrl, { title: linkTitle });
5941
- const linkTextNode = $createTextNode(linkText);
5942
- linkTextNode.setFormat(textNode.getFormat());
5943
- linkNode.append(linkTextNode);
5944
- textNode.replace(linkNode);
5945
- return linkTextNode;
5946
- },
5947
- trigger: ")",
5948
- type: "text-match"
6711
+ const defaultBlockImage = this.config?.defaultBlockImage !== false;
6712
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
6713
+ if (!markdownService) return;
6714
+ markdownService.registerMarkdownWriter(ImageNode.getType(), (ctx, node) => {
6715
+ if ($isImageNode(node)) ctx.appendLine(`![${node.altText}](${node.src})`);
6716
+ });
6717
+ markdownService.registerMarkdownWriter(BlockImageNode.getType(), (ctx, node) => {
6718
+ if ($isBlockImageNode(node)) ctx.appendLine(`![${node.altText}](${node.src})\n\n`);
6719
+ });
6720
+ markdownService.registerMarkdownReader("image", (node) => {
6721
+ const altText = node.alt;
6722
+ const src = node.url;
6723
+ return INodeHelper.createTypeNode(defaultBlockImage ? BlockImageNode.getType() : ImageNode.getType(), {
6724
+ altText,
6725
+ showCaption: false,
6726
+ src,
6727
+ version: 1
6728
+ });
6729
+ });
6730
+ }
6731
+ registerINode() {
6732
+ const service = this.kernel.requireService(INodeService);
6733
+ if (!service) return;
6734
+ service.registerProcessNodeTree(({ root }) => {
6735
+ const loopNodes = (node) => {
6736
+ if ("children" in node && Array.isArray(node.children)) {
6737
+ if (node.type === "paragraph" && node.children.length === 1 && node.children[0].type === BlockImageNode.getType()) return node.children[0];
6738
+ node.children = node.children.map((child) => {
6739
+ return loopNodes(child);
6740
+ });
6741
+ }
6742
+ return node;
6743
+ };
6744
+ root.children = root.children.map((child) => {
6745
+ return loopNodes(child);
6746
+ });
6747
+ });
6748
+ }
6749
+ };
6750
+ //#endregion
6751
+ //#region src/plugins/link/node/LinkNode.ts
6752
+ const logger = createDebugLogger("plugin", "link");
6753
+ const SUPPORTED_URL_PROTOCOLS$1 = new Set([
6754
+ "http:",
6755
+ "https:",
6756
+ "mailto:",
6757
+ "sms:",
6758
+ "tel:"
6759
+ ]);
6760
+ const HOVER_LINK_COMMAND = createCommand("HOVER_LINK_COMMAND");
6761
+ const HOVER_OUT_LINK_COMMAND = createCommand("HOVER_OUT_LINK_COMMAND");
6762
+ /** @noInheritDoc */
6763
+ var LinkNode = class LinkNode extends ElementNode {
6764
+ static getType() {
6765
+ return "link";
6766
+ }
6767
+ static clone(node) {
6768
+ return new LinkNode(node.__url, {
6769
+ rel: node.__rel,
6770
+ target: node.__target,
6771
+ title: node.__title
6772
+ }, node.__key);
6773
+ }
6774
+ constructor(url = "", attributes = {}, key) {
6775
+ super(key);
6776
+ const { target = null, rel = null, title = null } = attributes;
6777
+ this.__url = url;
6778
+ this.__target = target;
6779
+ this.__rel = rel;
6780
+ this.__title = title;
6781
+ }
6782
+ createDOM(config, editor) {
6783
+ logger.debug("🔍 config", config);
6784
+ const element = document.createElement("a");
6785
+ this.updateLinkDOM(null, element, config);
6786
+ addClassNamesToElement(element, config.theme.link);
6787
+ element.addEventListener("mouseenter", (event) => {
6788
+ if (event.target instanceof HTMLElement) {
6789
+ event.target.classList.add("hover");
6790
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
6791
+ event,
6792
+ linkNode: this
6793
+ });
6794
+ }
6795
+ });
6796
+ element.addEventListener("mouseleave", (event) => {
6797
+ if (event.target instanceof HTMLElement) {
6798
+ event.target.classList.remove("hover");
6799
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
6800
+ }
6801
+ });
6802
+ return element;
6803
+ }
6804
+ updateLinkDOM(prevNode, anchor, _config) {
6805
+ if (isHTMLAnchorElement(anchor)) {
6806
+ if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url);
6807
+ for (const attr of [
6808
+ "target",
6809
+ "rel",
6810
+ "title"
6811
+ ]) {
6812
+ const key = `__${attr}`;
6813
+ const value = this[key];
6814
+ if (!prevNode || prevNode[key] !== value) if (value) anchor[attr] = value;
6815
+ else anchor.removeAttribute(attr);
6816
+ }
6817
+ }
6818
+ }
6819
+ updateDOM(prevNode, anchor, config) {
6820
+ this.updateLinkDOM(prevNode, anchor, config);
6821
+ return false;
6822
+ }
6823
+ static importDOM() {
6824
+ return { a: () => ({
6825
+ conversion: $convertAnchorElement,
6826
+ priority: 1
6827
+ }) };
6828
+ }
6829
+ static importJSON(serializedNode) {
6830
+ return $createLinkNode().updateFromJSON(serializedNode);
6831
+ }
6832
+ updateFromJSON(serializedNode) {
6833
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
6834
+ }
6835
+ sanitizeUrl(url) {
6836
+ url = formatUrl(url);
6837
+ try {
6838
+ const parsedUrl = new URL(formatUrl(url));
6839
+ if (!SUPPORTED_URL_PROTOCOLS$1.has(parsedUrl.protocol)) return "about:blank";
6840
+ } catch {
6841
+ return url;
6842
+ }
6843
+ return url;
6844
+ }
6845
+ exportJSON() {
6846
+ return {
6847
+ ...super.exportJSON(),
6848
+ rel: this.getRel(),
6849
+ target: this.getTarget(),
6850
+ title: this.getTitle(),
6851
+ url: this.getURL()
6852
+ };
6853
+ }
6854
+ getURL() {
6855
+ return this.getLatest().__url;
6856
+ }
6857
+ setURL(url) {
6858
+ const writable = this.getWritable();
6859
+ writable.__url = url;
6860
+ return writable;
6861
+ }
6862
+ getTarget() {
6863
+ return this.getLatest().__target;
6864
+ }
6865
+ setTarget(target) {
6866
+ const writable = this.getWritable();
6867
+ writable.__target = target;
6868
+ return writable;
6869
+ }
6870
+ getRel() {
6871
+ return this.getLatest().__rel;
6872
+ }
6873
+ setRel(rel) {
6874
+ const writable = this.getWritable();
6875
+ writable.__rel = rel;
6876
+ return writable;
6877
+ }
6878
+ getTitle() {
6879
+ return this.getLatest().__title;
6880
+ }
6881
+ setTitle(title) {
6882
+ const writable = this.getWritable();
6883
+ writable.__title = title;
6884
+ return writable;
6885
+ }
6886
+ insertNewAfter(_, restoreSelection = true) {
6887
+ const linkNode = $createLinkNode(this.__url, {
6888
+ rel: this.__rel,
6889
+ target: this.__target,
6890
+ title: this.__title
6891
+ });
6892
+ this.insertAfter(linkNode, restoreSelection);
6893
+ return linkNode;
6894
+ }
6895
+ canInsertTextBefore() {
6896
+ return false;
6897
+ }
6898
+ canInsertTextAfter() {
6899
+ return false;
6900
+ }
6901
+ canBeEmpty() {
6902
+ return false;
6903
+ }
6904
+ isInline() {
6905
+ return true;
6906
+ }
6907
+ extractWithChild(child, selection) {
6908
+ if (!$isRangeSelection(selection)) return false;
6909
+ const anchorNode = selection.anchor.getNode();
6910
+ const focusNode = selection.focus.getNode();
6911
+ return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && selection.getTextContent().length > 0;
6912
+ }
6913
+ isEmailURI() {
6914
+ return this.__url.startsWith("mailto:");
6915
+ }
6916
+ isWebSiteURI() {
6917
+ return this.__url.startsWith("https://") || this.__url.startsWith("http://");
6918
+ }
6919
+ };
6920
+ function $convertAnchorElement(domNode) {
6921
+ let node = null;
6922
+ if (isHTMLAnchorElement(domNode)) {
6923
+ const content = domNode.textContent;
6924
+ if (content !== null && content !== "" || domNode.children.length > 0) node = $createLinkNode(domNode.getAttribute("href") || "", {
6925
+ rel: domNode.getAttribute("rel"),
6926
+ target: domNode.getAttribute("target"),
6927
+ title: domNode.getAttribute("title")
6928
+ });
6929
+ }
6930
+ return { node };
6931
+ }
6932
+ /**
6933
+ * Takes a URL and creates a LinkNode.
6934
+ * @param url - The URL the LinkNode should direct to.
6935
+ * @param attributes - Optional HTML a tag attributes \\{ target, rel, title \\}
6936
+ * @returns The LinkNode.
6937
+ */
6938
+ function $createLinkNode(url = "", attributes) {
6939
+ return $applyNodeReplacement(new LinkNode(url, attributes));
6940
+ }
6941
+ /**
6942
+ * Determines if node is a LinkNode.
6943
+ * @param node - The node to be checked.
6944
+ * @returns true if node is a LinkNode, false otherwise.
6945
+ */
6946
+ function $isLinkNode(node) {
6947
+ return node instanceof LinkNode;
6948
+ }
6949
+ var AutoLinkNode = class AutoLinkNode extends LinkNode {
6950
+ constructor(url = "", attributes = {}, key) {
6951
+ super(url, attributes, key);
6952
+ this.__isUnlinked = attributes.isUnlinked !== void 0 && attributes.isUnlinked !== null ? attributes.isUnlinked : false;
6953
+ }
6954
+ static getType() {
6955
+ return "autolink";
6956
+ }
6957
+ static clone(node) {
6958
+ return new AutoLinkNode(node.__url, {
6959
+ isUnlinked: node.__isUnlinked,
6960
+ rel: node.__rel,
6961
+ target: node.__target,
6962
+ title: node.__title
6963
+ }, node.__key);
6964
+ }
6965
+ getIsUnlinked() {
6966
+ return this.__isUnlinked;
6967
+ }
6968
+ setIsUnlinked(value) {
6969
+ const self = this.getWritable();
6970
+ self.__isUnlinked = value;
6971
+ return self;
6972
+ }
6973
+ createDOM(config, editor) {
6974
+ logger.debug("🔍 config", config);
6975
+ if (this.__isUnlinked) return document.createElement("span");
6976
+ else return super.createDOM(config, editor);
6977
+ }
6978
+ updateDOM(prevNode, anchor, config) {
6979
+ return super.updateDOM(prevNode, anchor, config) || prevNode.__isUnlinked !== this.__isUnlinked;
6980
+ }
6981
+ static importJSON(serializedNode) {
6982
+ return $createAutoLinkNode().updateFromJSON(serializedNode);
6983
+ }
6984
+ updateFromJSON(serializedNode) {
6985
+ return super.updateFromJSON(serializedNode).setIsUnlinked(serializedNode.isUnlinked || false);
6986
+ }
6987
+ static importDOM() {
6988
+ return null;
6989
+ }
6990
+ exportJSON() {
6991
+ return {
6992
+ ...super.exportJSON(),
6993
+ isUnlinked: this.__isUnlinked
6994
+ };
6995
+ }
6996
+ insertNewAfter(selection, restoreSelection = true) {
6997
+ const element = this.getParentOrThrow().insertNewAfter(selection, restoreSelection);
6998
+ if ($isElementNode(element)) {
6999
+ const linkNode = $createAutoLinkNode(this.__url, {
7000
+ isUnlinked: this.__isUnlinked,
7001
+ rel: this.__rel,
7002
+ target: this.__target,
7003
+ title: this.__title
7004
+ });
7005
+ element.append(linkNode);
7006
+ return linkNode;
7007
+ }
7008
+ return null;
7009
+ }
7010
+ };
7011
+ /**
7012
+ * Takes a URL and creates an AutoLinkNode. AutoLinkNodes are generally automatically generated
7013
+ * during typing, which is especially useful when a button to generate a LinkNode is not practical.
7014
+ * @param url - The URL the LinkNode should direct to.
7015
+ * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
7016
+ * @returns The LinkNode.
7017
+ */
7018
+ function $createAutoLinkNode(url = "", attributes) {
7019
+ return $applyNodeReplacement(new AutoLinkNode(url, attributes));
7020
+ }
7021
+ /**
7022
+ * Determines if node is an AutoLinkNode.
7023
+ * @param node - The node to be checked.
7024
+ * @returns true if node is an AutoLinkNode, false otherwise.
7025
+ */
7026
+ function $isAutoLinkNode(node) {
7027
+ return node instanceof AutoLinkNode;
7028
+ }
7029
+ const TOGGLE_LINK_COMMAND = createCommand("TOGGLE_LINK_COMMAND");
7030
+ function $getPointNode(point, offset) {
7031
+ if (point.type === "element") {
7032
+ const node = point.getNode();
7033
+ assert($isElementNode(node), "$getPointNode: element point is not an ElementNode");
7034
+ return node.getChildren()[point.offset + offset] || null;
7035
+ }
7036
+ return null;
7037
+ }
7038
+ /**
7039
+ * Preserve the logical start/end of a RangeSelection in situations where
7040
+ * the point is an element that may be reparented in the callback.
7041
+ *
7042
+ * @param $fn The function to run
7043
+ * @returns The result of the callback
7044
+ */
7045
+ function $withSelectedNodes($fn) {
7046
+ const initialSelection = $getSelection();
7047
+ if (!$isRangeSelection(initialSelection)) return $fn();
7048
+ const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection);
7049
+ const isBackwards = normalized.isBackward();
7050
+ const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0);
7051
+ const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1);
7052
+ const rval = $fn();
7053
+ if (anchorNode || focusNode) {
7054
+ const updatedSelection = $getSelection();
7055
+ if ($isRangeSelection(updatedSelection)) {
7056
+ const finalSelection = updatedSelection.clone();
7057
+ if (anchorNode) {
7058
+ const anchorParent = anchorNode.getParent();
7059
+ if (anchorParent) finalSelection.anchor.set(anchorParent.getKey(), anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), "element");
7060
+ }
7061
+ if (focusNode) {
7062
+ const focusParent = focusNode.getParent();
7063
+ if (focusParent) finalSelection.focus.set(focusParent.getKey(), focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), "element");
7064
+ }
7065
+ $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection));
7066
+ }
7067
+ }
7068
+ return rval;
7069
+ }
7070
+ /**
7071
+ * Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null,
7072
+ * but saves any children and brings them up to the parent node.
7073
+ * @param url - The URL the link directs to.
7074
+ * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
7075
+ */
7076
+ function $toggleLink(url, attributes = {}) {
7077
+ const { target, title } = attributes;
7078
+ const rel = attributes.rel === void 0 ? "noreferrer" : attributes.rel;
7079
+ const selection = $getSelection();
7080
+ if (selection === null || !$isRangeSelection(selection) && !$isNodeSelection(selection)) return;
7081
+ if ($isNodeSelection(selection)) {
7082
+ const nodes = selection.getNodes();
7083
+ if (nodes.length === 0) return;
7084
+ nodes.forEach((node) => {
7085
+ if (url === null) {
7086
+ const linkParent = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
7087
+ if (linkParent) {
7088
+ linkParent.insertBefore(node);
7089
+ if (linkParent.getChildren().length === 0) linkParent.remove();
7090
+ }
7091
+ } else {
7092
+ const existingLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
7093
+ if (existingLink) {
7094
+ existingLink.setURL(url);
7095
+ if (target !== void 0) existingLink.setTarget(target);
7096
+ if (rel !== void 0) existingLink.setRel(rel);
7097
+ } else {
7098
+ const linkNode = $createLinkNode(url, {
7099
+ rel,
7100
+ target
7101
+ });
7102
+ node.insertBefore(linkNode);
7103
+ linkNode.append(node);
7104
+ }
7105
+ }
7106
+ });
7107
+ return;
7108
+ }
7109
+ const nodes = selection.extract();
7110
+ if (url === null) {
7111
+ nodes.forEach((node) => {
7112
+ const parentLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
7113
+ if (parentLink) {
7114
+ const children = parentLink.getChildren();
7115
+ for (const child of children) parentLink.insertBefore(child);
7116
+ parentLink.remove();
7117
+ }
7118
+ });
7119
+ return;
7120
+ }
7121
+ const updatedNodes = /* @__PURE__ */ new Set();
7122
+ const updateLinkNode = (linkNode) => {
7123
+ if (updatedNodes.has(linkNode.getKey())) return;
7124
+ updatedNodes.add(linkNode.getKey());
7125
+ linkNode.setURL(url);
7126
+ if (target !== void 0) linkNode.setTarget(target);
7127
+ if (rel !== void 0) linkNode.setRel(rel);
7128
+ if (title !== void 0) linkNode.setTitle(title);
7129
+ };
7130
+ if (nodes.length === 1) {
7131
+ const firstNode = nodes[0];
7132
+ const linkNode = $getAncestor(firstNode, $isLinkNode);
7133
+ if (linkNode !== null) return updateLinkNode(linkNode);
7134
+ }
7135
+ $withSelectedNodes(() => {
7136
+ let linkNode = null;
7137
+ for (const node of nodes) {
7138
+ if (!node.isAttached()) continue;
7139
+ const parentLinkNode = $getAncestor(node, $isLinkNode);
7140
+ if (parentLinkNode) {
7141
+ updateLinkNode(parentLinkNode);
7142
+ continue;
7143
+ }
7144
+ if ($isElementNode(node)) {
7145
+ if (!node.isInline()) continue;
7146
+ if ($isLinkNode(node)) {
7147
+ if (!$isAutoLinkNode(node) && (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))) {
7148
+ updateLinkNode(node);
7149
+ linkNode = node;
7150
+ continue;
7151
+ }
7152
+ for (const child of node.getChildren()) node.insertBefore(child);
7153
+ node.remove();
7154
+ continue;
7155
+ }
7156
+ }
7157
+ const prevLinkNode = node.getPreviousSibling();
7158
+ if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) {
7159
+ prevLinkNode.append(node);
7160
+ continue;
7161
+ }
7162
+ linkNode = $createLinkNode(url, {
7163
+ rel,
7164
+ target,
7165
+ title
7166
+ });
7167
+ node.insertAfter(linkNode);
7168
+ linkNode.append(node);
7169
+ }
7170
+ });
7171
+ }
7172
+ function $getAncestor(node, predicate) {
7173
+ let parent = node;
7174
+ while (parent !== null && parent.getParent() !== null && !predicate(parent)) parent = parent.getParentOrThrow();
7175
+ return predicate(parent) ? parent : null;
7176
+ }
7177
+ const PHONE_NUMBER_REGEX = /^\+?[\d\s()-]{5,}$/;
7178
+ /**
7179
+ * Formats a URL string by adding appropriate protocol if missing
7180
+ *
7181
+ * @param url - URL to format
7182
+ * @returns Formatted URL with appropriate protocol
7183
+ */
7184
+ function formatUrl(url) {
7185
+ if (/^[a-z][\d+.a-z-]*:/i.test(url)) return url;
7186
+ else if (/^[#./]/.test(url)) return url;
7187
+ else if (url.includes("@")) return `mailto:${url}`;
7188
+ else if (PHONE_NUMBER_REGEX.test(url)) return `tel:${url}`;
7189
+ return url;
7190
+ }
7191
+ //#endregion
7192
+ //#region src/plugins/link/command/index.ts
7193
+ const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
7194
+ const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
7195
+ function registerLinkCommand(editor) {
7196
+ return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
7197
+ const { url, title = url } = payload;
7198
+ editor.update(() => {
7199
+ const linkNode = $createLinkNode(url, { title });
7200
+ const textNode = $createTextNode(title);
7201
+ linkNode.append(textNode);
7202
+ $insertNodes([linkNode]);
7203
+ });
7204
+ return false;
7205
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
7206
+ const { key, text } = payload;
7207
+ editor.update(() => {
7208
+ const linkNode = $getNodeByKey(key);
7209
+ if (linkNode) {
7210
+ const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
7211
+ const textNode = $createTextNode(text);
7212
+ newLinkNode.append(textNode);
7213
+ linkNode?.replace(newLinkNode);
7214
+ newLinkNode.select(1);
7215
+ }
7216
+ });
7217
+ return false;
7218
+ }, COMMAND_PRIORITY_EDITOR));
7219
+ }
7220
+ //#endregion
7221
+ //#region src/plugins/link/service/i-link-service.ts
7222
+ const ILinkService = genServiceId("LinkService");
7223
+ var LinkService = class extends EventEmitter {
7224
+ constructor(..._args) {
7225
+ super(..._args);
7226
+ this._enableLinkToolbar = true;
7227
+ }
7228
+ get enableLinkToolbar() {
7229
+ return this._enableLinkToolbar;
7230
+ }
7231
+ setLinkToolbar(enable) {
7232
+ this._enableLinkToolbar = enable;
7233
+ this.emit("linkToolbarChange", enable);
7234
+ }
7235
+ };
7236
+ //#endregion
7237
+ //#region src/plugins/link/utils/index.ts
7238
+ const SUPPORTED_URL_PROTOCOLS = new Set([
7239
+ "http:",
7240
+ "https:",
7241
+ "mailto:",
7242
+ "sms:",
7243
+ "tel:"
7244
+ ]);
7245
+ function sanitizeUrl(url) {
7246
+ try {
7247
+ const parsedUrl = new URL(url);
7248
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) return "about:blank";
7249
+ } catch {
7250
+ return url;
7251
+ }
7252
+ return url;
7253
+ }
7254
+ const urlRegExp = /* @__PURE__ */ new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\w$&+,:;=-]+@)?[\d.A-Za-z-]+|(?:www.|[\w$&+,:;=-]+@)[\d.A-Za-z-]+)((?:\/[%+./~\w-_]*)?\??[\w%&+.;=@-]*#?\w*)?)/);
7255
+ function extractUrlFromText(text) {
7256
+ const match = urlRegExp.exec(text);
7257
+ if (!match) return null;
7258
+ const raw = match[0];
7259
+ const start = match.index ?? text.indexOf(raw);
7260
+ const trimmed = raw.replace(/[)\],.;:]+$/u, "");
7261
+ return {
7262
+ index: start,
7263
+ length: trimmed.length,
7264
+ url: trimmed
7265
+ };
7266
+ }
7267
+ function getSelectedNode(selection) {
7268
+ const anchor = selection.anchor;
7269
+ const focus = selection.focus;
7270
+ const anchorNode = selection.anchor.getNode();
7271
+ const focusNode = selection.focus.getNode();
7272
+ if (anchorNode === focusNode) return anchorNode;
7273
+ if (selection.isBackward()) return $isAtNodeEnd(focus) ? anchorNode : focusNode;
7274
+ else return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
7275
+ }
7276
+ //#endregion
7277
+ //#region src/plugins/link/plugin/registry.ts
7278
+ function registerLinkCommands(editor, kernel, options) {
7279
+ const { validateUrl, attributes, enableHotkey = true } = options || {};
7280
+ const state = { isLink: false };
7281
+ const registrations = [editor.registerUpdateListener(() => {
7282
+ const selection = editor.getEditorState().read(() => $getSelection());
7283
+ if (!selection) return;
7284
+ if ($isRangeSelection(selection)) editor.getEditorState().read(() => {
7285
+ const node = getSelectedNode(selection);
7286
+ state.isLink = $isLinkNode(node.getParent()) || $isLinkNode(node);
7287
+ });
7288
+ else state.isLink = false;
7289
+ }), editor.registerCommand(TOGGLE_LINK_COMMAND, (payload) => {
7290
+ if (payload === null) {
7291
+ $toggleLink(payload);
7292
+ return true;
7293
+ } else if (typeof payload === "string") {
7294
+ if (validateUrl === void 0 || validateUrl(payload)) {
7295
+ $toggleLink(payload, attributes);
7296
+ return true;
7297
+ }
7298
+ return false;
7299
+ } else {
7300
+ const { url, target, rel, title } = payload;
7301
+ $toggleLink(url, {
7302
+ ...attributes,
7303
+ rel,
7304
+ target,
7305
+ title
7306
+ });
7307
+ return true;
7308
+ }
7309
+ }, COMMAND_PRIORITY_LOW)];
7310
+ registrations.push(kernel.registerHotkey(HotkeyEnum.Link, () => {
7311
+ if (state.isLink) {
7312
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
7313
+ return;
7314
+ }
7315
+ let nextUrl = sanitizeUrl("https://");
7316
+ let expandTo = null;
7317
+ editor.getEditorState().read(() => {
7318
+ const selection = $getSelection();
7319
+ if ($isRangeSelection(selection)) if (!selection.isCollapsed()) {
7320
+ const maybeUrl = formatUrl(selection.getTextContent().trim());
7321
+ if (validateUrl?.(maybeUrl)) nextUrl = maybeUrl;
7322
+ } else {
7323
+ const found = extractUrlFromText(selection.anchor.getNode().getTextContent());
7324
+ if (found && validateUrl?.(formatUrl(found.url))) {
7325
+ nextUrl = formatUrl(found.url);
7326
+ expandTo = {
7327
+ index: found.index,
7328
+ length: found.length
7329
+ };
7330
+ }
7331
+ }
7332
+ });
7333
+ editor.update(() => {
7334
+ if (expandTo) {
7335
+ const selection = $getSelection();
7336
+ if ($isRangeSelection(selection)) {
7337
+ const anchorNode = selection.anchor.getNode();
7338
+ selection.anchor.set(anchorNode.getKey(), expandTo.index, "text");
7339
+ selection.focus.set(anchorNode.getKey(), expandTo.index + expandTo.length, "text");
7340
+ }
7341
+ }
7342
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, nextUrl);
7343
+ });
7344
+ }, {
7345
+ enabled: enableHotkey,
7346
+ preventDefault: true,
7347
+ stopPropagation: true
7348
+ }));
7349
+ return mergeRegister(...registrations);
7350
+ }
7351
+ //#endregion
7352
+ //#region src/plugins/link/plugin/index.ts
7353
+ const LinkPlugin = class extends KernelPlugin {
7354
+ static {
7355
+ this.pluginName = "LinkPlugin";
7356
+ }
7357
+ constructor(kernel, config) {
7358
+ super();
7359
+ this.kernel = kernel;
7360
+ this.config = config;
7361
+ this.linkRegex = /^https?:\/\/\S+$/;
7362
+ this.service = new LinkService();
7363
+ kernel.registerNodes([LinkNode, AutoLinkNode]);
7364
+ kernel.registerService(ILinkService, this.service);
7365
+ if (config?.theme) kernel.registerThemes(config.theme);
7366
+ if (config?.linkRegex) this.linkRegex = config.linkRegex;
7367
+ }
7368
+ onInit(editor) {
7369
+ this.register(registerLinkCommand(editor));
7370
+ this.register(registerLinkCommands(editor, this.kernel, {
7371
+ attributes: this.config?.attributes,
7372
+ enableHotkey: this.config?.enableHotkey,
7373
+ validateUrl: this.config?.validateUrl
7374
+ }));
7375
+ this.register(editor.registerCommand(PASTE_COMMAND, (payload) => {
7376
+ const { clipboardData } = payload;
7377
+ if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
7378
+ const data = clipboardData.getData("text/plain").trim();
7379
+ if (this.linkRegex.test(data)) {
7380
+ payload.stopImmediatePropagation();
7381
+ payload.preventDefault();
7382
+ editor.dispatchCommand(INSERT_LINK_COMMAND, { url: data });
7383
+ return true;
7384
+ }
7385
+ }
7386
+ return false;
7387
+ }, COMMAND_PRIORITY_NORMAL));
7388
+ this.registerMarkdown();
7389
+ this.registerLiteXml();
7390
+ }
7391
+ registerLiteXml() {
7392
+ const litexmlService = this.kernel.requireService(ILitexmlService);
7393
+ if (!litexmlService) return;
7394
+ litexmlService.registerXMLWriter(LinkNode.getType(), (node, ctx) => {
7395
+ if ($isLinkNode(node)) {
7396
+ const attributes = { href: node.getURL() };
7397
+ return ctx.createXmlNode("a", attributes);
7398
+ }
7399
+ return false;
7400
+ });
7401
+ litexmlService.registerXMLReader("a", (xmlNode, children) => {
7402
+ return [INodeHelper.createElementNode("link", {
7403
+ children,
7404
+ direction: "ltr",
7405
+ format: "",
7406
+ indent: 0,
7407
+ type: "link",
7408
+ url: xmlNode.getAttribute("href") || "",
7409
+ version: 1
7410
+ })];
7411
+ });
7412
+ }
7413
+ registerMarkdown() {
7414
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
7415
+ regExp: /\[([^[]+)]\(([^\s()]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)\s?$/,
7416
+ replace: (textNode, match) => {
7417
+ const [, linkText, linkUrl, linkTitle] = match;
7418
+ const linkNode = $createLinkNode(linkUrl, { title: linkTitle });
7419
+ const linkTextNode = $createTextNode(linkText);
7420
+ linkTextNode.setFormat(textNode.getFormat());
7421
+ linkNode.append(linkTextNode);
7422
+ textNode.replace(linkNode);
7423
+ return linkTextNode;
7424
+ },
7425
+ trigger: ")",
7426
+ type: "text-match"
5949
7427
  });
5950
7428
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
5951
7429
  if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
5952
7430
  });
5953
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
5954
- return [INodeHelper.createElementNode("link", {
7431
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
7432
+ return [INodeHelper.createElementNode("link", {
7433
+ children,
7434
+ direction: "ltr",
7435
+ format: "",
7436
+ indent: 0,
7437
+ title: node.title || void 0,
7438
+ type: "link",
7439
+ url: node.url || "",
7440
+ version: 1
7441
+ })];
7442
+ });
7443
+ }
7444
+ };
7445
+ //#endregion
7446
+ //#region src/utils/cx.ts
7447
+ const cx = (...classNames) => classNames.filter(Boolean).join(" ");
7448
+ //#endregion
7449
+ //#region src/plugins/list/utils/index.ts
7450
+ const LIST_INDENT_SIZE = 4;
7451
+ function getIndent(whitespaces) {
7452
+ const tabs = whitespaces.match(/\t/g);
7453
+ const spaces = whitespaces.match(/ /g);
7454
+ let indent = 0;
7455
+ if (tabs) indent += tabs.length;
7456
+ if (spaces) indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
7457
+ return indent;
7458
+ }
7459
+ const listReplace = (listType) => {
7460
+ return (parentNode, children, match, isImport) => {
7461
+ const previousNode = parentNode.getPreviousSibling();
7462
+ const nextNode = parentNode.getNextSibling();
7463
+ const listItem = $createListItemNode(listType === "check" ? match[3] === "x" : void 0);
7464
+ if ($isListNode(nextNode) && nextNode.getListType() === listType) {
7465
+ const firstChild = nextNode.getFirstChild();
7466
+ if (firstChild !== null) firstChild.insertBefore(listItem);
7467
+ else nextNode.append(listItem);
7468
+ parentNode.remove();
7469
+ } else if ($isListNode(previousNode) && previousNode.getListType() === listType) {
7470
+ previousNode.append(listItem);
7471
+ parentNode.remove();
7472
+ } else {
7473
+ const list = $createListNode(listType, listType === "number" ? Number(match[2]) : void 0);
7474
+ list.append(listItem);
7475
+ parentNode.replace(list);
7476
+ }
7477
+ listItem.append(...children);
7478
+ if (!isImport) listItem.select(0, 0);
7479
+ const indent = getIndent(match[1]);
7480
+ if (indent) listItem.setIndent(indent);
7481
+ };
7482
+ };
7483
+ function $indentOverTab(selection) {
7484
+ if ($filter(selection.getNodes(), (node) => {
7485
+ if ($isBlockElementNode(node) && node.canIndent()) return node;
7486
+ return null;
7487
+ }).length > 0) return true;
7488
+ const anchor = selection.anchor;
7489
+ const focus = selection.focus;
7490
+ const first = focus.isBefore(anchor) ? focus : anchor;
7491
+ const firstBlock = $getNearestBlockElementAncestorOrThrow(first.getNode());
7492
+ if (firstBlock.canIndent()) {
7493
+ const firstBlockKey = firstBlock.getKey();
7494
+ let selectionAtStart = $createRangeSelection();
7495
+ selectionAtStart.anchor.set(firstBlockKey, 0, "element");
7496
+ selectionAtStart.focus.set(firstBlockKey, 0, "element");
7497
+ selectionAtStart = $normalizeSelection__EXPERIMENTAL(selectionAtStart);
7498
+ if (selectionAtStart.anchor.is(first)) return true;
7499
+ }
7500
+ return false;
7501
+ }
7502
+ //#endregion
7503
+ //#region src/plugins/list/plugin/checkList.ts
7504
+ /**
7505
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
7506
+ *
7507
+ * This source code is licensed under the MIT license found in the
7508
+ * LICENSE file in the root directory of this source tree.
7509
+ *
7510
+ */
7511
+ const INSERT_CHECK_LIST_COMMAND = createCommand("INSERT_CHECK_LIST_COMMAND");
7512
+ function isHeadlessEditor$1(editor) {
7513
+ return editor._headless === true;
7514
+ }
7515
+ function handleCheckItemEvent(event, callback) {
7516
+ const target = event.target;
7517
+ if (!isHTMLElement$1(target)) return;
7518
+ const firstChild = target.firstChild;
7519
+ if (isHTMLElement$1(firstChild) && (firstChild.tagName === "UL" || firstChild.tagName === "OL")) return;
7520
+ const parentNode = target.parentNode;
7521
+ if (!parentNode || parentNode.__lexicalListType !== "check") return;
7522
+ const rect = target.getBoundingClientRect();
7523
+ const zoom = calculateZoomLevel(target);
7524
+ const clientX = event.clientX / zoom;
7525
+ const beforeStyles = window.getComputedStyle ? window.getComputedStyle(target, "::before") : { width: "0px" };
7526
+ const beforeWidthInPixels = parseFloat(beforeStyles.width);
7527
+ const beforeMargin = parseFloat(beforeStyles.marginLeft);
7528
+ const clickAreaPadding = event.pointerType === "touch" ? 32 : 0;
7529
+ if (target.dir === "rtl" ? clientX < rect.right + clickAreaPadding && clientX > rect.right - beforeWidthInPixels - clickAreaPadding : clientX > rect.left + beforeMargin - clickAreaPadding && clientX < rect.left + beforeMargin + beforeWidthInPixels + clickAreaPadding) callback();
7530
+ }
7531
+ function handleClick(event) {
7532
+ handleCheckItemEvent(event, () => {
7533
+ if (isHTMLElement$1(event.target)) {
7534
+ const domNode = event.target;
7535
+ const editor = getNearestEditorFromDOMNode(domNode);
7536
+ if (editor && editor.isEditable()) editor.update(() => {
7537
+ const node = $getNearestNodeFromDOMNode(domNode);
7538
+ if ($isListItemNode(node)) {
7539
+ domNode.focus();
7540
+ node.toggleChecked();
7541
+ }
7542
+ });
7543
+ }
7544
+ });
7545
+ }
7546
+ function handlePointerDown(event) {
7547
+ handleCheckItemEvent(event, () => {
7548
+ event.preventDefault();
7549
+ });
7550
+ }
7551
+ function getActiveCheckListItem() {
7552
+ const activeElement = document.activeElement;
7553
+ return isHTMLElement$1(activeElement) && activeElement.tagName === "LI" && activeElement.parentNode && activeElement.parentNode.__lexicalListType === "check" ? activeElement : null;
7554
+ }
7555
+ function findCheckListItemSibling(node, backward) {
7556
+ let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
7557
+ let parent = node;
7558
+ while (!sibling && $isListItemNode(parent)) {
7559
+ parent = parent.getParentOrThrow().getParent();
7560
+ if (parent) sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
7561
+ }
7562
+ while ($isListItemNode(sibling)) {
7563
+ const firstChild = backward ? sibling.getLastChild() : sibling.getFirstChild();
7564
+ if (!$isListNode(firstChild)) return sibling;
7565
+ sibling = backward ? firstChild.getLastChild() : firstChild.getFirstChild();
7566
+ }
7567
+ return null;
7568
+ }
7569
+ function handleArrowUpOrDown(event, editor, backward) {
7570
+ const activeItem = getActiveCheckListItem();
7571
+ if (activeItem) editor.update(() => {
7572
+ const listItem = $getNearestNodeFromDOMNode(activeItem);
7573
+ if (!$isListItemNode(listItem)) return;
7574
+ const nextListItem = findCheckListItemSibling(listItem, backward);
7575
+ if (nextListItem) {
7576
+ nextListItem.selectStart();
7577
+ const dom = editor.getElementByKey(nextListItem.__key);
7578
+ if (dom) {
7579
+ event.preventDefault();
7580
+ setTimeout(() => {
7581
+ dom.focus();
7582
+ }, 0);
7583
+ }
7584
+ }
7585
+ });
7586
+ return false;
7587
+ }
7588
+ function registerCheckList(editor) {
7589
+ const registrations = [
7590
+ editor.registerCommand(INSERT_CHECK_LIST_COMMAND, () => {
7591
+ $insertList("check");
7592
+ return true;
7593
+ }, COMMAND_PRIORITY_LOW),
7594
+ editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => {
7595
+ return handleArrowUpOrDown(event, editor, false);
7596
+ }, COMMAND_PRIORITY_LOW),
7597
+ editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => {
7598
+ return handleArrowUpOrDown(event, editor, true);
7599
+ }, COMMAND_PRIORITY_LOW),
7600
+ editor.registerCommand(KEY_ARROW_LEFT_COMMAND, (event) => {
7601
+ return editor.getEditorState().read(() => {
7602
+ const selection = $getSelection();
7603
+ if ($isRangeSelection(selection) && selection.isCollapsed()) {
7604
+ const { anchor } = selection;
7605
+ const isElement = anchor.type === "element";
7606
+ if (isElement || anchor.offset === 0) {
7607
+ const anchorNode = anchor.getNode();
7608
+ const elementNode = $findMatchingParent(anchorNode, (node) => $isElementNode(node) && !node.isInline());
7609
+ if ($isListItemNode(elementNode)) {
7610
+ const parent = elementNode.getParent();
7611
+ if ($isListNode(parent) && parent.getListType() === "check" && (isElement || elementNode.getFirstDescendant() === anchorNode)) {
7612
+ const domNode = editor.getElementByKey(elementNode.__key);
7613
+ if (domNode && document.activeElement !== domNode) {
7614
+ domNode.focus();
7615
+ event.preventDefault();
7616
+ return true;
7617
+ }
7618
+ }
7619
+ }
7620
+ }
7621
+ }
7622
+ return false;
7623
+ });
7624
+ }, COMMAND_PRIORITY_LOW)
7625
+ ];
7626
+ if (!isHeadlessEditor$1(editor)) registrations.push(editor.registerRootListener((rootElement, prevElement) => {
7627
+ if (rootElement !== null) {
7628
+ rootElement.addEventListener("click", handleClick);
7629
+ rootElement.addEventListener("pointerdown", handlePointerDown);
7630
+ }
7631
+ if (prevElement !== null) {
7632
+ prevElement.removeEventListener("click", handleClick);
7633
+ prevElement.removeEventListener("pointerdown", handlePointerDown);
7634
+ }
7635
+ }));
7636
+ return mergeRegister(...registrations);
7637
+ }
7638
+ //#endregion
7639
+ //#region src/plugins/list/plugin/registry.ts
7640
+ function registerListCommands(editor, kernel, options) {
7641
+ const { enableHotkey = true } = options || {};
7642
+ return mergeRegister(kernel.registerHotkey(HotkeyEnum.BulletList, () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, void 0), {
7643
+ enabled: enableHotkey,
7644
+ preventDefault: true,
7645
+ stopImmediatePropagation: true
7646
+ }), kernel.registerHotkey(HotkeyEnum.NumberList, () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, void 0), {
7647
+ enabled: enableHotkey,
7648
+ preventDefault: true,
7649
+ stopImmediatePropagation: true
7650
+ }), editor.registerCommand(KEY_TAB_COMMAND, (event) => {
7651
+ const selection = $getSelection();
7652
+ if (!$isRangeSelection(selection)) return false;
7653
+ event.preventDefault();
7654
+ const command = $indentOverTab(selection) ? event.shiftKey ? OUTDENT_CONTENT_COMMAND : INDENT_CONTENT_COMMAND : INSERT_TAB_COMMAND;
7655
+ return editor.dispatchCommand(command, void 0);
7656
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_BACKSPACE_COMMAND, (event) => {
7657
+ const selection = $getSelection();
7658
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
7659
+ const anchor = selection.anchor;
7660
+ if (anchor.offset !== 0) return false;
7661
+ const anchorNode = anchor.getNode();
7662
+ let listItemNode;
7663
+ if ($isListItemNode(anchorNode)) listItemNode = anchorNode;
7664
+ else if ($isTextNode(anchorNode)) {
7665
+ if (anchorNode.getPreviousSibling()) return false;
7666
+ const parent = anchorNode.getParentOrThrow();
7667
+ if (!$isListItemNode(parent)) return false;
7668
+ listItemNode = parent;
7669
+ }
7670
+ if (!listItemNode || !$isRootNode(listItemNode.getParent()?.getParent())) return false;
7671
+ const listNode = listItemNode.getParentOrThrow();
7672
+ queueMicrotask(() => {
7673
+ editor.update(() => {
7674
+ if (!listItemNode) return;
7675
+ let newlistNode;
7676
+ if (listItemNode.getPreviousSibling() === null) {
7677
+ listItemNode.replace($createParagraphNode(), true).select(0, 0);
7678
+ return;
7679
+ }
7680
+ let next = listItemNode.getNextSibling();
7681
+ if (next) newlistNode = $createListNode(listNode.getListType(), listItemNode.getValue());
7682
+ while (next && newlistNode) {
7683
+ next.remove();
7684
+ newlistNode.append(next);
7685
+ next = next.getNextSibling();
7686
+ }
7687
+ const p = listItemNode.replace($createParagraphNode(), true);
7688
+ p.remove();
7689
+ listNode.insertAfter(p);
7690
+ if (newlistNode) p.insertAfter(newlistNode);
7691
+ p.select(0, 0);
7692
+ });
7693
+ });
7694
+ event.stopImmediatePropagation();
7695
+ event.preventDefault();
7696
+ return true;
7697
+ }, COMMAND_PRIORITY_LOW));
7698
+ }
7699
+ //#endregion
7700
+ //#region src/plugins/list/plugin/index.ts
7701
+ const ORDERED_LIST_REGEX = /^(\s*)(\d+)\.\s/;
7702
+ const UNORDERED_LIST_REGEX = /^(\s*)[*+-]\s/;
7703
+ const CHECK_LIST_REGEX = /^(\s*)(?:-\s)?\s?(\[(\s|x)?])\s/i;
7704
+ const ListPlugin = class extends KernelPlugin {
7705
+ static {
7706
+ this.pluginName = "ListPlugin";
7707
+ }
7708
+ constructor(kernel, config) {
7709
+ super();
7710
+ this.kernel = kernel;
7711
+ this.config = config;
7712
+ kernel.registerNodes([ListNode, ListItemNode]);
7713
+ kernel.registerThemes({ list: {
7714
+ checklist: "editor_listItemCheck",
7715
+ listitem: "editor_listItem",
7716
+ listitemChecked: "editor_listItemChecked",
7717
+ listitemUnchecked: "editor_listItemUnchecked",
7718
+ nested: { listitem: "editor_listItemNested" },
7719
+ ol: cx("editor_listOrdered", config?.theme),
7720
+ olDepth: [
7721
+ "editor_listOrdered dp1",
7722
+ "editor_listOrdered dp2",
7723
+ "editor_listOrdered dp3",
7724
+ "editor_listOrdered dp4",
7725
+ "editor_listOrdered dp5"
7726
+ ],
7727
+ ul: cx("editor_listUnordered", config?.theme)
7728
+ } });
7729
+ }
7730
+ onInit(editor) {
7731
+ this.register(registerList(editor));
7732
+ this.register(registerCheckList(editor));
7733
+ this.register(registerListStrictIndentTransform(editor));
7734
+ this.register(registerListCommands(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }));
7735
+ this.registerMarkdown();
7736
+ this.registerLiteXml();
7737
+ }
7738
+ registerLiteXml() {
7739
+ const litexmlService = this.kernel.requireService(ILitexmlService);
7740
+ if (!litexmlService) return;
7741
+ litexmlService.registerXMLWriter(ListNode.getType(), (node, ctx) => {
7742
+ if ($isListNode(node)) {
7743
+ const tagName = node.getListType() === "number" ? "ol" : "ul";
7744
+ const attributes = {};
7745
+ if (node.getListType() === "number" && node.getStart() !== 1) attributes.start = node.getStart().toString();
7746
+ return ctx.createXmlNode(tagName, attributes);
7747
+ }
7748
+ return false;
7749
+ });
7750
+ litexmlService.registerXMLWriter(ListItemNode.getType(), (node, ctx) => {
7751
+ if ($isListItemNode(node)) return ctx.createXmlNode("li");
7752
+ return false;
7753
+ });
7754
+ litexmlService.registerXMLReader("ol", (xmlNode, children) => {
7755
+ return INodeHelper.createElementNode("list", {
7756
+ children,
7757
+ direction: "ltr",
7758
+ format: "",
7759
+ indent: 0,
7760
+ listType: "number",
7761
+ start: xmlNode.getAttribute("start") ? parseInt(xmlNode.getAttribute("start"), 10) : 1,
7762
+ tag: "ol",
7763
+ version: 1
7764
+ });
7765
+ });
7766
+ litexmlService.registerXMLReader("ul", (xmlNode, children) => {
7767
+ return INodeHelper.createElementNode("list", {
7768
+ children,
7769
+ direction: "ltr",
7770
+ format: "",
7771
+ indent: 0,
7772
+ listType: "bullet",
7773
+ start: 1,
7774
+ tag: "ul",
7775
+ version: 1
7776
+ });
7777
+ });
7778
+ litexmlService.registerXMLReader("li", (xmlNode, children) => {
7779
+ return INodeHelper.createElementNode("listitem", {
5955
7780
  children,
5956
7781
  direction: "ltr",
5957
7782
  format: "",
5958
7783
  indent: 0,
5959
- title: node.title || void 0,
5960
- type: "link",
5961
- url: node.url || "",
7784
+ type: "listitem",
5962
7785
  version: 1
5963
- })];
7786
+ });
7787
+ });
7788
+ }
7789
+ registerMarkdown() {
7790
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
7791
+ if (!markdownService) return;
7792
+ markdownService.registerMarkdownShortCut({
7793
+ regExp: UNORDERED_LIST_REGEX,
7794
+ replace: listReplace("bullet"),
7795
+ type: "element"
7796
+ });
7797
+ markdownService.registerMarkdownShortCut({
7798
+ regExp: ORDERED_LIST_REGEX,
7799
+ replace: listReplace("number"),
7800
+ type: "element"
7801
+ });
7802
+ markdownService.registerMarkdownShortCut({
7803
+ regExp: CHECK_LIST_REGEX,
7804
+ replace: listReplace("check"),
7805
+ type: "element"
7806
+ });
7807
+ markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
7808
+ if ($isListNode(node)) ctx.wrap("", "\n");
7809
+ });
7810
+ const getLevel = (node) => {
7811
+ if (!node) return 0;
7812
+ if ($isRootNode(node.getParent())) return 0;
7813
+ const parent = node.getParent();
7814
+ if (!parent) return 0;
7815
+ return getLevel($getNearestNodeOfType(parent, ListNode)) + 1;
7816
+ };
7817
+ markdownService.registerMarkdownWriter(ListItemNode.getType(), (ctx, node) => {
7818
+ const parent = node.getParent();
7819
+ if ($isListItemNode(node) && $isListNode(parent)) {
7820
+ if ($isListNode(node.getFirstChild())) return;
7821
+ const level = getLevel(parent);
7822
+ const prefix = " ".repeat(level);
7823
+ switch (parent.getListType()) {
7824
+ case "bullet":
7825
+ ctx.wrap(prefix + "- ", "\n");
7826
+ break;
7827
+ case "number":
7828
+ ctx.wrap(`${prefix}${node.getValue()}. `, "\n");
7829
+ break;
7830
+ case "check":
7831
+ ctx.wrap(`${prefix}- [${node.getChecked() ? "x" : " "}] `, "\n");
7832
+ break;
7833
+ default: break;
7834
+ }
7835
+ }
7836
+ });
7837
+ markdownService.registerMarkdownReader("list", (node, children) => {
7838
+ const isCheck = node.children?.[0]?.type === "listItem" && typeof node.children?.[0]?.checked === "boolean";
7839
+ let start = node.start || 1;
7840
+ return INodeHelper.createElementNode("list", {
7841
+ children: children.map((v) => {
7842
+ if (v.type === "listitem") v.value = start++;
7843
+ return v;
7844
+ }),
7845
+ direction: "ltr",
7846
+ format: "",
7847
+ indent: 0,
7848
+ listType: isCheck ? "check" : node.ordered ? "number" : "bullet",
7849
+ start: node.start || 1,
7850
+ tag: node.ordered ? "ol" : "ul",
7851
+ version: 1
7852
+ });
7853
+ });
7854
+ markdownService.registerMarkdownReader("listItem", (node, children, index) => {
7855
+ return children.map((v) => {
7856
+ if (v.type === "paragraph") return INodeHelper.createElementNode("listitem", {
7857
+ checked: typeof node.checked === "boolean" ? node.checked : void 0,
7858
+ children: v.children,
7859
+ direction: "ltr",
7860
+ format: "",
7861
+ indent: 0,
7862
+ type: "listitem",
7863
+ value: index + 1,
7864
+ version: 1
7865
+ });
7866
+ else if (v.type === "list") return INodeHelper.createElementNode("listitem", {
7867
+ children: [v],
7868
+ direction: "ltr",
7869
+ format: "",
7870
+ indent: 0,
7871
+ type: "listitem",
7872
+ value: index + 1,
7873
+ version: 1
7874
+ });
7875
+ return v;
7876
+ });
5964
7877
  });
5965
7878
  }
5966
7879
  };
5967
7880
  //#endregion
5968
- //#region src/utils/cx.ts
5969
- const cx = (...classNames) => classNames.filter(Boolean).join(" ");
5970
- //#endregion
5971
- //#region src/plugins/list/utils/index.ts
5972
- const LIST_INDENT_SIZE = 4;
5973
- function getIndent(whitespaces) {
5974
- const tabs = whitespaces.match(/\t/g);
5975
- const spaces = whitespaces.match(/ /g);
5976
- let indent = 0;
5977
- if (tabs) indent += tabs.length;
5978
- if (spaces) indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
5979
- return indent;
5980
- }
5981
- const listReplace = (listType) => {
5982
- return (parentNode, children, match, isImport) => {
5983
- const previousNode = parentNode.getPreviousSibling();
5984
- const nextNode = parentNode.getNextSibling();
5985
- const listItem = $createListItemNode(listType === "check" ? match[3] === "x" : void 0);
5986
- if ($isListNode(nextNode) && nextNode.getListType() === listType) {
5987
- const firstChild = nextNode.getFirstChild();
5988
- if (firstChild !== null) firstChild.insertBefore(listItem);
5989
- else nextNode.append(listItem);
5990
- parentNode.remove();
5991
- } else if ($isListNode(previousNode) && previousNode.getListType() === listType) {
5992
- previousNode.append(listItem);
5993
- parentNode.remove();
5994
- } else {
5995
- const list = $createListNode(listType, listType === "number" ? Number(match[2]) : void 0);
5996
- list.append(listItem);
5997
- parentNode.replace(list);
5998
- }
5999
- listItem.append(...children);
6000
- if (!isImport) listItem.select(0, 0);
6001
- const indent = getIndent(match[1]);
6002
- if (indent) listItem.setIndent(indent);
6003
- };
7881
+ //#region src/plugins/math/node/index.ts
7882
+ var MathInlineNode = class MathInlineNode extends DecoratorNode {
7883
+ static getType() {
7884
+ return "math";
7885
+ }
7886
+ static clone(node) {
7887
+ return new MathInlineNode(node.__code, node.__key);
7888
+ }
7889
+ static importJSON(serializedNode) {
7890
+ return $createMathInlineNode().updateFromJSON(serializedNode);
7891
+ }
7892
+ static importDOM() {
7893
+ return { span: (node) => {
7894
+ if (node.classList.contains("math-inline")) return {
7895
+ conversion: $convertMathInlineElement,
7896
+ priority: 0
7897
+ };
7898
+ return null;
7899
+ } };
7900
+ }
7901
+ constructor(code = "", key) {
7902
+ super(key);
7903
+ this.__code = code;
7904
+ }
7905
+ get code() {
7906
+ return this.__code;
7907
+ }
7908
+ updateCode(newCode) {
7909
+ const writer = this.getWritable();
7910
+ writer.__code = newCode;
7911
+ }
7912
+ exportDOM() {
7913
+ const span = document.createElement("span");
7914
+ span.className = "math-inline";
7915
+ return { element: span };
7916
+ }
7917
+ createDOM(config) {
7918
+ const element = document.createElement("span");
7919
+ addClassNamesToElement(element, config.theme.mathInline);
7920
+ return element;
7921
+ }
7922
+ exportJSON() {
7923
+ return {
7924
+ ...super.exportJSON(),
7925
+ code: this.__code
7926
+ };
7927
+ }
7928
+ updateFromJSON(serializedNode) {
7929
+ const node = super.updateFromJSON(serializedNode);
7930
+ this.__code = serializedNode.code || "";
7931
+ return node;
7932
+ }
7933
+ getTextContent() {
7934
+ return `$${this.code}$`;
7935
+ }
7936
+ isInline() {
7937
+ return true;
7938
+ }
7939
+ updateDOM() {
7940
+ return false;
7941
+ }
7942
+ decorate(editor) {
7943
+ const decorator = getKernelFromEditor(editor)?.getDecorator(MathInlineNode.getType());
7944
+ if (!decorator) return null;
7945
+ if (typeof decorator === "function") return decorator(this, editor);
7946
+ return {
7947
+ queryDOM: decorator.queryDOM,
7948
+ render: decorator.render(this, editor)
7949
+ };
7950
+ }
6004
7951
  };
6005
- function $indentOverTab(selection) {
6006
- if ($filter(selection.getNodes(), (node) => {
6007
- if ($isBlockElementNode(node) && node.canIndent()) return node;
6008
- return null;
6009
- }).length > 0) return true;
6010
- const anchor = selection.anchor;
6011
- const focus = selection.focus;
6012
- const first = focus.isBefore(anchor) ? focus : anchor;
6013
- const firstBlock = $getNearestBlockElementAncestorOrThrow(first.getNode());
6014
- if (firstBlock.canIndent()) {
6015
- const firstBlockKey = firstBlock.getKey();
6016
- let selectionAtStart = $createRangeSelection();
6017
- selectionAtStart.anchor.set(firstBlockKey, 0, "element");
6018
- selectionAtStart.focus.set(firstBlockKey, 0, "element");
6019
- selectionAtStart = $normalizeSelection__EXPERIMENTAL(selectionAtStart);
6020
- if (selectionAtStart.anchor.is(first)) return true;
7952
+ var MathBlockNode = class MathBlockNode extends DecoratorNode {
7953
+ static getType() {
7954
+ return "mathBlock";
6021
7955
  }
6022
- return false;
7956
+ static clone(node) {
7957
+ return new MathBlockNode(node.__code, node.__key);
7958
+ }
7959
+ static importJSON(serializedNode) {
7960
+ return $createMathBlockNode().updateFromJSON(serializedNode);
7961
+ }
7962
+ static importDOM() {
7963
+ return { div: (node) => {
7964
+ if (node.classList.contains("math-block")) return {
7965
+ conversion: $convertMathBlockElement,
7966
+ priority: 0
7967
+ };
7968
+ return null;
7969
+ } };
7970
+ }
7971
+ constructor(code = "", key) {
7972
+ super(key);
7973
+ this.__code = code;
7974
+ }
7975
+ get code() {
7976
+ return this.__code;
7977
+ }
7978
+ updateCode(newCode) {
7979
+ const writer = this.getWritable();
7980
+ writer.__code = newCode;
7981
+ }
7982
+ exportDOM() {
7983
+ const div = document.createElement("div");
7984
+ div.className = "math-block";
7985
+ return { element: div };
7986
+ }
7987
+ createDOM(config) {
7988
+ const element = document.createElement("div");
7989
+ addClassNamesToElement(element, config.theme.mathBlock);
7990
+ return element;
7991
+ }
7992
+ exportJSON() {
7993
+ return {
7994
+ ...super.exportJSON(),
7995
+ code: this.__code
7996
+ };
7997
+ }
7998
+ updateFromJSON(serializedNode) {
7999
+ const node = super.updateFromJSON(serializedNode);
8000
+ this.__code = serializedNode.code || "";
8001
+ return node;
8002
+ }
8003
+ getTextContent() {
8004
+ return `$$\n${this.code}\n$$\n`;
8005
+ }
8006
+ isInline() {
8007
+ return false;
8008
+ }
8009
+ updateDOM() {
8010
+ return false;
8011
+ }
8012
+ decorate(editor) {
8013
+ const decorator = getKernelFromEditor(editor)?.getDecorator(MathBlockNode.getType());
8014
+ if (!decorator) return null;
8015
+ if (typeof decorator === "function") return decorator(this, editor);
8016
+ return {
8017
+ queryDOM: decorator.queryDOM,
8018
+ render: decorator.render(this, editor)
8019
+ };
8020
+ }
8021
+ };
8022
+ function $createMathInlineNode(code = "") {
8023
+ return $applyNodeReplacement(new MathInlineNode(code));
8024
+ }
8025
+ function $createMathBlockNode(code = "") {
8026
+ return $applyNodeReplacement(new MathBlockNode(code));
8027
+ }
8028
+ function $convertMathInlineElement() {
8029
+ return { node: $createMathInlineNode() };
8030
+ }
8031
+ function $convertMathBlockElement() {
8032
+ return { node: $createMathBlockNode() };
6023
8033
  }
6024
8034
  //#endregion
6025
- //#region src/plugins/list/plugin/checkList.ts
8035
+ //#region src/plugins/math/command/index.ts
8036
+ const INSERT_MATH_COMMAND = createCommand("INSERT_MATH_COMMAND");
8037
+ const UPDATE_MATH_COMMAND = createCommand("UPDATE_MATH_COMMAND");
8038
+ const SELECT_MATH_SIDE_COMMAND = createCommand("SELECT_MATH_SIDE_COMMAND");
8039
+ function registerMathCommand(editor) {
8040
+ return mergeRegister(editor.registerCommand(INSERT_MATH_COMMAND, (payload) => {
8041
+ const { code } = payload;
8042
+ editor.update(() => {
8043
+ const mathNode = $createMathInlineNode(code);
8044
+ $insertNodes([mathNode]);
8045
+ if ($isRootOrShadowRoot(mathNode.getParentOrThrow())) $wrapNodeInElement(mathNode, $createParagraphNode).selectEnd();
8046
+ });
8047
+ return true;
8048
+ }, COMMAND_PRIORITY_HIGH), editor.registerCommand(UPDATE_MATH_COMMAND, (payload) => {
8049
+ const { code, key } = payload;
8050
+ editor.update(() => {
8051
+ const mathCode = $getNodeByKey(key);
8052
+ if (mathCode) {
8053
+ mathCode.updateCode(code);
8054
+ mathCode.selectNext();
8055
+ }
8056
+ });
8057
+ return true;
8058
+ }, COMMAND_PRIORITY_HIGH), editor.registerCommand(SELECT_MATH_SIDE_COMMAND, (payload) => {
8059
+ const { key, prev } = payload;
8060
+ editor.update(() => {
8061
+ const mathNode = $getNodeByKey(key);
8062
+ if (mathNode) if (prev) mathNode.selectPrevious();
8063
+ else mathNode.selectNext();
8064
+ });
8065
+ return true;
8066
+ }, COMMAND_PRIORITY_HIGH));
8067
+ }
8068
+ //#endregion
8069
+ //#region src/plugins/math/utils/index.ts
6026
8070
  /**
6027
- * Copyright (c) Meta Platforms, Inc. and affiliates.
8071
+ * Heuristic: does the content between `$...$` look like a math formula?
6028
8072
  *
6029
- * This source code is licensed under the MIT license found in the
6030
- * LICENSE file in the root directory of this source tree.
8073
+ * Positive signals (any one math):
8074
+ * - LaTeX commands \alpha \frac …
8075
+ * - Structural chars ^ _ { } ( )
8076
+ * - Operators / relations + - * / = < > | ! ,
8077
+ * - Digits 0-9
8078
+ * - Single ASCII letter x y n (common math variable)
6031
8079
  *
8080
+ * Everything else (multi-char words, CJK, spaced plain text) → not math.
6032
8081
  */
6033
- const INSERT_CHECK_LIST_COMMAND = createCommand("INSERT_CHECK_LIST_COMMAND");
6034
- function isHeadlessEditor$1(editor) {
6035
- return editor._headless === true;
6036
- }
6037
- function handleCheckItemEvent(event, callback) {
6038
- const target = event.target;
6039
- if (!isHTMLElement$1(target)) return;
6040
- const firstChild = target.firstChild;
6041
- if (isHTMLElement$1(firstChild) && (firstChild.tagName === "UL" || firstChild.tagName === "OL")) return;
6042
- const parentNode = target.parentNode;
6043
- if (!parentNode || parentNode.__lexicalListType !== "check") return;
6044
- const rect = target.getBoundingClientRect();
6045
- const zoom = calculateZoomLevel(target);
6046
- const clientX = event.clientX / zoom;
6047
- const beforeStyles = window.getComputedStyle ? window.getComputedStyle(target, "::before") : { width: "0px" };
6048
- const beforeWidthInPixels = parseFloat(beforeStyles.width);
6049
- const beforeMargin = parseFloat(beforeStyles.marginLeft);
6050
- const clickAreaPadding = event.pointerType === "touch" ? 32 : 0;
6051
- if (target.dir === "rtl" ? clientX < rect.right + clickAreaPadding && clientX > rect.right - beforeWidthInPixels - clickAreaPadding : clientX > rect.left + beforeMargin - clickAreaPadding && clientX < rect.left + beforeMargin + beforeWidthInPixels + clickAreaPadding) callback();
6052
- }
6053
- function handleClick(event) {
6054
- handleCheckItemEvent(event, () => {
6055
- if (isHTMLElement$1(event.target)) {
6056
- const domNode = event.target;
6057
- const editor = getNearestEditorFromDOMNode(domNode);
6058
- if (editor && editor.isEditable()) editor.update(() => {
6059
- const node = $getNearestNodeFromDOMNode(domNode);
6060
- if ($isListItemNode(node)) {
6061
- domNode.focus();
6062
- node.toggleChecked();
6063
- }
6064
- });
6065
- }
6066
- });
6067
- }
6068
- function handlePointerDown(event) {
6069
- handleCheckItemEvent(event, () => {
6070
- event.preventDefault();
6071
- });
6072
- }
6073
- function getActiveCheckListItem() {
6074
- const activeElement = document.activeElement;
6075
- return isHTMLElement$1(activeElement) && activeElement.tagName === "LI" && activeElement.parentNode && activeElement.parentNode.__lexicalListType === "check" ? activeElement : null;
8082
+ function isLikelyMathContent(content) {
8083
+ const trimmed = content.trim();
8084
+ if (!trimmed) return false;
8085
+ if (/\\[A-Za-z]/.test(trimmed)) return true;
8086
+ if (/[\d!()*+,/<=>^_{|}-]/.test(trimmed)) return true;
8087
+ if (/^[A-Za-z]$/.test(trimmed)) return true;
8088
+ return false;
6076
8089
  }
6077
- function findCheckListItemSibling(node, backward) {
6078
- let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
6079
- let parent = node;
6080
- while (!sibling && $isListItemNode(parent)) {
6081
- parent = parent.getParentOrThrow().getParent();
6082
- if (parent) sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
8090
+ //#endregion
8091
+ //#region src/plugins/math/plugin/index.ts
8092
+ const MathPlugin = class extends KernelPlugin {
8093
+ static {
8094
+ this.pluginName = "MathPlugin";
6083
8095
  }
6084
- while ($isListItemNode(sibling)) {
6085
- const firstChild = backward ? sibling.getLastChild() : sibling.getFirstChild();
6086
- if (!$isListNode(firstChild)) return sibling;
6087
- sibling = backward ? firstChild.getLastChild() : firstChild.getFirstChild();
8096
+ constructor(kernel, config) {
8097
+ super();
8098
+ this.kernel = kernel;
8099
+ this.config = config;
8100
+ this.logger = createDebugLogger("plugin", "math");
8101
+ kernel.registerNodes([MathInlineNode, MathBlockNode]);
8102
+ if (config?.theme) kernel.registerThemes(config?.theme);
8103
+ this.registerDecorator(kernel, MathInlineNode.getType(), (node, editor) => {
8104
+ return config?.decorator ? config.decorator(node, editor) : null;
8105
+ });
8106
+ this.registerDecorator(kernel, MathBlockNode.getType(), (node, editor) => {
8107
+ return config?.decorator ? config.decorator(node, editor) : null;
8108
+ });
6088
8109
  }
6089
- return null;
6090
- }
6091
- function handleArrowUpOrDown(event, editor, backward) {
6092
- const activeItem = getActiveCheckListItem();
6093
- if (activeItem) editor.update(() => {
6094
- const listItem = $getNearestNodeFromDOMNode(activeItem);
6095
- if (!$isListItemNode(listItem)) return;
6096
- const nextListItem = findCheckListItemSibling(listItem, backward);
6097
- if (nextListItem) {
6098
- nextListItem.selectStart();
6099
- const dom = editor.getElementByKey(nextListItem.__key);
6100
- if (dom) {
6101
- event.preventDefault();
6102
- setTimeout(() => {
6103
- dom.focus();
6104
- }, 0);
8110
+ onInit(editor) {
8111
+ this.register(registerMathCommand(editor));
8112
+ this.registerMarkdown();
8113
+ this.registerLiteXml();
8114
+ }
8115
+ registerLiteXml() {
8116
+ const litexmlService = this.kernel.requireService(ILitexmlService);
8117
+ if (!litexmlService) return;
8118
+ litexmlService.registerXMLWriter(MathInlineNode.getType(), (node, ctx) => {
8119
+ if (node instanceof MathInlineNode) {
8120
+ const attributes = { code: node.getTextContent() };
8121
+ return ctx.createXmlNode("math", attributes);
6105
8122
  }
6106
- }
6107
- });
6108
- return false;
6109
- }
6110
- function registerCheckList(editor) {
6111
- const registrations = [
6112
- editor.registerCommand(INSERT_CHECK_LIST_COMMAND, () => {
6113
- $insertList("check");
8123
+ return false;
8124
+ });
8125
+ litexmlService.registerXMLWriter(MathBlockNode.getType(), (node, ctx) => {
8126
+ if (node instanceof MathBlockNode) {
8127
+ const attributes = { code: node.getTextContent() };
8128
+ return ctx.createXmlNode("mathBlock", attributes);
8129
+ }
8130
+ return false;
8131
+ });
8132
+ litexmlService.registerXMLReader("math", (xmlNode) => {
8133
+ return INodeHelper.createElementNode(MathInlineNode.getType(), {
8134
+ code: xmlNode.getAttribute("code") || "",
8135
+ version: 1
8136
+ });
8137
+ });
8138
+ litexmlService.registerXMLReader("mathBlock", (xmlNode) => {
8139
+ return INodeHelper.createElementNode(MathBlockNode.getType(), {
8140
+ code: xmlNode.getAttribute("code") || "",
8141
+ version: 1
8142
+ });
8143
+ });
8144
+ }
8145
+ registerMarkdown() {
8146
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
8147
+ markdownService?.registerMarkdownShortCut({
8148
+ regExp: /\$([^\s$](?:[^$]*[^\s$])?)\$\s?$/,
8149
+ replace: (textNode, match) => {
8150
+ const [, code] = match;
8151
+ if (!isLikelyMathContent(code)) return;
8152
+ const mathNode = $createMathInlineNode(code);
8153
+ this.logger.debug("Math node inserted:", mathNode);
8154
+ textNode.insertBefore(mathNode);
8155
+ textNode.setTextContent("");
8156
+ textNode.select();
8157
+ },
8158
+ trigger: "$",
8159
+ type: "text-match"
8160
+ });
8161
+ markdownService?.registerMarkdownShortCut({
8162
+ regExp: /^(\$\$)$/,
8163
+ replace: (parentNode, _1, _2, isImport) => {
8164
+ const node = $createMathBlockNode();
8165
+ if (isImport || parentNode.getNextSibling()) parentNode.replace(node);
8166
+ else parentNode.insertBefore(node);
8167
+ const sel = $createNodeSelection();
8168
+ sel.add(node.getKey());
8169
+ $setSelection(sel);
8170
+ },
8171
+ trigger: "enter",
8172
+ type: "element"
8173
+ });
8174
+ markdownService?.registerMarkdownWriter(MathInlineNode.getType(), (ctx, node) => {
8175
+ ctx.appendLine(node.getTextContent());
6114
8176
  return true;
6115
- }, COMMAND_PRIORITY_LOW),
6116
- editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => {
6117
- return handleArrowUpOrDown(event, editor, false);
6118
- }, COMMAND_PRIORITY_LOW),
6119
- editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => {
6120
- return handleArrowUpOrDown(event, editor, true);
6121
- }, COMMAND_PRIORITY_LOW),
6122
- editor.registerCommand(KEY_ARROW_LEFT_COMMAND, (event) => {
6123
- return editor.getEditorState().read(() => {
6124
- const selection = $getSelection();
6125
- if ($isRangeSelection(selection) && selection.isCollapsed()) {
6126
- const { anchor } = selection;
6127
- const isElement = anchor.type === "element";
6128
- if (isElement || anchor.offset === 0) {
6129
- const anchorNode = anchor.getNode();
6130
- const elementNode = $findMatchingParent(anchorNode, (node) => $isElementNode(node) && !node.isInline());
6131
- if ($isListItemNode(elementNode)) {
6132
- const parent = elementNode.getParent();
6133
- if ($isListNode(parent) && parent.getListType() === "check" && (isElement || elementNode.getFirstDescendant() === anchorNode)) {
6134
- const domNode = editor.getElementByKey(elementNode.__key);
6135
- if (domNode && document.activeElement !== domNode) {
6136
- domNode.focus();
6137
- event.preventDefault();
6138
- return true;
6139
- }
6140
- }
6141
- }
6142
- }
6143
- }
6144
- return false;
8177
+ });
8178
+ markdownService?.registerMarkdownWriter(MathBlockNode.getType(), (ctx, node) => {
8179
+ ctx.appendLine(node.getTextContent());
8180
+ return true;
8181
+ });
8182
+ markdownService?.registerMarkdownReader("inlineMath", (node) => {
8183
+ return INodeHelper.createElementNode("math", {
8184
+ code: node.value,
8185
+ version: 1
6145
8186
  });
6146
- }, COMMAND_PRIORITY_LOW)
6147
- ];
6148
- if (!isHeadlessEditor$1(editor)) registrations.push(editor.registerRootListener((rootElement, prevElement) => {
6149
- if (rootElement !== null) {
6150
- rootElement.addEventListener("click", handleClick);
6151
- rootElement.addEventListener("pointerdown", handlePointerDown);
6152
- }
6153
- if (prevElement !== null) {
6154
- prevElement.removeEventListener("click", handleClick);
6155
- prevElement.removeEventListener("pointerdown", handlePointerDown);
6156
- }
6157
- }));
6158
- return mergeRegister(...registrations);
6159
- }
6160
- //#endregion
6161
- //#region src/plugins/list/plugin/registry.ts
6162
- function registerListCommands(editor, kernel, options) {
6163
- const { enableHotkey = true } = options || {};
6164
- return mergeRegister(kernel.registerHotkey(HotkeyEnum.BulletList, () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, void 0), {
6165
- enabled: enableHotkey,
6166
- preventDefault: true,
6167
- stopImmediatePropagation: true
6168
- }), kernel.registerHotkey(HotkeyEnum.NumberList, () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, void 0), {
6169
- enabled: enableHotkey,
6170
- preventDefault: true,
6171
- stopImmediatePropagation: true
6172
- }), editor.registerCommand(KEY_TAB_COMMAND, (event) => {
6173
- const selection = $getSelection();
6174
- if (!$isRangeSelection(selection)) return false;
6175
- event.preventDefault();
6176
- const command = $indentOverTab(selection) ? event.shiftKey ? OUTDENT_CONTENT_COMMAND : INDENT_CONTENT_COMMAND : INSERT_TAB_COMMAND;
6177
- return editor.dispatchCommand(command, void 0);
6178
- }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_BACKSPACE_COMMAND, (event) => {
6179
- const selection = $getSelection();
6180
- if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
6181
- const anchor = selection.anchor;
6182
- if (anchor.offset !== 0) return false;
6183
- const anchorNode = anchor.getNode();
6184
- let listItemNode;
6185
- if ($isListItemNode(anchorNode)) listItemNode = anchorNode;
6186
- else if ($isTextNode(anchorNode)) {
6187
- if (anchorNode.getPreviousSibling()) return false;
6188
- const parent = anchorNode.getParentOrThrow();
6189
- if (!$isListItemNode(parent)) return false;
6190
- listItemNode = parent;
6191
- }
6192
- if (!listItemNode || !$isRootNode(listItemNode.getParent()?.getParent())) return false;
6193
- const listNode = listItemNode.getParentOrThrow();
6194
- queueMicrotask(() => {
6195
- editor.update(() => {
6196
- if (!listItemNode) return;
6197
- let newlistNode;
6198
- if (listItemNode.getPreviousSibling() === null) {
6199
- listItemNode.replace($createParagraphNode(), true).select(0, 0);
6200
- return;
6201
- }
6202
- let next = listItemNode.getNextSibling();
6203
- if (next) newlistNode = $createListNode(listNode.getListType(), listItemNode.getValue());
6204
- while (next && newlistNode) {
6205
- next.remove();
6206
- newlistNode.append(next);
6207
- next = next.getNextSibling();
6208
- }
6209
- const p = listItemNode.replace($createParagraphNode(), true);
6210
- p.remove();
6211
- listNode.insertAfter(p);
6212
- if (newlistNode) p.insertAfter(newlistNode);
6213
- p.select(0, 0);
8187
+ });
8188
+ markdownService?.registerMarkdownReader("math", (node) => {
8189
+ return INodeHelper.createElementNode("mathBlock", {
8190
+ code: node.value,
8191
+ version: 1
6214
8192
  });
6215
8193
  });
6216
- event.stopImmediatePropagation();
6217
- event.preventDefault();
8194
+ }
8195
+ };
8196
+ //#endregion
8197
+ //#region src/plugins/mention/node/MentionNode.ts
8198
+ var MentionNode = class MentionNode extends DecoratorNode {
8199
+ static getType() {
8200
+ return "mention";
8201
+ }
8202
+ static clone(node) {
8203
+ return new MentionNode(node.__label, node.__metadata, node.__key);
8204
+ }
8205
+ static importJSON(serializedNode) {
8206
+ return $createMentionNode().updateFromJSON(serializedNode);
8207
+ }
8208
+ static importDOM() {
8209
+ return { span: (node) => {
8210
+ if (node.classList.contains("mention")) return {
8211
+ conversion: $convertMentionElement,
8212
+ priority: 0
8213
+ };
8214
+ return null;
8215
+ } };
8216
+ }
8217
+ constructor(label = "", metadata = {}, key) {
8218
+ super(key);
8219
+ this.__label = label;
8220
+ this.__metadata = metadata;
8221
+ }
8222
+ get label() {
8223
+ return this.__label;
8224
+ }
8225
+ get metadata() {
8226
+ return this.__metadata;
8227
+ }
8228
+ exportDOM() {
8229
+ return { element: document.createElement("span") };
8230
+ }
8231
+ createDOM(config) {
8232
+ const element = document.createElement("span");
8233
+ addClassNamesToElement(element, config.theme.mention);
8234
+ return element;
8235
+ }
8236
+ getTextContent() {
8237
+ return this.label;
8238
+ }
8239
+ isInline() {
8240
+ return true;
8241
+ }
8242
+ updateDOM() {
8243
+ return false;
8244
+ }
8245
+ exportJSON() {
8246
+ return {
8247
+ ...super.exportJSON(),
8248
+ label: this.label,
8249
+ metadata: this.metadata
8250
+ };
8251
+ }
8252
+ updateFromJSON(serializedNode) {
8253
+ const node = super.updateFromJSON(serializedNode);
8254
+ this.__label = serializedNode.label || "";
8255
+ this.__metadata = serializedNode.metadata || {};
8256
+ return node;
8257
+ }
8258
+ decorate(editor) {
8259
+ const decorator = getKernelFromEditor(editor)?.getDecorator(MentionNode.getType());
8260
+ if (!decorator) return null;
8261
+ if (typeof decorator === "function") return decorator(this, editor);
8262
+ return {
8263
+ queryDOM: decorator.queryDOM,
8264
+ render: decorator.render(this, editor)
8265
+ };
8266
+ }
8267
+ };
8268
+ function $createMentionNode(label, metadata) {
8269
+ return $applyNodeReplacement(new MentionNode(label, metadata));
8270
+ }
8271
+ function $convertMentionElement() {
8272
+ return { node: $createMentionNode() };
8273
+ }
8274
+ function $isMentionNode(node) {
8275
+ return node.getType() === MentionNode.getType();
8276
+ }
8277
+ //#endregion
8278
+ //#region src/plugins/mention/command/index.ts
8279
+ const INSERT_MENTION_COMMAND = createCommand("INSERT_MENTION_COMMAND");
8280
+ function registerMentionCommand(editor) {
8281
+ return editor.registerCommand(INSERT_MENTION_COMMAND, (payload) => {
8282
+ const { metadata, label } = payload;
8283
+ editor.update(() => {
8284
+ const mentionNode = $createMentionNode(label, metadata);
8285
+ $insertNodes([mentionNode]);
8286
+ if ($isRootOrShadowRoot(mentionNode.getParentOrThrow())) $wrapNodeInElement(mentionNode, $createParagraphNode).selectEnd();
8287
+ });
6218
8288
  return true;
6219
- }, COMMAND_PRIORITY_LOW));
8289
+ }, COMMAND_PRIORITY_HIGH);
6220
8290
  }
6221
8291
  //#endregion
6222
- //#region src/plugins/list/plugin/index.ts
6223
- const ORDERED_LIST_REGEX = /^(\s*)(\d+)\.\s/;
6224
- const UNORDERED_LIST_REGEX = /^(\s*)[*+-]\s/;
6225
- const CHECK_LIST_REGEX = /^(\s*)(?:-\s)?\s?(\[(\s|x)?])\s/i;
6226
- const ListPlugin = class extends KernelPlugin {
8292
+ //#region src/plugins/mention/plugin/register.ts
8293
+ function registerMentionNodeSelectionObserver(editor) {
8294
+ const selectMentionKeys = [];
8295
+ return editor.registerUpdateListener(({ editorState }) => {
8296
+ const selection = editorState.read(() => $getSelection());
8297
+ const newSelectMentionKeys = [];
8298
+ if ($isNodeSelection(selection)) editorState.read(() => selection.getNodes()).forEach((node) => {
8299
+ if (node.getType() === "mention") newSelectMentionKeys.push(node.getKey());
8300
+ });
8301
+ else if ($isRangeSelection(selection) && !selection.isCollapsed()) editorState.read(() => {
8302
+ selection.getNodes().forEach((node) => {
8303
+ if ($isMentionNode(node)) newSelectMentionKeys.push(node.getKey());
8304
+ });
8305
+ });
8306
+ const removeKeys = selectMentionKeys.filter((key) => !newSelectMentionKeys.includes(key));
8307
+ const addKeys = newSelectMentionKeys.filter((key) => !selectMentionKeys.includes(key));
8308
+ selectMentionKeys.length = 0;
8309
+ selectMentionKeys.push(...newSelectMentionKeys);
8310
+ removeKeys.forEach((key) => {
8311
+ editor.getElementByKey(key)?.classList.remove("selected");
8312
+ });
8313
+ addKeys.forEach((key) => {
8314
+ editor.getElementByKey(key)?.classList.add("selected");
8315
+ });
8316
+ });
8317
+ }
8318
+ //#endregion
8319
+ //#region src/plugins/mention/plugin/index.ts
8320
+ const MentionPlugin = class extends KernelPlugin {
6227
8321
  static {
6228
- this.pluginName = "ListPlugin";
8322
+ this.pluginName = "MentionPlugin";
6229
8323
  }
6230
8324
  constructor(kernel, config) {
6231
8325
  super();
6232
8326
  this.kernel = kernel;
6233
8327
  this.config = config;
6234
- kernel.registerNodes([ListNode, ListItemNode]);
6235
- kernel.registerThemes({ list: {
6236
- checklist: "editor_listItemCheck",
6237
- listitem: "editor_listItem",
6238
- listitemChecked: "editor_listItemChecked",
6239
- listitemUnchecked: "editor_listItemUnchecked",
6240
- nested: { listitem: "editor_listItemNested" },
6241
- ol: cx("editor_listOrdered", config?.theme),
6242
- olDepth: [
6243
- "editor_listOrdered dp1",
6244
- "editor_listOrdered dp2",
6245
- "editor_listOrdered dp3",
6246
- "editor_listOrdered dp4",
6247
- "editor_listOrdered dp5"
6248
- ],
6249
- ul: cx("editor_listUnordered", config?.theme)
6250
- } });
8328
+ kernel.registerNodes([MentionNode]);
8329
+ if (config?.theme) kernel.registerThemes(config?.theme);
8330
+ this.registerDecorator(kernel, MentionNode.getType(), (node, editor) => {
8331
+ return config?.decorator ? config.decorator(node, editor) : null;
8332
+ });
6251
8333
  }
6252
8334
  onInit(editor) {
6253
- this.register(registerList(editor));
6254
- this.register(registerCheckList(editor));
6255
- this.register(registerListStrictIndentTransform(editor));
6256
- this.register(registerListCommands(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }));
8335
+ this.register(registerMentionCommand(editor));
8336
+ if (this.config?.decorator) this.register(registerMentionNodeSelectionObserver(editor));
6257
8337
  this.registerMarkdown();
6258
8338
  this.registerLiteXml();
6259
8339
  }
6260
8340
  registerLiteXml() {
6261
- const litexmlService = this.kernel.requireService(ILitexmlService);
6262
- if (!litexmlService) return;
6263
- litexmlService.registerXMLWriter(ListNode.getType(), (node, ctx) => {
6264
- if ($isListNode(node)) {
6265
- const tagName = node.getListType() === "number" ? "ol" : "ul";
6266
- const attributes = {};
6267
- if (node.getListType() === "number" && node.getStart() !== 1) attributes.start = node.getStart().toString();
6268
- return ctx.createXmlNode(tagName, attributes);
8341
+ this.kernel.requireService(ILitexmlService)?.registerXMLWriter(MentionNode.getType(), (node, ctx) => {
8342
+ if ($isMentionNode(node)) {
8343
+ const attributes = { label: node.label };
8344
+ if (node.metadata && Object.keys(node.metadata).length > 0) attributes.metadata = JSON.stringify(node.metadata);
8345
+ return ctx.createXmlNode("mention", attributes);
6269
8346
  }
6270
8347
  return false;
6271
8348
  });
6272
- litexmlService.registerXMLWriter(ListItemNode.getType(), (node, ctx) => {
6273
- if ($isListItemNode(node)) return ctx.createXmlNode("li");
6274
- return false;
6275
- });
6276
- litexmlService.registerXMLReader("ol", (xmlNode, children) => {
6277
- return INodeHelper.createElementNode("list", {
6278
- children,
6279
- direction: "ltr",
6280
- format: "",
6281
- indent: 0,
6282
- listType: "number",
6283
- start: xmlNode.getAttribute("start") ? parseInt(xmlNode.getAttribute("start"), 10) : 1,
6284
- tag: "ol",
6285
- version: 1
6286
- });
6287
- });
6288
- litexmlService.registerXMLReader("ul", (xmlNode, children) => {
6289
- return INodeHelper.createElementNode("list", {
6290
- children,
6291
- direction: "ltr",
6292
- format: "",
6293
- indent: 0,
6294
- listType: "bullet",
6295
- start: 1,
6296
- tag: "ul",
6297
- version: 1
6298
- });
6299
- });
6300
- litexmlService.registerXMLReader("li", (xmlNode, children) => {
6301
- return INodeHelper.createElementNode("listitem", {
6302
- children,
6303
- direction: "ltr",
6304
- format: "",
6305
- indent: 0,
6306
- type: "listitem",
6307
- version: 1
8349
+ this.kernel.requireService(ILitexmlService)?.registerXMLReader("mention", (xmlNode) => {
8350
+ return INodeHelper.createElementNode(MentionNode.getType(), {
8351
+ label: xmlNode.getAttribute("label") || "",
8352
+ metadata: xmlNode.getAttribute("metadata") ? JSON.parse(xmlNode.getAttribute("metadata")) : {}
6308
8353
  });
6309
8354
  });
6310
8355
  }
6311
8356
  registerMarkdown() {
6312
- const markdownService = this.kernel.requireService(IMarkdownShortCutService);
6313
- if (!markdownService) return;
6314
- markdownService.registerMarkdownShortCut({
6315
- regExp: UNORDERED_LIST_REGEX,
6316
- replace: listReplace("bullet"),
6317
- type: "element"
6318
- });
6319
- markdownService.registerMarkdownShortCut({
6320
- regExp: ORDERED_LIST_REGEX,
6321
- replace: listReplace("number"),
6322
- type: "element"
6323
- });
6324
- markdownService.registerMarkdownShortCut({
6325
- regExp: CHECK_LIST_REGEX,
6326
- replace: listReplace("check"),
6327
- type: "element"
6328
- });
6329
- markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
6330
- if ($isListNode(node)) ctx.wrap("", "\n");
6331
- });
6332
- const getLevel = (node) => {
6333
- if (!node) return 0;
6334
- if ($isRootNode(node.getParent())) return 0;
6335
- const parent = node.getParent();
6336
- if (!parent) return 0;
6337
- return getLevel($getNearestNodeOfType(parent, ListNode)) + 1;
6338
- };
6339
- markdownService.registerMarkdownWriter(ListItemNode.getType(), (ctx, node) => {
6340
- const parent = node.getParent();
6341
- if ($isListItemNode(node) && $isListNode(parent)) {
6342
- if ($isListNode(node.getFirstChild())) return;
6343
- const level = getLevel(parent);
6344
- const prefix = " ".repeat(level);
6345
- switch (parent.getListType()) {
6346
- case "bullet":
6347
- ctx.wrap(prefix + "- ", "\n");
6348
- break;
6349
- case "number":
6350
- ctx.wrap(`${prefix}${node.getValue()}. `, "\n");
6351
- break;
6352
- case "check":
6353
- ctx.wrap(`${prefix}- [${node.getChecked() ? "x" : " "}] `, "\n");
6354
- break;
6355
- default: break;
8357
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(MentionNode.getType(), (ctx, node) => {
8358
+ if ($isMentionNode(node)) {
8359
+ if (this.config?.markdownWriter) {
8360
+ ctx.appendLine(this.config.markdownWriter(node));
8361
+ return;
6356
8362
  }
8363
+ ctx.appendLine(`${node.label}`);
6357
8364
  }
6358
8365
  });
6359
- markdownService.registerMarkdownReader("list", (node, children) => {
6360
- const isCheck = node.children?.[0]?.type === "listItem" && typeof node.children?.[0]?.checked === "boolean";
6361
- let start = node.start || 1;
6362
- return INodeHelper.createElementNode("list", {
6363
- children: children.map((v) => {
6364
- if (v.type === "listitem") v.value = start++;
6365
- return v;
6366
- }),
6367
- direction: "ltr",
6368
- format: "",
6369
- indent: 0,
6370
- listType: isCheck ? "check" : node.ordered ? "number" : "bullet",
6371
- start: node.start || 1,
6372
- tag: node.ordered ? "ol" : "ul",
6373
- version: 1
6374
- });
6375
- });
6376
- markdownService.registerMarkdownReader("listItem", (node, children, index) => {
6377
- return children.map((v) => {
6378
- if (v.type === "paragraph") return INodeHelper.createElementNode("listitem", {
6379
- checked: typeof node.checked === "boolean" ? node.checked : void 0,
6380
- children: v.children,
6381
- direction: "ltr",
6382
- format: "",
6383
- indent: 0,
6384
- type: "listitem",
6385
- value: index + 1,
6386
- version: 1
6387
- });
6388
- else if (v.type === "list") return INodeHelper.createElementNode("listitem", {
6389
- children: [v],
6390
- direction: "ltr",
6391
- format: "",
6392
- indent: 0,
6393
- type: "listitem",
6394
- value: index + 1,
6395
- version: 1
6396
- });
6397
- return v;
6398
- });
6399
- });
8366
+ if (this.config?.markdownReader) this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("html", (node, children) => {
8367
+ return this.config?.markdownReader ? this.config.markdownReader(node, children) || false : false;
8368
+ }, 1);
6400
8369
  }
6401
8370
  };
6402
8371
  //#endregion
@@ -7286,85 +9255,6 @@ const TablePlugin = class extends KernelPlugin {
7286
9255
  }
7287
9256
  };
7288
9257
  //#endregion
7289
- //#region src/plugins/codeblock/utils/language.ts
7290
- function getCodeLanguageByInput(input) {
7291
- if (!input) return "plaintext";
7292
- const inputLang = input.toLocaleLowerCase();
7293
- return bundledLanguagesInfo.find((lang) => lang.id === inputLang || lang.aliases?.includes(inputLang))?.id || "plaintext";
7294
- }
7295
- //#endregion
7296
- //#region src/headless/plugins/codeblock.ts
7297
- const createCodeChildren = (code) => code.split("\n").flatMap((text, index, array) => {
7298
- const textNode = INodeHelper.createTextNode(text);
7299
- textNode.type = "code-highlight";
7300
- if (index === array.length - 1) return textNode;
7301
- return [textNode, {
7302
- type: "linebreak",
7303
- version: 1
7304
- }];
7305
- }).flat();
7306
- const HeadlessCodeblockPlugin = class extends KernelPlugin {
7307
- static {
7308
- this.pluginName = "HeadlessCodeblockPlugin";
7309
- }
7310
- constructor(kernel) {
7311
- super();
7312
- this.kernel = kernel;
7313
- kernel.registerNodes([CodeNode, CodeHighlightNode]);
7314
- kernel.registerThemes({ code: "editor-code" });
7315
- }
7316
- onInit() {
7317
- this.registerMarkdown();
7318
- this.registerLiteXml();
7319
- }
7320
- registerLiteXml() {
7321
- const litexmlService = this.kernel.requireService(ILitexmlService);
7322
- if (!litexmlService) return;
7323
- litexmlService.registerXMLWriter(CodeNode.getType(), (node, ctx) => {
7324
- const codeNode = node;
7325
- return ctx.createXmlNode("code", { lang: codeNode.getLanguage() || "plaintext" }, codeNode.getTextContent());
7326
- });
7327
- litexmlService.registerXMLReader("code", (xmlElement, children) => {
7328
- const text = children.map((value) => value.text || "").join("");
7329
- return INodeHelper.createElementNode(CodeNode.getType(), {
7330
- children: createCodeChildren(text),
7331
- direction: "ltr",
7332
- format: "",
7333
- indent: 0,
7334
- language: xmlElement.getAttribute("lang"),
7335
- version: 1
7336
- });
7337
- });
7338
- }
7339
- registerMarkdown() {
7340
- const markdownService = this.kernel.requireService(IMarkdownShortCutService);
7341
- if (!markdownService) return;
7342
- markdownService.registerMarkdownWriter(CodeNode.getType(), (ctx, node) => {
7343
- if ($isCodeNode(node)) ctx.wrap("```" + (node.getLanguage() || "") + "\n", "\n```\n");
7344
- });
7345
- markdownService.registerMarkdownWriter(TabNode.getType(), (ctx) => {
7346
- ctx.appendLine(" ");
7347
- });
7348
- markdownService.registerMarkdownWriter(CodeHighlightNode.getType(), (ctx, node) => {
7349
- if ($isCodeHighlightNode(node)) ctx.appendLine(node.getTextContent());
7350
- });
7351
- markdownService.registerMarkdownWriter("linebreak", (ctx, node) => {
7352
- if ($isCodeNode(node.getParent())) ctx.appendLine("\n");
7353
- });
7354
- markdownService.registerMarkdownReader("code", (node) => {
7355
- const language = node.lang ? getCodeLanguageByInput(node.lang) : "plaintext";
7356
- return INodeHelper.createElementNode("code", {
7357
- children: createCodeChildren(node.value),
7358
- direction: "ltr",
7359
- format: "",
7360
- indent: 0,
7361
- language,
7362
- version: 1
7363
- });
7364
- });
7365
- }
7366
- };
7367
- //#endregion
7368
9258
  //#region src/headless/extract-media-from-editor-state.ts
7369
9259
  const IMAGE_TYPES = new Set(["image", "block-image"]);
7370
9260
  const FILE_TYPE = "file";
@@ -7502,10 +9392,15 @@ const preserveSerializedCodeTextInEditorData = (editorData) => ({
7502
9392
  });
7503
9393
  const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
7504
9394
  [CommonPlugin, { enableHotkey: false }],
9395
+ INodePlugin,
7505
9396
  MarkdownPlugin,
7506
9397
  [LinkPlugin, { enableHotkey: false }],
7507
9398
  CodePlugin,
7508
- HeadlessCodeblockPlugin,
9399
+ CodemirrorPlugin,
9400
+ ImagePlugin,
9401
+ FilePlugin,
9402
+ MathPlugin,
9403
+ MentionPlugin,
7509
9404
  HRPlugin,
7510
9405
  ListPlugin,
7511
9406
  TablePlugin,