@lobehub/editor 4.9.8 → 4.10.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.
package/es/headless.js CHANGED
@@ -6,8 +6,8 @@ import EventEmitter from "eventemitter3";
6
6
  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 } from "lexical";
7
7
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
8
8
  import createDebug from "debug";
9
- import { $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertNodeToNearestRoot, CAN_USE_DOM, IS_APPLE as isApple, addClassNamesToElement, calculateZoomLevel, isHTMLElement as isHTMLElement$1, mergeRegister } from "@lexical/utils";
10
- import { $setBlocksType } from "@lexical/selection";
9
+ 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 { $isAtNodeEnd, $setBlocksType } from "@lexical/selection";
11
11
  import { registerDragonSupport } from "@lexical/dragon";
12
12
  import { $createListItemNode, $createListNode, $insertList, $isListItemNode, $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode, registerList, registerListStrictIndentTransform } from "@lexical/list";
13
13
  import { DOMParser as DOMParser$1 } from "@xmldom/xmldom";
@@ -621,7 +621,7 @@ const isHotkeyMatch = (event, hotkey) => {
621
621
  };
622
622
  //#endregion
623
623
  //#region src/utils/hotkey/registerHotkey.ts
624
- const logger$6 = createDebugLogger("hotkey");
624
+ const logger$7 = createDebugLogger("hotkey");
625
625
  const registerHotkey = (hotkey, callback, options = {}) => {
626
626
  return (e) => {
627
627
  if (!isHotkeyMatch(e, hotkey.keys)) return false;
@@ -633,7 +633,7 @@ const registerHotkey = (hotkey, callback, options = {}) => {
633
633
  ...keys,
634
634
  scopes: hotkey.scopes
635
635
  });
636
- logger$6.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
636
+ logger$7.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
637
637
  return true;
638
638
  };
639
639
  };
@@ -701,6 +701,10 @@ const noop = () => {};
701
701
  function createEmptyEditorState() {
702
702
  return createEditor().getEditorState();
703
703
  }
704
+ function assert(cond, message) {
705
+ if (cond) return;
706
+ throw new Error(message);
707
+ }
704
708
  function getNodeKeyFromDOMNode(dom, editor) {
705
709
  return dom[`__lexicalKey_${editor._key}`];
706
710
  }
@@ -1378,7 +1382,7 @@ function registerCommands(editor) {
1378
1382
  }
1379
1383
  //#endregion
1380
1384
  //#region src/plugins/common/node/cursor.ts
1381
- const logger$5 = createDebugLogger("common", "cursor");
1385
+ const logger$6 = createDebugLogger("common", "cursor");
1382
1386
  var CardLikeElementNode = class extends ElementNode {
1383
1387
  isCardLike() {
1384
1388
  return true;
@@ -1417,7 +1421,7 @@ function registerCursorNode(editor) {
1417
1421
  const needAddCursor = [];
1418
1422
  for (const [kClass, nodeMaps] of mutatedNodes) if (DecoratorNode.prototype.isPrototypeOf(kClass.prototype)) for (const [key, mutation] of nodeMaps) {
1419
1423
  const node = $getNodeByKey(key);
1420
- logger$5.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1424
+ logger$6.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1421
1425
  if (mutation === "created" && node?.isInline() && node.getNextSibling() === null) needAddCursor.push(node);
1422
1426
  }
1423
1427
  if (needAddCursor.length > 0) editor.update(() => {
@@ -1519,7 +1523,7 @@ function registerCursorNode(editor) {
1519
1523
  $setSelection(sel);
1520
1524
  return true;
1521
1525
  } catch (error) {
1522
- logger$5.error("❌ Cursor selection error:", error);
1526
+ logger$6.error("❌ Cursor selection error:", error);
1523
1527
  }
1524
1528
  else if ($isCursorNode(focusNode)) try {
1525
1529
  const { key: anchorKey, offset: anchorOffset, type: anchorType } = selection.anchor;
@@ -1530,7 +1534,7 @@ function registerCursorNode(editor) {
1530
1534
  $setSelection(sel);
1531
1535
  return true;
1532
1536
  } catch (error) {
1533
- logger$5.error("❌ Cursor navigation error:", error);
1537
+ logger$6.error("❌ Cursor navigation error:", error);
1534
1538
  }
1535
1539
  return false;
1536
1540
  }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, (event) => {
@@ -1709,7 +1713,7 @@ function charToId(char) {
1709
1713
  }
1710
1714
  //#endregion
1711
1715
  //#region src/plugins/litexml/command/index.ts
1712
- const logger$4 = createDebugLogger("plugin", "litexml");
1716
+ const logger$5 = createDebugLogger("plugin", "litexml");
1713
1717
  function toArrayXml(litexml) {
1714
1718
  return Array.isArray(litexml) ? litexml : [litexml];
1715
1719
  }
@@ -1721,7 +1725,7 @@ function tryParseChild(child, editor) {
1721
1725
  oldNode
1722
1726
  };
1723
1727
  } catch (error) {
1724
- logger$4.error("❌ Error parsing child node:", error);
1728
+ logger$5.error("❌ Error parsing child node:", error);
1725
1729
  return {
1726
1730
  newNode: null,
1727
1731
  oldNode: null
@@ -1834,12 +1838,12 @@ function registerLiteXMLCommand(editor, dataSource) {
1834
1838
  delay: true
1835
1839
  }, dataSource);
1836
1840
  break;
1837
- default: logger$4.warn(`⚠️ Unknown action type: ${action}`);
1841
+ default: logger$5.warn(`⚠️ Unknown action type: ${action}`);
1838
1842
  }
1839
1843
  });
1840
1844
  return false;
1841
1845
  } catch (error) {
1842
- logger$4.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
1846
+ logger$5.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
1843
1847
  return false;
1844
1848
  }
1845
1849
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
@@ -1864,9 +1868,9 @@ function handleModify(editor, dataSource, arrayXml, delay) {
1864
1868
  try {
1865
1869
  const { oldNode, newNode } = tryParseChild(child, editor);
1866
1870
  if (oldNode && newNode) handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor);
1867
- else logger$4.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
1871
+ else logger$5.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
1868
1872
  } catch (error) {
1869
- logger$4.error("❌ Error replacing node:", error);
1873
+ logger$5.error("❌ Error replacing node:", error);
1870
1874
  }
1871
1875
  });
