@haklex/rich-editor 0.0.38 → 0.0.40

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 (34) hide show
  1. package/dist/{RichEditor-B9KIgSwn.js → RichEditor-DnJwe663.js} +582 -18
  2. package/dist/components/RichEditor.d.ts.map +1 -1
  3. package/dist/components/decorators/AlertEditDecorator.d.ts.map +1 -1
  4. package/dist/components/decorators/CodeBlockEditDecorator.d.ts.map +1 -1
  5. package/dist/components/renderers/CodeBlockRenderer.d.ts +5 -0
  6. package/dist/components/renderers/CodeBlockRenderer.d.ts.map +1 -1
  7. package/dist/editor.mjs +2 -2
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.mjs +198 -35
  11. package/dist/nodes/CodeBlockEditNode.d.ts +9 -0
  12. package/dist/nodes/CodeBlockEditNode.d.ts.map +1 -1
  13. package/dist/nodes/CodeBlockNode.d.ts +1 -0
  14. package/dist/nodes/CodeBlockNode.d.ts.map +1 -1
  15. package/dist/nodes/MentionNode.d.ts +2 -0
  16. package/dist/nodes/MentionNode.d.ts.map +1 -1
  17. package/dist/plugins/BlockExitPlugin.d.ts +2 -0
  18. package/dist/plugins/BlockExitPlugin.d.ts.map +1 -0
  19. package/dist/plugins/BlockIdPlugin.d.ts +3 -0
  20. package/dist/plugins/BlockIdPlugin.d.ts.map +1 -0
  21. package/dist/rich-editor.css +1 -1
  22. package/dist/static-entry.mjs +1 -1
  23. package/dist/{theme-D1COY7pa.js → theme-gVNBI_ET.js} +79 -50
  24. package/dist/transformers/code-block.d.ts +3 -0
  25. package/dist/transformers/code-block.d.ts.map +1 -0
  26. package/dist/transformers/index.d.ts +1 -0
  27. package/dist/transformers/index.d.ts.map +1 -1
  28. package/dist/transformers/quote.d.ts +3 -0
  29. package/dist/transformers/quote.d.ts.map +1 -0
  30. package/dist/utils/codeBlockSelectionIntent.d.ts +4 -0
  31. package/dist/utils/codeBlockSelectionIntent.d.ts.map +1 -0
  32. package/dist/utils/comment-anchor.d.ts +32 -0
  33. package/dist/utils/comment-anchor.d.ts.map +1 -0
  34. package/package.json +5 -5
@@ -12,10 +12,10 @@ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
12
12
  import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
13
13
  import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
14
14
  import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
15
- import { V as $isAlertQuoteNode, d as RendererWrapper, W as AlertRenderer, X as SpoilerNode, Y as MentionNode, Z as FootnoteNode, L as KaTeXInlineNode, _ as AlertQuoteNode, f as editorTheme, a0 as $isBannerNode, a1 as BannerRenderer, a2 as BannerNode, a3 as normalizeBannerType, a4 as $isCodeBlockNode, a5 as CodeBlockRenderer, a6 as CodeBlockNode, i as useFootnoteDefinitions, G as FootnoteSectionNode, t as $isGridContainerNode, I as GridContainerNode, b as builtinNodes, K as KaTeXBlockNode, J as ImageNode, a7 as VideoNode, O as LinkCardNode, a8 as DetailsNode, Q as MermaidNode, F as FootnoteDefinitionsProvider, p as $createImageNode, T as computeImageMeta, S as OPEN_IMAGE_UPLOAD_DIALOG_COMMAND, a9 as $createKaTeXInlineNode, aa as $createKaTeXBlockNode, ab as $createDetailsNode, ac as $createFootnoteNode, s as $isFootnoteSectionNode, $ as $createFootnoteSectionNode, ad as $createMentionNode, g as extractTextContent, ae as $createSpoilerNode, r as $createMermaidNode, C as ColorSchemeProvider, R as RendererConfigProvider } from "./theme-D1COY7pa.js";
16
- import { $getNodeByKey, $insertNodes, createEditor, $nodesOfType, $getRoot, createCommand, COMMAND_PRIORITY_EDITOR, $parseSerializedNode, $getSelection, $isRangeSelection, $createParagraphNode, COMMAND_PRIORITY_HIGH, PASTE_COMMAND, $createTextNode, KEY_ENTER_COMMAND } from "lexical";
15
+ import { Y as $isAlertQuoteNode, d as RendererWrapper, Z as AlertRenderer, _ as SpoilerNode, T as MentionNode, a0 as FootnoteNode, O as KaTeXInlineNode, a1 as AlertQuoteNode, f as editorTheme, a2 as $isBannerNode, a3 as BannerRenderer, a4 as BannerNode, a5 as normalizeBannerType, a6 as $isCodeBlockNode, a7 as CodeBlockRenderer, a8 as CodeBlockNode, i as useFootnoteDefinitions, I as FootnoteSectionNode, v as $isGridContainerNode, K as GridContainerNode, b as builtinNodes, M as KaTeXBlockNode, L as ImageNode, a9 as VideoNode, Q as LinkCardNode, aa as DetailsNode, U as MermaidNode, F as FootnoteDefinitionsProvider, p as $createImageNode, W as computeImageMeta, V as OPEN_IMAGE_UPLOAD_DIALOG_COMMAND, ab as $createKaTeXInlineNode, ac as $createKaTeXBlockNode, ad as $createDetailsNode, ae as $createFootnoteNode, t as $isFootnoteSectionNode, $ as $createFootnoteSectionNode, r as $createMentionNode, g as extractTextContent, af as $createSpoilerNode, s as $createMermaidNode, C as ColorSchemeProvider, R as RendererConfigProvider } from "./theme-gVNBI_ET.js";
16
+ import { $getNodeByKey, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_ARROW_DOWN_COMMAND, COMMAND_PRIORITY_HIGH, $getRoot, $createParagraphNode, $isParagraphNode, $getSelection, $isRangeSelection, $insertNodes, createEditor, $isElementNode, $isDecoratorNode, $createNodeSelection, $setSelection, $nodesOfType, createCommand, COMMAND_PRIORITY_EDITOR, $isRootNode, $isNodeSelection, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_ARROW_UP_COMMAND, $isTextNode, createState, $getState, $addUpdateTag, $setState, $parseSerializedNode, PASTE_COMMAND, $createTextNode, $createLineBreakNode } from "lexical";
17
17
  import { Info, Lightbulb, TriangleAlert, Flag, LayoutGrid, Plus, Minus, Check, Upload, Link2 } from "lucide-react";
