@haklex/rich-editor 0.0.65 → 0.0.67
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/README.md +107 -300
- package/dist/AlertQuoteEditNode-AgVEMFEi.js +292 -0
- package/dist/KaTeXRenderer-C8jv_5xr.js +214 -0
- package/dist/LinkCardRenderer-QmkOlyXb.js +36 -0
- package/dist/{LinkFavicon-DpURZdO_.js → LinkFavicon-B9SOaYCR.js} +5 -24
- package/dist/MermaidPlugin-CIQbyjXF.js +97 -0
- package/dist/RubyRenderer-DbeobSoH.js +13 -0
- package/dist/SubmitShortcutPlugin-BRK63XHl.js +1511 -0
- package/dist/commands-entry.d.ts +9 -0
- package/dist/commands-entry.d.ts.map +1 -0
- package/dist/commands-entry.mjs +25 -0
- package/dist/components/ContentEditable.d.ts +2 -2
- package/dist/components/ContentEditable.d.ts.map +1 -1
- package/dist/components/CorePlugins.d.ts +2 -0
- package/dist/components/CorePlugins.d.ts.map +1 -0
- package/dist/components/LinkFavicon.d.ts.map +1 -1
- package/dist/components/RichEditor.d.ts +1 -1
- package/dist/components/RichEditor.d.ts.map +1 -1
- package/dist/components/RichEditorShell.d.ts +26 -0
- package/dist/components/RichEditorShell.d.ts.map +1 -0
- package/dist/components/decorators/AlertEditDecorator.d.ts +2 -2
- package/dist/components/decorators/AlertEditDecorator.d.ts.map +1 -1
- package/dist/components/decorators/BannerEditDecorator.d.ts +1 -1
- package/dist/components/decorators/BannerEditDecorator.d.ts.map +1 -1
- package/dist/components/decorators/CodeBlockEditDecorator.d.ts +2 -2
- package/dist/components/decorators/CodeBlockEditDecorator.d.ts.map +1 -1
- package/dist/components/decorators/GridEditDecorator.d.ts +2 -2
- package/dist/components/decorators/GridEditDecorator.d.ts.map +1 -1
- package/dist/components/renderers/AlertRenderer.d.ts +1 -1
- package/dist/components/renderers/AlertRenderer.d.ts.map +1 -1
- package/dist/components/renderers/AlertStaticDecorator.d.ts +1 -1
- package/dist/components/renderers/AlertStaticDecorator.d.ts.map +1 -1
- package/dist/components/renderers/BannerRenderer.d.ts +1 -1
- package/dist/components/renderers/BannerRenderer.d.ts.map +1 -1
- package/dist/components/renderers/BannerStaticDecorator.d.ts +1 -1
- package/dist/components/renderers/BannerStaticDecorator.d.ts.map +1 -1
- package/dist/components/renderers/CodeBlockRenderer.d.ts +5 -5
- package/dist/components/renderers/CodeBlockRenderer.d.ts.map +1 -1
- package/dist/components/renderers/FootnoteRenderer.d.ts.map +1 -1
- package/dist/components/renderers/FootnoteSectionEditRenderer.d.ts +1 -1
- package/dist/components/renderers/FootnoteSectionEditRenderer.d.ts.map +1 -1
- package/dist/components/renderers/FootnoteSectionRenderer.d.ts +1 -1
- package/dist/components/renderers/FootnoteSectionRenderer.d.ts.map +1 -1
- package/dist/components/renderers/FootnoteStaticRenderer.d.ts +1 -1
- package/dist/components/renderers/FootnoteStaticRenderer.d.ts.map +1 -1
- package/dist/components/renderers/GridStaticDecorator.d.ts +2 -2
- package/dist/components/renderers/GridStaticDecorator.d.ts.map +1 -1
- package/dist/components/renderers/ImageRenderer.d.ts +4 -4
- package/dist/components/renderers/ImageRenderer.d.ts.map +1 -1
- package/dist/components/renderers/KaTeXRenderer.d.ts +1 -1
- package/dist/components/renderers/KaTeXRenderer.d.ts.map +1 -1
- package/dist/components/renderers/LinkCardRenderer.d.ts +4 -4
- package/dist/components/renderers/LinkCardRenderer.d.ts.map +1 -1
- package/dist/components/renderers/MentionRenderer.d.ts +2 -2
- package/dist/components/renderers/MentionRenderer.d.ts.map +1 -1
- package/dist/components/renderers/RubyRenderer.d.ts +1 -1
- package/dist/components/renderers/RubyRenderer.d.ts.map +1 -1
- package/dist/components/renderers/VideoRenderer.d.ts +3 -3
- package/dist/components/renderers/VideoRenderer.d.ts.map +1 -1
- package/dist/config-QGdXDSW9.js +1235 -0
- package/dist/context/ImageUploadContext.d.ts.map +1 -1
- package/dist/context/RendererConfigContext.d.ts +1 -1
- package/dist/context/RendererConfigContext.d.ts.map +1 -1
- package/dist/favicon-BQgbXF_a.js +43 -0
- package/dist/index.d.ts +10 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +151 -161
- package/dist/node-registry-CSRSWJ0q.js +765 -0
- package/dist/nodes/CodeBlockEditNode.d.ts +6 -6
- package/dist/nodes/DetailsNode.d.ts.map +1 -1
- package/dist/nodes/GridContainerNode.d.ts.map +1 -1
- package/dist/nodes/GridEditNode.d.ts.map +1 -1
- package/dist/nodes/ImageNode.d.ts +4 -4
- package/dist/nodes/ImageNode.d.ts.map +1 -1
- package/dist/nodes/LinkCardNode.d.ts +4 -4
- package/dist/nodes/LinkCardNode.d.ts.map +1 -1
- package/dist/nodes/VideoNode.d.ts +2 -2
- package/dist/nodes/VideoNode.d.ts.map +1 -1
- package/dist/nodes-entry.d.ts +28 -0
- package/dist/nodes-entry.d.ts.map +1 -0
- package/dist/nodes-entry.mjs +44 -0
- package/dist/plugins/AutoLinkPlugin.d.ts.map +1 -1
- package/dist/plugins/BlockIdPlugin.d.ts.map +1 -1
- package/dist/plugins/ImageUploadPlugin.d.ts +2 -2
- package/dist/plugins/ImageUploadPlugin.d.ts.map +1 -1
- package/dist/plugins/MarkdownPastePlugin.d.ts.map +1 -1
- package/dist/plugins/OnChangePlugin.d.ts +1 -1
- package/dist/plugins/OnChangePlugin.d.ts.map +1 -1
- package/dist/plugins/image-upload-command.d.ts.map +1 -1
- package/dist/plugins/image-upload.css.d.ts +1 -4
- package/dist/plugins/image-upload.css.d.ts.map +1 -1
- package/dist/plugins-entry.d.ts +22 -0
- package/dist/plugins-entry.d.ts.map +1 -0
- package/dist/plugins-entry.mjs +27 -0
- package/dist/renderers-entry.d.ts +23 -0
- package/dist/renderers-entry.d.ts.map +1 -0
- package/dist/renderers-entry.mjs +60 -0
- package/dist/rich-editor.css +1 -1
- package/dist/{utils-fpeaZV1R.js → shared.css-DNuMYx6Q.js} +4 -1
- package/dist/static-entry.mjs +23 -21
- package/dist/styles/article.css.d.ts.map +1 -1
- package/dist/styles-entry.mjs +1 -2
- package/dist/theme-CRza9nbF.js +924 -0
- package/dist/transformers/code-block.d.ts.map +1 -1
- package/dist/transformers/footnote.d.ts.map +1 -1
- package/dist/transformers/grid-container.d.ts.map +1 -1
- package/dist/types/renderer-config.d.ts +6 -6
- package/dist/types/renderer-config.d.ts.map +1 -1
- package/dist/types/slash-menu.d.ts +6 -6
- package/dist/types/slash-menu.d.ts.map +1 -1
- package/dist/types.d.ts +18 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/comment-anchor.d.ts +8 -8
- package/dist/utils/comment-anchor.d.ts.map +1 -1
- package/package.json +50 -33
- package/dist/RichEditor-EI3rR1-9.js +0 -2789
- package/dist/components/decorators/nested-doc-dialog.css.d.ts +0 -28
- package/dist/components/decorators/nested-doc-dialog.css.d.ts.map +0 -1
- package/dist/editor.d.ts +0 -6
- package/dist/editor.d.ts.map +0 -1
- package/dist/editor.mjs +0 -12
- package/dist/favicon-C6Esc0tp.js +0 -2470
- package/dist/shared.css-BqX4HjVE.js +0 -5
|
@@ -1,2789 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
-
import { PortalThemeProvider } from "@haklex/rich-style-token";
|
|
6
|
-
import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
|
|
7
|
-
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
8
|
-
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
9
|
-
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
10
|
-
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
11
|
-
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
12
|
-
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
13
|
-
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
|
|
14
|
-
import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
|
|
15
|
-
import { a3 as $isAlertQuoteNode, d as RendererWrapper, a4 as AlertRenderer, a5 as SpoilerNode, Y as MentionNode, a6 as FootnoteNode, U as KaTeXInlineNode, a7 as AlertQuoteNode, f as editorTheme, a8 as $isBannerNode, a9 as BannerRenderer, aa as BannerNode, ab as normalizeBannerType, ac as $isCodeBlockNode, ad as CodeBlockRenderer, ae as CodeBlockNode, i as useFootnoteDefinitions, O as FootnoteSectionNode, z as $isGridContainerNode, Q as GridContainerNode, b as builtinNodes, T as KaTeXBlockNode, S as ImageNode, af as VideoNode, W as LinkCardNode, ag as DetailsNode, Z as MermaidNode, a0 as RubyNode, F as FootnoteDefinitionsProvider, s as $createImageNode, a1 as computeImageMeta, _ as OPEN_IMAGE_UPLOAD_DIALOG_COMMAND, ah as $createKaTeXInlineNode, ai as $createKaTeXBlockNode, p as getHostname, q as probeFavicon, aj as $createDetailsNode, ak as $createFootnoteNode, y as $isFootnoteSectionNode, $ as $createFootnoteSectionNode, v as $createMentionNode, g as extractTextContent, x as $createRubyNode, al as $createSpoilerNode, w as $createMermaidNode, C as ColorSchemeProvider, R as RendererConfigProvider } from "./favicon-C6Esc0tp.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, COMMAND_PRIORITY_LOW, PASTE_COMMAND, $createTextNode, $createLineBreakNode } from "lexical";
|
|
17
|
-
import { Info, Lightbulb, TriangleAlert, Flag, LayoutGrid, Plus, Minus, Check, Upload, Link2 } from "lucide-react";
|
|
18
|
-
import { useCallback, useEffect, createElement, useState, createContext, use, useRef, useMemo } from "react";
|
|
19
|
-
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
20
|
-
import { ContentEditable as ContentEditable$1 } from "@lexical/react/LexicalContentEditable";
|
|
21
|
-
import { LexicalNestedComposer } from "@lexical/react/LexicalNestedComposer";
|
|
22
|
-
import { CodeNode } from "@lexical/code";
|
|
23
|
-
import { HorizontalRuleNode, $createHorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/extension";
|
|
24
|
-
import { LinkNode, AutoLinkNode, createLinkMatcherWithRegExp, registerAutoLink } from "@lexical/link";
|
|
25
|
-
import { ListNode, ListItemNode } from "@lexical/list";
|
|
26
|
-
import { HeadingNode, QuoteNode, $isQuoteNode, DRAG_DROP_PASTE, $createQuoteNode } from "@lexical/rich-text";
|
|
27
|
-
import { TableNode, TableCellNode, TableRowNode } from "@lexical/table";
|
|
28
|
-
import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
|
|
29
|
-
import { nanoid } from "nanoid";
|
|
30
|
-
import { HORIZONTAL_RULE_BLOCK_TRANSFORMER as HORIZONTAL_RULE_BLOCK_TRANSFORMER$1, 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, RUBY_TRANSFORMER as RUBY_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, TABLE_BLOCK_TRANSFORMER } from "@haklex/rich-headless/transformers";
|
|
31
|
-
import { Dialog, DialogPopup, DialogTitle, SegmentedControl } from "@haklex/rich-editor-ui";
|
|
32
|
-
import { b as clsx, g as getVariantClass } from "./utils-fpeaZV1R.js";
|
|
33
|
-
import { CHECK_LIST, TRANSFORMERS, QUOTE, CODE, $convertFromMarkdownString } from "@lexical/markdown";
|
|
34
|
-
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
|
35
|
-
function ExitBlockPlugin({
|
|
36
|
-
parentEditor,
|
|
37
|
-
nodeKey
|
|
38
|
-
}) {
|
|
39
|
-
const [editor] = useLexicalComposerContext();
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
const focusParent = () => {
|
|
42
|
-
parentEditor.focus(() => {
|
|
43
|
-
parentEditor.update(() => {
|
|
44
|
-
const alertNode = $getNodeByKey(nodeKey);
|
|
45
|
-
if (alertNode) {
|
|
46
|
-
const next = alertNode.getNextSibling();
|
|
47
|
-
if (next) {
|
|
48
|
-
next.selectStart();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
const exitAlert = (removeEmpty) => {
|
|
55
|
-
const root = $getRoot();
|
|
56
|
-
const lastChild = root.getLastChild();
|
|
57
|
-
const shouldRemoveAlert = removeEmpty && root.getChildrenSize() <= 1;
|
|
58
|
-
if (removeEmpty && lastChild) {
|
|
59
|
-
lastChild.remove();
|
|
60
|
-
}
|
|
61
|
-
if (shouldRemoveAlert) {
|
|
62
|
-
parentEditor.update(
|
|
63
|
-
() => {
|
|
64
|
-
const alertNode = $getNodeByKey(nodeKey);
|
|
65
|
-
if (alertNode) {
|
|
66
|
-
const paragraph = $createParagraphNode();
|
|
67
|
-
alertNode.insertAfter(paragraph);
|
|
68
|
-
alertNode.remove();
|
|
69
|
-
paragraph.selectStart();
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
{ onUpdate: focusParent }
|
|
73
|
-
);
|
|
74
|
-
} else {
|
|
75
|
-
parentEditor.update(
|
|
76
|
-
() => {
|
|
77
|
-
const alertNode = $getNodeByKey(nodeKey);
|
|
78
|
-
if (alertNode) {
|
|
79
|
-
let next = alertNode.getNextSibling();
|
|
80
|
-
if (!next || !$isParagraphNode(next)) {
|
|
81
|
-
next = $createParagraphNode();
|
|
82
|
-
alertNode.insertAfter(next);
|
|
83
|
-
}
|
|
84
|
-
next.selectStart();
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
{ onUpdate: focusParent }
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
const isAtLastEmptyParagraph = () => {
|
|
92
|
-
const selection = $getSelection();
|
|
93
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
94
|
-
return false;
|
|
95
|
-
const anchorNode = selection.anchor.getNode();
|
|
96
|
-
const topLevelElement = anchorNode.getTopLevelElement();
|
|
97
|
-
return topLevelElement && $isParagraphNode(topLevelElement) && topLevelElement.getTextContent() === "" && topLevelElement.getNextSibling() === null;
|
|
98
|
-
};
|
|
99
|
-
const unregisterEnter = editor.registerCommand(
|
|
100
|
-
KEY_ENTER_COMMAND,
|
|
101
|
-
(event) => {
|
|
102
|
-
if (event?.metaKey || event?.ctrlKey) {
|
|
103
|
-
event.preventDefault();
|
|
104
|
-
exitAlert(false);
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
if (!isAtLastEmptyParagraph()) return false;
|
|
108
|
-
event?.preventDefault();
|
|
109
|
-
exitAlert(true);
|
|
110
|
-
return true;
|
|
111
|
-
},
|
|
112
|
-
COMMAND_PRIORITY_CRITICAL
|
|
113
|
-
);
|
|
114
|
-
const unregisterArrowDown = editor.registerCommand(
|
|
115
|
-
KEY_ARROW_DOWN_COMMAND,
|
|
116
|
-
(event) => {
|
|
117
|
-
if (!isAtLastEmptyParagraph()) return false;
|
|
118
|
-
event?.preventDefault();
|
|
119
|
-
exitAlert(false);
|
|
120
|
-
return true;
|
|
121
|
-
},
|
|
122
|
-
COMMAND_PRIORITY_HIGH
|
|
123
|
-
);
|
|
124
|
-
return () => {
|
|
125
|
-
unregisterEnter();
|
|
126
|
-
unregisterArrowDown();
|
|
127
|
-
};
|
|
128
|
-
}, [editor, parentEditor, nodeKey]);
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
function AlertEditDecorator({
|
|
132
|
-
nodeKey,
|
|
133
|
-
alertType,
|
|
134
|
-
contentEditor
|
|
135
|
-
}) {
|
|
136
|
-
const [editor] = useLexicalComposerContext();
|
|
137
|
-
const editable = editor.isEditable();
|
|
138
|
-
const handleTypeChange = useCallback(
|
|
139
|
-
(newType) => {
|
|
140
|
-
editor.update(() => {
|
|
141
|
-
const node = $getNodeByKey(nodeKey);
|
|
142
|
-
if ($isAlertQuoteNode(node)) {
|
|
143
|
-
node.setAlertType(newType);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
},
|
|
147
|
-
[editor, nodeKey]
|
|
148
|
-
);
|
|
149
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
-
/* @__PURE__ */ jsx(
|
|
151
|
-
RendererWrapper,
|
|
152
|
-
{
|
|
153
|
-
rendererKey: "Alert",
|
|
154
|
-
defaultRenderer: AlertRenderer,
|
|
155
|
-
props: {
|
|
156
|
-
type: alertType,
|
|
157
|
-
editable,
|
|
158
|
-
onTypeChange: editable ? handleTypeChange : void 0
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
),
|
|
162
|
-
/* @__PURE__ */ jsx("div", { className: "rich-alert-content", children: /* @__PURE__ */ jsxs(LexicalNestedComposer, { initialEditor: contentEditor, children: [
|
|
163
|
-
/* @__PURE__ */ jsx(
|
|
164
|
-
RichTextPlugin,
|
|
165
|
-
{
|
|
166
|
-
contentEditable: /* @__PURE__ */ jsx(
|
|
167
|
-
ContentEditable$1,
|
|
168
|
-
{
|
|
169
|
-
className: "rich-alert-content-editable",
|
|
170
|
-
style: { outline: "none" },
|
|
171
|
-
"aria-placeholder": "",
|
|
172
|
-
placeholder: /* @__PURE__ */ jsx("span", { style: { display: "none" } })
|
|
173
|
-
}
|
|
174
|
-
),
|
|
175
|
-
ErrorBoundary: LexicalErrorBoundary
|
|
176
|
-
}
|
|
177
|
-
),
|
|
178
|
-
/* @__PURE__ */ jsx(ListPlugin, {}),
|
|
179
|
-
/* @__PURE__ */ jsx(LinkPlugin, {}),
|
|
180
|
-
/* @__PURE__ */ jsx(ExitBlockPlugin, { parentEditor: editor, nodeKey })
|
|
181
|
-
] }) })
|
|
182
|
-
] });
|
|
183
|
-
}
|
|
184
|
-
const NESTED_EDITOR_NODES = [
|
|
185
|
-
HeadingNode,
|
|
186
|
-
QuoteNode,
|
|
187
|
-
ListNode,
|
|
188
|
-
ListItemNode,
|
|
189
|
-
LinkNode,
|
|
190
|
-
AutoLinkNode,
|
|
191
|
-
HorizontalRuleNode,
|
|
192
|
-
CodeNode,
|
|
193
|
-
TableNode,
|
|
194
|
-
TableCellNode,
|
|
195
|
-
TableRowNode,
|
|
196
|
-
SpoilerNode,
|
|
197
|
-
MentionNode,
|
|
198
|
-
FootnoteNode,
|
|
199
|
-
KaTeXInlineNode
|
|
200
|
-
];
|
|
201
|
-
function createContentEditor$1() {
|
|
202
|
-
return createEditor({
|
|
203
|
-
namespace: "AlertContent",
|
|
204
|
-
nodes: NESTED_EDITOR_NODES,
|
|
205
|
-
theme: editorTheme,
|
|
206
|
-
onError: (error) => {
|
|
207
|
-
console.error("[AlertContent]", error);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
const _AlertQuoteEditNode = class _AlertQuoteEditNode extends AlertQuoteNode {
|
|
212
|
-
constructor(alertType, contentState, key) {
|
|
213
|
-
super(alertType, contentState, key);
|
|
214
|
-
__publicField(this, "__contentEditor");
|
|
215
|
-
this.__contentEditor = createContentEditor$1();
|
|
216
|
-
if (contentState) {
|
|
217
|
-
const editorState = this.__contentEditor.parseEditorState(contentState);
|
|
218
|
-
this.__contentEditor.setEditorState(editorState);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
static clone(node) {
|
|
222
|
-
const cloned = new _AlertQuoteEditNode(
|
|
223
|
-
node.__alertType,
|
|
224
|
-
node.__contentState,
|
|
225
|
-
node.__key
|
|
226
|
-
);
|
|
227
|
-
cloned.__contentEditor = node.__contentEditor;
|
|
228
|
-
return cloned;
|
|
229
|
-
}
|
|
230
|
-
getContentEditor() {
|
|
231
|
-
return this.__contentEditor;
|
|
232
|
-
}
|
|
233
|
-
static importJSON(serializedNode) {
|
|
234
|
-
const node = new _AlertQuoteEditNode(
|
|
235
|
-
serializedNode.alertType,
|
|
236
|
-
serializedNode.content
|
|
237
|
-
);
|
|
238
|
-
return node;
|
|
239
|
-
}
|
|
240
|
-
exportJSON() {
|
|
241
|
-
return {
|
|
242
|
-
...super.exportJSON(),
|
|
243
|
-
type: "alert-quote",
|
|
244
|
-
alertType: this.__alertType,
|
|
245
|
-
content: this.__contentEditor.getEditorState().toJSON(),
|
|
246
|
-
version: 1
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
decorate(_editor, _config) {
|
|
250
|
-
return createElement(AlertEditDecorator, {
|
|
251
|
-
nodeKey: this.__key,
|
|
252
|
-
alertType: this.__alertType,
|
|
253
|
-
contentEditor: this.__contentEditor
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
__publicField(_AlertQuoteEditNode, "commandItems", [
|
|
258
|
-
{
|
|
259
|
-
title: "Callout",
|
|
260
|
-
icon: createElement(Info, { size: 20 }),
|
|
261
|
-
description: "Info callout block",
|
|
262
|
-
keywords: ["alert", "note", "info", "callout"],
|
|
263
|
-
section: "ADVANCED",
|
|
264
|
-
placement: ["slash", "toolbar"],
|
|
265
|
-
group: "insert",
|
|
266
|
-
onSelect: (editor) => {
|
|
267
|
-
editor.update(() => {
|
|
268
|
-
$insertNodes([$createAlertQuoteEditNode("note")]);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
title: "Tip",
|
|
274
|
-
icon: createElement(Lightbulb, { size: 20 }),
|
|
275
|
-
description: "Highlight a useful tip",
|
|
276
|
-
keywords: ["alert", "tip", "hint"],
|
|
277
|
-
section: "ADVANCED",
|
|
278
|
-
placement: ["slash", "toolbar"],
|
|
279
|
-
group: "insert",
|
|
280
|
-
onSelect: (editor) => {
|
|
281
|
-
editor.update(() => {
|
|
282
|
-
$insertNodes([$createAlertQuoteEditNode("tip")]);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
title: "Warning",
|
|
288
|
-
icon: createElement(TriangleAlert, { size: 20 }),
|
|
289
|
-
description: "Warn about something",
|
|
290
|
-
keywords: ["alert", "warning", "caution"],
|
|
291
|
-
section: "ADVANCED",
|
|
292
|
-
placement: ["slash", "toolbar"],
|
|
293
|
-
group: "insert",
|
|
294
|
-
onSelect: (editor) => {
|
|
295
|
-
editor.update(() => {
|
|
296
|
-
$insertNodes([$createAlertQuoteEditNode("warning")]);
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
]);
|
|
301
|
-
let AlertQuoteEditNode = _AlertQuoteEditNode;
|
|
302
|
-
function $createAlertQuoteEditNode(alertType, contentState) {
|
|
303
|
-
return new AlertQuoteEditNode(alertType, contentState);
|
|
304
|
-
}
|
|
305
|
-
function BannerEditDecorator({
|
|
306
|
-
nodeKey,
|
|
307
|
-
bannerType,
|
|
308
|
-
contentEditor
|
|
309
|
-
}) {
|
|
310
|
-
const [editor] = useLexicalComposerContext();
|
|
311
|
-
const editable = editor.isEditable();
|
|
312
|
-
const handleTypeChange = useCallback(
|
|
313
|
-
(newType) => {
|
|
314
|
-
editor.update(() => {
|
|
315
|
-
const node = $getNodeByKey(nodeKey);
|
|
316
|
-
if ($isBannerNode(node)) {
|
|
317
|
-
node.setBannerType(newType);
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
},
|
|
321
|
-
[editor, nodeKey]
|
|
322
|
-
);
|
|
323
|
-
return /* @__PURE__ */ jsxs("div", { className: "rich-banner-inner", children: [
|
|
324
|
-
/* @__PURE__ */ jsx(
|
|
325
|
-
RendererWrapper,
|
|
326
|
-
{
|
|
327
|
-
rendererKey: "Banner",
|
|
328
|
-
defaultRenderer: BannerRenderer,
|
|
329
|
-
props: {
|
|
330
|
-
type: bannerType,
|
|
331
|
-
editable,
|
|
332
|
-
onTypeChange: editable ? handleTypeChange : void 0
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
),
|
|
336
|
-
/* @__PURE__ */ jsx("div", { className: "rich-banner-content", children: /* @__PURE__ */ jsxs(LexicalNestedComposer, { initialEditor: contentEditor, children: [
|
|
337
|
-
/* @__PURE__ */ jsx(
|
|
338
|
-
RichTextPlugin,
|
|
339
|
-
{
|
|
340
|
-
contentEditable: /* @__PURE__ */ jsx(
|
|
341
|
-
ContentEditable$1,
|
|
342
|
-
{
|
|
343
|
-
className: "rich-banner-content-editable",
|
|
344
|
-
style: { outline: "none" },
|
|
345
|
-
"aria-placeholder": "",
|
|
346
|
-
placeholder: /* @__PURE__ */ jsx("span", { style: { display: "none" } })
|
|
347
|
-
}
|
|
348
|
-
),
|
|
349
|
-
ErrorBoundary: LexicalErrorBoundary
|
|
350
|
-
}
|
|
351
|
-
),
|
|
352
|
-
/* @__PURE__ */ jsx(ListPlugin, {}),
|
|
353
|
-
/* @__PURE__ */ jsx(LinkPlugin, {})
|
|
354
|
-
] }) })
|
|
355
|
-
] });
|
|
356
|
-
}
|
|
357
|
-
function createContentEditor() {
|
|
358
|
-
return createEditor({
|
|
359
|
-
namespace: "BannerContent",
|
|
360
|
-
nodes: NESTED_EDITOR_NODES,
|
|
361
|
-
theme: editorTheme,
|
|
362
|
-
onError: (error) => {
|
|
363
|
-
console.error("[BannerContent]", error);
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
const _BannerEditNode = class _BannerEditNode extends BannerNode {
|
|
368
|
-
constructor(bannerType, contentState, key) {
|
|
369
|
-
super(bannerType, contentState, key);
|
|
370
|
-
__publicField(this, "__contentEditor");
|
|
371
|
-
this.__contentEditor = createContentEditor();
|
|
372
|
-
if (contentState) {
|
|
373
|
-
const editorState = this.__contentEditor.parseEditorState(contentState);
|
|
374
|
-
this.__contentEditor.setEditorState(editorState);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
static clone(node) {
|
|
378
|
-
const cloned = new _BannerEditNode(
|
|
379
|
-
node.__bannerType,
|
|
380
|
-
node.__contentState,
|
|
381
|
-
node.__key
|
|
382
|
-
);
|
|
383
|
-
cloned.__contentEditor = node.__contentEditor;
|
|
384
|
-
return cloned;
|
|
385
|
-
}
|
|
386
|
-
getContentEditor() {
|
|
387
|
-
return this.__contentEditor;
|
|
388
|
-
}
|
|
389
|
-
static importJSON(serializedNode) {
|
|
390
|
-
const legacy = serializedNode;
|
|
391
|
-
const bannerType = normalizeBannerType(serializedNode.bannerType);
|
|
392
|
-
if (serializedNode.content) {
|
|
393
|
-
return new _BannerEditNode(bannerType, serializedNode.content);
|
|
394
|
-
}
|
|
395
|
-
if (legacy.children) {
|
|
396
|
-
const content2 = {
|
|
397
|
-
root: {
|
|
398
|
-
children: legacy.children,
|
|
399
|
-
direction: null,
|
|
400
|
-
format: "",
|
|
401
|
-
indent: 0,
|
|
402
|
-
type: "root",
|
|
403
|
-
version: 1
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
return new _BannerEditNode(bannerType, content2);
|
|
407
|
-
}
|
|
408
|
-
return new _BannerEditNode(bannerType);
|
|
409
|
-
}
|
|
410
|
-
exportJSON() {
|
|
411
|
-
return {
|
|
412
|
-
...super.exportJSON(),
|
|
413
|
-
type: "banner",
|
|
414
|
-
bannerType: this.__bannerType,
|
|
415
|
-
content: this.__contentEditor.getEditorState().toJSON(),
|
|
416
|
-
version: 1
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
decorate(_editor, _config) {
|
|
420
|
-
return createElement(BannerEditDecorator, {
|
|
421
|
-
nodeKey: this.__key,
|
|
422
|
-
bannerType: this.__bannerType,
|
|
423
|
-
contentEditor: this.__contentEditor
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
__publicField(_BannerEditNode, "commandItems", [
|
|
428
|
-
{
|
|
429
|
-
title: "Banner",
|
|
430
|
-
icon: createElement(Flag, { size: 20 }),
|
|
431
|
-
description: "Highlighted banner block",
|
|
432
|
-
keywords: ["banner", "notice", "announcement"],
|
|
433
|
-
section: "ADVANCED",
|
|
434
|
-
placement: ["slash", "toolbar"],
|
|
435
|
-
group: "insert",
|
|
436
|
-
onSelect: (editor) => {
|
|
437
|
-
editor.update(() => {
|
|
438
|
-
$insertNodes([$createBannerEditNode("note")]);
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
]);
|
|
443
|
-
let BannerEditNode = _BannerEditNode;
|
|
444
|
-
function $createBannerEditNode(bannerType, contentState) {
|
|
445
|
-
return new BannerEditNode(bannerType, contentState);
|
|
446
|
-
}
|
|
447
|
-
const codeBlockCursorIntentMap = /* @__PURE__ */ new Map();
|
|
448
|
-
function setCodeBlockCursorIntent(nodeKey, placement) {
|
|
449
|
-
codeBlockCursorIntentMap.set(nodeKey, placement);
|
|
450
|
-
}
|
|
451
|
-
function consumeCodeBlockCursorIntent(nodeKey) {
|
|
452
|
-
const placement = codeBlockCursorIntentMap.get(nodeKey);
|
|
453
|
-
if (!placement) return null;
|
|
454
|
-
codeBlockCursorIntentMap.delete(nodeKey);
|
|
455
|
-
return placement;
|
|
456
|
-
}
|
|
457
|
-
function CodeBlockEditDecorator({
|
|
458
|
-
nodeKey,
|
|
459
|
-
code,
|
|
460
|
-
language
|
|
461
|
-
}) {
|
|
462
|
-
const [editor] = useLexicalComposerContext();
|
|
463
|
-
const [isSelected] = useLexicalNodeSelection(nodeKey);
|
|
464
|
-
const [cursorPlacement, setCursorPlacement] = useState(
|
|
465
|
-
"start"
|
|
466
|
-
);
|
|
467
|
-
const editable = editor.isEditable();
|
|
468
|
-
useEffect(() => {
|
|
469
|
-
if (!editable || !isSelected) return;
|
|
470
|
-
setCursorPlacement(consumeCodeBlockCursorIntent(nodeKey) ?? "start");
|
|
471
|
-
}, [editable, isSelected, nodeKey]);
|
|
472
|
-
const handleCodeChange = useCallback(
|
|
473
|
-
(newCode) => {
|
|
474
|
-
editor.update(() => {
|
|
475
|
-
const node = $getNodeByKey(nodeKey);
|
|
476
|
-
if ($isCodeBlockNode(node)) {
|
|
477
|
-
node.setCode(newCode);
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
},
|
|
481
|
-
[editor, nodeKey]
|
|
482
|
-
);
|
|
483
|
-
const handleLanguageChange = useCallback(
|
|
484
|
-
(newLanguage) => {
|
|
485
|
-
editor.update(() => {
|
|
486
|
-
const node = $getNodeByKey(nodeKey);
|
|
487
|
-
if ($isCodeBlockNode(node)) {
|
|
488
|
-
node.setLanguage(newLanguage);
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
},
|
|
492
|
-
[editor, nodeKey]
|
|
493
|
-
);
|
|
494
|
-
const handleDelete = useCallback(() => {
|
|
495
|
-
editor.getRootElement()?.focus({ preventScroll: true });
|
|
496
|
-
editor.update(() => {
|
|
497
|
-
const node = $getNodeByKey(nodeKey);
|
|
498
|
-
if (!node) return;
|
|
499
|
-
const prev = node.getPreviousSibling();
|
|
500
|
-
const next = node.getNextSibling();
|
|
501
|
-
const parent = node.getParent();
|
|
502
|
-
node.remove();
|
|
503
|
-
if (prev) {
|
|
504
|
-
if ($isElementNode(prev)) {
|
|
505
|
-
prev.selectEnd();
|
|
506
|
-
} else {
|
|
507
|
-
if ($isDecoratorNode(prev) && prev.getType() === "code-block") {
|
|
508
|
-
setCodeBlockCursorIntent(prev.getKey(), "end");
|
|
509
|
-
}
|
|
510
|
-
const selection = $createNodeSelection();
|
|
511
|
-
selection.add(prev.getKey());
|
|
512
|
-
$setSelection(selection);
|
|
513
|
-
}
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
if (next) {
|
|
517
|
-
if ($isElementNode(next)) {
|
|
518
|
-
next.selectStart();
|
|
519
|
-
} else {
|
|
520
|
-
if ($isDecoratorNode(next) && next.getType() === "code-block") {
|
|
521
|
-
setCodeBlockCursorIntent(next.getKey(), "start");
|
|
522
|
-
}
|
|
523
|
-
const selection = $createNodeSelection();
|
|
524
|
-
selection.add(next.getKey());
|
|
525
|
-
$setSelection(selection);
|
|
526
|
-
}
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
if (parent) {
|
|
530
|
-
const p = $createParagraphNode();
|
|
531
|
-
parent.append(p);
|
|
532
|
-
p.selectStart();
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
}, [editor, nodeKey]);
|
|
536
|
-
const handleExitBlock = useCallback(
|
|
537
|
-
(direction) => {
|
|
538
|
-
editor.getRootElement()?.focus({ preventScroll: true });
|
|
539
|
-
editor.update(() => {
|
|
540
|
-
const node = $getNodeByKey(nodeKey);
|
|
541
|
-
if (!node) return;
|
|
542
|
-
if (direction === "before") {
|
|
543
|
-
const prev = node.getPreviousSibling();
|
|
544
|
-
if (!prev) return;
|
|
545
|
-
if ($isElementNode(prev)) {
|
|
546
|
-
prev.selectEnd();
|
|
547
|
-
} else {
|
|
548
|
-
if ($isDecoratorNode(prev) && prev.getType() === "code-block") {
|
|
549
|
-
setCodeBlockCursorIntent(prev.getKey(), "end");
|
|
550
|
-
}
|
|
551
|
-
const selection = $createNodeSelection();
|
|
552
|
-
selection.add(prev.getKey());
|
|
553
|
-
$setSelection(selection);
|
|
554
|
-
}
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
let next = node.getNextSibling();
|
|
558
|
-
if (!next) {
|
|
559
|
-
const p = $createParagraphNode();
|
|
560
|
-
node.insertAfter(p);
|
|
561
|
-
next = p;
|
|
562
|
-
}
|
|
563
|
-
if ($isElementNode(next)) {
|
|
564
|
-
next.selectStart();
|
|
565
|
-
} else {
|
|
566
|
-
if ($isDecoratorNode(next) && next.getType() === "code-block") {
|
|
567
|
-
setCodeBlockCursorIntent(next.getKey(), "start");
|
|
568
|
-
}
|
|
569
|
-
const selection = $createNodeSelection();
|
|
570
|
-
selection.add(next.getKey());
|
|
571
|
-
$setSelection(selection);
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
},
|
|
575
|
-
[editor, nodeKey]
|
|
576
|
-
);
|
|
577
|
-
return /* @__PURE__ */ jsx(
|
|
578
|
-
RendererWrapper,
|
|
579
|
-
{
|
|
580
|
-
rendererKey: "CodeBlock",
|
|
581
|
-
defaultRenderer: CodeBlockRenderer,
|
|
582
|
-
props: {
|
|
583
|
-
code,
|
|
584
|
-
language,
|
|
585
|
-
editable,
|
|
586
|
-
onCodeChange: editable ? handleCodeChange : void 0,
|
|
587
|
-
onLanguageChange: editable ? handleLanguageChange : void 0,
|
|
588
|
-
onDelete: editable ? handleDelete : void 0,
|
|
589
|
-
onExitBlock: editable ? handleExitBlock : void 0,
|
|
590
|
-
selected: editable ? isSelected : false,
|
|
591
|
-
cursorPlacement: editable ? cursorPlacement : "start"
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
const _CodeBlockEditNode = class _CodeBlockEditNode extends CodeBlockNode {
|
|
597
|
-
static clone(node) {
|
|
598
|
-
return new _CodeBlockEditNode(node.__code, node.__language, node.__key);
|
|
599
|
-
}
|
|
600
|
-
static importJSON(serializedNode) {
|
|
601
|
-
return new _CodeBlockEditNode(serializedNode.code, serializedNode.language);
|
|
602
|
-
}
|
|
603
|
-
decorate(_editor, _config) {
|
|
604
|
-
return createElement(CodeBlockEditDecorator, {
|
|
605
|
-
nodeKey: this.__key,
|
|
606
|
-
code: this.__code,
|
|
607
|
-
language: this.__language
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
__publicField(_CodeBlockEditNode, "commandItems", CodeBlockNode.commandItems.map((item) => ({
|
|
612
|
-
...item,
|
|
613
|
-
onSelect: (editor) => {
|
|
614
|
-
editor.update(() => {
|
|
615
|
-
$insertNodes([new _CodeBlockEditNode("", "text")]);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
})));
|
|
619
|
-
let CodeBlockEditNode = _CodeBlockEditNode;
|
|
620
|
-
function $createCodeBlockEditNode(code, language) {
|
|
621
|
-
return new CodeBlockEditNode(code, language);
|
|
622
|
-
}
|
|
623
|
-
function FootnoteSectionEditRenderer({
|
|
624
|
-
definitions
|
|
625
|
-
}) {
|
|
626
|
-
const [editor] = useLexicalComposerContext();
|
|
627
|
-
const { displayNumberMap } = useFootnoteDefinitions();
|
|
628
|
-
const sortedEntries = Object.entries(definitions).sort(
|
|
629
|
-
([a], [b]) => (displayNumberMap[a] ?? 0) - (displayNumberMap[b] ?? 0)
|
|
630
|
-
);
|
|
631
|
-
const handleContentChange = useCallback(
|
|
632
|
-
(identifier, newContent) => {
|
|
633
|
-
editor.update(() => {
|
|
634
|
-
const sectionNodes = $nodesOfType(FootnoteSectionNode);
|
|
635
|
-
if (sectionNodes.length > 0) {
|
|
636
|
-
sectionNodes[0].setDefinition(identifier, newContent);
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
},
|
|
640
|
-
[editor]
|
|
641
|
-
);
|
|
642
|
-
const handleRemove = useCallback(
|
|
643
|
-
(identifier) => {
|
|
644
|
-
editor.update(() => {
|
|
645
|
-
const sectionNodes = $nodesOfType(FootnoteSectionNode);
|
|
646
|
-
if (sectionNodes.length > 0) {
|
|
647
|
-
sectionNodes[0].removeDefinition(identifier);
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
},
|
|
651
|
-
[editor]
|
|
652
|
-
);
|
|
653
|
-
if (sortedEntries.length === 0) return null;
|
|
654
|
-
return /* @__PURE__ */ jsxs(
|
|
655
|
-
"div",
|
|
656
|
-
{
|
|
657
|
-
className: "rich-footnote-section-content rich-footnote-section-edit",
|
|
658
|
-
role: "doc-endnotes",
|
|
659
|
-
children: [
|
|
660
|
-
/* @__PURE__ */ jsx("hr", { className: "rich-footnote-section-divider" }),
|
|
661
|
-
/* @__PURE__ */ jsx("ol", { className: "rich-footnote-section-list", children: sortedEntries.map(([identifier, content2]) => {
|
|
662
|
-
const displayNum = displayNumberMap[identifier] ?? identifier;
|
|
663
|
-
return /* @__PURE__ */ jsxs(
|
|
664
|
-
"li",
|
|
665
|
-
{
|
|
666
|
-
id: `footnote-${identifier}`,
|
|
667
|
-
className: "rich-footnote-section-item rich-footnote-section-item-edit",
|
|
668
|
-
children: [
|
|
669
|
-
/* @__PURE__ */ jsxs("span", { className: "rich-footnote-section-item-num", children: [
|
|
670
|
-
displayNum,
|
|
671
|
-
"."
|
|
672
|
-
] }),
|
|
673
|
-
/* @__PURE__ */ jsx(
|
|
674
|
-
"input",
|
|
675
|
-
{
|
|
676
|
-
type: "text",
|
|
677
|
-
className: "rich-footnote-section-item-input",
|
|
678
|
-
value: content2,
|
|
679
|
-
onChange: (e) => handleContentChange(identifier, e.target.value),
|
|
680
|
-
placeholder: `Footnote content for [^${identifier}]`
|
|
681
|
-
}
|
|
682
|
-
),
|
|
683
|
-
/* @__PURE__ */ jsx(
|
|
684
|
-
"button",
|
|
685
|
-
{
|
|
686
|
-
type: "button",
|
|
687
|
-
className: "rich-footnote-section-item-remove",
|
|
688
|
-
onClick: () => handleRemove(identifier),
|
|
689
|
-
"aria-label": `Remove footnote ${identifier}`,
|
|
690
|
-
children: "×"
|
|
691
|
-
}
|
|
692
|
-
)
|
|
693
|
-
]
|
|
694
|
-
},
|
|
695
|
-
identifier
|
|
696
|
-
);
|
|
697
|
-
}) })
|
|
698
|
-
]
|
|
699
|
-
}
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
class FootnoteSectionEditNode extends FootnoteSectionNode {
|
|
703
|
-
static getType() {
|
|
704
|
-
return "footnote-section";
|
|
705
|
-
}
|
|
706
|
-
static clone(node) {
|
|
707
|
-
return new FootnoteSectionEditNode({ ...node.__definitions }, node.__key);
|
|
708
|
-
}
|
|
709
|
-
static importJSON(serializedNode) {
|
|
710
|
-
return new FootnoteSectionEditNode(serializedNode.definitions);
|
|
711
|
-
}
|
|
712
|
-
decorate(_editor, _config) {
|
|
713
|
-
return createElement(FootnoteSectionEditRenderer, {
|
|
714
|
-
definitions: this.__definitions,
|
|
715
|
-
nodeKey: this.__key
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
const COL_OPTIONS = [1, 2, 3, 4];
|
|
720
|
-
function GridEditDecorator({
|
|
721
|
-
nodeKey,
|
|
722
|
-
cols: initialCols,
|
|
723
|
-
gap,
|
|
724
|
-
cellEditors
|
|
725
|
-
}) {
|
|
726
|
-
const [editor] = useLexicalComposerContext();
|
|
727
|
-
const [currentCols, setCurrentCols] = useState(initialCols);
|
|
728
|
-
useEffect(() => {
|
|
729
|
-
return editor.registerUpdateListener(({ editorState }) => {
|
|
730
|
-
editorState.read(() => {
|
|
731
|
-
const node = $getNodeByKey(nodeKey);
|
|
732
|
-
if ($isGridContainerNode(node)) {
|
|
733
|
-
setCurrentCols(node.getCols());
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
});
|
|
737
|
-
}, [editor, nodeKey]);
|
|
738
|
-
const handleSetCols = useCallback(
|
|
739
|
-
(cols) => {
|
|
740
|
-
editor.update(() => {
|
|
741
|
-
const node = $getNodeByKey(nodeKey);
|
|
742
|
-
if ($isGridContainerNode(node)) {
|
|
743
|
-
node.setCols(cols);
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
},
|
|
747
|
-
[editor, nodeKey]
|
|
748
|
-
);
|
|
749
|
-
const handleAddRow = useCallback(() => {
|
|
750
|
-
editor.update(() => {
|
|
751
|
-
const node = $getNodeByKey(nodeKey);
|
|
752
|
-
if ($isGridContainerNode(node)) {
|
|
753
|
-
node.addCells(node.getCols());
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
}, [editor, nodeKey]);
|
|
757
|
-
const handleRemoveRow = useCallback(() => {
|
|
758
|
-
editor.update(() => {
|
|
759
|
-
const node = $getNodeByKey(nodeKey);
|
|
760
|
-
if (!$isGridEditNode(node)) return;
|
|
761
|
-
const cols = node.getCols();
|
|
762
|
-
const editors = node.getCellEditors();
|
|
763
|
-
if (editors.length <= cols) return;
|
|
764
|
-
const lastRow = editors.slice(-cols);
|
|
765
|
-
const allEmpty = lastRow.every(
|
|
766
|
-
(cellEditor) => cellEditor.getEditorState().read(() => $getRoot().getTextContentSize() === 0)
|
|
767
|
-
);
|
|
768
|
-
if (!allEmpty) return;
|
|
769
|
-
node.removeCells(cols);
|
|
770
|
-
});
|
|
771
|
-
}, [editor, nodeKey]);
|
|
772
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
773
|
-
/* @__PURE__ */ jsxs(
|
|
774
|
-
"div",
|
|
775
|
-
{
|
|
776
|
-
className: "rich-grid-toolbar",
|
|
777
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
778
|
-
children: [
|
|
779
|
-
/* @__PURE__ */ jsx("span", { className: "rich-grid-toolbar-icon", children: /* @__PURE__ */ jsx(LayoutGrid, { size: 14 }) }),
|
|
780
|
-
COL_OPTIONS.map((n) => /* @__PURE__ */ jsx(
|
|
781
|
-
"button",
|
|
782
|
-
{
|
|
783
|
-
type: "button",
|
|
784
|
-
className: `rich-grid-col-btn${n === currentCols ? " rich-grid-col-btn-active" : ""}`,
|
|
785
|
-
onClick: () => handleSetCols(n),
|
|
786
|
-
"aria-label": `${n} columns`,
|
|
787
|
-
children: n
|
|
788
|
-
},
|
|
789
|
-
n
|
|
790
|
-
)),
|
|
791
|
-
/* @__PURE__ */ jsx("div", { className: "rich-grid-toolbar-divider" }),
|
|
792
|
-
/* @__PURE__ */ jsx(
|
|
793
|
-
"button",
|
|
794
|
-
{
|
|
795
|
-
type: "button",
|
|
796
|
-
className: "rich-grid-action-btn",
|
|
797
|
-
onClick: handleAddRow,
|
|
798
|
-
"aria-label": "Add row",
|
|
799
|
-
children: /* @__PURE__ */ jsx(Plus, { size: 14 })
|
|
800
|
-
}
|
|
801
|
-
),
|
|
802
|
-
/* @__PURE__ */ jsx(
|
|
803
|
-
"button",
|
|
804
|
-
{
|
|
805
|
-
type: "button",
|
|
806
|
-
className: "rich-grid-action-btn",
|
|
807
|
-
onClick: handleRemoveRow,
|
|
808
|
-
"aria-label": "Remove row",
|
|
809
|
-
children: /* @__PURE__ */ jsx(Minus, { size: 14 })
|
|
810
|
-
}
|
|
811
|
-
)
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
),
|
|
815
|
-
/* @__PURE__ */ jsx(
|
|
816
|
-
"div",
|
|
817
|
-
{
|
|
818
|
-
className: "rich-grid-inner",
|
|
819
|
-
style: {
|
|
820
|
-
gridTemplateColumns: `repeat(${currentCols}, 1fr)`,
|
|
821
|
-
gap
|
|
822
|
-
},
|
|
823
|
-
children: cellEditors.map((cellEditor, i) => /* @__PURE__ */ jsx("div", { className: "rich-grid-cell", children: /* @__PURE__ */ jsxs(LexicalNestedComposer, { initialEditor: cellEditor, children: [
|
|
824
|
-
/* @__PURE__ */ jsx(
|
|
825
|
-
RichTextPlugin,
|
|
826
|
-
{
|
|
827
|
-
contentEditable: /* @__PURE__ */ jsx(
|
|
828
|
-
ContentEditable$1,
|
|
829
|
-
{
|
|
830
|
-
className: "rich-grid-cell-editable",
|
|
831
|
-
"aria-placeholder": "",
|
|
832
|
-
placeholder: /* @__PURE__ */ jsx("span", { style: { display: "none" } })
|
|
833
|
-
}
|
|
834
|
-
),
|
|
835
|
-
ErrorBoundary: LexicalErrorBoundary
|
|
836
|
-
}
|
|
837
|
-
),
|
|
838
|
-
/* @__PURE__ */ jsx(ListPlugin, {}),
|
|
839
|
-
/* @__PURE__ */ jsx(LinkPlugin, {})
|
|
840
|
-
] }) }, i))
|
|
841
|
-
}
|
|
842
|
-
)
|
|
843
|
-
] });
|
|
844
|
-
}
|
|
845
|
-
function createCellEditor() {
|
|
846
|
-
return createEditor({
|
|
847
|
-
namespace: "GridCell",
|
|
848
|
-
nodes: NESTED_EDITOR_NODES,
|
|
849
|
-
theme: editorTheme,
|
|
850
|
-
onError: (error) => {
|
|
851
|
-
console.error("[GridCell]", error);
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
const _GridEditNode = class _GridEditNode extends GridContainerNode {
|
|
856
|
-
constructor(cols = 2, gap, cellStates, key) {
|
|
857
|
-
super(cols, gap, cellStates, key);
|
|
858
|
-
__publicField(this, "__cellEditors");
|
|
859
|
-
this.__cellEditors = this.__cellStates.map((state) => {
|
|
860
|
-
const editor = createCellEditor();
|
|
861
|
-
const editorState = editor.parseEditorState(state);
|
|
862
|
-
editor.setEditorState(editorState);
|
|
863
|
-
return editor;
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
static clone(node) {
|
|
867
|
-
const cloned = new _GridEditNode(
|
|
868
|
-
node.__cols,
|
|
869
|
-
node.__gap,
|
|
870
|
-
node.__cellStates,
|
|
871
|
-
node.__key
|
|
872
|
-
);
|
|
873
|
-
cloned.__cellEditors = [...node.__cellEditors];
|
|
874
|
-
return cloned;
|
|
875
|
-
}
|
|
876
|
-
getCellEditors() {
|
|
877
|
-
return this.getLatest().__cellEditors;
|
|
878
|
-
}
|
|
879
|
-
setCols(cols) {
|
|
880
|
-
const writable = this.getWritable();
|
|
881
|
-
const prev = writable.__cellEditors.length;
|
|
882
|
-
writable.__cols = cols;
|
|
883
|
-
if (cols > prev) {
|
|
884
|
-
for (let i = prev; i < cols; i++) {
|
|
885
|
-
const editor = createCellEditor();
|
|
886
|
-
writable.__cellEditors.push(editor);
|
|
887
|
-
const emptyState = {
|
|
888
|
-
root: {
|
|
889
|
-
children: [
|
|
890
|
-
{
|
|
891
|
-
type: "paragraph",
|
|
892
|
-
children: [],
|
|
893
|
-
direction: null,
|
|
894
|
-
format: "",
|
|
895
|
-
indent: 0,
|
|
896
|
-
textFormat: 0,
|
|
897
|
-
textStyle: "",
|
|
898
|
-
version: 1
|
|
899
|
-
}
|
|
900
|
-
],
|
|
901
|
-
direction: null,
|
|
902
|
-
format: "",
|
|
903
|
-
indent: 0,
|
|
904
|
-
type: "root",
|
|
905
|
-
version: 1
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
writable.__cellStates.push(emptyState);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
addCells(count) {
|
|
913
|
-
const writable = this.getWritable();
|
|
914
|
-
for (let i = 0; i < count; i++) {
|
|
915
|
-
const editor = createCellEditor();
|
|
916
|
-
writable.__cellEditors.push(editor);
|
|
917
|
-
const emptyState = {
|
|
918
|
-
root: {
|
|
919
|
-
children: [
|
|
920
|
-
{
|
|
921
|
-
type: "paragraph",
|
|
922
|
-
children: [],
|
|
923
|
-
direction: null,
|
|
924
|
-
format: "",
|
|
925
|
-
indent: 0,
|
|
926
|
-
textFormat: 0,
|
|
927
|
-
textStyle: "",
|
|
928
|
-
version: 1
|
|
929
|
-
}
|
|
930
|
-
],
|
|
931
|
-
direction: null,
|
|
932
|
-
format: "",
|
|
933
|
-
indent: 0,
|
|
934
|
-
type: "root",
|
|
935
|
-
version: 1
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
writable.__cellStates.push(emptyState);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
removeCells(count) {
|
|
942
|
-
const writable = this.getWritable();
|
|
943
|
-
const editors = writable.__cellEditors;
|
|
944
|
-
const states = writable.__cellStates;
|
|
945
|
-
const toRemove = Math.min(count, editors.length);
|
|
946
|
-
for (let i = 0; i < toRemove; i++) {
|
|
947
|
-
const editor = editors.at(-1);
|
|
948
|
-
if (!editor) break;
|
|
949
|
-
const isEmpty = editor.getEditorState().read(() => {
|
|
950
|
-
return $getRoot().getTextContentSize() === 0;
|
|
951
|
-
});
|
|
952
|
-
if (!isEmpty) break;
|
|
953
|
-
editors.pop();
|
|
954
|
-
states.pop();
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
static importJSON(serializedNode) {
|
|
958
|
-
const legacy = serializedNode;
|
|
959
|
-
const cols = legacy.cols || 2;
|
|
960
|
-
const rawGap = legacy.gap;
|
|
961
|
-
const gap = typeof rawGap === "number" ? `${rawGap}px` : rawGap;
|
|
962
|
-
if (legacy.cells && legacy.cells.length > 0) {
|
|
963
|
-
return new _GridEditNode(cols, gap, legacy.cells);
|
|
964
|
-
}
|
|
965
|
-
if (legacy.children) {
|
|
966
|
-
const cellStates = legacy.children.map(
|
|
967
|
-
(child) => {
|
|
968
|
-
return {
|
|
969
|
-
root: {
|
|
970
|
-
children: [child],
|
|
971
|
-
direction: null,
|
|
972
|
-
format: "",
|
|
973
|
-
indent: 0,
|
|
974
|
-
type: "root",
|
|
975
|
-
version: 1
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
);
|
|
980
|
-
return new _GridEditNode(cols, gap, cellStates);
|
|
981
|
-
}
|
|
982
|
-
return new _GridEditNode(cols, gap);
|
|
983
|
-
}
|
|
984
|
-
exportJSON() {
|
|
985
|
-
return {
|
|
986
|
-
...super.exportJSON(),
|
|
987
|
-
type: "grid-container",
|
|
988
|
-
cols: this.__cols,
|
|
989
|
-
gap: this.__gap,
|
|
990
|
-
cells: this.__cellEditors.map(
|
|
991
|
-
(editor) => editor.getEditorState().toJSON()
|
|
992
|
-
),
|
|
993
|
-
version: 1
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
decorate(_editor, _config) {
|
|
997
|
-
return createElement(GridEditDecorator, {
|
|
998
|
-
nodeKey: this.__key,
|
|
999
|
-
cols: this.__cols,
|
|
1000
|
-
gap: this.__gap,
|
|
1001
|
-
cellEditors: this.__cellEditors
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
};
|
|
1005
|
-
__publicField(_GridEditNode, "slashMenuItems", [
|
|
1006
|
-
{
|
|
1007
|
-
title: "Grid",
|
|
1008
|
-
icon: createElement(LayoutGrid, { size: 20 }),
|
|
1009
|
-
description: "Grid layout container",
|
|
1010
|
-
keywords: ["grid", "columns", "layout"],
|
|
1011
|
-
section: "LAYOUT",
|
|
1012
|
-
onSelect: (editor) => {
|
|
1013
|
-
editor.update(() => {
|
|
1014
|
-
$insertNodes([$createGridEditNode(2)]);
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
]);
|
|
1019
|
-
let GridEditNode = _GridEditNode;
|
|
1020
|
-
function $createGridEditNode(cols = 2, gap) {
|
|
1021
|
-
return new GridEditNode(cols, gap);
|
|
1022
|
-
}
|
|
1023
|
-
function $isGridEditNode(node) {
|
|
1024
|
-
return node instanceof GridEditNode;
|
|
1025
|
-
}
|
|
1026
|
-
const customEditNodes = [
|
|
1027
|
-
SpoilerNode,
|
|
1028
|
-
MentionNode,
|
|
1029
|
-
KaTeXInlineNode,
|
|
1030
|
-
KaTeXBlockNode,
|
|
1031
|
-
ImageNode,
|
|
1032
|
-
AlertQuoteEditNode,
|
|
1033
|
-
CodeBlockEditNode,
|
|
1034
|
-
FootnoteNode,
|
|
1035
|
-
FootnoteSectionEditNode,
|
|
1036
|
-
VideoNode,
|
|
1037
|
-
LinkCardNode,
|
|
1038
|
-
DetailsNode,
|
|
1039
|
-
GridEditNode,
|
|
1040
|
-
BannerEditNode,
|
|
1041
|
-
MermaidNode,
|
|
1042
|
-
RubyNode
|
|
1043
|
-
];
|
|
1044
|
-
const allEditNodes = [
|
|
1045
|
-
...builtinNodes,
|
|
1046
|
-
...customEditNodes
|
|
1047
|
-
];
|
|
1048
|
-
const ImageUploadContext = createContext(null);
|
|
1049
|
-
function ImageUploadProvider({
|
|
1050
|
-
upload,
|
|
1051
|
-
children
|
|
1052
|
-
}) {
|
|
1053
|
-
return /* @__PURE__ */ jsx(ImageUploadContext.Provider, { value: upload, children });
|
|
1054
|
-
}
|
|
1055
|
-
function useImageUpload() {
|
|
1056
|
-
return use(ImageUploadContext);
|
|
1057
|
-
}
|
|
1058
|
-
let _resolvedEditNodes = null;
|
|
1059
|
-
function setResolvedEditNodes(nodes) {
|
|
1060
|
-
_resolvedEditNodes = nodes;
|
|
1061
|
-
}
|
|
1062
|
-
function getResolvedEditNodes() {
|
|
1063
|
-
return _resolvedEditNodes ?? allEditNodes;
|
|
1064
|
-
}
|
|
1065
|
-
const INSERT_ALERT_COMMAND = createCommand("INSERT_ALERT");
|
|
1066
|
-
function AlertPlugin() {
|
|
1067
|
-
const [editor] = useLexicalComposerContext();
|
|
1068
|
-
useEffect(() => {
|
|
1069
|
-
return editor.registerCommand(
|
|
1070
|
-
INSERT_ALERT_COMMAND,
|
|
1071
|
-
(alertType) => {
|
|
1072
|
-
$insertNodes([$createAlertQuoteEditNode(alertType)]);
|
|
1073
|
-
return true;
|
|
1074
|
-
},
|
|
1075
|
-
COMMAND_PRIORITY_EDITOR
|
|
1076
|
-
);
|
|
1077
|
-
}, [editor]);
|
|
1078
|
-
return null;
|
|
1079
|
-
}
|
|
1080
|
-
function AutoFocusPlugin() {
|
|
1081
|
-
const [editor] = useLexicalComposerContext();
|
|
1082
|
-
useEffect(() => {
|
|
1083
|
-
const root = editor.getRootElement();
|
|
1084
|
-
if (root) {
|
|
1085
|
-
root.focus({ preventScroll: true });
|
|
1086
|
-
} else {
|
|
1087
|
-
editor.focus();
|
|
1088
|
-
}
|
|
1089
|
-
}, [editor]);
|
|
1090
|
-
return null;
|
|
1091
|
-
}
|
|
1092
|
-
const URL_REGEX = /https?:\/\/(?:www\.)?[-\w@:%.+~#=]{1,256}\.[a-zA-Z]{2}[-\w()@:%+.~#?&/=]*/;
|
|
1093
|
-
const EMAIL_REGEX = /(?:[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,})/i;
|
|
1094
|
-
const URL_MATCHER = createLinkMatcherWithRegExp(URL_REGEX);
|
|
1095
|
-
const EMAIL_MATCHER = createLinkMatcherWithRegExp(
|
|
1096
|
-
EMAIL_REGEX,
|
|
1097
|
-
(text) => `mailto:${text}`
|
|
1098
|
-
);
|
|
1099
|
-
const DEFAULT_MATCHERS = [URL_MATCHER, EMAIL_MATCHER];
|
|
1100
|
-
function AutoLinkPlugin({ matchers }) {
|
|
1101
|
-
const [editor] = useLexicalComposerContext();
|
|
1102
|
-
useEffect(() => {
|
|
1103
|
-
return registerAutoLink(editor, {
|
|
1104
|
-
matchers: matchers ?? DEFAULT_MATCHERS,
|
|
1105
|
-
changeHandlers: [],
|
|
1106
|
-
excludeParents: []
|
|
1107
|
-
});
|
|
1108
|
-
}, [editor, matchers]);
|
|
1109
|
-
return null;
|
|
1110
|
-
}
|
|
1111
|
-
function selectDecoratorNode(node, cursorPlacement = "start") {
|
|
1112
|
-
if (node.getType() === "code-block") {
|
|
1113
|
-
setCodeBlockCursorIntent(node.getKey(), cursorPlacement);
|
|
1114
|
-
}
|
|
1115
|
-
const selection = $createNodeSelection();
|
|
1116
|
-
selection.add(node.getKey());
|
|
1117
|
-
$setSelection(selection);
|
|
1118
|
-
}
|
|
1119
|
-
function isAtTopLevelBoundary(selection, direction) {
|
|
1120
|
-
const point = selection.anchor;
|
|
1121
|
-
const topLevel = point.getNode().getTopLevelElementOrThrow();
|
|
1122
|
-
const pointNode = point.getNode();
|
|
1123
|
-
if (point.type === "text") {
|
|
1124
|
-
if (!$isTextNode(pointNode)) return false;
|
|
1125
|
-
const expectedOffset = direction === "start" ? 0 : pointNode.getTextContentSize();
|
|
1126
|
-
if (point.offset !== expectedOffset) return false;
|
|
1127
|
-
} else {
|
|
1128
|
-
if (!$isElementNode(pointNode)) return false;
|
|
1129
|
-
const expectedOffset = direction === "start" ? 0 : pointNode.getChildrenSize();
|
|
1130
|
-
if (point.offset !== expectedOffset) return false;
|
|
1131
|
-
}
|
|
1132
|
-
let current = pointNode;
|
|
1133
|
-
while (current && current !== topLevel) {
|
|
1134
|
-
const sibling = direction === "start" ? current.getPreviousSibling() : current.getNextSibling();
|
|
1135
|
-
if (sibling !== null) return false;
|
|
1136
|
-
current = current.getParent();
|
|
1137
|
-
}
|
|
1138
|
-
return current === topLevel;
|
|
1139
|
-
}
|
|
1140
|
-
function isSingleLineParagraph(node) {
|
|
1141
|
-
return $isParagraphNode(node) && !node.getTextContent().includes("\n");
|
|
1142
|
-
}
|
|
1143
|
-
function BlockExitPlugin() {
|
|
1144
|
-
const [editor] = useLexicalComposerContext();
|
|
1145
|
-
useEffect(() => {
|
|
1146
|
-
const unregisterArrowDown = editor.registerCommand(
|
|
1147
|
-
KEY_ARROW_DOWN_COMMAND,
|
|
1148
|
-
(event) => {
|
|
1149
|
-
const selection = $getSelection();
|
|
1150
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
1151
|
-
return false;
|
|
1152
|
-
}
|
|
1153
|
-
const anchorNode = selection.anchor.getNode();
|
|
1154
|
-
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
1155
|
-
const shouldSelectNextDecorator = isAtTopLevelBoundary(selection, "end") || isSingleLineParagraph(topLevelElement);
|
|
1156
|
-
if (!$isQuoteNode(topLevelElement) && shouldSelectNextDecorator) {
|
|
1157
|
-
const next2 = topLevelElement.getNextSibling();
|
|
1158
|
-
if (next2 && $isDecoratorNode(next2) && next2.isKeyboardSelectable()) {
|
|
1159
|
-
event?.preventDefault();
|
|
1160
|
-
selectDecoratorNode(next2, "start");
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
const element = $isElementNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
1165
|
-
let quoteChild = element;
|
|
1166
|
-
let quoteNode = null;
|
|
1167
|
-
let current = element;
|
|
1168
|
-
while (current) {
|
|
1169
|
-
const parent = current.getParent();
|
|
1170
|
-
if (!parent || $isRootNode(parent)) break;
|
|
1171
|
-
if ($isQuoteNode(parent)) {
|
|
1172
|
-
quoteNode = parent;
|
|
1173
|
-
quoteChild = current;
|
|
1174
|
-
break;
|
|
1175
|
-
}
|
|
1176
|
-
current = parent;
|
|
1177
|
-
}
|
|
1178
|
-
if (!quoteNode) return false;
|
|
1179
|
-
if (quoteChild.getNextSibling() !== null) return false;
|
|
1180
|
-
if (!$isParagraphNode(quoteChild) || quoteChild.getTextContent() !== "")
|
|
1181
|
-
return false;
|
|
1182
|
-
event?.preventDefault();
|
|
1183
|
-
let next = quoteNode.getNextSibling();
|
|
1184
|
-
if (!next) {
|
|
1185
|
-
next = $createParagraphNode();
|
|
1186
|
-
quoteNode.insertAfter(next);
|
|
1187
|
-
}
|
|
1188
|
-
next.selectStart();
|
|
1189
|
-
return true;
|
|
1190
|
-
},
|
|
1191
|
-
COMMAND_PRIORITY_CRITICAL
|
|
1192
|
-
);
|
|
1193
|
-
const unregisterCmdEnter = editor.registerCommand(
|
|
1194
|
-
KEY_ENTER_COMMAND,
|
|
1195
|
-
(event) => {
|
|
1196
|
-
if (!event?.metaKey && !event?.ctrlKey) return false;
|
|
1197
|
-
const selection = $getSelection();
|
|
1198
|
-
if ($isNodeSelection(selection)) {
|
|
1199
|
-
const nodes = selection.getNodes();
|
|
1200
|
-
if (nodes.length !== 1) return false;
|
|
1201
|
-
const node = nodes[0];
|
|
1202
|
-
if (!$isDecoratorNode(node)) return false;
|
|
1203
|
-
event.preventDefault();
|
|
1204
|
-
let next2 = node.getNextSibling();
|
|
1205
|
-
if (!next2) {
|
|
1206
|
-
next2 = $createParagraphNode();
|
|
1207
|
-
node.insertAfter(next2);
|
|
1208
|
-
}
|
|
1209
|
-
if ($isElementNode(next2)) {
|
|
1210
|
-
next2.selectStart();
|
|
1211
|
-
} else if ($isDecoratorNode(next2)) {
|
|
1212
|
-
selectDecoratorNode(next2, "start");
|
|
1213
|
-
}
|
|
1214
|
-
return true;
|
|
1215
|
-
}
|
|
1216
|
-
if (!$isRangeSelection(selection)) return false;
|
|
1217
|
-
const anchorNode = selection.anchor.getNode();
|
|
1218
|
-
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
1219
|
-
if ($isParagraphNode(topLevelElement)) return false;
|
|
1220
|
-
event.preventDefault();
|
|
1221
|
-
let next = topLevelElement.getNextSibling();
|
|
1222
|
-
if (!next || !$isParagraphNode(next)) {
|
|
1223
|
-
next = $createParagraphNode();
|
|
1224
|
-
topLevelElement.insertAfter(next);
|
|
1225
|
-
}
|
|
1226
|
-
next.selectStart();
|
|
1227
|
-
return true;
|
|
1228
|
-
},
|
|
1229
|
-
COMMAND_PRIORITY_CRITICAL
|
|
1230
|
-
);
|
|
1231
|
-
function handleDeleteDecorator(event) {
|
|
1232
|
-
const selection = $getSelection();
|
|
1233
|
-
if (!$isNodeSelection(selection)) return false;
|
|
1234
|
-
const nodes = selection.getNodes();
|
|
1235
|
-
if (nodes.length !== 1) return false;
|
|
1236
|
-
const node = nodes[0];
|
|
1237
|
-
if (!$isDecoratorNode(node)) return false;
|
|
1238
|
-
event?.preventDefault();
|
|
1239
|
-
const prev = node.getPreviousSibling();
|
|
1240
|
-
const next = node.getNextSibling();
|
|
1241
|
-
node.remove();
|
|
1242
|
-
if (prev && $isElementNode(prev)) {
|
|
1243
|
-
prev.selectEnd();
|
|
1244
|
-
} else if (prev && $isDecoratorNode(prev)) {
|
|
1245
|
-
selectDecoratorNode(prev, "end");
|
|
1246
|
-
} else if (next && $isElementNode(next)) {
|
|
1247
|
-
next.selectStart();
|
|
1248
|
-
} else if (next && $isDecoratorNode(next)) {
|
|
1249
|
-
selectDecoratorNode(next, "start");
|
|
1250
|
-
} else {
|
|
1251
|
-
const root = $getRoot();
|
|
1252
|
-
if (root && $isElementNode(root)) {
|
|
1253
|
-
const p = $createParagraphNode();
|
|
1254
|
-
root.append(p);
|
|
1255
|
-
p.selectStart();
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
return true;
|
|
1259
|
-
}
|
|
1260
|
-
const unregisterBackspace = editor.registerCommand(
|
|
1261
|
-
KEY_BACKSPACE_COMMAND,
|
|
1262
|
-
handleDeleteDecorator,
|
|
1263
|
-
COMMAND_PRIORITY_HIGH
|
|
1264
|
-
);
|
|
1265
|
-
const unregisterDelete = editor.registerCommand(
|
|
1266
|
-
KEY_DELETE_COMMAND,
|
|
1267
|
-
handleDeleteDecorator,
|
|
1268
|
-
COMMAND_PRIORITY_HIGH
|
|
1269
|
-
);
|
|
1270
|
-
const unregisterArrowUpDecorator = editor.registerCommand(
|
|
1271
|
-
KEY_ARROW_UP_COMMAND,
|
|
1272
|
-
(event) => {
|
|
1273
|
-
const selection = $getSelection();
|
|
1274
|
-
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
1275
|
-
const anchorNode = selection.anchor.getNode();
|
|
1276
|
-
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
1277
|
-
const shouldSelectPreviousDecorator = isAtTopLevelBoundary(selection, "start") || isSingleLineParagraph(topLevelElement);
|
|
1278
|
-
if (!$isQuoteNode(topLevelElement) && shouldSelectPreviousDecorator) {
|
|
1279
|
-
const prev2 = topLevelElement.getPreviousSibling();
|
|
1280
|
-
if (prev2 && $isDecoratorNode(prev2) && prev2.isKeyboardSelectable()) {
|
|
1281
|
-
event?.preventDefault();
|
|
1282
|
-
selectDecoratorNode(prev2, "end");
|
|
1283
|
-
return true;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
if (!$isNodeSelection(selection)) return false;
|
|
1288
|
-
const nodes = selection.getNodes();
|
|
1289
|
-
if (nodes.length !== 1) return false;
|
|
1290
|
-
const node = nodes[0];
|
|
1291
|
-
if (!$isDecoratorNode(node)) return false;
|
|
1292
|
-
const prev = node.getPreviousSibling();
|
|
1293
|
-
if (prev && $isElementNode(prev)) {
|
|
1294
|
-
event?.preventDefault();
|
|
1295
|
-
prev.selectEnd();
|
|
1296
|
-
return true;
|
|
1297
|
-
}
|
|
1298
|
-
if (prev && $isDecoratorNode(prev)) {
|
|
1299
|
-
event?.preventDefault();
|
|
1300
|
-
selectDecoratorNode(prev, "end");
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
return false;
|
|
1304
|
-
},
|
|
1305
|
-
COMMAND_PRIORITY_CRITICAL
|
|
1306
|
-
);
|
|
1307
|
-
const unregisterArrowDownDecorator = editor.registerCommand(
|
|
1308
|
-
KEY_ARROW_DOWN_COMMAND,
|
|
1309
|
-
(event) => {
|
|
1310
|
-
const selection = $getSelection();
|
|
1311
|
-
if (!$isNodeSelection(selection)) return false;
|
|
1312
|
-
const nodes = selection.getNodes();
|
|
1313
|
-
if (nodes.length !== 1) return false;
|
|
1314
|
-
const node = nodes[0];
|
|
1315
|
-
if (!$isDecoratorNode(node)) return false;
|
|
1316
|
-
const next = node.getNextSibling();
|
|
1317
|
-
if (next && $isElementNode(next)) {
|
|
1318
|
-
event?.preventDefault();
|
|
1319
|
-
next.selectStart();
|
|
1320
|
-
return true;
|
|
1321
|
-
}
|
|
1322
|
-
if (next && $isDecoratorNode(next)) {
|
|
1323
|
-
event?.preventDefault();
|
|
1324
|
-
selectDecoratorNode(next, "start");
|
|
1325
|
-
return true;
|
|
1326
|
-
}
|
|
1327
|
-
return false;
|
|
1328
|
-
},
|
|
1329
|
-
COMMAND_PRIORITY_CRITICAL
|
|
1330
|
-
);
|
|
1331
|
-
return () => {
|
|
1332
|
-
unregisterArrowDown();
|
|
1333
|
-
unregisterCmdEnter();
|
|
1334
|
-
unregisterBackspace();
|
|
1335
|
-
unregisterDelete();
|
|
1336
|
-
unregisterArrowUpDecorator();
|
|
1337
|
-
unregisterArrowDownDecorator();
|
|
1338
|
-
};
|
|
1339
|
-
}, [editor]);
|
|
1340
|
-
return null;
|
|
1341
|
-
}
|
|
1342
|
-
const blockIdState = createState("blockId", {
|
|
1343
|
-
parse: (v) => typeof v === "string" ? v : ""
|
|
1344
|
-
});
|
|
1345
|
-
const NORMALIZATION_TAG = "block-id-normalization";
|
|
1346
|
-
function buildPreviousIdIndex(editorState) {
|
|
1347
|
-
return editorState.read(() => {
|
|
1348
|
-
const map = /* @__PURE__ */ new Map();
|
|
1349
|
-
for (const child of $getRoot().getChildren()) {
|
|
1350
|
-
const id = $getState(child, blockIdState);
|
|
1351
|
-
if (!id) continue;
|
|
1352
|
-
const set = map.get(id) ?? /* @__PURE__ */ new Set();
|
|
1353
|
-
set.add(child.getKey());
|
|
1354
|
-
map.set(id, set);
|
|
1355
|
-
}
|
|
1356
|
-
return map;
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
function collectRootChildren(editorState) {
|
|
1360
|
-
return editorState.read(
|
|
1361
|
-
() => $getRoot().getChildren().map((child) => $getState(child, blockIdState))
|
|
1362
|
-
);
|
|
1363
|
-
}
|
|
1364
|
-
function hasDuplicateOrMissingId(children) {
|
|
1365
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1366
|
-
for (const id of children) {
|
|
1367
|
-
if (!id || seen.has(id)) {
|
|
1368
|
-
return true;
|
|
1369
|
-
}
|
|
1370
|
-
seen.add(id);
|
|
1371
|
-
}
|
|
1372
|
-
return false;
|
|
1373
|
-
}
|
|
1374
|
-
function generateBlockId(used) {
|
|
1375
|
-
let id = "";
|
|
1376
|
-
do {
|
|
1377
|
-
id = nanoid(8);
|
|
1378
|
-
} while (used.has(id));
|
|
1379
|
-
return id;
|
|
1380
|
-
}
|
|
1381
|
-
function pickKeeperKey(nodes, previousKeys) {
|
|
1382
|
-
if (!nodes.length) return null;
|
|
1383
|
-
if (previousKeys?.size) {
|
|
1384
|
-
for (const node of nodes) {
|
|
1385
|
-
if (previousKeys.has(node.getKey())) {
|
|
1386
|
-
return node.getKey();
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
return nodes[0]?.getKey() ?? null;
|
|
1391
|
-
}
|
|
1392
|
-
function normalizeRootBlockIds(editor, previousIdIndex) {
|
|
1393
|
-
editor.update(
|
|
1394
|
-
() => {
|
|
1395
|
-
$addUpdateTag("history-merge");
|
|
1396
|
-
const children = $getRoot().getChildren();
|
|
1397
|
-
const groupedById = /* @__PURE__ */ new Map();
|
|
1398
|
-
for (const child of children) {
|
|
1399
|
-
const id = $getState(child, blockIdState);
|
|
1400
|
-
if (!id) continue;
|
|
1401
|
-
const bucket = groupedById.get(id) ?? [];
|
|
1402
|
-
bucket.push(child);
|
|
1403
|
-
groupedById.set(id, bucket);
|
|
1404
|
-
}
|
|
1405
|
-
const keeperById = /* @__PURE__ */ new Map();
|
|
1406
|
-
for (const [id, nodes] of groupedById) {
|
|
1407
|
-
if (nodes.length <= 1) continue;
|
|
1408
|
-
const keeperKey = pickKeeperKey(nodes, previousIdIndex.get(id));
|
|
1409
|
-
if (keeperKey) {
|
|
1410
|
-
keeperById.set(id, keeperKey);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
const used = /* @__PURE__ */ new Set();
|
|
1414
|
-
for (const child of children) {
|
|
1415
|
-
let id = $getState(child, blockIdState);
|
|
1416
|
-
const keeperKey = id ? keeperById.get(id) : null;
|
|
1417
|
-
const shouldRegenerate = !id || used.has(id) || keeperKey !== void 0 && child.getKey() !== keeperKey;
|
|
1418
|
-
if (shouldRegenerate) {
|
|
1419
|
-
id = generateBlockId(used);
|
|
1420
|
-
$setState(child, blockIdState, id);
|
|
1421
|
-
}
|
|
1422
|
-
used.add(id);
|
|
1423
|
-
}
|
|
1424
|
-
},
|
|
1425
|
-
{ tag: NORMALIZATION_TAG }
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
function BlockIdPlugin() {
|
|
1429
|
-
const [editor] = useLexicalComposerContext();
|
|
1430
|
-
useEffect(() => {
|
|
1431
|
-
const initialChildren = collectRootChildren(editor.getEditorState());
|
|
1432
|
-
if (hasDuplicateOrMissingId(initialChildren)) {
|
|
1433
|
-
normalizeRootBlockIds(editor, /* @__PURE__ */ new Map());
|
|
1434
|
-
}
|
|
1435
|
-
return editor.registerUpdateListener(
|
|
1436
|
-
({ tags, editorState, prevEditorState }) => {
|
|
1437
|
-
if (tags.has(NORMALIZATION_TAG)) return;
|
|
1438
|
-
const children = collectRootChildren(editorState);
|
|
1439
|
-
if (!hasDuplicateOrMissingId(children)) return;
|
|
1440
|
-
const previousIdIndex = buildPreviousIdIndex(prevEditorState);
|
|
1441
|
-
normalizeRootBlockIds(editor, previousIdIndex);
|
|
1442
|
-
}
|
|
1443
|
-
);
|
|
1444
|
-
}, [editor]);
|
|
1445
|
-
return null;
|
|
1446
|
-
}
|
|
1447
|
-
function EditorRefPlugin({ onEditorReady }) {
|
|
1448
|
-
const [editor] = useLexicalComposerContext();
|
|
1449
|
-
const callbackRef = useRef(onEditorReady);
|
|
1450
|
-
callbackRef.current = onEditorReady;
|
|
1451
|
-
useEffect(() => {
|
|
1452
|
-
callbackRef.current?.(editor);
|
|
1453
|
-
return () => callbackRef.current?.(null);
|
|
1454
|
-
}, [editor]);
|
|
1455
|
-
return null;
|
|
1456
|
-
}
|
|
1457
|
-
function FootnotePlugin({ children }) {
|
|
1458
|
-
const [editor] = useLexicalComposerContext();
|
|
1459
|
-
const [definitions, setDefinitions] = useState({});
|
|
1460
|
-
const [displayNumberMap, setDisplayNumberMap] = useState({});
|
|
1461
|
-
const pendingUpdateRef = useRef(false);
|
|
1462
|
-
useEffect(() => {
|
|
1463
|
-
return editor.registerUpdateListener(({ editorState }) => {
|
|
1464
|
-
editorState.read(() => {
|
|
1465
|
-
const footnoteNodes = $nodesOfType(FootnoteNode);
|
|
1466
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1467
|
-
const numberMap = {};
|
|
1468
|
-
let counter = 1;
|
|
1469
|
-
for (const node of footnoteNodes) {
|
|
1470
|
-
const id = node.getIdentifier();
|
|
1471
|
-
if (!seen.has(id)) {
|
|
1472
|
-
seen.add(id);
|
|
1473
|
-
numberMap[id] = counter++;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
setDisplayNumberMap(numberMap);
|
|
1477
|
-
const sectionNodes = $nodesOfType(FootnoteSectionNode);
|
|
1478
|
-
if (sectionNodes.length > 0) {
|
|
1479
|
-
setDefinitions(sectionNodes[0].getDefinitions());
|
|
1480
|
-
} else {
|
|
1481
|
-
setDefinitions({});
|
|
1482
|
-
}
|
|
1483
|
-
if (!editor.isEditable() || pendingUpdateRef.current) return;
|
|
1484
|
-
if (footnoteNodes.length === 0 && sectionNodes.length > 0) {
|
|
1485
|
-
pendingUpdateRef.current = true;
|
|
1486
|
-
queueMicrotask(() => {
|
|
1487
|
-
editor.update(() => {
|
|
1488
|
-
const sections = $nodesOfType(FootnoteSectionNode);
|
|
1489
|
-
for (const s of sections) s.remove();
|
|
1490
|
-
});
|
|
1491
|
-
pendingUpdateRef.current = false;
|
|
1492
|
-
});
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
if (footnoteNodes.length > 0 && sectionNodes.length === 0) {
|
|
1496
|
-
const seenSnapshot = [...seen];
|
|
1497
|
-
pendingUpdateRef.current = true;
|
|
1498
|
-
queueMicrotask(() => {
|
|
1499
|
-
editor.update(() => {
|
|
1500
|
-
const root = $getRoot();
|
|
1501
|
-
const defs = {};
|
|
1502
|
-
for (const id of seenSnapshot) {
|
|
1503
|
-
defs[id] = "";
|
|
1504
|
-
}
|
|
1505
|
-
const section = $parseSerializedNode({
|
|
1506
|
-
type: "footnote-section",
|
|
1507
|
-
definitions: defs,
|
|
1508
|
-
version: 1
|
|
1509
|
-
});
|
|
1510
|
-
root.append(section);
|
|
1511
|
-
});
|
|
1512
|
-
pendingUpdateRef.current = false;
|
|
1513
|
-
});
|
|
1514
|
-
} else if (sectionNodes.length > 0) {
|
|
1515
|
-
const existingDefs = sectionNodes[0].getDefinitions();
|
|
1516
|
-
const missingIds = [...seen].filter((id) => !(id in existingDefs));
|
|
1517
|
-
const orphanIds = Object.keys(existingDefs).filter(
|
|
1518
|
-
(id) => !seen.has(id)
|
|
1519
|
-
);
|
|
1520
|
-
if (missingIds.length > 0 || orphanIds.length > 0) {
|
|
1521
|
-
pendingUpdateRef.current = true;
|
|
1522
|
-
queueMicrotask(() => {
|
|
1523
|
-
editor.update(() => {
|
|
1524
|
-
const freshSections = $nodesOfType(FootnoteSectionNode);
|
|
1525
|
-
if (freshSections.length > 0) {
|
|
1526
|
-
for (const id of missingIds) {
|
|
1527
|
-
freshSections[0].setDefinition(id, "");
|
|
1528
|
-
}
|
|
1529
|
-
for (const id of orphanIds) {
|
|
1530
|
-
freshSections[0].removeDefinition(id);
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
});
|
|
1534
|
-
pendingUpdateRef.current = false;
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
});
|
|
1539
|
-
});
|
|
1540
|
-
}, [editor]);
|
|
1541
|
-
return /* @__PURE__ */ jsx(
|
|
1542
|
-
FootnoteDefinitionsProvider,
|
|
1543
|
-
{
|
|
1544
|
-
definitions,
|
|
1545
|
-
displayNumberMap,
|
|
1546
|
-
children
|
|
1547
|
-
}
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
const HORIZONTAL_RULE_REGEX = /^(---|\*\*\*|___)\s?$/;
|
|
1551
|
-
const HORIZONTAL_RULE_BLOCK_TRANSFORMER = {
|
|
1552
|
-
...HORIZONTAL_RULE_BLOCK_TRANSFORMER$1,
|
|
1553
|
-
dependencies: [HorizontalRuleNode],
|
|
1554
|
-
regExp: HORIZONTAL_RULE_REGEX,
|
|
1555
|
-
replace: (parentNode) => {
|
|
1556
|
-
const hrNode = $createHorizontalRuleNode();
|
|
1557
|
-
const paragraph = $createParagraphNode();
|
|
1558
|
-
parentNode.replace(hrNode);
|
|
1559
|
-
hrNode.insertAfter(paragraph);
|
|
1560
|
-
paragraph.selectStart();
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
function HorizontalRulePlugin() {
|
|
1564
|
-
const [editor] = useLexicalComposerContext();
|
|
1565
|
-
useEffect(() => {
|
|
1566
|
-
const unregisterInsert = editor.registerCommand(
|
|
1567
|
-
INSERT_HORIZONTAL_RULE_COMMAND,
|
|
1568
|
-
() => {
|
|
1569
|
-
const selection = $getSelection();
|
|
1570
|
-
if (!$isRangeSelection(selection)) return false;
|
|
1571
|
-
const focusNode = selection.focus.getNode();
|
|
1572
|
-
const topLevel = focusNode.getTopLevelElement();
|
|
1573
|
-
if (!topLevel) return false;
|
|
1574
|
-
const hrNode = $createHorizontalRuleNode();
|
|
1575
|
-
const paragraph = $createParagraphNode();
|
|
1576
|
-
topLevel.insertAfter(hrNode);
|
|
1577
|
-
hrNode.insertAfter(paragraph);
|
|
1578
|
-
paragraph.selectStart();
|
|
1579
|
-
return true;
|
|
1580
|
-
},
|
|
1581
|
-
COMMAND_PRIORITY_EDITOR
|
|
1582
|
-
);
|
|
1583
|
-
const unregisterEnter = editor.registerCommand(
|
|
1584
|
-
KEY_ENTER_COMMAND,
|
|
1585
|
-
(event) => {
|
|
1586
|
-
const selection = $getSelection();
|
|
1587
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
1588
|
-
return false;
|
|
1589
|
-
}
|
|
1590
|
-
const anchorNode = selection.anchor.getNode();
|
|
1591
|
-
if (!$isTextNode(anchorNode)) return false;
|
|
1592
|
-
const textContent = anchorNode.getTextContent();
|
|
1593
|
-
if (!HORIZONTAL_RULE_REGEX.test(textContent)) return false;
|
|
1594
|
-
const parentNode = anchorNode.getParent();
|
|
1595
|
-
if (!parentNode) return false;
|
|
1596
|
-
event?.preventDefault();
|
|
1597
|
-
const hrNode = $createHorizontalRuleNode();
|
|
1598
|
-
const paragraph = $createParagraphNode();
|
|
1599
|
-
parentNode.replace(hrNode);
|
|
1600
|
-
hrNode.insertAfter(paragraph);
|
|
1601
|
-
paragraph.selectStart();
|
|
1602
|
-
return true;
|
|
1603
|
-
},
|
|
1604
|
-
COMMAND_PRIORITY_LOW
|
|
1605
|
-
);
|
|
1606
|
-
return () => {
|
|
1607
|
-
unregisterInsert();
|
|
1608
|
-
unregisterEnter();
|
|
1609
|
-
};
|
|
1610
|
-
}, [editor]);
|
|
1611
|
-
return null;
|
|
1612
|
-
}
|
|
1613
|
-
const INSERT_IMAGE_COMMAND = createCommand(
|
|
1614
|
-
"INSERT_IMAGE_COMMAND"
|
|
1615
|
-
);
|
|
1616
|
-
function ImagePlugin() {
|
|
1617
|
-
const [editor] = useLexicalComposerContext();
|
|
1618
|
-
useEffect(() => {
|
|
1619
|
-
return editor.registerCommand(
|
|
1620
|
-
INSERT_IMAGE_COMMAND,
|
|
1621
|
-
(payload) => {
|
|
1622
|
-
const imageNode = $createImageNode(payload);
|
|
1623
|
-
$insertNodes([imageNode]);
|
|
1624
|
-
return true;
|
|
1625
|
-
},
|
|
1626
|
-
COMMAND_PRIORITY_EDITOR
|
|
1627
|
-
);
|
|
1628
|
-
}, [editor]);
|
|
1629
|
-
return null;
|
|
1630
|
-
}
|
|
1631
|
-
var draggingWrapperClass = "rich-image-upload-dragging";
|
|
1632
|
-
var toastStack = "_1x58dbf1";
|
|
1633
|
-
var toast = "_1x58dbf2";
|
|
1634
|
-
var toastVariant = { info: "_1x58dbf3", success: "_1x58dbf4", error: "_1x58dbf5" };
|
|
1635
|
-
var spinner = "_1x58dbf7";
|
|
1636
|
-
var dialogPopup = "_1x58dbf8";
|
|
1637
|
-
var dialogHeader = "_1x58dbf9";
|
|
1638
|
-
var dialogTitle = "_1x58dbfa";
|
|
1639
|
-
var dialogBody = "_1x58dbfb";
|
|
1640
|
-
var tabWrap = "_1x58dbfc";
|
|
1641
|
-
var uploadDropzone = "_1x58dbfd";
|
|
1642
|
-
var uploadDropzoneState = { idle: "_1x58dbfe", active: "_1x58dbff", busy: "_1x58dbfg" };
|
|
1643
|
-
var uploadDropIcon = "_1x58dbfh";
|
|
1644
|
-
var uploadDropTitle = "_1x58dbfi";
|
|
1645
|
-
var uploadDropDesc = "_1x58dbfj";
|
|
1646
|
-
var hiddenInput = "_1x58dbfk";
|
|
1647
|
-
var uploadBusyWrap = "_1x58dbfl";
|
|
1648
|
-
var uploadProgress = "_1x58dbfm";
|
|
1649
|
-
var urlInputRow = "_1x58dbfn";
|
|
1650
|
-
var textInput = "_1x58dbfo";
|
|
1651
|
-
var actionButton = "_1x58dbfq _1x58dbfp";
|
|
1652
|
-
var secondaryButton = "_1x58dbfr _1x58dbfp";
|
|
1653
|
-
var urlPreview = "_1x58dbfs";
|
|
1654
|
-
var urlPreviewImage = "_1x58dbft";
|
|
1655
|
-
var helperText = "_1x58dbfu";
|
|
1656
|
-
var dialogFooter = "_1x58dbfv";
|
|
1657
|
-
var dialogFooterActions = "_1x58dbfw";
|
|
1658
|
-
function isImageFile(file) {
|
|
1659
|
-
return file.type.startsWith("image/");
|
|
1660
|
-
}
|
|
1661
|
-
function hasImageData(dataTransfer) {
|
|
1662
|
-
if (!dataTransfer) return false;
|
|
1663
|
-
if (Array.from(dataTransfer.files).some(isImageFile)) return true;
|
|
1664
|
-
return Array.from(dataTransfer.items).some(
|
|
1665
|
-
(item) => item.type.startsWith("image/")
|
|
1666
|
-
);
|
|
1667
|
-
}
|
|
1668
|
-
function isSafeImageUrl(url) {
|
|
1669
|
-
return !/^(?:javascript\s*:|vbscript\s*:|data\s*:(?!image\/))/i.test(url);
|
|
1670
|
-
}
|
|
1671
|
-
function loadImageByUrl(src) {
|
|
1672
|
-
return new Promise((resolve, reject) => {
|
|
1673
|
-
const image = new Image();
|
|
1674
|
-
image.onload = () => {
|
|
1675
|
-
resolve({
|
|
1676
|
-
width: image.naturalWidth || image.width,
|
|
1677
|
-
height: image.naturalHeight || image.height
|
|
1678
|
-
});
|
|
1679
|
-
};
|
|
1680
|
-
image.onerror = () => reject(new Error("Failed to load image"));
|
|
1681
|
-
image.src = src;
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
function readAsDataUrl(file) {
|
|
1685
|
-
return new Promise((resolve, reject) => {
|
|
1686
|
-
const reader = new FileReader();
|
|
1687
|
-
reader.onload = () => resolve(String(reader.result));
|
|
1688
|
-
reader.onerror = () => reject(reader.error ?? new Error("File read failed"));
|
|
1689
|
-
reader.readAsDataURL(file);
|
|
1690
|
-
});
|
|
1691
|
-
}
|
|
1692
|
-
async function defaultImageUpload(file) {
|
|
1693
|
-
return {
|
|
1694
|
-
src: await readAsDataUrl(file),
|
|
1695
|
-
altText: file.name
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
function ImageUploadPlugin({ onUpload }) {
|
|
1699
|
-
const [editor] = useLexicalComposerContext();
|
|
1700
|
-
const uploadRef = useRef(onUpload);
|
|
1701
|
-
uploadRef.current = onUpload;
|
|
1702
|
-
const fileInputRef = useRef(null);
|
|
1703
|
-
const toastTimerRef = useRef(null);
|
|
1704
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
1705
|
-
const [tab, setTab] = useState("upload");
|
|
1706
|
-
const [rootDragActive, setRootDragActive] = useState(false);
|
|
1707
|
-
const [dialogDragActive, setDialogDragActive] = useState(false);
|
|
1708
|
-
const [pendingUploads, setPendingUploads] = useState(0);
|
|
1709
|
-
const [dialogUploading, setDialogUploading] = useState(false);
|
|
1710
|
-
const [toast$1, setToast] = useState(null);
|
|
1711
|
-
const [urlInput, setUrlInput] = useState("");
|
|
1712
|
-
const [urlPreview$1, setUrlPreview] = useState(null);
|
|
1713
|
-
const [urlMeta, setUrlMeta] = useState(null);
|
|
1714
|
-
const [urlLoading, setUrlLoading] = useState(false);
|
|
1715
|
-
const [urlError, setUrlError] = useState(null);
|
|
1716
|
-
const tabItems = useMemo(
|
|
1717
|
-
() => [
|
|
1718
|
-
{ value: "upload", label: "Upload" },
|
|
1719
|
-
{ value: "url", label: "URL" }
|
|
1720
|
-
],
|
|
1721
|
-
[]
|
|
1722
|
-
);
|
|
1723
|
-
const pushToast = useCallback(
|
|
1724
|
-
(kind, message) => {
|
|
1725
|
-
setToast({ kind, message });
|
|
1726
|
-
if (toastTimerRef.current) {
|
|
1727
|
-
window.clearTimeout(toastTimerRef.current);
|
|
1728
|
-
}
|
|
1729
|
-
toastTimerRef.current = window.setTimeout(() => setToast(null), 2200);
|
|
1730
|
-
},
|
|
1731
|
-
[]
|
|
1732
|
-
);
|
|
1733
|
-
const insertByUpload = useCallback(
|
|
1734
|
-
async (file, options) => {
|
|
1735
|
-
if (!isImageFile(file)) return false;
|
|
1736
|
-
const closeDialog = Boolean(options?.closeDialog);
|
|
1737
|
-
setPendingUploads((value) => value + 1);
|
|
1738
|
-
if (closeDialog) setDialogUploading(true);
|
|
1739
|
-
try {
|
|
1740
|
-
const [result, meta] = await Promise.all([
|
|
1741
|
-
uploadRef.current(file),
|
|
1742
|
-
computeImageMeta(file)
|
|
1743
|
-
]);
|
|
1744
|
-
editor.update(() => {
|
|
1745
|
-
const node = $createImageNode({
|
|
1746
|
-
src: result.src,
|
|
1747
|
-
altText: result.altText ?? file.name,
|
|
1748
|
-
width: result.width ?? meta.width,
|
|
1749
|
-
height: result.height ?? meta.height,
|
|
1750
|
-
thumbhash: result.thumbhash ?? meta.thumbhash
|
|
1751
|
-
});
|
|
1752
|
-
$insertNodes([node]);
|
|
1753
|
-
});
|
|
1754
|
-
if (closeDialog) {
|
|
1755
|
-
setDialogOpen(false);
|
|
1756
|
-
setUrlInput("");
|
|
1757
|
-
setUrlPreview(null);
|
|
1758
|
-
setUrlMeta(null);
|
|
1759
|
-
setUrlError(null);
|
|
1760
|
-
}
|
|
1761
|
-
pushToast("success", "Image uploaded");
|
|
1762
|
-
return true;
|
|
1763
|
-
} catch (err) {
|
|
1764
|
-
console.error("[ImageUploadPlugin]", err);
|
|
1765
|
-
pushToast("error", "Image upload failed");
|
|
1766
|
-
return false;
|
|
1767
|
-
} finally {
|
|
1768
|
-
setPendingUploads((value) => Math.max(value - 1, 0));
|
|
1769
|
-
setDialogUploading(false);
|
|
1770
|
-
}
|
|
1771
|
-
},
|
|
1772
|
-
[editor, pushToast]
|
|
1773
|
-
);
|
|
1774
|
-
const handleFiles = useCallback(
|
|
1775
|
-
(files) => {
|
|
1776
|
-
const images = files.filter(isImageFile);
|
|
1777
|
-
if (images.length === 0) return false;
|
|
1778
|
-
for (const file of images) {
|
|
1779
|
-
void insertByUpload(file);
|
|
1780
|
-
}
|
|
1781
|
-
return true;
|
|
1782
|
-
},
|
|
1783
|
-
[insertByUpload]
|
|
1784
|
-
);
|
|
1785
|
-
useEffect(() => {
|
|
1786
|
-
const unregisterDragDrop = editor.registerCommand(
|
|
1787
|
-
DRAG_DROP_PASTE,
|
|
1788
|
-
(files) => handleFiles(files),
|
|
1789
|
-
COMMAND_PRIORITY_HIGH
|
|
1790
|
-
);
|
|
1791
|
-
const unregisterPaste = editor.registerCommand(
|
|
1792
|
-
PASTE_COMMAND,
|
|
1793
|
-
(event) => {
|
|
1794
|
-
const clipboardData = "clipboardData" in event ? event.clipboardData : null;
|
|
1795
|
-
if (!clipboardData) return false;
|
|
1796
|
-
const files = Array.from(clipboardData.files);
|
|
1797
|
-
if (files.some(isImageFile)) {
|
|
1798
|
-
return handleFiles(files);
|
|
1799
|
-
}
|
|
1800
|
-
return false;
|
|
1801
|
-
},
|
|
1802
|
-
COMMAND_PRIORITY_HIGH
|
|
1803
|
-
);
|
|
1804
|
-
const unregisterOpenDialog = editor.registerCommand(
|
|
1805
|
-
OPEN_IMAGE_UPLOAD_DIALOG_COMMAND,
|
|
1806
|
-
() => {
|
|
1807
|
-
setDialogOpen(true);
|
|
1808
|
-
return true;
|
|
1809
|
-
},
|
|
1810
|
-
COMMAND_PRIORITY_EDITOR
|
|
1811
|
-
);
|
|
1812
|
-
const rootElement = editor.getRootElement();
|
|
1813
|
-
const wrapper = rootElement?.parentElement ?? null;
|
|
1814
|
-
if (!wrapper) {
|
|
1815
|
-
return () => {
|
|
1816
|
-
unregisterDragDrop();
|
|
1817
|
-
unregisterPaste();
|
|
1818
|
-
unregisterOpenDialog();
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
let dragCounter = 0;
|
|
1822
|
-
const setWrapperDragging = (next) => {
|
|
1823
|
-
setRootDragActive(next);
|
|
1824
|
-
wrapper.classList.toggle(draggingWrapperClass, next);
|
|
1825
|
-
};
|
|
1826
|
-
const onDragEnter = (event) => {
|
|
1827
|
-
if (!hasImageData(event.dataTransfer)) return;
|
|
1828
|
-
dragCounter += 1;
|
|
1829
|
-
setWrapperDragging(true);
|
|
1830
|
-
};
|
|
1831
|
-
const onDragOver = (event) => {
|
|
1832
|
-
if (!hasImageData(event.dataTransfer)) return;
|
|
1833
|
-
event.preventDefault();
|
|
1834
|
-
};
|
|
1835
|
-
const onDragLeave = () => {
|
|
1836
|
-
dragCounter = Math.max(dragCounter - 1, 0);
|
|
1837
|
-
if (dragCounter === 0) {
|
|
1838
|
-
setWrapperDragging(false);
|
|
1839
|
-
}
|
|
1840
|
-
};
|
|
1841
|
-
const onDrop = () => {
|
|
1842
|
-
dragCounter = 0;
|
|
1843
|
-
setWrapperDragging(false);
|
|
1844
|
-
};
|
|
1845
|
-
rootElement?.addEventListener("dragenter", onDragEnter);
|
|
1846
|
-
rootElement?.addEventListener("dragover", onDragOver);
|
|
1847
|
-
rootElement?.addEventListener("dragleave", onDragLeave);
|
|
1848
|
-
rootElement?.addEventListener("drop", onDrop);
|
|
1849
|
-
return () => {
|
|
1850
|
-
if (toastTimerRef.current) {
|
|
1851
|
-
window.clearTimeout(toastTimerRef.current);
|
|
1852
|
-
}
|
|
1853
|
-
unregisterDragDrop();
|
|
1854
|
-
unregisterPaste();
|
|
1855
|
-
unregisterOpenDialog();
|
|
1856
|
-
setWrapperDragging(false);
|
|
1857
|
-
rootElement?.removeEventListener("dragenter", onDragEnter);
|
|
1858
|
-
rootElement?.removeEventListener("dragover", onDragOver);
|
|
1859
|
-
rootElement?.removeEventListener("dragleave", onDragLeave);
|
|
1860
|
-
rootElement?.removeEventListener("drop", onDrop);
|
|
1861
|
-
};
|
|
1862
|
-
}, [editor, handleFiles]);
|
|
1863
|
-
const resetUrlState = useCallback(() => {
|
|
1864
|
-
setUrlInput("");
|
|
1865
|
-
setUrlPreview(null);
|
|
1866
|
-
setUrlMeta(null);
|
|
1867
|
-
setUrlError(null);
|
|
1868
|
-
setUrlLoading(false);
|
|
1869
|
-
}, []);
|
|
1870
|
-
const handleDialogFile = useCallback(
|
|
1871
|
-
async (file) => {
|
|
1872
|
-
if (!file) return;
|
|
1873
|
-
await insertByUpload(file, { closeDialog: true });
|
|
1874
|
-
},
|
|
1875
|
-
[insertByUpload]
|
|
1876
|
-
);
|
|
1877
|
-
const handleUrlPreview = useCallback(async () => {
|
|
1878
|
-
const nextUrl = urlInput.trim();
|
|
1879
|
-
if (!nextUrl) return;
|
|
1880
|
-
if (!isSafeImageUrl(nextUrl)) {
|
|
1881
|
-
setUrlError("Unsupported URL scheme");
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
setUrlLoading(true);
|
|
1885
|
-
setUrlError(null);
|
|
1886
|
-
try {
|
|
1887
|
-
const meta = await loadImageByUrl(nextUrl);
|
|
1888
|
-
setUrlMeta(meta);
|
|
1889
|
-
setUrlPreview(nextUrl);
|
|
1890
|
-
} catch {
|
|
1891
|
-
setUrlPreview(null);
|
|
1892
|
-
setUrlMeta(null);
|
|
1893
|
-
setUrlError("Could not load this image URL");
|
|
1894
|
-
} finally {
|
|
1895
|
-
setUrlLoading(false);
|
|
1896
|
-
}
|
|
1897
|
-
}, [urlInput]);
|
|
1898
|
-
const handleInsertByUrl = useCallback(() => {
|
|
1899
|
-
if (!urlPreview$1 || !isSafeImageUrl(urlPreview$1)) return;
|
|
1900
|
-
editor.update(() => {
|
|
1901
|
-
const node = $createImageNode({
|
|
1902
|
-
src: urlPreview$1,
|
|
1903
|
-
altText: "",
|
|
1904
|
-
width: urlMeta?.width,
|
|
1905
|
-
height: urlMeta?.height
|
|
1906
|
-
});
|
|
1907
|
-
$insertNodes([node]);
|
|
1908
|
-
});
|
|
1909
|
-
pushToast("success", "Image inserted");
|
|
1910
|
-
setDialogOpen(false);
|
|
1911
|
-
resetUrlState();
|
|
1912
|
-
}, [editor, pushToast, resetUrlState, urlMeta, urlPreview$1]);
|
|
1913
|
-
const helperMessage = pendingUploads > 0 ? `Uploading ${pendingUploads} image${pendingUploads > 1 ? "s" : ""}...` : rootDragActive ? "Drop image files to upload" : null;
|
|
1914
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1915
|
-
(helperMessage || toast$1) && /* @__PURE__ */ jsxs("div", { className: toastStack, children: [
|
|
1916
|
-
helperMessage && /* @__PURE__ */ jsxs("div", { className: `${toast} ${toastVariant.info}`, children: [
|
|
1917
|
-
/* @__PURE__ */ jsx("span", { className: spinner }),
|
|
1918
|
-
helperMessage
|
|
1919
|
-
] }),
|
|
1920
|
-
toast$1 && /* @__PURE__ */ jsxs("div", { className: `${toast} ${toastVariant[toast$1.kind]}`, children: [
|
|
1921
|
-
toast$1.kind === "success" ? /* @__PURE__ */ jsx(Check, { size: 12 }) : /* @__PURE__ */ jsx(Info, { size: 12 }),
|
|
1922
|
-
toast$1.message
|
|
1923
|
-
] })
|
|
1924
|
-
] }),
|
|
1925
|
-
/* @__PURE__ */ jsx(
|
|
1926
|
-
Dialog,
|
|
1927
|
-
{
|
|
1928
|
-
open: dialogOpen,
|
|
1929
|
-
onOpenChange: (nextOpen) => {
|
|
1930
|
-
if (!nextOpen && dialogUploading) return;
|
|
1931
|
-
setDialogOpen(nextOpen);
|
|
1932
|
-
if (!nextOpen) {
|
|
1933
|
-
resetUrlState();
|
|
1934
|
-
setDialogUploading(false);
|
|
1935
|
-
setDialogDragActive(false);
|
|
1936
|
-
}
|
|
1937
|
-
},
|
|
1938
|
-
children: /* @__PURE__ */ jsxs(
|
|
1939
|
-
DialogPopup,
|
|
1940
|
-
{
|
|
1941
|
-
className: dialogPopup,
|
|
1942
|
-
showCloseButton: !dialogUploading,
|
|
1943
|
-
children: [
|
|
1944
|
-
/* @__PURE__ */ jsx("div", { className: dialogHeader, children: /* @__PURE__ */ jsx(DialogTitle, { className: dialogTitle, children: "Insert image" }) }),
|
|
1945
|
-
/* @__PURE__ */ jsxs("div", { className: dialogBody, children: [
|
|
1946
|
-
/* @__PURE__ */ jsx("div", { className: tabWrap, children: /* @__PURE__ */ jsx(
|
|
1947
|
-
SegmentedControl,
|
|
1948
|
-
{
|
|
1949
|
-
items: tabItems,
|
|
1950
|
-
value: tab,
|
|
1951
|
-
onChange: setTab,
|
|
1952
|
-
fullWidth: true
|
|
1953
|
-
}
|
|
1954
|
-
) }),
|
|
1955
|
-
tab === "upload" ? /* @__PURE__ */ jsxs(
|
|
1956
|
-
"div",
|
|
1957
|
-
{
|
|
1958
|
-
role: "button",
|
|
1959
|
-
tabIndex: 0,
|
|
1960
|
-
className: `${uploadDropzone} ${uploadDropzoneState[dialogUploading ? "busy" : dialogDragActive ? "active" : "idle"]}`,
|
|
1961
|
-
onClick: () => {
|
|
1962
|
-
if (!dialogUploading) fileInputRef.current?.click();
|
|
1963
|
-
},
|
|
1964
|
-
onKeyDown: (event) => {
|
|
1965
|
-
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1966
|
-
event.preventDefault();
|
|
1967
|
-
if (!dialogUploading) fileInputRef.current?.click();
|
|
1968
|
-
},
|
|
1969
|
-
onDragEnter: (event) => {
|
|
1970
|
-
if (hasImageData(event.dataTransfer)) {
|
|
1971
|
-
event.preventDefault();
|
|
1972
|
-
setDialogDragActive(true);
|
|
1973
|
-
}
|
|
1974
|
-
},
|
|
1975
|
-
onDragOver: (event) => {
|
|
1976
|
-
if (hasImageData(event.dataTransfer)) {
|
|
1977
|
-
event.preventDefault();
|
|
1978
|
-
}
|
|
1979
|
-
},
|
|
1980
|
-
onDragLeave: () => setDialogDragActive(false),
|
|
1981
|
-
onDrop: (event) => {
|
|
1982
|
-
event.preventDefault();
|
|
1983
|
-
setDialogDragActive(false);
|
|
1984
|
-
const file = Array.from(event.dataTransfer.files).find(
|
|
1985
|
-
isImageFile
|
|
1986
|
-
);
|
|
1987
|
-
void handleDialogFile(file ?? null);
|
|
1988
|
-
},
|
|
1989
|
-
children: [
|
|
1990
|
-
/* @__PURE__ */ jsx(
|
|
1991
|
-
"input",
|
|
1992
|
-
{
|
|
1993
|
-
ref: fileInputRef,
|
|
1994
|
-
className: hiddenInput,
|
|
1995
|
-
type: "file",
|
|
1996
|
-
accept: "image/*",
|
|
1997
|
-
onChange: (event) => {
|
|
1998
|
-
const file = event.currentTarget.files?.[0] ?? null;
|
|
1999
|
-
void handleDialogFile(file);
|
|
2000
|
-
event.currentTarget.value = "";
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
),
|
|
2004
|
-
dialogUploading ? /* @__PURE__ */ jsx("div", { className: uploadBusyWrap, children: /* @__PURE__ */ jsxs("div", { className: uploadProgress, children: [
|
|
2005
|
-
/* @__PURE__ */ jsx("span", { className: spinner }),
|
|
2006
|
-
"Uploading image..."
|
|
2007
|
-
] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2008
|
-
/* @__PURE__ */ jsx("span", { className: uploadDropIcon, children: /* @__PURE__ */ jsx(Upload, { size: 18 }) }),
|
|
2009
|
-
/* @__PURE__ */ jsx("span", { className: uploadDropTitle, children: "Click to upload or drag and drop" }),
|
|
2010
|
-
/* @__PURE__ */ jsx("span", { className: uploadDropDesc, children: "PNG, JPG, GIF, WebP" })
|
|
2011
|
-
] })
|
|
2012
|
-
]
|
|
2013
|
-
}
|
|
2014
|
-
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2015
|
-
/* @__PURE__ */ jsxs("div", { className: urlInputRow, children: [
|
|
2016
|
-
/* @__PURE__ */ jsx(
|
|
2017
|
-
"input",
|
|
2018
|
-
{
|
|
2019
|
-
className: textInput,
|
|
2020
|
-
type: "url",
|
|
2021
|
-
placeholder: "https://example.com/image.jpg",
|
|
2022
|
-
value: urlInput,
|
|
2023
|
-
onChange: (event) => {
|
|
2024
|
-
setUrlInput(event.target.value);
|
|
2025
|
-
setUrlError(null);
|
|
2026
|
-
setUrlPreview(null);
|
|
2027
|
-
setUrlMeta(null);
|
|
2028
|
-
},
|
|
2029
|
-
onKeyDown: (event) => {
|
|
2030
|
-
if (event.key === "Enter") {
|
|
2031
|
-
event.preventDefault();
|
|
2032
|
-
if (urlPreview$1) {
|
|
2033
|
-
handleInsertByUrl();
|
|
2034
|
-
} else {
|
|
2035
|
-
void handleUrlPreview();
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
),
|
|
2041
|
-
/* @__PURE__ */ jsx(
|
|
2042
|
-
"button",
|
|
2043
|
-
{
|
|
2044
|
-
type: "button",
|
|
2045
|
-
className: secondaryButton,
|
|
2046
|
-
disabled: urlLoading || !urlInput.trim(),
|
|
2047
|
-
onClick: () => void handleUrlPreview(),
|
|
2048
|
-
children: urlLoading ? "Loading" : "Preview"
|
|
2049
|
-
}
|
|
2050
|
-
),
|
|
2051
|
-
/* @__PURE__ */ jsx(
|
|
2052
|
-
"button",
|
|
2053
|
-
{
|
|
2054
|
-
type: "button",
|
|
2055
|
-
className: actionButton,
|
|
2056
|
-
disabled: !urlPreview$1,
|
|
2057
|
-
onClick: handleInsertByUrl,
|
|
2058
|
-
children: "Insert"
|
|
2059
|
-
}
|
|
2060
|
-
)
|
|
2061
|
-
] }),
|
|
2062
|
-
urlError && /* @__PURE__ */ jsxs(
|
|
2063
|
-
"span",
|
|
2064
|
-
{
|
|
2065
|
-
className: `${helperText} ${toastVariant.error}`,
|
|
2066
|
-
children: [
|
|
2067
|
-
/* @__PURE__ */ jsx(Info, { size: 12 }),
|
|
2068
|
-
urlError
|
|
2069
|
-
]
|
|
2070
|
-
}
|
|
2071
|
-
),
|
|
2072
|
-
urlPreview$1 && /* @__PURE__ */ jsx("div", { className: urlPreview, children: /* @__PURE__ */ jsx(
|
|
2073
|
-
"img",
|
|
2074
|
-
{
|
|
2075
|
-
className: urlPreviewImage,
|
|
2076
|
-
src: urlPreview$1,
|
|
2077
|
-
alt: "Preview"
|
|
2078
|
-
}
|
|
2079
|
-
) })
|
|
2080
|
-
] })
|
|
2081
|
-
] }),
|
|
2082
|
-
/* @__PURE__ */ jsxs("div", { className: dialogFooter, children: [
|
|
2083
|
-
/* @__PURE__ */ jsxs("span", { className: helperText, children: [
|
|
2084
|
-
/* @__PURE__ */ jsx(Link2, { size: 12 }),
|
|
2085
|
-
"You can also paste images or drag files directly into the editor."
|
|
2086
|
-
] }),
|
|
2087
|
-
/* @__PURE__ */ jsx("div", { className: dialogFooterActions, children: /* @__PURE__ */ jsx(
|
|
2088
|
-
"button",
|
|
2089
|
-
{
|
|
2090
|
-
type: "button",
|
|
2091
|
-
className: secondaryButton,
|
|
2092
|
-
onClick: () => setDialogOpen(false),
|
|
2093
|
-
disabled: dialogUploading,
|
|
2094
|
-
children: "Close"
|
|
2095
|
-
}
|
|
2096
|
-
) })
|
|
2097
|
-
] })
|
|
2098
|
-
]
|
|
2099
|
-
}
|
|
2100
|
-
)
|
|
2101
|
-
}
|
|
2102
|
-
)
|
|
2103
|
-
] });
|
|
2104
|
-
}
|
|
2105
|
-
const INSERT_KATEX_INLINE_COMMAND = createCommand(
|
|
2106
|
-
"INSERT_KATEX_INLINE"
|
|
2107
|
-
);
|
|
2108
|
-
const INSERT_KATEX_BLOCK_COMMAND = createCommand("INSERT_KATEX_BLOCK");
|
|
2109
|
-
function KaTeXPlugin() {
|
|
2110
|
-
const [editor] = useLexicalComposerContext();
|
|
2111
|
-
useEffect(() => {
|
|
2112
|
-
const unregisterInline = editor.registerCommand(
|
|
2113
|
-
INSERT_KATEX_INLINE_COMMAND,
|
|
2114
|
-
(equation) => {
|
|
2115
|
-
const node = $createKaTeXInlineNode(equation);
|
|
2116
|
-
$insertNodes([node]);
|
|
2117
|
-
return true;
|
|
2118
|
-
},
|
|
2119
|
-
COMMAND_PRIORITY_EDITOR
|
|
2120
|
-
);
|
|
2121
|
-
const unregisterBlock = editor.registerCommand(
|
|
2122
|
-
INSERT_KATEX_BLOCK_COMMAND,
|
|
2123
|
-
(equation) => {
|
|
2124
|
-
const node = $createKaTeXBlockNode(equation);
|
|
2125
|
-
$insertNodes([node]);
|
|
2126
|
-
return true;
|
|
2127
|
-
},
|
|
2128
|
-
COMMAND_PRIORITY_EDITOR
|
|
2129
|
-
);
|
|
2130
|
-
return () => {
|
|
2131
|
-
unregisterInline();
|
|
2132
|
-
unregisterBlock();
|
|
2133
|
-
};
|
|
2134
|
-
}, [editor]);
|
|
2135
|
-
return null;
|
|
2136
|
-
}
|
|
2137
|
-
function applyFavicon(dom, href) {
|
|
2138
|
-
if (dom.dataset.faviconHref === href) return;
|
|
2139
|
-
dom.dataset.faviconHref = href;
|
|
2140
|
-
const hostname = getHostname(href);
|
|
2141
|
-
if (!hostname) return;
|
|
2142
|
-
probeFavicon(hostname).then((faviconUrl) => {
|
|
2143
|
-
if (faviconUrl) {
|
|
2144
|
-
dom.style.setProperty("--rc-link-favicon", `url(${faviconUrl})`);
|
|
2145
|
-
dom.dataset.favicon = "loaded";
|
|
2146
|
-
}
|
|
2147
|
-
});
|
|
2148
|
-
}
|
|
2149
|
-
function LinkFaviconPlugin() {
|
|
2150
|
-
const [editor] = useLexicalComposerContext();
|
|
2151
|
-
useEffect(() => {
|
|
2152
|
-
const handleMutations = (mutations) => {
|
|
2153
|
-
for (const [nodeKey, mutation] of mutations) {
|
|
2154
|
-
if (mutation === "destroyed") continue;
|
|
2155
|
-
const dom = editor.getElementByKey(nodeKey);
|
|
2156
|
-
if (!dom) continue;
|
|
2157
|
-
const href = dom.getAttribute("href");
|
|
2158
|
-
if (href) applyFavicon(dom, href);
|
|
2159
|
-
}
|
|
2160
|
-
};
|
|
2161
|
-
const unregisterLink = editor.registerMutationListener(
|
|
2162
|
-
LinkNode,
|
|
2163
|
-
handleMutations
|
|
2164
|
-
);
|
|
2165
|
-
const unregisterAutoLink = editor.registerMutationListener(
|
|
2166
|
-
AutoLinkNode,
|
|
2167
|
-
handleMutations
|
|
2168
|
-
);
|
|
2169
|
-
return () => {
|
|
2170
|
-
unregisterLink();
|
|
2171
|
-
unregisterAutoLink();
|
|
2172
|
-
};
|
|
2173
|
-
}, [editor]);
|
|
2174
|
-
return null;
|
|
2175
|
-
}
|
|
2176
|
-
const ALERT_TYPE_MAP = {
|
|
2177
|
-
NOTE: "note",
|
|
2178
|
-
TIP: "tip",
|
|
2179
|
-
IMPORTANT: "important",
|
|
2180
|
-
WARNING: "warning",
|
|
2181
|
-
CAUTION: "caution"
|
|
2182
|
-
};
|
|
2183
|
-
const GIT_ALERT_TRANSFORMER = {
|
|
2184
|
-
...GIT_ALERT_TRANSFORMER$1,
|
|
2185
|
-
dependencies: [AlertQuoteNode],
|
|
2186
|
-
regExp: /^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/,
|
|
2187
|
-
replace: (parentNode, children, match) => {
|
|
2188
|
-
const typeKey = match[1];
|
|
2189
|
-
const alertType = ALERT_TYPE_MAP[typeKey] || "note";
|
|
2190
|
-
const serializedChildren = children.map((child) => child.exportJSON());
|
|
2191
|
-
const content2 = {
|
|
2192
|
-
root: {
|
|
2193
|
-
children: [
|
|
2194
|
-
{
|
|
2195
|
-
type: "paragraph",
|
|
2196
|
-
children: serializedChildren,
|
|
2197
|
-
direction: null,
|
|
2198
|
-
format: "",
|
|
2199
|
-
indent: 0,
|
|
2200
|
-
textFormat: 0,
|
|
2201
|
-
textStyle: "",
|
|
2202
|
-
version: 1
|
|
2203
|
-
}
|
|
2204
|
-
],
|
|
2205
|
-
direction: null,
|
|
2206
|
-
format: "",
|
|
2207
|
-
indent: 0,
|
|
2208
|
-
type: "root",
|
|
2209
|
-
version: 1
|
|
2210
|
-
}
|
|
2211
|
-
};
|
|
2212
|
-
const alertNode = $createAlertQuoteEditNode(alertType, content2);
|
|
2213
|
-
parentNode.replace(alertNode);
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
function findCodeBlockKlass(nodes) {
|
|
2217
|
-
return nodes.find((n) => n.getType?.() === "code-block") || CodeBlockNode;
|
|
2218
|
-
}
|
|
2219
|
-
const CODE_BLOCK_MULTILINE_TRANSFORMER = {
|
|
2220
|
-
dependencies: [],
|
|
2221
|
-
regExpEnd: {
|
|
2222
|
-
optional: true,
|
|
2223
|
-
regExp: /[ \t]*```$/
|
|
2224
|
-
},
|
|
2225
|
-
regExpStart: /^[ \t]*```([\w-]+)?/,
|
|
2226
|
-
replace: (rootNode, _children, startMatch, _endMatch, linesInBetween) => {
|
|
2227
|
-
const lang = startMatch[1] || "";
|
|
2228
|
-
let code = "";
|
|
2229
|
-
if (linesInBetween) {
|
|
2230
|
-
const lines = [...linesInBetween];
|
|
2231
|
-
while (lines.length > 0 && !lines[0].length) lines.shift();
|
|
2232
|
-
while (lines.length > 0 && !lines.at(-1)?.length) lines.pop();
|
|
2233
|
-
code = lines.join("\n");
|
|
2234
|
-
}
|
|
2235
|
-
const Klass = findCodeBlockKlass(getResolvedEditNodes());
|
|
2236
|
-
const node = new Klass(code, lang);
|
|
2237
|
-
rootNode.replace(node);
|
|
2238
|
-
const selection = $createNodeSelection();
|
|
2239
|
-
selection.add(node.getKey());
|
|
2240
|
-
$setSelection(selection);
|
|
2241
|
-
},
|
|
2242
|
-
type: "multiline-element"
|
|
2243
|
-
};
|
|
2244
|
-
const BANNER_TYPE_MAP = {
|
|
2245
|
-
note: "note",
|
|
2246
|
-
info: "note",
|
|
2247
|
-
tip: "tip",
|
|
2248
|
-
success: "tip",
|
|
2249
|
-
important: "important",
|
|
2250
|
-
warning: "warning",
|
|
2251
|
-
warn: "warning",
|
|
2252
|
-
error: "caution",
|
|
2253
|
-
danger: "caution",
|
|
2254
|
-
caution: "caution"
|
|
2255
|
-
};
|
|
2256
|
-
const CONTAINER_TRANSFORMER = {
|
|
2257
|
-
...CONTAINER_TRANSFORMER$1,
|
|
2258
|
-
dependencies: [BannerNode, DetailsNode],
|
|
2259
|
-
regExp: /^:::\s*(\w+)(?:\{([^}]*)\})?\s*$/,
|
|
2260
|
-
replace: (parentNode, children, match) => {
|
|
2261
|
-
const type = match[1];
|
|
2262
|
-
const params = match[2];
|
|
2263
|
-
if (type in BANNER_TYPE_MAP) {
|
|
2264
|
-
const bannerType = BANNER_TYPE_MAP[type];
|
|
2265
|
-
const serializedChildren = children.map(
|
|
2266
|
-
(child) => child.exportJSON()
|
|
2267
|
-
);
|
|
2268
|
-
const content2 = {
|
|
2269
|
-
root: {
|
|
2270
|
-
children: [
|
|
2271
|
-
{
|
|
2272
|
-
type: "paragraph",
|
|
2273
|
-
children: serializedChildren,
|
|
2274
|
-
direction: null,
|
|
2275
|
-
format: "",
|
|
2276
|
-
indent: 0,
|
|
2277
|
-
textFormat: 0,
|
|
2278
|
-
textStyle: "",
|
|
2279
|
-
version: 1
|
|
2280
|
-
}
|
|
2281
|
-
],
|
|
2282
|
-
direction: null,
|
|
2283
|
-
format: "",
|
|
2284
|
-
indent: 0,
|
|
2285
|
-
type: "root",
|
|
2286
|
-
version: 1
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
const banner = $createBannerEditNode(bannerType, content2);
|
|
2290
|
-
parentNode.replace(banner);
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
if (type === "details") {
|
|
2294
|
-
const summaryMatch = params?.match(/summary="([^"]*)"/);
|
|
2295
|
-
const summary = summaryMatch ? summaryMatch[1] : "Details";
|
|
2296
|
-
const details = $createDetailsNode(summary);
|
|
2297
|
-
children.forEach((child) => {
|
|
2298
|
-
details.append(child);
|
|
2299
|
-
});
|
|
2300
|
-
parentNode.replace(details);
|
|
2301
|
-
return;
|
|
2302
|
-
}
|
|
2303
|
-
const paragraph = $createParagraphNode();
|
|
2304
|
-
paragraph.append($createTextNode(`::: ${type}`));
|
|
2305
|
-
children.forEach((child) => {
|
|
2306
|
-
paragraph.append(child);
|
|
2307
|
-
});
|
|
2308
|
-
parentNode.replace(paragraph);
|
|
2309
|
-
}
|
|
2310
|
-
};
|
|
2311
|
-
const FOOTNOTE_TRANSFORMER = {
|
|
2312
|
-
...FOOTNOTE_TRANSFORMER$1,
|
|
2313
|
-
dependencies: [FootnoteNode],
|
|
2314
|
-
replace: (textNode, match) => {
|
|
2315
|
-
const footnoteNode = $createFootnoteNode(match[1]);
|
|
2316
|
-
textNode.replace(footnoteNode);
|
|
2317
|
-
}
|
|
2318
|
-
};
|
|
2319
|
-
const FOOTNOTE_SECTION_TRANSFORMER = {
|
|
2320
|
-
...FOOTNOTE_SECTION_TRANSFORMER$1,
|
|
2321
|
-
dependencies: [FootnoteSectionNode],
|
|
2322
|
-
regExp: /^\[\^(\w+)\]:\s+(.+)$/,
|
|
2323
|
-
replace: (parentNode, _children, match) => {
|
|
2324
|
-
const identifier = match[1];
|
|
2325
|
-
const content2 = match[2];
|
|
2326
|
-
const root = parentNode.getParent();
|
|
2327
|
-
if (root) {
|
|
2328
|
-
const children = root.getChildren();
|
|
2329
|
-
for (const child of children) {
|
|
2330
|
-
if ($isFootnoteSectionNode(child)) {
|
|
2331
|
-
child.setDefinition(identifier, content2);
|
|
2332
|
-
parentNode.remove();
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
const sectionNode = $createFootnoteSectionNode({ [identifier]: content2 });
|
|
2338
|
-
parentNode.replace(sectionNode);
|
|
2339
|
-
}
|
|
2340
|
-
};
|
|
2341
|
-
const KATEX_INLINE_TRANSFORMER = {
|
|
2342
|
-
...KATEX_INLINE_TRANSFORMER$1,
|
|
2343
|
-
dependencies: [KaTeXInlineNode],
|
|
2344
|
-
replace: (textNode, match) => {
|
|
2345
|
-
const katexNode = $createKaTeXInlineNode(match[1]);
|
|
2346
|
-
textNode.replace(katexNode);
|
|
2347
|
-
}
|
|
2348
|
-
};
|
|
2349
|
-
const KATEX_BLOCK_TRANSFORMER = {
|
|
2350
|
-
...KATEX_BLOCK_TRANSFORMER$1,
|
|
2351
|
-
dependencies: [KaTeXBlockNode],
|
|
2352
|
-
replace: (textNode, match) => {
|
|
2353
|
-
const katexNode = $createKaTeXBlockNode(match[1]);
|
|
2354
|
-
textNode.replace(katexNode);
|
|
2355
|
-
}
|
|
2356
|
-
};
|
|
2357
|
-
const MENTION_TRANSFORMER = {
|
|
2358
|
-
...MENTION_TRANSFORMER$1,
|
|
2359
|
-
dependencies: [MentionNode],
|
|
2360
|
-
replace: (textNode, match) => {
|
|
2361
|
-
const displayName = match[1] || void 0;
|
|
2362
|
-
const mentionNode = $createMentionNode(match[2], match[3], displayName);
|
|
2363
|
-
textNode.replace(mentionNode);
|
|
2364
|
-
}
|
|
2365
|
-
};
|
|
2366
|
-
const QUOTE_TRANSFORMER = {
|
|
2367
|
-
dependencies: [QuoteNode],
|
|
2368
|
-
export: (node, exportChildren) => {
|
|
2369
|
-
if (!$isQuoteNode(node)) {
|
|
2370
|
-
return null;
|
|
2371
|
-
}
|
|
2372
|
-
const lines = exportChildren(node).split("\n");
|
|
2373
|
-
return lines.map((line) => `> ${line}`).join("\n");
|
|
2374
|
-
},
|
|
2375
|
-
regExp: /^>\s/,
|
|
2376
|
-
replace: (parentNode, children, _match, isImport) => {
|
|
2377
|
-
if (isImport) {
|
|
2378
|
-
const previousNode = parentNode.getPreviousSibling();
|
|
2379
|
-
if ($isQuoteNode(previousNode)) {
|
|
2380
|
-
previousNode.splice(previousNode.getChildrenSize(), 0, [
|
|
2381
|
-
$createLineBreakNode(),
|
|
2382
|
-
...children
|
|
2383
|
-
]);
|
|
2384
|
-
parentNode.remove();
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
const node = $createQuoteNode();
|
|
2389
|
-
const paragraph = $createParagraphNode();
|
|
2390
|
-
paragraph.append(...children);
|
|
2391
|
-
node.append(paragraph);
|
|
2392
|
-
parentNode.replace(node);
|
|
2393
|
-
if (!isImport) {
|
|
2394
|
-
paragraph.select(0, 0);
|
|
2395
|
-
}
|
|
2396
|
-
},
|
|
2397
|
-
type: "element"
|
|
2398
|
-
};
|
|
2399
|
-
function quoteAttr(value) {
|
|
2400
|
-
return value.replaceAll('"', '\\"');
|
|
2401
|
-
}
|
|
2402
|
-
const GRID_CONTAINER_BLOCK_TRANSFORMER = {
|
|
2403
|
-
dependencies: [],
|
|
2404
|
-
export: (node) => {
|
|
2405
|
-
if (!$isGridContainerNode(node)) return null;
|
|
2406
|
-
const cells = node.getCellStates().map((state) => extractTextContent(state));
|
|
2407
|
-
const body = cells.map((content2) => `::: cell
|
|
2408
|
-
${content2}
|
|
2409
|
-
:::`).join("\n");
|
|
2410
|
-
return `::: grid{cols=${node.getCols()} gap="${quoteAttr(node.getGap())}"}
|
|
2411
|
-
${body}
|
|
2412
|
-
:::`;
|
|
2413
|
-
},
|
|
2414
|
-
regExp: /a^/,
|
|
2415
|
-
replace: () => {
|
|
2416
|
-
},
|
|
2417
|
-
type: "element"
|
|
2418
|
-
};
|
|
2419
|
-
const RUBY_TRANSFORMER = {
|
|
2420
|
-
...RUBY_TRANSFORMER$1,
|
|
2421
|
-
dependencies: [RubyNode],
|
|
2422
|
-
replace: (textNode, match) => {
|
|
2423
|
-
const rubyNode = $createRubyNode(match[2] ?? "");
|
|
2424
|
-
const baseText = match[1] ?? "";
|
|
2425
|
-
if (baseText) {
|
|
2426
|
-
rubyNode.append($createTextNode(baseText));
|
|
2427
|
-
}
|
|
2428
|
-
textNode.replace(rubyNode);
|
|
2429
|
-
}
|
|
2430
|
-
};
|
|
2431
|
-
const SPOILER_TRANSFORMER = {
|
|
2432
|
-
...SPOILER_TRANSFORMER$1,
|
|
2433
|
-
dependencies: [SpoilerNode],
|
|
2434
|
-
replace: (textNode, match) => {
|
|
2435
|
-
const spoilerNode = $createSpoilerNode();
|
|
2436
|
-
spoilerNode.append($createTextNode(match[1]));
|
|
2437
|
-
textNode.replace(spoilerNode);
|
|
2438
|
-
}
|
|
2439
|
-
};
|
|
2440
|
-
const ALL_TRANSFORMERS = [
|
|
2441
|
-
// Inline transformers
|
|
2442
|
-
SPOILER_TRANSFORMER,
|
|
2443
|
-
MENTION_TRANSFORMER,
|
|
2444
|
-
FOOTNOTE_TRANSFORMER,
|
|
2445
|
-
INSERT_TRANSFORMER,
|
|
2446
|
-
SUPERSCRIPT_TRANSFORMER,
|
|
2447
|
-
SUBSCRIPT_TRANSFORMER,
|
|
2448
|
-
RUBY_TRANSFORMER,
|
|
2449
|
-
KATEX_INLINE_TRANSFORMER,
|
|
2450
|
-
// Block transformers (order matters - more specific first)
|
|
2451
|
-
FOOTNOTE_SECTION_TRANSFORMER,
|
|
2452
|
-
CONTAINER_TRANSFORMER,
|
|
2453
|
-
GIT_ALERT_TRANSFORMER,
|
|
2454
|
-
CHECK_LIST,
|
|
2455
|
-
KATEX_BLOCK_TRANSFORMER,
|
|
2456
|
-
IMAGE_BLOCK_TRANSFORMER,
|
|
2457
|
-
VIDEO_BLOCK_TRANSFORMER,
|
|
2458
|
-
CODE_BLOCK_NODE_TRANSFORMER,
|
|
2459
|
-
CODE_BLOCK_MULTILINE_TRANSFORMER,
|
|
2460
|
-
LINK_CARD_BLOCK_TRANSFORMER,
|
|
2461
|
-
MERMAID_BLOCK_TRANSFORMER,
|
|
2462
|
-
GRID_CONTAINER_BLOCK_TRANSFORMER,
|
|
2463
|
-
HORIZONTAL_RULE_BLOCK_TRANSFORMER,
|
|
2464
|
-
TABLE_BLOCK_TRANSFORMER,
|
|
2465
|
-
QUOTE_TRANSFORMER,
|
|
2466
|
-
...TRANSFORMERS.filter((t) => t !== QUOTE && t !== CODE)
|
|
2467
|
-
];
|
|
2468
|
-
function getVSCodePasteData(clipboardData) {
|
|
2469
|
-
const raw = clipboardData.getData("vscode-editor-data");
|
|
2470
|
-
if (!raw) return null;
|
|
2471
|
-
try {
|
|
2472
|
-
const data = JSON.parse(raw);
|
|
2473
|
-
return { language: data.mode || "text" };
|
|
2474
|
-
} catch {
|
|
2475
|
-
return null;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
function hasRichHTML(clipboardData) {
|
|
2479
|
-
const html = clipboardData.getData("text/html");
|
|
2480
|
-
if (!html) return false;
|
|
2481
|
-
if (/data-vscode|vscode-/i.test(html)) return false;
|
|
2482
|
-
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
2483
|
-
const richTags = doc.body.querySelectorAll(
|
|
2484
|
-
"strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]"
|
|
2485
|
-
);
|
|
2486
|
-
return richTags.length > 0;
|
|
2487
|
-
}
|
|
2488
|
-
function detectMarkdown(text) {
|
|
2489
|
-
let score = 0;
|
|
2490
|
-
if (/^#{1,6}\s+\S/m.test(text)) score += 5;
|
|
2491
|
-
if (/^```[\w-]*$/m.test(text)) score += 5;
|
|
2492
|
-
if (/\[.+?\]\(.+?\)/.test(text)) score += 4;
|
|
2493
|
-
if (/!\[.*?\]\(.+?\)/.test(text)) score += 5;
|
|
2494
|
-
if (/^\|.+\|$/m.test(text) && /^\|[-:\s|]+\|$/m.test(text)) score += 5;
|
|
2495
|
-
if (/^>\s*\[!(?:NOTE|TIP|WARNING|CAUTION|IMPORTANT)\]/im.test(text))
|
|
2496
|
-
score += 5;
|
|
2497
|
-
if (/^[-*]\s+\[[ x]\]/m.test(text)) score += 4;
|
|
2498
|
-
if (/\*\*.+?\*\*/.test(text)) score += 2;
|
|
2499
|
-
if (/(?<!\*)\*(?!\*)(?!\s).+?(?<!\s)(?<!\*)\*(?!\*)/.test(text)) score += 1;
|
|
2500
|
-
if (/^[-*+]\s+\S/m.test(text)) score += 1;
|
|
2501
|
-
if (/^\d+\.\s+\S/m.test(text)) score += 1;
|
|
2502
|
-
if (/^>\s+\S/m.test(text)) score += 1;
|
|
2503
|
-
if (/`.+?`/.test(text)) score += 1;
|
|
2504
|
-
if (/^[-*_]{3,}$/m.test(text)) score += 2;
|
|
2505
|
-
const paragraphs = text.split(/\n{2,}/).filter(Boolean);
|
|
2506
|
-
if (paragraphs.length >= 2) score += 5;
|
|
2507
|
-
if (text.length < 20) score -= 3;
|
|
2508
|
-
if (!text.includes("\n")) score -= 2;
|
|
2509
|
-
return score >= 5;
|
|
2510
|
-
}
|
|
2511
|
-
function convertAndInsert(markdown) {
|
|
2512
|
-
const tempEditor = createEditor({
|
|
2513
|
-
namespace: "markdown-paste-temp",
|
|
2514
|
-
nodes: getResolvedEditNodes(),
|
|
2515
|
-
onError: () => {
|
|
2516
|
-
}
|
|
2517
|
-
});
|
|
2518
|
-
tempEditor.update(
|
|
2519
|
-
() => {
|
|
2520
|
-
$convertFromMarkdownString(markdown, ALL_TRANSFORMERS);
|
|
2521
|
-
},
|
|
2522
|
-
{ discrete: true }
|
|
2523
|
-
);
|
|
2524
|
-
const serializedChildren = tempEditor.getEditorState().toJSON().root.children;
|
|
2525
|
-
const nodes = serializedChildren.map(
|
|
2526
|
-
(s) => $parseSerializedNode(s)
|
|
2527
|
-
);
|
|
2528
|
-
$insertNodes(nodes);
|
|
2529
|
-
}
|
|
2530
|
-
function MarkdownPastePlugin() {
|
|
2531
|
-
const [editor] = useLexicalComposerContext();
|
|
2532
|
-
useEffect(() => {
|
|
2533
|
-
return editor.registerCommand(
|
|
2534
|
-
PASTE_COMMAND,
|
|
2535
|
-
(event) => {
|
|
2536
|
-
const clipboardData = "clipboardData" in event ? event.clipboardData : null;
|
|
2537
|
-
if (!clipboardData) return false;
|
|
2538
|
-
if (Array.from(clipboardData.files).some(
|
|
2539
|
-
(f) => f.type.startsWith("image/")
|
|
2540
|
-
))
|
|
2541
|
-
return false;
|
|
2542
|
-
const vscodeData = getVSCodePasteData(clipboardData);
|
|
2543
|
-
if (vscodeData) {
|
|
2544
|
-
const code = clipboardData.getData("text/plain");
|
|
2545
|
-
if (code) {
|
|
2546
|
-
const segments = code.split(/\r?\n{3,}/).filter(Boolean);
|
|
2547
|
-
const nodes = segments.map(
|
|
2548
|
-
(s) => $createCodeBlockEditNode(
|
|
2549
|
-
s.replace(/^\s*\n/, "").replace(/\n\s*$/, ""),
|
|
2550
|
-
vscodeData.language
|
|
2551
|
-
)
|
|
2552
|
-
);
|
|
2553
|
-
$insertNodes(nodes);
|
|
2554
|
-
event.preventDefault();
|
|
2555
|
-
return true;
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
if (hasRichHTML(clipboardData)) return false;
|
|
2559
|
-
const text = clipboardData.getData("text/plain");
|
|
2560
|
-
if (!text || !detectMarkdown(text)) return false;
|
|
2561
|
-
try {
|
|
2562
|
-
event.preventDefault();
|
|
2563
|
-
convertAndInsert(text);
|
|
2564
|
-
return true;
|
|
2565
|
-
} catch {
|
|
2566
|
-
return false;
|
|
2567
|
-
}
|
|
2568
|
-
},
|
|
2569
|
-
COMMAND_PRIORITY_HIGH
|
|
2570
|
-
);
|
|
2571
|
-
}, [editor]);
|
|
2572
|
-
return null;
|
|
2573
|
-
}
|
|
2574
|
-
function MarkdownShortcutsPlugin() {
|
|
2575
|
-
return /* @__PURE__ */ jsx(MarkdownShortcutPlugin, { transformers: ALL_TRANSFORMERS });
|
|
2576
|
-
}
|
|
2577
|
-
const INSERT_MERMAID_COMMAND = createCommand("INSERT_MERMAID");
|
|
2578
|
-
function MermaidPlugin() {
|
|
2579
|
-
const [editor] = useLexicalComposerContext();
|
|
2580
|
-
useEffect(() => {
|
|
2581
|
-
return editor.registerCommand(
|
|
2582
|
-
INSERT_MERMAID_COMMAND,
|
|
2583
|
-
(diagram) => {
|
|
2584
|
-
const node = $createMermaidNode(diagram);
|
|
2585
|
-
$insertNodes([node]);
|
|
2586
|
-
return true;
|
|
2587
|
-
},
|
|
2588
|
-
COMMAND_PRIORITY_EDITOR
|
|
2589
|
-
);
|
|
2590
|
-
}, [editor]);
|
|
2591
|
-
return null;
|
|
2592
|
-
}
|
|
2593
|
-
function OnChangePlugin({ onChange, debounceMs }) {
|
|
2594
|
-
const [editor] = useLexicalComposerContext();
|
|
2595
|
-
const timerRef = useRef(void 0);
|
|
2596
|
-
const onChangeRef = useRef(onChange);
|
|
2597
|
-
onChangeRef.current = onChange;
|
|
2598
|
-
useEffect(() => {
|
|
2599
|
-
const unregister = editor.registerUpdateListener(({ editorState }) => {
|
|
2600
|
-
const fn = onChangeRef.current;
|
|
2601
|
-
if (!fn) return;
|
|
2602
|
-
if (debounceMs && debounceMs > 0) {
|
|
2603
|
-
clearTimeout(timerRef.current);
|
|
2604
|
-
timerRef.current = setTimeout(() => {
|
|
2605
|
-
fn(editorState.toJSON());
|
|
2606
|
-
}, debounceMs);
|
|
2607
|
-
} else {
|
|
2608
|
-
fn(editorState.toJSON());
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
return () => {
|
|
2612
|
-
clearTimeout(timerRef.current);
|
|
2613
|
-
unregister();
|
|
2614
|
-
};
|
|
2615
|
-
}, [editor, debounceMs]);
|
|
2616
|
-
return null;
|
|
2617
|
-
}
|
|
2618
|
-
function SubmitShortcutPlugin({ onSubmit }) {
|
|
2619
|
-
const [editor] = useLexicalComposerContext();
|
|
2620
|
-
useEffect(() => {
|
|
2621
|
-
if (!onSubmit) return;
|
|
2622
|
-
return editor.registerCommand(
|
|
2623
|
-
KEY_ENTER_COMMAND,
|
|
2624
|
-
(event) => {
|
|
2625
|
-
if (event && (event.metaKey || event.ctrlKey)) {
|
|
2626
|
-
event.preventDefault();
|
|
2627
|
-
onSubmit();
|
|
2628
|
-
return true;
|
|
2629
|
-
}
|
|
2630
|
-
return false;
|
|
2631
|
-
},
|
|
2632
|
-
COMMAND_PRIORITY_HIGH
|
|
2633
|
-
);
|
|
2634
|
-
}, [editor, onSubmit]);
|
|
2635
|
-
return null;
|
|
2636
|
-
}
|
|
2637
|
-
var contentWrapper = "b94s950";
|
|
2638
|
-
var content = "b94s951";
|
|
2639
|
-
var placeholder = "b94s952";
|
|
2640
|
-
function ContentEditable({
|
|
2641
|
-
className,
|
|
2642
|
-
placeholder: placeholder$1,
|
|
2643
|
-
hasHeader
|
|
2644
|
-
}) {
|
|
2645
|
-
const paddingTop = hasHeader ? 40 : 12;
|
|
2646
|
-
return /* @__PURE__ */ jsx(
|
|
2647
|
-
"div",
|
|
2648
|
-
{
|
|
2649
|
-
className: clsx("rich-editor__content-wrapper", contentWrapper),
|
|
2650
|
-
style: { "--ce-padding-top": `${paddingTop}px` },
|
|
2651
|
-
children: /* @__PURE__ */ jsx(
|
|
2652
|
-
ContentEditable$1,
|
|
2653
|
-
{
|
|
2654
|
-
className: clsx("rich-editor__content", content, className),
|
|
2655
|
-
"aria-placeholder": placeholder$1 ?? "",
|
|
2656
|
-
placeholder: /* @__PURE__ */ jsx(
|
|
2657
|
-
"div",
|
|
2658
|
-
{
|
|
2659
|
-
className: clsx("rich-editor__placeholder", placeholder),
|
|
2660
|
-
style: { display: placeholder$1 ? void 0 : "none" },
|
|
2661
|
-
children: placeholder$1
|
|
2662
|
-
}
|
|
2663
|
-
)
|
|
2664
|
-
}
|
|
2665
|
-
)
|
|
2666
|
-
}
|
|
2667
|
-
);
|
|
2668
|
-
}
|
|
2669
|
-
function RichEditor({
|
|
2670
|
-
initialValue,
|
|
2671
|
-
onChange,
|
|
2672
|
-
variant = "article",
|
|
2673
|
-
theme = "light",
|
|
2674
|
-
placeholder: placeholder2 = "Write something...",
|
|
2675
|
-
onSubmit,
|
|
2676
|
-
autoFocus = false,
|
|
2677
|
-
className,
|
|
2678
|
-
contentClassName,
|
|
2679
|
-
style,
|
|
2680
|
-
actions,
|
|
2681
|
-
header,
|
|
2682
|
-
onEditorReady,
|
|
2683
|
-
extraNodes,
|
|
2684
|
-
rendererConfig,
|
|
2685
|
-
imageUpload,
|
|
2686
|
-
debounceMs,
|
|
2687
|
-
children
|
|
2688
|
-
}) {
|
|
2689
|
-
const nodes = extraNodes ? [...allEditNodes, ...extraNodes] : allEditNodes;
|
|
2690
|
-
setResolvedEditNodes(nodes);
|
|
2691
|
-
const initialConfig = {
|
|
2692
|
-
namespace: "RichEditor",
|
|
2693
|
-
theme: editorTheme,
|
|
2694
|
-
nodes,
|
|
2695
|
-
editable: true,
|
|
2696
|
-
onError: (error) => {
|
|
2697
|
-
console.error("[RichEditor]", error);
|
|
2698
|
-
},
|
|
2699
|
-
...initialValue ? { editorState: JSON.stringify(initialValue) } : {}
|
|
2700
|
-
};
|
|
2701
|
-
const variantClass = getVariantClass(variant);
|
|
2702
|
-
const resolvedImageUpload = imageUpload ?? defaultImageUpload;
|
|
2703
|
-
return /* @__PURE__ */ jsx(PortalThemeProvider, { className: variantClass, theme, children: /* @__PURE__ */ jsx(ColorSchemeProvider, { colorScheme: theme, children: /* @__PURE__ */ jsx(
|
|
2704
|
-
RendererConfigProvider,
|
|
2705
|
-
{
|
|
2706
|
-
config: rendererConfig,
|
|
2707
|
-
mode: "editor",
|
|
2708
|
-
variant,
|
|
2709
|
-
children: /* @__PURE__ */ jsx(ImageUploadProvider, { upload: resolvedImageUpload, children: /* @__PURE__ */ jsx(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx(FootnotePlugin, { children: /* @__PURE__ */ jsxs(
|
|
2710
|
-
"div",
|
|
2711
|
-
{
|
|
2712
|
-
className: clsx("rich-editor", variantClass, className),
|
|
2713
|
-
style: { ...style, maxWidth: "none" },
|
|
2714
|
-
"data-theme": theme,
|
|
2715
|
-
suppressHydrationWarning: true,
|
|
2716
|
-
children: [
|
|
2717
|
-
header,
|
|
2718
|
-
/* @__PURE__ */ jsx(
|
|
2719
|
-
RichTextPlugin,
|
|
2720
|
-
{
|
|
2721
|
-
contentEditable: /* @__PURE__ */ jsx(
|
|
2722
|
-
ContentEditable,
|
|
2723
|
-
{
|
|
2724
|
-
className: contentClassName,
|
|
2725
|
-
placeholder: placeholder2,
|
|
2726
|
-
hasHeader: !!header
|
|
2727
|
-
}
|
|
2728
|
-
),
|
|
2729
|
-
ErrorBoundary: LexicalErrorBoundary
|
|
2730
|
-
}
|
|
2731
|
-
),
|
|
2732
|
-
/* @__PURE__ */ jsx(HistoryPlugin, {}),
|
|
2733
|
-
/* @__PURE__ */ jsx(ListPlugin, {}),
|
|
2734
|
-
/* @__PURE__ */ jsx(LinkPlugin, {}),
|
|
2735
|
-
/* @__PURE__ */ jsx(TabIndentationPlugin, {}),
|
|
2736
|
-
/* @__PURE__ */ jsx(TablePlugin, {}),
|
|
2737
|
-
/* @__PURE__ */ jsx(MarkdownShortcutsPlugin, {}),
|
|
2738
|
-
/* @__PURE__ */ jsx(MarkdownPastePlugin, {}),
|
|
2739
|
-
/* @__PURE__ */ jsx(OnChangePlugin, { onChange, debounceMs }),
|
|
2740
|
-
/* @__PURE__ */ jsx(SubmitShortcutPlugin, { onSubmit }),
|
|
2741
|
-
/* @__PURE__ */ jsx(ImagePlugin, {}),
|
|
2742
|
-
/* @__PURE__ */ jsx(ImageUploadPlugin, { onUpload: resolvedImageUpload }),
|
|
2743
|
-
/* @__PURE__ */ jsx(KaTeXPlugin, {}),
|
|
2744
|
-
/* @__PURE__ */ jsx(AlertPlugin, {}),
|
|
2745
|
-
/* @__PURE__ */ jsx(MermaidPlugin, {}),
|
|
2746
|
-
/* @__PURE__ */ jsx(HorizontalRulePlugin, {}),
|
|
2747
|
-
/* @__PURE__ */ jsx(CheckListPlugin, {}),
|
|
2748
|
-
/* @__PURE__ */ jsx(BlockExitPlugin, {}),
|
|
2749
|
-
/* @__PURE__ */ jsx(AutoLinkPlugin, {}),
|
|
2750
|
-
/* @__PURE__ */ jsx(LinkFaviconPlugin, {}),
|
|
2751
|
-
/* @__PURE__ */ jsx(BlockIdPlugin, {}),
|
|
2752
|
-
/* @__PURE__ */ jsx(EditorRefPlugin, { onEditorReady }),
|
|
2753
|
-
autoFocus && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
|
|
2754
|
-
children,
|
|
2755
|
-
actions && /* @__PURE__ */ jsx("div", { className: "rich-editor__actions", children: actions })
|
|
2756
|
-
]
|
|
2757
|
-
}
|
|
2758
|
-
) }) }) })
|
|
2759
|
-
}
|
|
2760
|
-
) }) });
|
|
2761
|
-
}
|
|
2762
|
-
export {
|
|
2763
|
-
ALL_TRANSFORMERS as A,
|
|
2764
|
-
BlockExitPlugin as B,
|
|
2765
|
-
FootnotePlugin as F,
|
|
2766
|
-
HorizontalRulePlugin as H,
|
|
2767
|
-
INSERT_ALERT_COMMAND as I,
|
|
2768
|
-
KaTeXPlugin as K,
|
|
2769
|
-
LinkFaviconPlugin as L,
|
|
2770
|
-
MarkdownShortcutsPlugin as M,
|
|
2771
|
-
NESTED_EDITOR_NODES as N,
|
|
2772
|
-
RichEditor as R,
|
|
2773
|
-
allEditNodes as a,
|
|
2774
|
-
blockIdState as b,
|
|
2775
|
-
customEditNodes as c,
|
|
2776
|
-
AutoLinkPlugin as d,
|
|
2777
|
-
FootnoteSectionEditNode as e,
|
|
2778
|
-
INSERT_IMAGE_COMMAND as f,
|
|
2779
|
-
getResolvedEditNodes as g,
|
|
2780
|
-
INSERT_KATEX_BLOCK_COMMAND as h,
|
|
2781
|
-
INSERT_KATEX_INLINE_COMMAND as i,
|
|
2782
|
-
INSERT_MERMAID_COMMAND as j,
|
|
2783
|
-
ImagePlugin as k,
|
|
2784
|
-
ImageUploadPlugin as l,
|
|
2785
|
-
MermaidPlugin as m,
|
|
2786
|
-
defaultImageUpload as n,
|
|
2787
|
-
setResolvedEditNodes as s,
|
|
2788
|
-
useImageUpload as u
|
|
2789
|
-
};
|