1872
1876
  });
@@ -1890,7 +1894,7 @@ function handleModify(editor, dataSource, arrayXml, delay) {
1890
1894
  } else prevNode = prevNode.insertAfter(newNode);
1891
1895
  else $insertNodes([newNode]);
1892
1896
  } catch (error) {
1893
- logger$4.error("❌ Error replacing node:", error);
1897
+ logger$5.error("❌ Error replacing node:", error);
1894
1898
  }
1895
1899
  });
1896
1900
  });
@@ -2027,7 +2031,7 @@ function handleInsert(editor, payload, dataSource) {
2027
2031
  });
2028
2032
  }
2029
2033
  } catch (error) {
2030
- logger$4.error("❌ Error inserting node:", error);
2034
+ logger$5.error("❌ Error inserting node:", error);
2031
2035
  }
2032
2036
  });
2033
2037
  }
@@ -2167,7 +2171,7 @@ var LitexmlService = class {
2167
2171
  };
2168
2172
  //#endregion
2169
2173
  //#region src/plugins/litexml/data-source/litexml-data-source.ts
2170
- const logger$3 = createDebugLogger("plugin", "litexml");
2174
+ const logger$4 = createDebugLogger("plugin", "litexml");
2171
2175
  var IXmlWriterContext = class {
2172
2176
  createXmlNode(tagName, attributes, textContent) {
2173
2177
  return {
@@ -2195,7 +2199,7 @@ var LitexmlDataSource = class extends DataSource {
2195
2199
  const xml = this.parseXMLString(litexml);
2196
2200
  const inode = this.xmlToLexical(xml);
2197
2201
  this.getService?.(INodeService)?.processNodeTree(inode);
2198
- logger$3.debug("Parsed XML to Lexical State:", inode);
2202
+ logger$4.debug("Parsed XML to Lexical State:", inode);
2199
2203
  return inode;
2200
2204
  }
2201
2205
  /**
@@ -2216,7 +2220,7 @@ var LitexmlDataSource = class extends DataSource {
2216
2220
  });
2217
2221
  editor.setEditorState(newState);
2218
2222
  } catch (error) {
2219
- logger$3.error("Failed to parse XML:", error);
2223
+ logger$4.error("Failed to parse XML:", error);
2220
2224
  throw error;
2221
2225
  }
2222
2226
  }
@@ -2251,7 +2255,7 @@ var LitexmlDataSource = class extends DataSource {
2251
2255
  return this.lexicalToXML(rootNode);
2252
2256
  });
2253
2257
  } catch (error) {
2254
- logger$3.error("Failed to export to XML:", error);
2258
+ logger$4.error("Failed to export to XML:", error);
2255
2259
  throw error;
2256
2260
  }
2257
2261
  }
@@ -22546,7 +22550,7 @@ function remarkMath(options) {
22546
22550
  }
22547
22551
  //#endregion
22548
22552
  //#region src/plugins/markdown/utils/logger.ts
22549
- const logger$2 = createDebugLogger("plugin", "markdown");
22553
+ const logger$3 = createDebugLogger("plugin", "markdown");
22550
22554
  //#endregion
22551
22555
  //#region src/plugins/markdown/data-source/markdown/parse.ts
22552
22556
  const selfClosingHtmlTags = new Set([
@@ -22566,8 +22570,9 @@ const selfClosingHtmlTags = new Set([
22566
22570
  "wbr"
22567
22571
  ]);
22568
22572
  var MarkdownContext = class {
22569
- constructor(root) {
22573
+ constructor(root, markdown) {
22570
22574
  this.root = root;
22575
+ this.markdown = markdown;
22571
22576
  this.stack = [];
22572
22577
  }
22573
22578
  push(html) {
@@ -22583,15 +22588,36 @@ var MarkdownContext = class {
22583
22588
  return this.stack.pop();
22584
22589
  }
22585
22590
  };
22586
- function convertMdastToLexical(node, index, ctx, markdownReaders = {}) {
22591
+ const getNodeRawMarkdown = (node, markdown) => {
22592
+ const start = node.position?.start.offset;
22593
+ const end = node.position?.end.offset;
22594
+ if (typeof start === "number" && typeof end === "number") return markdown.slice(start, end);
22595
+ if ("value" in node && typeof node.value === "string") return node.value;
22596
+ return "";
22597
+ };
22598
+ const createFallbackRawNode = (node, ctx, parentType) => {
22599
+ const raw = getNodeRawMarkdown(node, ctx.markdown);
22600
+ if (!raw) return null;
22601
+ if (parentType === null || parentType === "root") return {
22602
+ ...INodeHelper.createParagraph(),
22603
+ children: [INodeHelper.createTextNode(raw)]
22604
+ };
22605
+ return INodeHelper.createTextNode(raw);
22606
+ };
22607
+ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentType = null) {
22587
22608
  switch (node.type) {
22588
22609
  case "text": return INodeHelper.createTextNode(node.value);
22589
22610
  default: {
22611
+ if (!markdownReaders[node.type]) return createFallbackRawNode(node, ctx, parentType);
22590
22612
  let children = [];
22591
22613
  if ("children" in node && Array.isArray(node.children)) {
22592
22614
  let htmlStack = [];
22593
22615
  children = node.children.reduce((ret, child, index) => {
22594
22616
  if (child.type === "html") {
22617
+ if (!markdownReaders["html"]) {
22618
+ ret.push(INodeHelper.createTextNode(child.value));
22619
+ return ret;
22620
+ }
22595
22621
  if (child.value.startsWith("<!--") && child.value.endsWith("-->")) return ret;
22596
22622
  const tag = child.value.replaceAll(/^<\/?|>$/g, "");
22597
22623
  const isEndTag = child.value.startsWith("</");
@@ -22617,7 +22643,7 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}) {
22617
22643
  const top = ctx.pop();
22618
22644
  htmlStack.pop();
22619
22645
  if (top?.tag !== tag) {
22620
- logger$2.warn("HTML tag mismatch:", tag);
22646
+ logger$3.warn("HTML tag mismatch:", tag);
22621
22647
  ret.push(...top?.children || []);
22622
22648
  return ret;
22623
22649
  }
@@ -22653,10 +22679,10 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}) {
22653
22679
  }
22654
22680
  if (htmlStack.length > 0) {
22655
22681
  const top = ctx.last;
22656
- if (top) top.children.push(convertMdastToLexical(child, index, ctx, markdownReaders));
22682
+ if (top) top.children.push(convertMdastToLexical(child, index, ctx, markdownReaders, node.type));
22657
22683
  return ret;
22658
22684
  }
22659
- ret.push(convertMdastToLexical(child, index, ctx, markdownReaders));
22685
+ ret.push(convertMdastToLexical(child, index, ctx, markdownReaders, node.type));
22660
22686
  return ret;
22661
22687
  }, []).filter(Boolean).flat();
22662
22688
  while (htmlStack.length > 0) {
@@ -22707,8 +22733,8 @@ function registerDefaultReaders(markdownReaders) {
22707
22733
  }
22708
22734
  function parseMarkdownToLexical(markdown, markdownReaders = {}) {
22709
22735
  const ast = remark().use(remarkCjkFriendly).use(remarkMath).use([[remarkGfm, { singleTilde: false }]]).parse(markdown);
22710
- logger$2.debug("Parsed MDAST:", ast);
22711
- const ctx = new MarkdownContext(ast);
22736
+ logger$3.debug("Parsed MDAST:", ast);
22737
+ const ctx = new MarkdownContext(ast, markdown);
22712
22738
  registerDefaultReaders(markdownReaders);
22713
22739
  return convertMdastToLexical(ast, 0, ctx, markdownReaders);
22714
22740
  }
@@ -22855,7 +22881,7 @@ function insertIRootNode(editor, root, selection) {
22855
22881
  }
22856
22882
  //#endregion
22857
22883
  //#region src/plugins/markdown/command/index.ts
22858
- const logger$1 = createDebugLogger("plugin", "markdown");
22884
+ const logger$2 = createDebugLogger("plugin", "markdown");
22859
22885
  const INSERT_MARKDOWN_COMMAND = createCommand("INSERT_MARKDOWN_COMMAND");
22860
22886
  const GET_MARKDOWN_SELECTION_COMMAND = createCommand("GET_MARKDOWN_SELECTION_COMMAND");
22861
22887
  function restoreToEntry(editor, entry) {
@@ -22869,7 +22895,7 @@ const getLineNumber = (content, charIndex) => {
22869
22895
  function registerMarkdownCommand(editor, kernel, service) {
22870
22896
  return mergeRegister(editor.registerCommand(INSERT_MARKDOWN_COMMAND, (payload) => {
22871
22897
  const { markdown } = payload;
22872
- logger$1.debug("INSERT_MARKDOWN_COMMAND payload:", payload);
22898
+ logger$2.debug("INSERT_MARKDOWN_COMMAND payload:", payload);
22873
22899
  restoreToEntry(editor, payload.historyState);
22874
22900
  setTimeout(() => {
22875
22901
  editor.update(() => {
@@ -22877,11 +22903,11 @@ function registerMarkdownCommand(editor, kernel, service) {
22877
22903
  const root = parseMarkdownToLexical(markdown, service.markdownReaders);
22878
22904
  const selection = $getSelection();
22879
22905
  const nodes = $generateNodesFromSerializedNodes(root.children);
22880
- logger$1.debug("INSERT_MARKDOWN_COMMAND nodes:", nodes);
22906
+ logger$2.debug("INSERT_MARKDOWN_COMMAND nodes:", nodes);
22881
22907
  $insertGeneratedNodes(editor, nodes, selection);
22882
22908
  return true;
22883
22909
  } catch (error) {
22884
- logger$1.error("Failed to handle markdown paste:", error);
22910
+ logger$2.error("Failed to handle markdown paste:", error);
22885
22911
  }
22886
22912
  }, { tag: HISTORY_PUSH_TAG });
22887
22913
  }, 0);
@@ -22912,8 +22938,8 @@ function registerMarkdownCommand(editor, kernel, service) {
22912
22938
  const startLine = getLineNumber(markdownContent, startIndex);
22913
22939
  const endLine = getLineNumber(markdownContent, endIndex);
22914
22940
  payload.onResult(startLine, endLine);
22915
- logger$1.debug("GET_MARKDOWN_SELECTION_COMMAND markdownContent:", markdownContent);
22916
- logger$1.debug("GET_MARKDOWN_SELECTION_COMMAND startLine:", startLine, "endLine:", endLine);
22941
+ logger$2.debug("GET_MARKDOWN_SELECTION_COMMAND markdownContent:", markdownContent);
22942
+ logger$2.debug("GET_MARKDOWN_SELECTION_COMMAND startLine:", startLine, "endLine:", endLine);
22917
22943
  return markdownContent;
22918
22944
  } });
22919
22945
  }
@@ -22973,7 +22999,7 @@ var MarkdownDataSource = class extends DataSource {
22973
22999
  }).processSync(markdown);
22974
23000
  return String(result);
22975
23001
  } catch (error) {
22976
- logger$2.error("Failed to format markdown:", error);
23002
+ logger$3.error("Failed to format markdown:", error);
22977
23003
  return markdown;
22978
23004
  }
22979
23005
  }
@@ -22986,7 +23012,7 @@ var MarkdownDataSource = class extends DataSource {
22986
23012
  read(editor, data) {
22987
23013
  const inode = { root: parseMarkdownToLexical(data, this.markdownService.markdownReaders) };
22988
23014
  this.getService?.(INodeService)?.processNodeTree(inode);
22989
- logger$2.debug("Parsed Lexical State:", inode);
23015
+ logger$3.debug("Parsed Lexical State:", inode);
22990
23016
  editor.setEditorState(editor.parseEditorState(inode));
22991
23017
  }
22992
23018
  write(editor, options) {
@@ -24401,7 +24427,7 @@ function runPasteHandlers(ctx, handlers) {
24401
24427
  }
24402
24428
  //#endregion
24403
24429
  //#region src/plugins/common/plugin/register.ts
24404
- const logger = createDebugLogger("plugin", "common");
24430
+ const logger$1 = createDebugLogger("plugin", "common");
24405
24431
  function resolveElement(element, isBackward, focusOffset) {
24406
24432
  const parent = element.getParent();
24407
24433
  let offset = focusOffset;
@@ -24502,7 +24528,7 @@ function registerRichKeydown(editor, kernel, options) {
24502
24528
  selection.insertText(text);
24503
24529
  });
24504
24530
  } catch (error) {
24505
- logger.error("❌ Failed to paste as plain text:", error);
24531
+ logger$1.error("❌ Failed to paste as plain text:", error);
24506
24532
  }
24507
24533
  }, {
24508
24534
  enabled: enableHotkey,
@@ -24760,7 +24786,7 @@ const CommonPlugin = class extends KernelPlugin {
24760
24786
  const breakMark = (this.config?.markdownOption ?? true) !== false ? "\n\n" : "\n";
24761
24787
  const formats = this.formats;
24762
24788
  if (formats.quote) markdownService.registerMarkdownShortCut({
24763
- regExp: /^>\s/,
24789
+ regExp: /^(>|》)\s/,
24764
24790
  replace: (parentNode, children, _match, isImport) => {
24765
24791
  if (isImport) {
24766
24792
  const previousNode = parentNode.getPreviousSibling();
@@ -25307,6 +25333,701 @@ const HRPlugin = class extends KernelPlugin {
25307
25333
  }
25308
25334
  };
25309
25335
  //#endregion
25336
+ //#region src/plugins/link/node/LinkNode.ts
25337
+ const logger = createDebugLogger("plugin", "link");
25338
+ const SUPPORTED_URL_PROTOCOLS$1 = new Set([
25339
+ "http:",
25340
+ "https:",
25341
+ "mailto:",
25342
+ "sms:",
25343
+ "tel:"
25344
+ ]);
25345
+ const HOVER_LINK_COMMAND = createCommand("HOVER_LINK_COMMAND");
25346
+ const HOVER_OUT_LINK_COMMAND = createCommand("HOVER_OUT_LINK_COMMAND");
25347
+ /** @noInheritDoc */
25348
+ var LinkNode = class LinkNode extends ElementNode {
25349
+ static getType() {
25350
+ return "link";
25351
+ }
25352
+ static clone(node) {
25353
+ return new LinkNode(node.__url, {
25354
+ rel: node.__rel,
25355
+ target: node.__target,
25356
+ title: node.__title
25357
+ }, node.__key);
25358
+ }
25359
+ constructor(url = "", attributes = {}, key) {
25360
+ super(key);
25361
+ const { target = null, rel = null, title = null } = attributes;
25362
+ this.__url = url;
25363
+ this.__target = target;
25364
+ this.__rel = rel;
25365
+ this.__title = title;
25366
+ }
25367
+ createDOM(config, editor) {
25368
+ logger.debug("🔍 config", config);
25369
+ const element = document.createElement("a");
25370
+ this.updateLinkDOM(null, element, config);
25371
+ addClassNamesToElement(element, config.theme.link);
25372
+ element.addEventListener("mouseenter", (event) => {
25373
+ if (event.target instanceof HTMLElement) {
25374
+ event.target.classList.add("hover");
25375
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
25376
+ event,
25377
+ linkNode: this
25378
+ });
25379
+ }
25380
+ });
25381
+ element.addEventListener("mouseleave", (event) => {
25382
+ if (event.target instanceof HTMLElement) {
25383
+ event.target.classList.remove("hover");
25384
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
25385
+ }
25386
+ });
25387
+ return element;
25388
+ }
25389
+ updateLinkDOM(prevNode, anchor, _config) {
25390
+ if (isHTMLAnchorElement(anchor)) {
25391
+ if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url);
25392
+ for (const attr of [
25393
+ "target",
25394
+ "rel",
25395
+ "title"
25396
+ ]) {
25397
+ const key = `__${attr}`;
25398
+ const value = this[key];
25399
+ if (!prevNode || prevNode[key] !== value) if (value) anchor[attr] = value;
25400
+ else anchor.removeAttribute(attr);
25401
+ }
25402
+ }
25403
+ }
25404
+ updateDOM(prevNode, anchor, config) {
25405
+ this.updateLinkDOM(prevNode, anchor, config);
25406
+ return false;
25407
+ }
25408
+ static importDOM() {
25409
+ return { a: () => ({
25410
+ conversion: $convertAnchorElement,
25411
+ priority: 1
25412
+ }) };
25413
+ }
25414
+ static importJSON(serializedNode) {
25415
+ return $createLinkNode().updateFromJSON(serializedNode);
25416
+ }
25417
+ updateFromJSON(serializedNode) {
25418
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
25419
+ }
25420
+ sanitizeUrl(url) {
25421
+ url = formatUrl(url);
25422
+ try {
25423
+ const parsedUrl = new URL(formatUrl(url));
25424
+ if (!SUPPORTED_URL_PROTOCOLS$1.has(parsedUrl.protocol)) return "about:blank";
25425
+ } catch {
25426
+ return url;
25427
+ }
25428
+ return url;
25429
+ }
25430
+ exportJSON() {
25431
+ return {
25432
+ ...super.exportJSON(),
25433
+ rel: this.getRel(),
25434
+ target: this.getTarget(),
25435
+ title: this.getTitle(),
25436
+ url: this.getURL()
25437
+ };
25438
+ }
25439
+ getURL() {
25440
+ return this.getLatest().__url;
25441
+ }
25442
+ setURL(url) {
25443
+ const writable = this.getWritable();
25444
+ writable.__url = url;
25445
+ return writable;
25446
+ }
25447
+ getTarget() {
25448
+ return this.getLatest().__target;
25449
+ }
25450
+ setTarget(target) {
25451
+ const writable = this.getWritable();
25452
+ writable.__target = target;
25453
+ return writable;
25454
+ }
25455
+ getRel() {
25456
+ return this.getLatest().__rel;
25457
+ }
25458
+ setRel(rel) {
25459
+ const writable = this.getWritable();
25460
+ writable.__rel = rel;
25461
+ return writable;
25462
+ }
25463
+ getTitle() {
25464
+ return this.getLatest().__title;
25465
+ }
25466
+ setTitle(title) {
25467
+ const writable = this.getWritable();
25468
+ writable.__title = title;
25469
+ return writable;
25470
+ }
25471
+ insertNewAfter(_, restoreSelection = true) {
25472
+ const linkNode = $createLinkNode(this.__url, {
25473
+ rel: this.__rel,
25474
+ target: this.__target,
25475
+ title: this.__title
25476
+ });
25477
+ this.insertAfter(linkNode, restoreSelection);
25478
+ return linkNode;
25479
+ }
25480
+ canInsertTextBefore() {
25481
+ return false;
25482
+ }
25483
+ canInsertTextAfter() {
25484
+ return false;
25485
+ }
25486
+ canBeEmpty() {
25487
+ return false;
25488
+ }
25489
+ isInline() {
25490
+ return true;
25491
+ }
25492
+ extractWithChild(child, selection) {
25493
+ if (!$isRangeSelection(selection)) return false;
25494
+ const anchorNode = selection.anchor.getNode();
25495
+ const focusNode = selection.focus.getNode();
25496
+ return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && selection.getTextContent().length > 0;
25497
+ }
25498
+ isEmailURI() {
25499
+ return this.__url.startsWith("mailto:");
25500
+ }
25501
+ isWebSiteURI() {
25502
+ return this.__url.startsWith("https://") || this.__url.startsWith("http://");
25503
+ }
25504
+ };
25505
+ function $convertAnchorElement(domNode) {
25506
+ let node = null;
25507
+ if (isHTMLAnchorElement(domNode)) {
25508
+ const content = domNode.textContent;
25509
+ if (content !== null && content !== "" || domNode.children.length > 0) node = $createLinkNode(domNode.getAttribute("href") || "", {
25510
+ rel: domNode.getAttribute("rel"),
25511
+ target: domNode.getAttribute("target"),
25512
+ title: domNode.getAttribute("title")
25513
+ });
25514
+ }
25515
+ return { node };
25516
+ }
25517
+ /**
25518
+ * Takes a URL and creates a LinkNode.
25519
+ * @param url - The URL the LinkNode should direct to.
25520
+ * @param attributes - Optional HTML a tag attributes \\{ target, rel, title \\}
25521
+ * @returns The LinkNode.
25522
+ */
25523
+ function $createLinkNode(url = "", attributes) {
25524
+ return $applyNodeReplacement(new LinkNode(url, attributes));
25525
+ }
25526
+ /**
25527
+ * Determines if node is a LinkNode.
25528
+ * @param node - The node to be checked.
25529
+ * @returns true if node is a LinkNode, false otherwise.
25530
+ */
25531
+ function $isLinkNode(node) {
25532
+ return node instanceof LinkNode;
25533
+ }
25534
+ var AutoLinkNode = class AutoLinkNode extends LinkNode {
25535
+ constructor(url = "", attributes = {}, key) {
25536
+ super(url, attributes, key);
25537
+ this.__isUnlinked = attributes.isUnlinked !== void 0 && attributes.isUnlinked !== null ? attributes.isUnlinked : false;
25538
+ }
25539
+ static getType() {
25540
+ return "autolink";
25541
+ }
25542
+ static clone(node) {
25543
+ return new AutoLinkNode(node.__url, {
25544
+ isUnlinked: node.__isUnlinked,
25545
+ rel: node.__rel,
25546
+ target: node.__target,
25547
+ title: node.__title
25548
+ }, node.__key);
25549
+ }
25550
+ getIsUnlinked() {
25551
+ return this.__isUnlinked;
25552
+ }
25553
+ setIsUnlinked(value) {
25554
+ const self = this.getWritable();
25555
+ self.__isUnlinked = value;
25556
+ return self;
25557
+ }
25558
+ createDOM(config, editor) {
25559
+ logger.debug("🔍 config", config);
25560
+ if (this.__isUnlinked) return document.createElement("span");
25561
+ else return super.createDOM(config, editor);
25562
+ }
25563
+ updateDOM(prevNode, anchor, config) {
25564
+ return super.updateDOM(prevNode, anchor, config) || prevNode.__isUnlinked !== this.__isUnlinked;
25565
+ }
25566
+ static importJSON(serializedNode) {
25567
+ return $createAutoLinkNode().updateFromJSON(serializedNode);
25568
+ }
25569
+ updateFromJSON(serializedNode) {
25570
+ return super.updateFromJSON(serializedNode).setIsUnlinked(serializedNode.isUnlinked || false);
25571
+ }
25572
+ static importDOM() {
25573
+ return null;
25574
+ }
25575
+ exportJSON() {
25576
+ return {
25577
+ ...super.exportJSON(),
25578
+ isUnlinked: this.__isUnlinked
25579
+ };
25580
+ }
25581
+ insertNewAfter(selection, restoreSelection = true) {
25582
+ const element = this.getParentOrThrow().insertNewAfter(selection, restoreSelection);
25583
+ if ($isElementNode(element)) {
25584
+ const linkNode = $createAutoLinkNode(this.__url, {
25585
+ isUnlinked: this.__isUnlinked,
25586
+ rel: this.__rel,
25587
+ target: this.__target,
25588
+ title: this.__title
25589
+ });
25590
+ element.append(linkNode);
25591
+ return linkNode;
25592
+ }
25593
+ return null;
25594
+ }
25595
+ };
25596
+ /**
25597
+ * Takes a URL and creates an AutoLinkNode. AutoLinkNodes are generally automatically generated
25598
+ * during typing, which is especially useful when a button to generate a LinkNode is not practical.
25599
+ * @param url - The URL the LinkNode should direct to.
25600
+ * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
25601
+ * @returns The LinkNode.
25602
+ */
25603
+ function $createAutoLinkNode(url = "", attributes) {
25604
+ return $applyNodeReplacement(new AutoLinkNode(url, attributes));
25605
+ }
25606
+ /**
25607
+ * Determines if node is an AutoLinkNode.
25608
+ * @param node - The node to be checked.
25609
+ * @returns true if node is an AutoLinkNode, false otherwise.
25610
+ */
25611
+ function $isAutoLinkNode(node) {
25612
+ return node instanceof AutoLinkNode;
25613
+ }
25614
+ const TOGGLE_LINK_COMMAND = createCommand("TOGGLE_LINK_COMMAND");
25615
+ function $getPointNode(point, offset) {
25616
+ if (point.type === "element") {
25617
+ const node = point.getNode();
25618
+ assert($isElementNode(node), "$getPointNode: element point is not an ElementNode");
25619
+ return node.getChildren()[point.offset + offset] || null;
25620
+ }
25621
+ return null;
25622
+ }
25623
+ /**
25624
+ * Preserve the logical start/end of a RangeSelection in situations where
25625
+ * the point is an element that may be reparented in the callback.
25626
+ *
25627
+ * @param $fn The function to run
25628
+ * @returns The result of the callback
25629
+ */
25630
+ function $withSelectedNodes($fn) {
25631
+ const initialSelection = $getSelection();
25632
+ if (!$isRangeSelection(initialSelection)) return $fn();
25633
+ const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection);
25634
+ const isBackwards = normalized.isBackward();
25635
+ const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0);
25636
+ const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1);
25637
+ const rval = $fn();
25638
+ if (anchorNode || focusNode) {
25639
+ const updatedSelection = $getSelection();
25640
+ if ($isRangeSelection(updatedSelection)) {
25641
+ const finalSelection = updatedSelection.clone();
25642
+ if (anchorNode) {
25643
+ const anchorParent = anchorNode.getParent();
25644
+ if (anchorParent) finalSelection.anchor.set(anchorParent.getKey(), anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), "element");
25645
+ }
25646
+ if (focusNode) {
25647
+ const focusParent = focusNode.getParent();
25648
+ if (focusParent) finalSelection.focus.set(focusParent.getKey(), focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), "element");
25649
+ }
25650
+ $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection));
25651
+ }
25652
+ }
25653
+ return rval;
25654
+ }
25655
+ /**
25656
+ * Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null,
25657
+ * but saves any children and brings them up to the parent node.
25658
+ * @param url - The URL the link directs to.
25659
+ * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
25660
+ */
25661
+ function $toggleLink(url, attributes = {}) {
25662
+ const { target, title } = attributes;
25663
+ const rel = attributes.rel === void 0 ? "noreferrer" : attributes.rel;
25664
+ const selection = $getSelection();
25665
+ if (selection === null || !$isRangeSelection(selection) && !$isNodeSelection(selection)) return;
25666
+ if ($isNodeSelection(selection)) {
25667
+ const nodes = selection.getNodes();
25668
+ if (nodes.length === 0) return;
25669
+ nodes.forEach((node) => {
25670
+ if (url === null) {
25671
+ const linkParent = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
25672
+ if (linkParent) {
25673
+ linkParent.insertBefore(node);
25674
+ if (linkParent.getChildren().length === 0) linkParent.remove();
25675
+ }
25676
+ } else {
25677
+ const existingLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
25678
+ if (existingLink) {
25679
+ existingLink.setURL(url);
25680
+ if (target !== void 0) existingLink.setTarget(target);
25681
+ if (rel !== void 0) existingLink.setRel(rel);
25682
+ } else {
25683
+ const linkNode = $createLinkNode(url, {
25684
+ rel,
25685
+ target
25686
+ });
25687
+ node.insertBefore(linkNode);
25688
+ linkNode.append(node);
25689
+ }
25690
+ }
25691
+ });
25692
+ return;
25693
+ }
25694
+ const nodes = selection.extract();
25695
+ if (url === null) {
25696
+ nodes.forEach((node) => {
25697
+ const parentLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
25698
+ if (parentLink) {
25699
+ const children = parentLink.getChildren();
25700
+ for (const child of children) parentLink.insertBefore(child);
25701
+ parentLink.remove();
25702
+ }
25703
+ });
25704
+ return;
25705
+ }
25706
+ const updatedNodes = /* @__PURE__ */ new Set();
25707
+ const updateLinkNode = (linkNode) => {
25708
+ if (updatedNodes.has(linkNode.getKey())) return;
25709
+ updatedNodes.add(linkNode.getKey());
25710
+ linkNode.setURL(url);
25711
+ if (target !== void 0) linkNode.setTarget(target);
25712
+ if (rel !== void 0) linkNode.setRel(rel);
25713
+ if (title !== void 0) linkNode.setTitle(title);
25714
+ };
25715
+ if (nodes.length === 1) {
25716
+ const firstNode = nodes[0];
25717
+ const linkNode = $getAncestor(firstNode, $isLinkNode);
25718
+ if (linkNode !== null) return updateLinkNode(linkNode);
25719
+ }
25720
+ $withSelectedNodes(() => {
25721
+ let linkNode = null;
25722
+ for (const node of nodes) {
25723
+ if (!node.isAttached()) continue;
25724
+ const parentLinkNode = $getAncestor(node, $isLinkNode);
25725
+ if (parentLinkNode) {
25726
+ updateLinkNode(parentLinkNode);
25727
+ continue;
25728
+ }
25729
+ if ($isElementNode(node)) {
25730
+ if (!node.isInline()) continue;
25731
+ if ($isLinkNode(node)) {
25732
+ if (!$isAutoLinkNode(node) && (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))) {
25733
+ updateLinkNode(node);
25734
+ linkNode = node;
25735
+ continue;
25736
+ }
25737
+ for (const child of node.getChildren()) node.insertBefore(child);
25738
+ node.remove();
25739
+ continue;
25740
+ }
25741
+ }
25742
+ const prevLinkNode = node.getPreviousSibling();
25743
+ if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) {
25744
+ prevLinkNode.append(node);
25745
+ continue;
25746
+ }
25747
+ linkNode = $createLinkNode(url, {
25748
+ rel,
25749
+ target,
25750
+ title
25751
+ });
25752
+ node.insertAfter(linkNode);
25753
+ linkNode.append(node);
25754
+ }
25755
+ });
25756
+ }
25757
+ function $getAncestor(node, predicate) {
25758
+ let parent = node;
25759
+ while (parent !== null && parent.getParent() !== null && !predicate(parent)) parent = parent.getParentOrThrow();
25760
+ return predicate(parent) ? parent : null;
25761
+ }
25762
+ const PHONE_NUMBER_REGEX = /^\+?[\d\s()-]{5,}$/;
25763
+ /**
25764
+ * Formats a URL string by adding appropriate protocol if missing
25765
+ *
25766
+ * @param url - URL to format
25767
+ * @returns Formatted URL with appropriate protocol
25768
+ */
25769
+ function formatUrl(url) {
25770
+ if (/^[a-z][\d+.a-z-]*:/i.test(url)) return url;
25771
+ else if (/^[#./]/.test(url)) return url;
25772
+ else if (url.includes("@")) return `mailto:${url}`;
25773
+ else if (PHONE_NUMBER_REGEX.test(url)) return `tel:${url}`;
25774
+ return url;
25775
+ }
25776
+ //#endregion
25777
+ //#region src/plugins/link/command/index.ts
25778
+ const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
25779
+ const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
25780
+ function registerLinkCommand(editor) {
25781
+ return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
25782
+ const { url, title = url } = payload;
25783
+ editor.update(() => {
25784
+ const linkNode = $createLinkNode(url, { title });
25785
+ const textNode = $createTextNode(title);
25786
+ linkNode.append(textNode);
25787
+ $insertNodes([linkNode]);
25788
+ });
25789
+ return false;
25790
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
25791
+ const { key, text } = payload;
25792
+ editor.update(() => {
25793
+ const linkNode = $getNodeByKey(key);
25794
+ if (linkNode) {
25795
+ const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
25796
+ const textNode = $createTextNode(text);
25797
+ newLinkNode.append(textNode);
25798
+ linkNode?.replace(newLinkNode);
25799
+ newLinkNode.select(1);
25800
+ }
25801
+ });
25802
+ return false;
25803
+ }, COMMAND_PRIORITY_EDITOR));
25804
+ }
25805
+ //#endregion
25806
+ //#region src/plugins/link/service/i-link-service.ts
25807
+ const ILinkService = genServiceId("LinkService");
25808
+ var LinkService = class extends EventEmitter {
25809
+ constructor(..._args) {
25810
+ super(..._args);
25811
+ this._enableLinkToolbar = true;
25812
+ }
25813
+ get enableLinkToolbar() {
25814
+ return this._enableLinkToolbar;
25815
+ }
25816
+ setLinkToolbar(enable) {
25817
+ this._enableLinkToolbar = enable;
25818
+ this.emit("linkToolbarChange", enable);
25819
+ }
25820
+ };
25821
+ //#endregion
25822
+ //#region src/plugins/link/utils/index.ts
25823
+ const SUPPORTED_URL_PROTOCOLS = new Set([
25824
+ "http:",
25825
+ "https:",
25826
+ "mailto:",
25827
+ "sms:",
25828
+ "tel:"
25829
+ ]);
25830
+ function sanitizeUrl(url) {
25831
+ try {
25832
+ const parsedUrl = new URL(url);
25833
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) return "about:blank";
25834
+ } catch {
25835
+ return url;
25836
+ }
25837
+ return url;
25838
+ }
25839
+ const urlRegExp = /* @__PURE__ */ new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\w$&+,:;=-]+@)?[\d.A-Za-z-]+|(?:www.|[\w$&+,:;=-]+@)[\d.A-Za-z-]+)((?:\/[%+./~\w-_]*)?\??[\w%&+.;=@-]*#?\w*)?)/);
25840
+ function extractUrlFromText(text) {
25841
+ const match = urlRegExp.exec(text);
25842
+ if (!match) return null;
25843
+ const raw = match[0];
25844
+ const start = match.index ?? text.indexOf(raw);
25845
+ const trimmed = raw.replace(/[)\],.;:]+$/u, "");
25846
+ return {
25847
+ index: start,
25848
+ length: trimmed.length,
25849
+ url: trimmed
25850
+ };
25851
+ }
25852
+ function getSelectedNode(selection) {
25853
+ const anchor = selection.anchor;
25854
+ const focus = selection.focus;
25855
+ const anchorNode = selection.anchor.getNode();
25856
+ const focusNode = selection.focus.getNode();
25857
+ if (anchorNode === focusNode) return anchorNode;
25858
+ if (selection.isBackward()) return $isAtNodeEnd(focus) ? anchorNode : focusNode;
25859
+ else return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
25860
+ }
25861
+ //#endregion
25862
+ //#region src/plugins/link/plugin/registry.ts
25863
+ function registerLinkCommands(editor, kernel, options) {
25864
+ const { validateUrl, attributes, enableHotkey = true } = options || {};
25865
+ const state = { isLink: false };
25866
+ const registrations = [editor.registerUpdateListener(() => {
25867
+ const selection = editor.getEditorState().read(() => $getSelection());
25868
+ if (!selection) return;
25869
+ if ($isRangeSelection(selection)) editor.getEditorState().read(() => {
25870
+ const node = getSelectedNode(selection);
25871
+ state.isLink = $isLinkNode(node.getParent()) || $isLinkNode(node);
25872
+ });
25873
+ else state.isLink = false;
25874
+ }), editor.registerCommand(TOGGLE_LINK_COMMAND, (payload) => {
25875
+ if (payload === null) {
25876
+ $toggleLink(payload);
25877
+ return true;
25878
+ } else if (typeof payload === "string") {
25879
+ if (validateUrl === void 0 || validateUrl(payload)) {
25880
+ $toggleLink(payload, attributes);
25881
+ return true;
25882
+ }
25883
+ return false;
25884
+ } else {
25885
+ const { url, target, rel, title } = payload;
25886
+ $toggleLink(url, {
25887
+ ...attributes,
25888
+ rel,
25889
+ target,
25890
+ title
25891
+ });
25892
+ return true;
25893
+ }
25894
+ }, COMMAND_PRIORITY_LOW)];
25895
+ registrations.push(kernel.registerHotkey(HotkeyEnum.Link, () => {
25896
+ if (state.isLink) {
25897
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
25898
+ return;
25899
+ }
25900
+ let nextUrl = sanitizeUrl("https://");
25901
+ let expandTo = null;
25902
+ editor.getEditorState().read(() => {
25903
+ const selection = $getSelection();
25904
+ if ($isRangeSelection(selection)) if (!selection.isCollapsed()) {
25905
+ const maybeUrl = formatUrl(selection.getTextContent().trim());
25906
+ if (validateUrl?.(maybeUrl)) nextUrl = maybeUrl;
25907
+ } else {
25908
+ const found = extractUrlFromText(selection.anchor.getNode().getTextContent());
25909
+ if (found && validateUrl?.(formatUrl(found.url))) {
25910
+ nextUrl = formatUrl(found.url);
25911
+ expandTo = {
25912
+ index: found.index,
25913
+ length: found.length
25914
+ };
25915
+ }
25916
+ }
25917
+ });
25918
+ editor.update(() => {
25919
+ if (expandTo) {
25920
+ const selection = $getSelection();
25921
+ if ($isRangeSelection(selection)) {
25922
+ const anchorNode = selection.anchor.getNode();
25923
+ selection.anchor.set(anchorNode.getKey(), expandTo.index, "text");
25924
+ selection.focus.set(anchorNode.getKey(), expandTo.index + expandTo.length, "text");
25925
+ }
25926
+ }
25927
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, nextUrl);
25928
+ });
25929
+ }, {
25930
+ enabled: enableHotkey,
25931
+ preventDefault: true,
25932
+ stopPropagation: true
25933
+ }));
25934
+ return mergeRegister(...registrations);
25935
+ }
25936
+ //#endregion
25937
+ //#region src/plugins/link/plugin/index.ts
25938
+ const LinkPlugin = class extends KernelPlugin {
25939
+ static {
25940
+ this.pluginName = "LinkPlugin";
25941
+ }
25942
+ constructor(kernel, config) {
25943
+ super();
25944
+ this.kernel = kernel;
25945
+ this.config = config;
25946
+ this.linkRegex = /^https?:\/\/\S+$/;
25947
+ this.service = new LinkService();
25948
+ kernel.registerNodes([LinkNode, AutoLinkNode]);
25949
+ kernel.registerService(ILinkService, this.service);
25950
+ if (config?.theme) kernel.registerThemes(config.theme);
25951
+ if (config?.linkRegex) this.linkRegex = config.linkRegex;
25952
+ }
25953
+ onInit(editor) {
25954
+ this.register(registerLinkCommand(editor));
25955
+ this.register(registerLinkCommands(editor, this.kernel, {
25956
+ attributes: this.config?.attributes,
25957
+ enableHotkey: this.config?.enableHotkey,
25958
+ validateUrl: this.config?.validateUrl
25959
+ }));
25960
+ this.register(editor.registerCommand(PASTE_COMMAND, (payload) => {
25961
+ const { clipboardData } = payload;
25962
+ if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
25963
+ const data = clipboardData.getData("text/plain").trim();
25964
+ if (this.linkRegex.test(data)) {
25965
+ payload.stopImmediatePropagation();
25966
+ payload.preventDefault();
25967
+ editor.dispatchCommand(INSERT_LINK_COMMAND, { url: data });
25968
+ return true;
25969
+ }
25970
+ }
25971
+ return false;
25972
+ }, COMMAND_PRIORITY_NORMAL));
25973
+ this.registerMarkdown();
25974
+ this.registerLiteXml();
25975
+ }
25976
+ registerLiteXml() {
25977
+ const litexmlService = this.kernel.requireService(ILitexmlService);
25978
+ if (!litexmlService) return;
25979
+ litexmlService.registerXMLWriter(LinkNode.getType(), (node, ctx) => {
25980
+ if ($isLinkNode(node)) {
25981
+ const attributes = { href: node.getURL() };
25982
+ return ctx.createXmlNode("a", attributes);
25983
+ }
25984
+ return false;
25985
+ });
25986
+ litexmlService.registerXMLReader("a", (xmlNode, children) => {
25987
+ return [INodeHelper.createElementNode("link", {
25988
+ children,
25989
+ direction: "ltr",
25990
+ format: "",
25991
+ indent: 0,
25992
+ type: "link",
25993
+ url: xmlNode.getAttribute("href") || "",
25994
+ version: 1
25995
+ })];
25996
+ });
25997
+ }
25998
+ registerMarkdown() {
25999
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
26000
+ regExp: /\[([^[]+)]\(([^\s()]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)\s?$/,
26001
+ replace: (textNode, match) => {
26002
+ const [, linkText, linkUrl, linkTitle] = match;
26003
+ const linkNode = $createLinkNode(linkUrl, { title: linkTitle });
26004
+ const linkTextNode = $createTextNode(linkText);
26005
+ linkTextNode.setFormat(textNode.getFormat());
26006
+ linkNode.append(linkTextNode);
26007
+ textNode.replace(linkNode);
26008
+ return linkTextNode;
26009
+ },
26010
+ trigger: ")",
26011
+ type: "text-match"
26012
+ });
26013
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
26014
+ if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
26015
+ });
26016
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
26017
+ return [INodeHelper.createElementNode("link", {
26018
+ children,
26019
+ direction: "ltr",
26020
+ format: "",
26021
+ indent: 0,
26022
+ title: node.title || void 0,
26023
+ type: "link",
26024
+ url: node.url || "",
26025
+ version: 1
26026
+ })];
26027
+ });
26028
+ }
26029
+ };
26030
+ //#endregion
25310
26031
  //#region src/utils/cx.ts
25311
26032
  const cx = (...classNames) => classNames.filter(Boolean).join(" ");
25312
26033
  //#endregion
@@ -26113,6 +26834,7 @@ const normalizeLegacyEditorData = (editorData) => {
26113
26834
  const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
26114
26835
  [CommonPlugin, { enableHotkey: false }],
26115
26836
  MarkdownPlugin,
26837
+ [LinkPlugin, { enableHotkey: false }],
26116
26838
  CodePlugin,
26117
26839
  HeadlessCodeblockPlugin,
26118
26840
  HRPlugin,