18
- import { useCallback, createElement, useState, useEffect, createContext, use, useRef, useMemo } from "react";
18
+ import { useCallback, useEffect, createElement, useState, createContext, use, useRef, useMemo } from "react";
19
19
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
20
20
  import { ContentEditable as ContentEditable$1 } from "@lexical/react/LexicalContentEditable";
21
21
  import { LexicalNestedComposer } from "@lexical/react/LexicalNestedComposer";
@@ -23,13 +23,110 @@ import { CodeNode } from "@lexical/code";
23
23
  import { HorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND, $createHorizontalRuleNode } from "@lexical/extension";
24
24
  import { LinkNode, AutoLinkNode, createLinkMatcherWithRegExp, registerAutoLink } from "@lexical/link";
25
25
  import { ListNode, ListItemNode } from "@lexical/list";
26
- import { HeadingNode, QuoteNode, DRAG_DROP_PASTE } from "@lexical/rich-text";
26
+ import { HeadingNode, QuoteNode, $isQuoteNode, DRAG_DROP_PASTE, $createQuoteNode } from "@lexical/rich-text";
27
27
  import { TableNode, TableCellNode, TableRowNode } from "@lexical/table";
28
+ import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
28
29
  import { Dialog, DialogPopup, DialogTitle, SegmentedControl } from "@haklex/rich-editor-ui";
29
30
  import { b as clsx, g as getVariantClass } from "./utils-fpeaZV1R.js";
30
31
  import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
31
- import { CHECK_LIST, TRANSFORMERS } from "@lexical/markdown";
32
+ import { CHECK_LIST, TRANSFORMERS, QUOTE, CODE } from "@lexical/markdown";
32
33
  import { GIT_ALERT_TRANSFORMER as GIT_ALERT_TRANSFORMER$1, CONTAINER_TRANSFORMER as CONTAINER_TRANSFORMER$1, FOOTNOTE_TRANSFORMER as FOOTNOTE_TRANSFORMER$1, FOOTNOTE_SECTION_TRANSFORMER as FOOTNOTE_SECTION_TRANSFORMER$1, KATEX_INLINE_TRANSFORMER as KATEX_INLINE_TRANSFORMER$1, KATEX_BLOCK_TRANSFORMER as KATEX_BLOCK_TRANSFORMER$1, MENTION_TRANSFORMER as MENTION_TRANSFORMER$1, SPOILER_TRANSFORMER as SPOILER_TRANSFORMER$1, INSERT_TRANSFORMER, SUPERSCRIPT_TRANSFORMER, SUBSCRIPT_TRANSFORMER, IMAGE_BLOCK_TRANSFORMER, VIDEO_BLOCK_TRANSFORMER, CODE_BLOCK_NODE_TRANSFORMER, LINK_CARD_BLOCK_TRANSFORMER, MERMAID_BLOCK_TRANSFORMER, HORIZONTAL_RULE_BLOCK_TRANSFORMER, TABLE_BLOCK_TRANSFORMER } from "@haklex/rich-headless/transformers";
