@liminis/editor 0.1.0-rc.1
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/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- package/package.json +175 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $createParagraphNode, $createTextNode, $getSelection, $isRangeSelection, TextNode, } from 'lexical';
|
|
5
|
+
import { $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
|
|
6
|
+
import { $createCodeNode } from '@lexical/code';
|
|
7
|
+
import { $createHorizontalRuleNode, $createCalloutNode, $createToggleNode, $createEquationNode, $createMermaidNode, $createC4Node, $createCustomListNode, $createCustomListItemNode } from './nodes/index.js';
|
|
8
|
+
import { $createTableNode, $createTableRowNode, $createTableCellNode, TableCellHeaderStates, } from '@lexical/table';
|
|
9
|
+
import { INSERT_IMAGE_COMMAND } from './ImagePlugin.js';
|
|
10
|
+
// Block options defined outside component to avoid recreation
|
|
11
|
+
const BLOCK_OPTIONS = [
|
|
12
|
+
{
|
|
13
|
+
key: 'paragraph',
|
|
14
|
+
label: 'Paragraph',
|
|
15
|
+
description: 'Plain text',
|
|
16
|
+
icon: '¶',
|
|
17
|
+
keywords: ['text', 'plain', 'paragraph'],
|
|
18
|
+
onSelect: (editor) => {
|
|
19
|
+
editor.update(() => {
|
|
20
|
+
const selection = $getSelection();
|
|
21
|
+
if ($isRangeSelection(selection)) {
|
|
22
|
+
const node = $createParagraphNode();
|
|
23
|
+
selection.insertNodes([node]);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: 'heading1',
|
|
30
|
+
label: 'Heading 1',
|
|
31
|
+
description: 'Large heading',
|
|
32
|
+
icon: 'H1',
|
|
33
|
+
keywords: ['h1', 'heading', 'title', 'large'],
|
|
34
|
+
onSelect: (editor) => {
|
|
35
|
+
editor.update(() => {
|
|
36
|
+
const selection = $getSelection();
|
|
37
|
+
if ($isRangeSelection(selection)) {
|
|
38
|
+
const node = $createHeadingNode('h1');
|
|
39
|
+
selection.insertNodes([node]);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'heading2',
|
|
46
|
+
label: 'Heading 2',
|
|
47
|
+
description: 'Medium heading',
|
|
48
|
+
icon: 'H2',
|
|
49
|
+
keywords: ['h2', 'heading', 'subtitle', 'medium'],
|
|
50
|
+
onSelect: (editor) => {
|
|
51
|
+
editor.update(() => {
|
|
52
|
+
const selection = $getSelection();
|
|
53
|
+
if ($isRangeSelection(selection)) {
|
|
54
|
+
const node = $createHeadingNode('h2');
|
|
55
|
+
selection.insertNodes([node]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'heading3',
|
|
62
|
+
label: 'Heading 3',
|
|
63
|
+
description: 'Small heading',
|
|
64
|
+
icon: 'H3',
|
|
65
|
+
keywords: ['h3', 'heading', 'small'],
|
|
66
|
+
onSelect: (editor) => {
|
|
67
|
+
editor.update(() => {
|
|
68
|
+
const selection = $getSelection();
|
|
69
|
+
if ($isRangeSelection(selection)) {
|
|
70
|
+
const node = $createHeadingNode('h3');
|
|
71
|
+
selection.insertNodes([node]);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'bulletList',
|
|
78
|
+
label: 'Bullet List',
|
|
79
|
+
description: 'Unordered list',
|
|
80
|
+
icon: '•',
|
|
81
|
+
keywords: ['bullet', 'list', 'unordered', 'ul'],
|
|
82
|
+
onSelect: (editor) => {
|
|
83
|
+
editor.update(() => {
|
|
84
|
+
const selection = $getSelection();
|
|
85
|
+
if ($isRangeSelection(selection)) {
|
|
86
|
+
const list = $createCustomListNode('bullet');
|
|
87
|
+
const item = $createCustomListItemNode();
|
|
88
|
+
list.append(item);
|
|
89
|
+
selection.insertNodes([list]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: 'numberedList',
|
|
96
|
+
label: 'Numbered List',
|
|
97
|
+
description: 'Ordered list',
|
|
98
|
+
icon: '1.',
|
|
99
|
+
keywords: ['numbered', 'list', 'ordered', 'ol'],
|
|
100
|
+
onSelect: (editor) => {
|
|
101
|
+
editor.update(() => {
|
|
102
|
+
const selection = $getSelection();
|
|
103
|
+
if ($isRangeSelection(selection)) {
|
|
104
|
+
const list = $createCustomListNode('number');
|
|
105
|
+
const item = $createCustomListItemNode();
|
|
106
|
+
list.append(item);
|
|
107
|
+
selection.insertNodes([list]);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: 'todoList',
|
|
114
|
+
label: 'Todo List',
|
|
115
|
+
description: 'Task list with checkboxes',
|
|
116
|
+
icon: '☐',
|
|
117
|
+
keywords: ['todo', 'task', 'checkbox', 'check'],
|
|
118
|
+
onSelect: (editor) => {
|
|
119
|
+
editor.update(() => {
|
|
120
|
+
const selection = $getSelection();
|
|
121
|
+
if ($isRangeSelection(selection)) {
|
|
122
|
+
const list = $createCustomListNode('check');
|
|
123
|
+
// Explicit false (not the default undefined): once export reads
|
|
124
|
+
// __taskChecked instead of Lexical's parent-type-gated
|
|
125
|
+
// getChecked(), an implicit undefined would default to null (plain
|
|
126
|
+
// bullet) and silently drop the checkbox on export.
|
|
127
|
+
const item = $createCustomListItemNode(false);
|
|
128
|
+
list.append(item);
|
|
129
|
+
selection.insertNodes([list]);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
key: 'quote',
|
|
136
|
+
label: 'Quote',
|
|
137
|
+
description: 'Blockquote',
|
|
138
|
+
icon: '"',
|
|
139
|
+
keywords: ['quote', 'blockquote', 'citation'],
|
|
140
|
+
onSelect: (editor) => {
|
|
141
|
+
editor.update(() => {
|
|
142
|
+
const selection = $getSelection();
|
|
143
|
+
if ($isRangeSelection(selection)) {
|
|
144
|
+
const node = $createQuoteNode();
|
|
145
|
+
selection.insertNodes([node]);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
key: 'code',
|
|
152
|
+
label: 'Code Block',
|
|
153
|
+
description: 'Code with syntax highlighting',
|
|
154
|
+
icon: '</>',
|
|
155
|
+
keywords: ['code', 'snippet', 'programming'],
|
|
156
|
+
onSelect: (editor) => {
|
|
157
|
+
editor.update(() => {
|
|
158
|
+
const selection = $getSelection();
|
|
159
|
+
if ($isRangeSelection(selection)) {
|
|
160
|
+
const node = $createCodeNode();
|
|
161
|
+
selection.insertNodes([node]);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: 'divider',
|
|
168
|
+
label: 'Divider',
|
|
169
|
+
description: 'Horizontal rule',
|
|
170
|
+
icon: '—',
|
|
171
|
+
keywords: ['divider', 'hr', 'line', 'horizontal'],
|
|
172
|
+
onSelect: (editor) => {
|
|
173
|
+
editor.update(() => {
|
|
174
|
+
const selection = $getSelection();
|
|
175
|
+
if ($isRangeSelection(selection)) {
|
|
176
|
+
const node = $createHorizontalRuleNode();
|
|
177
|
+
selection.insertNodes([node]);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: 'image',
|
|
184
|
+
label: 'Image',
|
|
185
|
+
description: 'Upload or embed an image',
|
|
186
|
+
icon: '🖼',
|
|
187
|
+
keywords: ['image', 'picture', 'photo', 'img', 'upload'],
|
|
188
|
+
onSelect: (editor) => {
|
|
189
|
+
editor.dispatchCommand(INSERT_IMAGE_COMMAND, undefined);
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: 'callout-note',
|
|
194
|
+
label: 'Note',
|
|
195
|
+
description: 'Info callout',
|
|
196
|
+
icon: 'ℹ',
|
|
197
|
+
keywords: ['note', 'info', 'callout', 'admonition'],
|
|
198
|
+
onSelect: (editor) => {
|
|
199
|
+
editor.update(() => {
|
|
200
|
+
const selection = $getSelection();
|
|
201
|
+
if ($isRangeSelection(selection)) {
|
|
202
|
+
const node = $createCalloutNode('note', '');
|
|
203
|
+
selection.insertNodes([node]);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
key: 'callout-tip',
|
|
210
|
+
label: 'Tip',
|
|
211
|
+
description: 'Helpful tip',
|
|
212
|
+
icon: '💡',
|
|
213
|
+
keywords: ['tip', 'hint', 'callout'],
|
|
214
|
+
onSelect: (editor) => {
|
|
215
|
+
editor.update(() => {
|
|
216
|
+
const selection = $getSelection();
|
|
217
|
+
if ($isRangeSelection(selection)) {
|
|
218
|
+
const node = $createCalloutNode('tip', '');
|
|
219
|
+
selection.insertNodes([node]);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
key: 'callout-warning',
|
|
226
|
+
label: 'Warning',
|
|
227
|
+
description: 'Warning callout',
|
|
228
|
+
icon: '⚠',
|
|
229
|
+
keywords: ['warning', 'caution', 'alert', 'callout'],
|
|
230
|
+
onSelect: (editor) => {
|
|
231
|
+
editor.update(() => {
|
|
232
|
+
const selection = $getSelection();
|
|
233
|
+
if ($isRangeSelection(selection)) {
|
|
234
|
+
const node = $createCalloutNode('warning', '');
|
|
235
|
+
selection.insertNodes([node]);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
key: 'toggle',
|
|
242
|
+
label: 'Toggle',
|
|
243
|
+
description: 'Collapsible section',
|
|
244
|
+
icon: '▶',
|
|
245
|
+
keywords: ['toggle', 'collapse', 'details', 'accordion'],
|
|
246
|
+
onSelect: (editor) => {
|
|
247
|
+
editor.update(() => {
|
|
248
|
+
const selection = $getSelection();
|
|
249
|
+
if ($isRangeSelection(selection)) {
|
|
250
|
+
const node = $createToggleNode('Toggle title', '', false);
|
|
251
|
+
selection.insertNodes([node]);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
key: 'table',
|
|
258
|
+
label: 'Table',
|
|
259
|
+
description: 'Insert a table',
|
|
260
|
+
icon: '⊞',
|
|
261
|
+
keywords: ['table', 'grid', 'matrix', 'spreadsheet'],
|
|
262
|
+
onSelect: (editor) => {
|
|
263
|
+
editor.update(() => {
|
|
264
|
+
const selection = $getSelection();
|
|
265
|
+
if ($isRangeSelection(selection)) {
|
|
266
|
+
// Create a 3x3 table with header row
|
|
267
|
+
const table = $createTableNode();
|
|
268
|
+
// Header row
|
|
269
|
+
const headerRow = $createTableRowNode();
|
|
270
|
+
for (let col = 0; col < 3; col++) {
|
|
271
|
+
const cell = $createTableCellNode(TableCellHeaderStates.ROW);
|
|
272
|
+
const paragraph = $createParagraphNode();
|
|
273
|
+
paragraph.append($createTextNode(`Header ${col + 1}`));
|
|
274
|
+
cell.append(paragraph);
|
|
275
|
+
headerRow.append(cell);
|
|
276
|
+
}
|
|
277
|
+
table.append(headerRow);
|
|
278
|
+
// Data rows
|
|
279
|
+
for (let row = 0; row < 2; row++) {
|
|
280
|
+
const tableRow = $createTableRowNode();
|
|
281
|
+
for (let col = 0; col < 3; col++) {
|
|
282
|
+
const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
|
|
283
|
+
const paragraph = $createParagraphNode();
|
|
284
|
+
paragraph.append($createTextNode(''));
|
|
285
|
+
cell.append(paragraph);
|
|
286
|
+
tableRow.append(cell);
|
|
287
|
+
}
|
|
288
|
+
table.append(tableRow);
|
|
289
|
+
}
|
|
290
|
+
selection.insertNodes([table]);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
key: 'equation-inline',
|
|
297
|
+
label: 'Inline Equation',
|
|
298
|
+
description: 'Math formula inline with text',
|
|
299
|
+
icon: '∑',
|
|
300
|
+
keywords: ['math', 'equation', 'latex', 'katex', 'formula', 'inline'],
|
|
301
|
+
onSelect: (editor) => {
|
|
302
|
+
editor.update(() => {
|
|
303
|
+
const selection = $getSelection();
|
|
304
|
+
if ($isRangeSelection(selection)) {
|
|
305
|
+
const node = $createEquationNode('E = mc^2', true);
|
|
306
|
+
selection.insertNodes([node]);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
key: 'equation-block',
|
|
313
|
+
label: 'Block Equation',
|
|
314
|
+
description: 'Centered math formula',
|
|
315
|
+
icon: '∫',
|
|
316
|
+
keywords: ['math', 'equation', 'latex', 'katex', 'formula', 'block', 'display'],
|
|
317
|
+
onSelect: (editor) => {
|
|
318
|
+
editor.update(() => {
|
|
319
|
+
const selection = $getSelection();
|
|
320
|
+
if ($isRangeSelection(selection)) {
|
|
321
|
+
const node = $createEquationNode('\\int_{a}^{b} f(x) \\, dx', false);
|
|
322
|
+
selection.insertNodes([node]);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
key: 'mermaid',
|
|
329
|
+
label: 'Mermaid Diagram',
|
|
330
|
+
description: 'Flowchart, sequence, or other diagram',
|
|
331
|
+
icon: '📊',
|
|
332
|
+
keywords: ['mermaid', 'diagram', 'flowchart', 'sequence', 'graph', 'chart', 'uml'],
|
|
333
|
+
onSelect: (editor) => {
|
|
334
|
+
editor.update(() => {
|
|
335
|
+
const selection = $getSelection();
|
|
336
|
+
if ($isRangeSelection(selection)) {
|
|
337
|
+
const node = $createMermaidNode(`flowchart TD
|
|
338
|
+
A[Start] --> B{Decision}
|
|
339
|
+
B -->|Yes| C[OK]
|
|
340
|
+
B -->|No| D[End]`);
|
|
341
|
+
selection.insertNodes([node]);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
key: 'c4',
|
|
348
|
+
label: 'C4 Architecture Diagram',
|
|
349
|
+
description: 'System context, container, or component diagram',
|
|
350
|
+
icon: '🏗',
|
|
351
|
+
keywords: ['c4', 'architecture', 'diagram', 'system', 'container', 'component'],
|
|
352
|
+
onSelect: (editor) => {
|
|
353
|
+
editor.update(() => {
|
|
354
|
+
const selection = $getSelection();
|
|
355
|
+
if ($isRangeSelection(selection)) {
|
|
356
|
+
const node = $createC4Node(`Person(user, "User", "End user of the system")
|
|
357
|
+
|
|
358
|
+
System_Boundary(mySystem, "My System") {
|
|
359
|
+
Container(web, "Web App", "React", "Delivers the UI")
|
|
360
|
+
Container(api, "API Server", "Node.js", "Business logic")
|
|
361
|
+
ContainerDb(db, "Database", "PostgreSQL", "Stores data")
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
Rel(user, web, "Uses", "HTTPS")
|
|
365
|
+
Rel(web, api, "Calls", "REST/JSON")
|
|
366
|
+
Rel(api, db, "Reads/Writes", "SQL")`);
|
|
367
|
+
selection.insertNodes([node]);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
export function SlashMenu({ isOpen, position, query, onClose }) {
|
|
374
|
+
const [editor] = useLexicalComposerContext();
|
|
375
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
376
|
+
const listRef = useRef(null);
|
|
377
|
+
const filteredOptions = useMemo(() => {
|
|
378
|
+
if (!query)
|
|
379
|
+
return BLOCK_OPTIONS;
|
|
380
|
+
const lowerQuery = query.toLowerCase();
|
|
381
|
+
// Filter and score options
|
|
382
|
+
const scored = BLOCK_OPTIONS
|
|
383
|
+
.map((option) => {
|
|
384
|
+
const labelLower = option.label.toLowerCase();
|
|
385
|
+
let score = 0;
|
|
386
|
+
// Exact label match gets highest score
|
|
387
|
+
if (labelLower === lowerQuery) {
|
|
388
|
+
score = 100;
|
|
389
|
+
}
|
|
390
|
+
// Label starts with query
|
|
391
|
+
else if (labelLower.startsWith(lowerQuery)) {
|
|
392
|
+
score = 80;
|
|
393
|
+
}
|
|
394
|
+
// Label contains query
|
|
395
|
+
else if (labelLower.includes(lowerQuery)) {
|
|
396
|
+
score = 60;
|
|
397
|
+
}
|
|
398
|
+
// Keyword starts with query
|
|
399
|
+
else if (option.keywords.some((kw) => kw.startsWith(lowerQuery))) {
|
|
400
|
+
score = 40;
|
|
401
|
+
}
|
|
402
|
+
// Keyword contains query
|
|
403
|
+
else if (option.keywords.some((kw) => kw.includes(lowerQuery))) {
|
|
404
|
+
score = 20;
|
|
405
|
+
}
|
|
406
|
+
return { option, score };
|
|
407
|
+
})
|
|
408
|
+
.filter(({ score }) => score > 0)
|
|
409
|
+
.sort((a, b) => b.score - a.score);
|
|
410
|
+
return scored.map(({ option }) => option);
|
|
411
|
+
}, [query]);
|
|
412
|
+
// Reset selection when query changes - use ref to track previous query
|
|
413
|
+
const prevQueryRef = useRef(query);
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (query !== prevQueryRef.current) {
|
|
416
|
+
prevQueryRef.current = query;
|
|
417
|
+
// Use microtask to avoid setState during render cycle
|
|
418
|
+
queueMicrotask(() => setSelectedIndex(0));
|
|
419
|
+
}
|
|
420
|
+
}, [query]);
|
|
421
|
+
// Scroll selected item into view
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (listRef.current && isOpen) {
|
|
424
|
+
const selectedItem = listRef.current.children[selectedIndex];
|
|
425
|
+
if (selectedItem) {
|
|
426
|
+
selectedItem.scrollIntoView({ block: 'nearest' });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}, [selectedIndex, isOpen]);
|
|
430
|
+
const handleSelect = useCallback((option) => {
|
|
431
|
+
// Remove the slash and query text before inserting
|
|
432
|
+
editor.update(() => {
|
|
433
|
+
const selection = $getSelection();
|
|
434
|
+
if ($isRangeSelection(selection)) {
|
|
435
|
+
const anchor = selection.anchor;
|
|
436
|
+
const anchorNode = anchor.getNode();
|
|
437
|
+
if (anchorNode instanceof TextNode) {
|
|
438
|
+
const text = anchorNode.getTextContent();
|
|
439
|
+
const slashIndex = text.lastIndexOf('/');
|
|
440
|
+
if (slashIndex >= 0) {
|
|
441
|
+
// Remove the "/" and query text
|
|
442
|
+
const before = text.substring(0, slashIndex);
|
|
443
|
+
anchorNode.setTextContent(before);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
option.onSelect(editor);
|
|
449
|
+
onClose();
|
|
450
|
+
}, [editor, onClose]);
|
|
451
|
+
// Handle keyboard navigation with native events (capture phase to intercept before Lexical)
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
if (!isOpen)
|
|
454
|
+
return;
|
|
455
|
+
const handleKeyDown = (event) => {
|
|
456
|
+
switch (event.key) {
|
|
457
|
+
case 'ArrowDown':
|
|
458
|
+
event.preventDefault();
|
|
459
|
+
event.stopPropagation();
|
|
460
|
+
setSelectedIndex((prev) => prev < filteredOptions.length - 1 ? prev + 1 : prev);
|
|
461
|
+
break;
|
|
462
|
+
case 'ArrowUp':
|
|
463
|
+
event.preventDefault();
|
|
464
|
+
event.stopPropagation();
|
|
465
|
+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
|
|
466
|
+
break;
|
|
467
|
+
case 'Enter':
|
|
468
|
+
event.preventDefault();
|
|
469
|
+
event.stopPropagation();
|
|
470
|
+
if (filteredOptions[selectedIndex]) {
|
|
471
|
+
handleSelect(filteredOptions[selectedIndex]);
|
|
472
|
+
}
|
|
473
|
+
break;
|
|
474
|
+
case 'Escape':
|
|
475
|
+
event.preventDefault();
|
|
476
|
+
event.stopPropagation();
|
|
477
|
+
onClose();
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
// Use capture phase to intercept events before they reach Lexical
|
|
482
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
483
|
+
return () => {
|
|
484
|
+
document.removeEventListener('keydown', handleKeyDown, true);
|
|
485
|
+
};
|
|
486
|
+
}, [isOpen, filteredOptions, selectedIndex, handleSelect, onClose]);
|
|
487
|
+
if (!isOpen || !position)
|
|
488
|
+
return null;
|
|
489
|
+
return (_jsxs("div", { className: "slash-menu", style: {
|
|
490
|
+
position: 'fixed',
|
|
491
|
+
top: position.top,
|
|
492
|
+
left: position.left,
|
|
493
|
+
}, children: [_jsx("div", { className: "slash-menu-header", children: "INSERT BLOCK" }), filteredOptions.length === 0 ? (_jsx("div", { className: "slash-menu-empty", children: "No matching blocks" })) : (_jsx("ul", { className: "slash-menu-list", role: "listbox", ref: listRef, children: filteredOptions.map((option, index) => (_jsxs("li", { role: "option", "aria-selected": index === selectedIndex, className: `slash-menu-item ${index === selectedIndex ? 'selected' : ''}`, onClick: () => handleSelect(option), onMouseEnter: () => setSelectedIndex(index), children: [_jsx("span", { className: "slash-menu-icon", children: option.icon }), _jsxs("div", { className: "slash-menu-content", children: [_jsx("span", { className: "slash-menu-label", children: option.label }), _jsx("span", { className: "slash-menu-description", children: option.description })] })] }, option.key))) }))] }));
|
|
494
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SlashMenuPlugin(): import("react").JSX.Element;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, TextNode, } from 'lexical';
|
|
5
|
+
import { SlashMenu } from './SlashMenu.js';
|
|
6
|
+
// Compare positions with tolerance to avoid updates for sub-pixel differences
|
|
7
|
+
function positionsEqual(a, b) {
|
|
8
|
+
if (a === null && b === null)
|
|
9
|
+
return true;
|
|
10
|
+
if (a === null || b === null)
|
|
11
|
+
return false;
|
|
12
|
+
return Math.abs(a.top - b.top) < 1 && Math.abs(a.left - b.left) < 1;
|
|
13
|
+
}
|
|
14
|
+
export function SlashMenuPlugin() {
|
|
15
|
+
const [editor] = useLexicalComposerContext();
|
|
16
|
+
const [menuState, setMenuState] = useState({
|
|
17
|
+
isOpen: false,
|
|
18
|
+
position: null,
|
|
19
|
+
query: '',
|
|
20
|
+
});
|
|
21
|
+
// Use refs to access state without causing effect re-registration
|
|
22
|
+
const menuStateRef = useRef(menuState);
|
|
23
|
+
// Sync state to ref in effect, not during render
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
menuStateRef.current = menuState;
|
|
26
|
+
}, [menuState]);
|
|
27
|
+
const closeMenu = useCallback(() => {
|
|
28
|
+
setMenuState({ isOpen: false, position: null, query: '' });
|
|
29
|
+
}, []);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const updateListener = editor.registerUpdateListener(({ editorState }) => {
|
|
32
|
+
editorState.read(() => {
|
|
33
|
+
const selection = $getSelection();
|
|
34
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
35
|
+
if (menuStateRef.current.isOpen) {
|
|
36
|
+
closeMenu();
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const anchor = selection.anchor;
|
|
41
|
+
const anchorNode = anchor.getNode();
|
|
42
|
+
if (!(anchorNode instanceof TextNode)) {
|
|
43
|
+
if (menuStateRef.current.isOpen) {
|
|
44
|
+
closeMenu();
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const text = anchorNode.getTextContent();
|
|
49
|
+
const offset = anchor.offset;
|
|
50
|
+
const textBeforeCursor = text.substring(0, offset);
|
|
51
|
+
// Find the last "/" in the text before cursor
|
|
52
|
+
const slashIndex = textBeforeCursor.lastIndexOf('/');
|
|
53
|
+
if (slashIndex === -1) {
|
|
54
|
+
if (menuStateRef.current.isOpen) {
|
|
55
|
+
closeMenu();
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Check if slash is at start of line or preceded by whitespace
|
|
60
|
+
const beforeSlash = textBeforeCursor.substring(0, slashIndex);
|
|
61
|
+
if (beforeSlash.length > 0 && !/\s$/.test(beforeSlash)) {
|
|
62
|
+
if (menuStateRef.current.isOpen) {
|
|
63
|
+
closeMenu();
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const query = textBeforeCursor.substring(slashIndex + 1);
|
|
68
|
+
// Don't show menu if there's a space in the query
|
|
69
|
+
if (/\s/.test(query)) {
|
|
70
|
+
if (menuStateRef.current.isOpen) {
|
|
71
|
+
closeMenu();
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Get position for menu - position at the slash character
|
|
76
|
+
const nativeSelection = window.getSelection();
|
|
77
|
+
if (!nativeSelection || nativeSelection.rangeCount === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const range = nativeSelection.getRangeAt(0);
|
|
81
|
+
// Create a range that starts at the slash position
|
|
82
|
+
const slashRange = document.createRange();
|
|
83
|
+
const textNode = range.startContainer;
|
|
84
|
+
if (textNode.nodeType === Node.TEXT_NODE) {
|
|
85
|
+
// Find the actual position of the slash in the text node
|
|
86
|
+
const textContent = textNode.textContent || '';
|
|
87
|
+
const slashPosInNode = textContent.lastIndexOf('/');
|
|
88
|
+
if (slashPosInNode >= 0) {
|
|
89
|
+
slashRange.setStart(textNode, slashPosInNode);
|
|
90
|
+
slashRange.setEnd(textNode, slashPosInNode + 1);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
slashRange.setStart(textNode, 0);
|
|
94
|
+
slashRange.setEnd(textNode, 0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
slashRange.setStart(textNode, 0);
|
|
99
|
+
slashRange.setEnd(textNode, 0);
|
|
100
|
+
}
|
|
101
|
+
const rect = slashRange.getBoundingClientRect();
|
|
102
|
+
const menuHeight = 320; // max-height from CSS
|
|
103
|
+
const menuWidth = 280; // min-width from CSS
|
|
104
|
+
const padding = 4;
|
|
105
|
+
// Check if menu would overflow bottom of viewport
|
|
106
|
+
const spaceBelow = window.innerHeight - rect.bottom - padding;
|
|
107
|
+
const spaceAbove = rect.top - padding;
|
|
108
|
+
let top;
|
|
109
|
+
if (spaceBelow >= menuHeight) {
|
|
110
|
+
// Enough space below - position below cursor
|
|
111
|
+
top = rect.bottom + padding;
|
|
112
|
+
}
|
|
113
|
+
else if (spaceAbove >= menuHeight) {
|
|
114
|
+
// Not enough below, but enough above - position above cursor
|
|
115
|
+
top = rect.top - menuHeight - padding;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Not enough space either way - position where there's more room
|
|
119
|
+
top =
|
|
120
|
+
spaceBelow > spaceAbove
|
|
121
|
+
? rect.bottom + padding
|
|
122
|
+
: rect.top - menuHeight - padding;
|
|
123
|
+
}
|
|
124
|
+
// Check horizontal overflow
|
|
125
|
+
let left = rect.left;
|
|
126
|
+
if (left + menuWidth > window.innerWidth) {
|
|
127
|
+
left = Math.max(padding, window.innerWidth - menuWidth - padding);
|
|
128
|
+
}
|
|
129
|
+
const newPosition = { top, left };
|
|
130
|
+
// Only update state if something changed
|
|
131
|
+
const currentState = menuStateRef.current;
|
|
132
|
+
if (!currentState.isOpen ||
|
|
133
|
+
currentState.query !== query ||
|
|
134
|
+
!positionsEqual(currentState.position, newPosition)) {
|
|
135
|
+
setMenuState({
|
|
136
|
+
isOpen: true,
|
|
137
|
+
position: newPosition,
|
|
138
|
+
query,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return updateListener;
|
|
144
|
+
}, [editor, closeMenu]);
|
|
145
|
+
// Close menu on backspace if query is empty and menu is open
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
return editor.registerCommand(KEY_BACKSPACE_COMMAND, () => {
|
|
148
|
+
if (menuStateRef.current.isOpen && menuStateRef.current.query === '') {
|
|
149
|
+
closeMenu();
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}, COMMAND_PRIORITY_LOW);
|
|
153
|
+
}, [editor, closeMenu]);
|
|
154
|
+
// Close menu on click outside
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!menuState.isOpen)
|
|
157
|
+
return;
|
|
158
|
+
const handleClick = (event) => {
|
|
159
|
+
const target = event.target;
|
|
160
|
+
if (!target.closest('.slash-menu')) {
|
|
161
|
+
closeMenu();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
document.addEventListener('click', handleClick);
|
|
165
|
+
return () => document.removeEventListener('click', handleClick);
|
|
166
|
+
}, [menuState.isOpen, closeMenu]);
|
|
167
|
+
return (_jsx(SlashMenu, { isOpen: menuState.isOpen, position: menuState.position, query: menuState.query, onClose: closeMenu }));
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function TableActionsPlugin(): import("react").JSX.Element | null;
|