34
+ function ExitBlockPlugin({
35
+ parentEditor,
36
+ nodeKey
37
+ }) {
38
+ const [editor] = useLexicalComposerContext();
39
+ useEffect(() => {
40
+ const focusParent = () => {
41
+ parentEditor.focus(() => {
42
+ parentEditor.update(() => {
43
+ const alertNode = $getNodeByKey(nodeKey);
44
+ if (alertNode) {
45
+ const next = alertNode.getNextSibling();
46
+ if (next) {
47
+ next.selectStart();
48
+ }
49
+ }
50
+ });
51
+ });
52
+ };
53
+ const exitAlert = (removeEmpty) => {
54
+ const root = $getRoot();
55
+ const lastChild = root.getLastChild();
56
+ const shouldRemoveAlert = removeEmpty && root.getChildrenSize() <= 1;
57
+ if (removeEmpty && lastChild) {
58
+ lastChild.remove();
59
+ }
60
+ if (shouldRemoveAlert) {
61
+ parentEditor.update(
62
+ () => {
63
+ const alertNode = $getNodeByKey(nodeKey);
64
+ if (alertNode) {
65
+ const paragraph = $createParagraphNode();
66
+ alertNode.insertAfter(paragraph);
67
+ alertNode.remove();
68
+ paragraph.selectStart();
69
+ }
70
+ },
71
+ { onUpdate: focusParent }
72
+ );
73
+ } else {
74
+ parentEditor.update(
75
+ () => {
76
+ const alertNode = $getNodeByKey(nodeKey);
77
+ if (alertNode) {
78
+ let next = alertNode.getNextSibling();
79
+ if (!next || !$isParagraphNode(next)) {
80
+ next = $createParagraphNode();
81
+ alertNode.insertAfter(next);
82
+ }
83
+ next.selectStart();
84
+ }
85
+ },
86
+ { onUpdate: focusParent }
87
+ );
88
+ }
89
+ };
90
+ const isAtLastEmptyParagraph = () => {
91
+ const selection = $getSelection();
92
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
93
+ return false;
94
+ const anchorNode = selection.anchor.getNode();
95
+ const topLevelElement = anchorNode.getTopLevelElement();
96
+ return topLevelElement && $isParagraphNode(topLevelElement) && topLevelElement.getTextContent() === "" && topLevelElement.getNextSibling() === null;
97
+ };
98
+ const unregisterEnter = editor.registerCommand(
99
+ KEY_ENTER_COMMAND,
100
+ (event) => {
101
+ if (event?.metaKey || event?.ctrlKey) {
102
+ event.preventDefault();
103
+ exitAlert(false);
104
+ return true;
105
+ }
106
+ if (!isAtLastEmptyParagraph()) return false;
107
+ event?.preventDefault();
108
+ exitAlert(true);
109
+ return true;
110
+ },
111
+ COMMAND_PRIORITY_CRITICAL
112
+ );
113
+ const unregisterArrowDown = editor.registerCommand(
114
+ KEY_ARROW_DOWN_COMMAND,
115
+ (event) => {
116
+ if (!isAtLastEmptyParagraph()) return false;
117
+ event?.preventDefault();
118
+ exitAlert(false);
119
+ return true;
120
+ },
121
+ COMMAND_PRIORITY_HIGH
122
+ );
123
+ return () => {
124
+ unregisterEnter();
125
+ unregisterArrowDown();
126
+ };
127
+ }, [editor, parentEditor, nodeKey]);
128
+ return null;
129
+ }
33
130
  function AlertEditDecorator({
34
131
  nodeKey,
35
132
  alertType,
@@ -78,7 +175,8 @@ function AlertEditDecorator({
78
175
  }
79
176
  ),
80
177
  /* @__PURE__ */ jsx(ListPlugin, {}),
81
- /* @__PURE__ */ jsx(LinkPlugin, {})
178
+ /* @__PURE__ */ jsx(LinkPlugin, {}),
179
+ /* @__PURE__ */ jsx(ExitBlockPlugin, { parentEditor: editor, nodeKey })
82
180
  ] }) })
83
181
  ] });
84
182
  }
@@ -337,13 +435,31 @@ let BannerEditNode = _BannerEditNode;
337
435
  function $createBannerEditNode(bannerType, contentState) {
338
436
  return new BannerEditNode(bannerType, contentState);
339
437
  }
438
+ const codeBlockCursorIntentMap = /* @__PURE__ */ new Map();
439
+ function setCodeBlockCursorIntent(nodeKey, placement) {
440
+ codeBlockCursorIntentMap.set(nodeKey, placement);
441
+ }
442
+ function consumeCodeBlockCursorIntent(nodeKey) {
443
+ const placement = codeBlockCursorIntentMap.get(nodeKey);
444
+ if (!placement) return null;
445
+ codeBlockCursorIntentMap.delete(nodeKey);
446
+ return placement;
447
+ }
340
448
  function CodeBlockEditDecorator({
341
449
  nodeKey,
342
450
  code,
343
451
  language
344
452
  }) {
345
453
  const [editor] = useLexicalComposerContext();
454
+ const [isSelected] = useLexicalNodeSelection(nodeKey);
455
+ const [cursorPlacement, setCursorPlacement] = useState(
456
+ "start"
457
+ );
346
458
  const editable = editor.isEditable();
459
+ useEffect(() => {
460
+ if (!editable || !isSelected) return;
461
+ setCursorPlacement(consumeCodeBlockCursorIntent(nodeKey) ?? "start");
462
+ }, [editable, isSelected, nodeKey]);
347
463
  const handleCodeChange = useCallback(
348
464
  (newCode) => {
349
465
  editor.update(() => {
@@ -355,6 +471,100 @@ function CodeBlockEditDecorator({
355
471
  },
356
472
  [editor, nodeKey]
357
473
  );
474
+ const handleLanguageChange = useCallback(
475
+ (newLanguage) => {
476
+ editor.update(() => {
477
+ const node = $getNodeByKey(nodeKey);
478
+ if ($isCodeBlockNode(node)) {
479
+ node.setLanguage(newLanguage);
480
+ }
481
+ });
482
+ },
483
+ [editor, nodeKey]
484
+ );
485
+ const handleDelete = useCallback(() => {
486
+ editor.getRootElement()?.focus({ preventScroll: true });
487
+ editor.update(() => {
488
+ const node = $getNodeByKey(nodeKey);
489
+ if (!node) return;
490
+ const prev = node.getPreviousSibling();
491
+ const next = node.getNextSibling();
492
+ const parent = node.getParent();
493
+ node.remove();
494
+ if (prev) {
495
+ if ($isElementNode(prev)) {
496
+ prev.selectEnd();
497
+ } else {
498
+ if ($isDecoratorNode(prev) && prev.getType() === "code-block") {
499
+ setCodeBlockCursorIntent(prev.getKey(), "end");
500
+ }
501
+ const selection = $createNodeSelection();
502
+ selection.add(prev.getKey());
503
+ $setSelection(selection);
504
+ }
505
+ return;
506
+ }
507
+ if (next) {
508
+ if ($isElementNode(next)) {
509
+ next.selectStart();
510
+ } else {
511
+ if ($isDecoratorNode(next) && next.getType() === "code-block") {
512
+ setCodeBlockCursorIntent(next.getKey(), "start");
513
+ }
514
+ const selection = $createNodeSelection();
515
+ selection.add(next.getKey());
516
+ $setSelection(selection);
517
+ }
518
+ return;
519
+ }
520
+ if (parent) {
521
+ const p = $createParagraphNode();
522
+ parent.append(p);
523
+ p.selectStart();
524
+ }
525
+ });
526
+ }, [editor, nodeKey]);
527
+ const handleExitBlock = useCallback(
528
+ (direction) => {
529
+ editor.getRootElement()?.focus({ preventScroll: true });
530
+ editor.update(() => {
531
+ const node = $getNodeByKey(nodeKey);
532
+ if (!node) return;
533
+ if (direction === "before") {
534
+ const prev = node.getPreviousSibling();
535
+ if (!prev) return;
536
+ if ($isElementNode(prev)) {
537
+ prev.selectEnd();
538
+ } else {
539
+ if ($isDecoratorNode(prev) && prev.getType() === "code-block") {
540
+ setCodeBlockCursorIntent(prev.getKey(), "end");
541
+ }
542
+ const selection = $createNodeSelection();
543
+ selection.add(prev.getKey());
544
+ $setSelection(selection);
545
+ }
546
+ return;
547
+ }
548
+ let next = node.getNextSibling();
549
+ if (!next) {
550
+ const p = $createParagraphNode();
551
+ node.insertAfter(p);
552
+ next = p;
553
+ }
554
+ if ($isElementNode(next)) {
555
+ next.selectStart();
556
+ } else {
557
+ if ($isDecoratorNode(next) && next.getType() === "code-block") {
558
+ setCodeBlockCursorIntent(next.getKey(), "start");
559
+ }
560
+ const selection = $createNodeSelection();
561
+ selection.add(next.getKey());
562
+ $setSelection(selection);
563
+ }
564
+ });
565
+ },
566
+ [editor, nodeKey]
567
+ );
358
568
  return /* @__PURE__ */ jsx(
359
569
  RendererWrapper,
360
570
  {
@@ -364,17 +574,22 @@ function CodeBlockEditDecorator({
364
574
  code,
365
575
  language,
366
576
  editable,
367
- onCodeChange: editable ? handleCodeChange : void 0
577
+ onCodeChange: editable ? handleCodeChange : void 0,
578
+ onLanguageChange: editable ? handleLanguageChange : void 0,
579
+ onDelete: editable ? handleDelete : void 0,
580
+ onExitBlock: editable ? handleExitBlock : void 0,
581
+ selected: editable ? isSelected : false,
582
+ cursorPlacement: editable ? cursorPlacement : "start"
368
583
  }
369
584
  }
370
585
  );
371
586
  }
372
- class CodeBlockEditNode extends CodeBlockNode {
587
+ const _CodeBlockEditNode = class _CodeBlockEditNode extends CodeBlockNode {
373
588
  static clone(node) {
374
- return new CodeBlockEditNode(node.__code, node.__language, node.__key);
589
+ return new _CodeBlockEditNode(node.__code, node.__language, node.__key);
375
590
  }
376
591
  static importJSON(serializedNode) {
377
- return new CodeBlockEditNode(serializedNode.code, serializedNode.language);
592
+ return new _CodeBlockEditNode(serializedNode.code, serializedNode.language);
378
593
  }
379
594
  decorate(_editor, _config) {
380
595
  return createElement(CodeBlockEditDecorator, {
@@ -383,7 +598,16 @@ class CodeBlockEditNode extends CodeBlockNode {
383
598
  language: this.__language
384
599
  });
385
600
  }
386
- }
601
+ };
602
+ __publicField(_CodeBlockEditNode, "slashMenuItems", CodeBlockNode.slashMenuItems.map((item) => ({
603
+ ...item,
604
+ onSelect: (editor) => {
605
+ editor.update(() => {
606
+ $insertNodes([new _CodeBlockEditNode("", "text")]);
607
+ });
608
+ }
609
+ })));
610
+ let CodeBlockEditNode = _CodeBlockEditNode;
387
611
  function FootnoteSectionEditRenderer({
388
612
  definitions
389
613
  }) {
@@ -871,6 +1095,280 @@ function AutoLinkPlugin({ matchers }) {
871
1095
  }, [editor, matchers]);
872
1096
  return null;
873
1097
  }
1098
+ function selectDecoratorNode(node, cursorPlacement = "start") {
1099
+ if (node.getType() === "code-block") {
1100
+ setCodeBlockCursorIntent(node.getKey(), cursorPlacement);
1101
+ }
1102
+ const selection = $createNodeSelection();
1103
+ selection.add(node.getKey());
1104
+ $setSelection(selection);
1105
+ }
1106
+ function isAtTopLevelBoundary(selection, direction) {
1107
+ const point = selection.anchor;
1108
+ const topLevel = point.getNode().getTopLevelElementOrThrow();
1109
+ const pointNode = point.getNode();
1110
+ if (point.type === "text") {
1111
+ if (!$isTextNode(pointNode)) return false;
1112
+ const expectedOffset = direction === "start" ? 0 : pointNode.getTextContentSize();
1113
+ if (point.offset !== expectedOffset) return false;
1114
+ } else {
1115
+ if (!$isElementNode(pointNode)) return false;
1116
+ const expectedOffset = direction === "start" ? 0 : pointNode.getChildrenSize();
1117
+ if (point.offset !== expectedOffset) return false;
1118
+ }
1119
+ let current = pointNode;
1120
+ while (current && current !== topLevel) {
1121
+ const sibling = direction === "start" ? current.getPreviousSibling() : current.getNextSibling();
1122
+ if (sibling !== null) return false;
1123
+ current = current.getParent();
1124
+ }
1125
+ return current === topLevel;
1126
+ }
1127
+ function isSingleLineParagraph(node) {
1128
+ return $isParagraphNode(node) && !node.getTextContent().includes("\n");
1129
+ }
1130
+ function BlockExitPlugin() {
1131
+ const [editor] = useLexicalComposerContext();
1132
+ useEffect(() => {
1133
+ const unregisterArrowDown = editor.registerCommand(
1134
+ KEY_ARROW_DOWN_COMMAND,
1135
+ (event) => {
1136
+ const selection = $getSelection();
1137
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
1138
+ return false;
1139
+ }
1140
+ const anchorNode = selection.anchor.getNode();
1141
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
1142
+ const shouldSelectNextDecorator = isAtTopLevelBoundary(selection, "end") || isSingleLineParagraph(topLevelElement);
1143
+ if (!$isQuoteNode(topLevelElement) && shouldSelectNextDecorator) {
1144
+ const next2 = topLevelElement.getNextSibling();
1145
+ if (next2 && $isDecoratorNode(next2) && next2.isKeyboardSelectable()) {
1146
+ event?.preventDefault();
1147
+ selectDecoratorNode(next2, "start");
1148
+ return true;
1149
+ }
1150
+ }
1151
+ const element = $isElementNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
1152
+ let quoteChild = element;
1153
+ let quoteNode = null;
1154
+ let current = element;
1155
+ while (current) {
1156
+ const parent = current.getParent();
1157
+ if (!parent || $isRootNode(parent)) break;
1158
+ if ($isQuoteNode(parent)) {
1159
+ quoteNode = parent;
1160
+ quoteChild = current;
1161
+ break;
1162
+ }
1163
+ current = parent;
1164
+ }
1165
+ if (!quoteNode) return false;
1166
+ if (quoteChild.getNextSibling() !== null) return false;
1167
+ if (!$isParagraphNode(quoteChild) || quoteChild.getTextContent() !== "")
1168
+ return false;
1169
+ event?.preventDefault();
1170
+ let next = quoteNode.getNextSibling();
1171
+ if (!next) {
1172
+ next = $createParagraphNode();
1173
+ quoteNode.insertAfter(next);
1174
+ }
1175
+ next.selectStart();
1176
+ return true;
1177
+ },
1178
+ COMMAND_PRIORITY_CRITICAL
1179
+ );
1180
+ const unregisterCmdEnter = editor.registerCommand(
1181
+ KEY_ENTER_COMMAND,
1182
+ (event) => {
1183
+ if (!event?.metaKey && !event?.ctrlKey) return false;
1184
+ const selection = $getSelection();
1185
+ if ($isNodeSelection(selection)) {
1186
+ const nodes = selection.getNodes();
1187
+ if (nodes.length !== 1) return false;
1188
+ const node = nodes[0];
1189
+ if (!$isDecoratorNode(node)) return false;
1190
+ event.preventDefault();
1191
+ let next2 = node.getNextSibling();
1192
+ if (!next2) {
1193
+ next2 = $createParagraphNode();
1194
+ node.insertAfter(next2);
1195
+ }
1196
+ if ($isElementNode(next2)) {
1197
+ next2.selectStart();
1198
+ } else if ($isDecoratorNode(next2)) {
1199
+ selectDecoratorNode(next2, "start");
1200
+ }
1201
+ return true;
1202
+ }
1203
+ if (!$isRangeSelection(selection)) return false;
1204
+ const anchorNode = selection.anchor.getNode();
1205
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
1206
+ if ($isParagraphNode(topLevelElement)) return false;
1207
+ event.preventDefault();
1208
+ let next = topLevelElement.getNextSibling();
1209
+ if (!next || !$isParagraphNode(next)) {
1210
+ next = $createParagraphNode();
1211
+ topLevelElement.insertAfter(next);
1212
+ }
1213
+ next.selectStart();
1214
+ return true;
1215
+ },
1216
+ COMMAND_PRIORITY_CRITICAL
1217
+ );
1218
+ function handleDeleteDecorator(event) {
1219
+ const selection = $getSelection();
1220
+ if (!$isNodeSelection(selection)) return false;
1221
+ const nodes = selection.getNodes();
1222
+ if (nodes.length !== 1) return false;
1223
+ const node = nodes[0];
1224
+ if (!$isDecoratorNode(node)) return false;
1225
+ event?.preventDefault();
1226
+ const prev = node.getPreviousSibling();
1227
+ const next = node.getNextSibling();
1228
+ node.remove();
1229
+ if (prev && $isElementNode(prev)) {
1230
+ prev.selectEnd();
1231
+ } else if (prev && $isDecoratorNode(prev)) {
1232
+ selectDecoratorNode(prev, "end");
1233
+ } else if (next && $isElementNode(next)) {
1234
+ next.selectStart();
1235
+ } else if (next && $isDecoratorNode(next)) {
1236
+ selectDecoratorNode(next, "start");
1237
+ } else {
1238
+ const root = $getRoot();
1239
+ if (root && $isElementNode(root)) {
1240
+ const p = $createParagraphNode();
1241
+ root.append(p);
1242
+ p.selectStart();
1243
+ }
1244
+ }
1245
+ return true;
1246
+ }
1247
+ const unregisterBackspace = editor.registerCommand(
1248
+ KEY_BACKSPACE_COMMAND,
1249
+ handleDeleteDecorator,
1250
+ COMMAND_PRIORITY_HIGH
1251
+ );
1252
+ const unregisterDelete = editor.registerCommand(
1253
+ KEY_DELETE_COMMAND,
1254
+ handleDeleteDecorator,
1255
+ COMMAND_PRIORITY_HIGH
1256
+ );
1257
+ const unregisterArrowUpDecorator = editor.registerCommand(
1258
+ KEY_ARROW_UP_COMMAND,
1259
+ (event) => {
1260
+ const selection = $getSelection();
1261
+ if ($isRangeSelection(selection) && selection.isCollapsed()) {
1262
+ const anchorNode = selection.anchor.getNode();
1263
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
1264
+ const shouldSelectPreviousDecorator = isAtTopLevelBoundary(selection, "start") || isSingleLineParagraph(topLevelElement);
1265
+ if (!$isQuoteNode(topLevelElement) && shouldSelectPreviousDecorator) {
1266
+ const prev2 = topLevelElement.getPreviousSibling();
1267
+ if (prev2 && $isDecoratorNode(prev2) && prev2.isKeyboardSelectable()) {
1268
+ event?.preventDefault();
1269
+ selectDecoratorNode(prev2, "end");
1270
+ return true;
1271
+ }
1272
+ }
1273
+ }
1274
+ if (!$isNodeSelection(selection)) return false;
1275
+ const nodes = selection.getNodes();
1276
+ if (nodes.length !== 1) return false;
1277
+ const node = nodes[0];
1278
+ if (!$isDecoratorNode(node)) return false;
1279
+ const prev = node.getPreviousSibling();
1280
+ if (prev && $isElementNode(prev)) {
1281
+ event?.preventDefault();
1282
+ prev.selectEnd();
1283
+ return true;
1284
+ }
1285
+ if (prev && $isDecoratorNode(prev)) {
1286
+ event?.preventDefault();
1287
+ selectDecoratorNode(prev, "end");
1288
+ return true;
1289
+ }
1290
+ return false;
1291
+ },
1292
+ COMMAND_PRIORITY_CRITICAL
1293
+ );
1294
+ const unregisterArrowDownDecorator = editor.registerCommand(
1295
+ KEY_ARROW_DOWN_COMMAND,
1296
+ (event) => {
1297
+ const selection = $getSelection();
1298
+ if (!$isNodeSelection(selection)) return false;
1299
+ const nodes = selection.getNodes();
1300
+ if (nodes.length !== 1) return false;
1301
+ const node = nodes[0];
1302
+ if (!$isDecoratorNode(node)) return false;
1303
+ const next = node.getNextSibling();
1304
+ if (next && $isElementNode(next)) {
1305
+ event?.preventDefault();
1306
+ next.selectStart();
1307
+ return true;
1308
+ }
1309
+ if (next && $isDecoratorNode(next)) {
1310
+ event?.preventDefault();
1311
+ selectDecoratorNode(next, "start");
1312
+ return true;
1313
+ }
1314
+ return false;
1315
+ },
1316
+ COMMAND_PRIORITY_CRITICAL
1317
+ );
1318
+ return () => {
1319
+ unregisterArrowDown();
1320
+ unregisterCmdEnter();
1321
+ unregisterBackspace();
1322
+ unregisterDelete();
1323
+ unregisterArrowUpDecorator();
1324
+ unregisterArrowDownDecorator();
1325
+ };
1326
+ }, [editor]);
1327
+ return null;
1328
+ }
1329
+ const blockIdState = createState("blockId", {
1330
+ parse: (v) => typeof v === "string" ? v : ""
1331
+ });
1332
+ const NORMALIZATION_TAG = "block-id-normalization";
1333
+ function BlockIdPlugin() {
1334
+ const [editor] = useLexicalComposerContext();
1335
+ useEffect(() => {
1336
+ return editor.registerUpdateListener(({ tags }) => {
1337
+ if (tags.has(NORMALIZATION_TAG)) return;
1338
+ editor.read(() => {
1339
+ const children = $getRoot().getChildren();
1340
+ let needsUpdate = false;
1341
+ const seen = /* @__PURE__ */ new Set();
1342
+ for (const child of children) {
1343
+ const id = $getState(child, blockIdState);
1344
+ if (id === "" || seen.has(id)) {
1345
+ needsUpdate = true;
1346
+ break;
1347
+ }
1348
+ seen.add(id);
1349
+ }
1350
+ if (!needsUpdate) return;
1351
+ editor.update(
1352
+ () => {
1353
+ $addUpdateTag("history-merge");
1354
+ const children2 = $getRoot().getChildren();
1355
+ const seen2 = /* @__PURE__ */ new Set();
1356
+ for (const child of children2) {
1357
+ let id = $getState(child, blockIdState);
1358
+ if (id === "" || seen2.has(id)) {
1359
+ id = crypto.randomUUID();
1360
+ $setState(child, blockIdState, id);
1361
+ }
1362
+ seen2.add(id);
1363
+ }
1364
+ },
1365
+ { tag: NORMALIZATION_TAG }
1366
+ );
1367
+ });
1368
+ });
1369
+ }, [editor]);
1370
+ return null;
1371
+ }
874
1372
  function EditorRefPlugin({ onEditorReady }) {
875
1373
  const [editor] = useLexicalComposerContext();
876
1374
  const callbackRef = useRef(onEditorReady);
@@ -1561,6 +2059,34 @@ const GIT_ALERT_TRANSFORMER = {
1561
2059
  parentNode.replace(alertNode);
1562
2060
  }
1563
2061
  };
2062
+ function findCodeBlockKlass(nodes) {
2063
+ return nodes.find((n) => n.getType?.() === "code-block") || CodeBlockNode;
2064
+ }
2065
+ const CODE_BLOCK_MULTILINE_TRANSFORMER = {
2066
+ dependencies: [],
2067
+ regExpEnd: {
2068
+ optional: true,
2069
+ regExp: /[ \t]*```$/
2070
+ },
2071
+ regExpStart: /^[ \t]*```([\w-]+)?/,
2072
+ replace: (rootNode, _children, startMatch, _endMatch, linesInBetween) => {
2073
+ const lang = startMatch[1] || "";
2074
+ let code = "";
2075
+ if (linesInBetween) {
2076
+ const lines = [...linesInBetween];
2077
+ while (lines.length > 0 && !lines[0].length) lines.shift();
2078
+ while (lines.length > 0 && !lines.at(-1).length) lines.pop();
2079
+ code = lines.join("\n");
2080
+ }
2081
+ const Klass = findCodeBlockKlass(getResolvedEditNodes());
2082
+ const node = new Klass(code, lang);
2083
+ rootNode.replace(node);
2084
+ const selection = $createNodeSelection();
2085
+ selection.add(node.getKey());
2086
+ $setSelection(selection);
2087
+ },
2088
+ type: "multiline-element"
2089
+ };
1564
2090
  const BANNER_TYPE_MAP = {
1565
2091
  note: "note",
1566
2092
  info: "note",
@@ -1683,6 +2209,39 @@ const MENTION_TRANSFORMER = {
1683
2209
  textNode.replace(mentionNode);
1684
2210
  }
1685
2211
  };
2212
+ const QUOTE_TRANSFORMER = {
2213
+ dependencies: [QuoteNode],
2214
+ export: (node, exportChildren) => {
2215
+ if (!$isQuoteNode(node)) {
2216
+ return null;
2217
+ }
2218
+ const lines = exportChildren(node).split("\n");
2219
+ return lines.map((line) => `> ${line}`).join("\n");
2220
+ },
2221
+ regExp: /^>\s/,
2222
+ replace: (parentNode, children, _match, isImport) => {
2223
+ if (isImport) {
2224
+ const previousNode = parentNode.getPreviousSibling();
2225
+ if ($isQuoteNode(previousNode)) {
2226
+ previousNode.splice(previousNode.getChildrenSize(), 0, [
2227
+ $createLineBreakNode(),
2228
+ ...children
2229
+ ]);
2230
+ parentNode.remove();
2231
+ return;
2232
+ }
2233
+ }
2234
+ const node = $createQuoteNode();
2235
+ const paragraph = $createParagraphNode();
2236
+ paragraph.append(...children);
2237
+ node.append(paragraph);
2238
+ parentNode.replace(node);
2239
+ if (!isImport) {
2240
+ paragraph.select(0, 0);
2241
+ }
2242
+ },
2243
+ type: "element"
2244
+ };
1686
2245
  function quoteAttr(value) {
1687
2246
  return value.replaceAll('"', '\\"');
1688
2247
  }
@@ -1730,12 +2289,14 @@ const ALL_TRANSFORMERS = [
1730
2289
  IMAGE_BLOCK_TRANSFORMER,
1731
2290
  VIDEO_BLOCK_TRANSFORMER,
1732
2291
  CODE_BLOCK_NODE_TRANSFORMER,
2292
+ CODE_BLOCK_MULTILINE_TRANSFORMER,
1733
2293
  LINK_CARD_BLOCK_TRANSFORMER,
1734
2294
  MERMAID_BLOCK_TRANSFORMER,
1735
2295
  GRID_CONTAINER_BLOCK_TRANSFORMER,
1736
2296
  HORIZONTAL_RULE_BLOCK_TRANSFORMER,
1737
2297
  TABLE_BLOCK_TRANSFORMER,
1738
- ...TRANSFORMERS
2298
+ QUOTE_TRANSFORMER,
2299
+ ...TRANSFORMERS.filter((t) => t !== QUOTE && t !== CODE)
1739
2300
  ];
1740
2301
  function MarkdownShortcutsPlugin() {
1741
2302
  return /* @__PURE__ */ jsx(MarkdownShortcutPlugin, { transformers: ALL_TRANSFORMERS });
@@ -1914,7 +2475,9 @@ function RichEditor({
1914
2475
  /* @__PURE__ */ jsx(MermaidPlugin, {}),
1915
2476
  /* @__PURE__ */ jsx(HorizontalRulePlugin, {}),
1916
2477
  /* @__PURE__ */ jsx(CheckListPlugin, {}),
2478
+ /* @__PURE__ */ jsx(BlockExitPlugin, {}),
1917
2479
  /* @__PURE__ */ jsx(AutoLinkPlugin, {}),
2480
+ /* @__PURE__ */ jsx(BlockIdPlugin, {}),
1918
2481
  /* @__PURE__ */ jsx(EditorRefPlugin, { onEditorReady }),
1919
2482
  autoFocus && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
1920
2483
  children,
@@ -1932,14 +2495,15 @@ export {
1932
2495
  NESTED_EDITOR_NODES as N,
1933
2496
  RichEditor as R,
1934
2497
  allEditNodes as a,
1935
- FootnoteSectionEditNode as b,
2498
+ blockIdState as b,
1936
2499
  customEditNodes as c,
1937
- INSERT_IMAGE_COMMAND as d,
1938
- INSERT_KATEX_BLOCK_COMMAND as e,
1939
- INSERT_KATEX_INLINE_COMMAND as f,
2500
+ FootnoteSectionEditNode as d,
2501
+ INSERT_IMAGE_COMMAND as e,
2502
+ INSERT_KATEX_BLOCK_COMMAND as f,
1940
2503
  getResolvedEditNodes as g,
1941
- INSERT_MERMAID_COMMAND as h,
1942
- defaultImageUpload as i,
2504
+ INSERT_KATEX_INLINE_COMMAND as h,
2505
+ INSERT_MERMAID_COMMAND as i,
2506
+ defaultImageUpload as j,
1943
2507
  setResolvedEditNodes as s,
1944
2508
  useImageUpload as u
1945
2509
  };