@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,398 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $createParagraphNode, $createTextNode, $getRoot, } from 'lexical';
|
|
5
|
+
import { $createTableCellNode, $createTableRowNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableCellHeaderStates, } from '@lexical/table';
|
|
6
|
+
function TableActionsMenu({ tableNode, rowIndex, colIndex, position, onClose }) {
|
|
7
|
+
const [editor] = useLexicalComposerContext();
|
|
8
|
+
const menuRef = useRef(null);
|
|
9
|
+
// Close on click outside
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const handleClickOutside = (e) => {
|
|
12
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
13
|
+
onClose();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
17
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
18
|
+
}, [onClose]);
|
|
19
|
+
// Close on escape
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const handleKeyDown = (e) => {
|
|
22
|
+
if (e.key === 'Escape') {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
onClose();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
28
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
29
|
+
}, [onClose]);
|
|
30
|
+
const getColumnCount = useCallback(() => {
|
|
31
|
+
return editor.getEditorState().read(() => {
|
|
32
|
+
const rows = tableNode.getChildren();
|
|
33
|
+
if (rows.length > 0 && $isTableRowNode(rows[0])) {
|
|
34
|
+
return rows[0].getChildren().length;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
});
|
|
38
|
+
}, [editor, tableNode]);
|
|
39
|
+
const isHeaderRow = useCallback(() => {
|
|
40
|
+
return editor.getEditorState().read(() => {
|
|
41
|
+
const rows = tableNode.getChildren();
|
|
42
|
+
const row = rows[rowIndex];
|
|
43
|
+
if ($isTableRowNode(row)) {
|
|
44
|
+
const cells = row.getChildren();
|
|
45
|
+
if (cells.length > 0 && $isTableCellNode(cells[0])) {
|
|
46
|
+
return cells[0].hasHeaderState(TableCellHeaderStates.ROW);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
});
|
|
51
|
+
}, [editor, tableNode, rowIndex]);
|
|
52
|
+
const addRowAbove = useCallback(() => {
|
|
53
|
+
editor.update(() => {
|
|
54
|
+
const columnCount = getColumnCount();
|
|
55
|
+
const rows = tableNode.getChildren();
|
|
56
|
+
// Check if we're adding above the header row
|
|
57
|
+
const targetRow = rows[rowIndex];
|
|
58
|
+
const isInsertingAboveHeader = rowIndex === 0 && $isTableRowNode(targetRow);
|
|
59
|
+
const newRow = $createTableRowNode();
|
|
60
|
+
for (let i = 0; i < columnCount; i++) {
|
|
61
|
+
// If inserting above header, new row becomes the header
|
|
62
|
+
const headerState = isInsertingAboveHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
|
|
63
|
+
const cell = $createTableCellNode(headerState);
|
|
64
|
+
const paragraph = $createParagraphNode();
|
|
65
|
+
paragraph.append($createTextNode(''));
|
|
66
|
+
cell.append(paragraph);
|
|
67
|
+
newRow.append(cell);
|
|
68
|
+
}
|
|
69
|
+
if (rowIndex === 0) {
|
|
70
|
+
tableNode.getFirstChild()?.insertBefore(newRow);
|
|
71
|
+
// Convert old header row to regular row
|
|
72
|
+
if ($isTableRowNode(targetRow)) {
|
|
73
|
+
for (const cell of targetRow.getChildren()) {
|
|
74
|
+
if ($isTableCellNode(cell)) {
|
|
75
|
+
cell.setHeaderStyles(TableCellHeaderStates.NO_STATUS);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const targetRow = rows[rowIndex];
|
|
82
|
+
if (targetRow) {
|
|
83
|
+
targetRow.insertBefore(newRow);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
onClose();
|
|
88
|
+
}, [editor, tableNode, rowIndex, getColumnCount, onClose]);
|
|
89
|
+
const addRowBelow = useCallback(() => {
|
|
90
|
+
editor.update(() => {
|
|
91
|
+
const columnCount = getColumnCount();
|
|
92
|
+
const rows = tableNode.getChildren();
|
|
93
|
+
const newRow = $createTableRowNode();
|
|
94
|
+
for (let i = 0; i < columnCount; i++) {
|
|
95
|
+
const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
|
|
96
|
+
const paragraph = $createParagraphNode();
|
|
97
|
+
paragraph.append($createTextNode(''));
|
|
98
|
+
cell.append(paragraph);
|
|
99
|
+
newRow.append(cell);
|
|
100
|
+
}
|
|
101
|
+
const targetRow = rows[rowIndex];
|
|
102
|
+
if (targetRow) {
|
|
103
|
+
targetRow.insertAfter(newRow);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
onClose();
|
|
107
|
+
}, [editor, tableNode, rowIndex, getColumnCount, onClose]);
|
|
108
|
+
const addColumnLeft = useCallback(() => {
|
|
109
|
+
editor.update(() => {
|
|
110
|
+
const rows = tableNode.getChildren();
|
|
111
|
+
for (let i = 0; i < rows.length; i++) {
|
|
112
|
+
const row = rows[i];
|
|
113
|
+
if ($isTableRowNode(row)) {
|
|
114
|
+
const cells = row.getChildren();
|
|
115
|
+
const isHeader = i === 0;
|
|
116
|
+
const headerState = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
|
|
117
|
+
const newCell = $createTableCellNode(headerState);
|
|
118
|
+
const paragraph = $createParagraphNode();
|
|
119
|
+
paragraph.append($createTextNode(isHeader ? 'Header' : ''));
|
|
120
|
+
newCell.append(paragraph);
|
|
121
|
+
if (colIndex === 0) {
|
|
122
|
+
row.getFirstChild()?.insertBefore(newCell);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const targetCell = cells[colIndex];
|
|
126
|
+
if (targetCell) {
|
|
127
|
+
targetCell.insertBefore(newCell);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
onClose();
|
|
134
|
+
}, [editor, tableNode, colIndex, onClose]);
|
|
135
|
+
const addColumnRight = useCallback(() => {
|
|
136
|
+
editor.update(() => {
|
|
137
|
+
const rows = tableNode.getChildren();
|
|
138
|
+
for (let i = 0; i < rows.length; i++) {
|
|
139
|
+
const row = rows[i];
|
|
140
|
+
if ($isTableRowNode(row)) {
|
|
141
|
+
const cells = row.getChildren();
|
|
142
|
+
const isHeader = i === 0;
|
|
143
|
+
const headerState = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
|
|
144
|
+
const newCell = $createTableCellNode(headerState);
|
|
145
|
+
const paragraph = $createParagraphNode();
|
|
146
|
+
paragraph.append($createTextNode(isHeader ? 'Header' : ''));
|
|
147
|
+
newCell.append(paragraph);
|
|
148
|
+
const targetCell = cells[colIndex];
|
|
149
|
+
if (targetCell) {
|
|
150
|
+
targetCell.insertAfter(newCell);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
onClose();
|
|
156
|
+
}, [editor, tableNode, colIndex, onClose]);
|
|
157
|
+
const deleteRow = useCallback(() => {
|
|
158
|
+
editor.update(() => {
|
|
159
|
+
const rows = tableNode.getChildren();
|
|
160
|
+
// Don't delete if it's the only row
|
|
161
|
+
if (rows.length <= 1) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const targetRow = rows[rowIndex];
|
|
165
|
+
if (targetRow && $isTableRowNode(targetRow)) {
|
|
166
|
+
// If deleting header row, make next row the header
|
|
167
|
+
if (rowIndex === 0 && rows.length > 1) {
|
|
168
|
+
const nextRow = rows[1];
|
|
169
|
+
if ($isTableRowNode(nextRow)) {
|
|
170
|
+
for (const cell of nextRow.getChildren()) {
|
|
171
|
+
if ($isTableCellNode(cell)) {
|
|
172
|
+
cell.setHeaderStyles(TableCellHeaderStates.ROW);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
targetRow.remove();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
onClose();
|
|
181
|
+
}, [editor, tableNode, rowIndex, onClose]);
|
|
182
|
+
const deleteColumn = useCallback(() => {
|
|
183
|
+
editor.update(() => {
|
|
184
|
+
const rows = tableNode.getChildren();
|
|
185
|
+
// Don't delete if it's the only column
|
|
186
|
+
if (rows.length > 0 && $isTableRowNode(rows[0])) {
|
|
187
|
+
const columnCount = rows[0].getChildren().length;
|
|
188
|
+
if (columnCount <= 1) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const row of rows) {
|
|
193
|
+
if ($isTableRowNode(row)) {
|
|
194
|
+
const cells = row.getChildren();
|
|
195
|
+
const targetCell = cells[colIndex];
|
|
196
|
+
if (targetCell) {
|
|
197
|
+
targetCell.remove();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
onClose();
|
|
203
|
+
}, [editor, tableNode, colIndex, onClose]);
|
|
204
|
+
const toggleHeaderRow = useCallback(() => {
|
|
205
|
+
editor.update(() => {
|
|
206
|
+
const rows = tableNode.getChildren();
|
|
207
|
+
const currentRow = rows[rowIndex];
|
|
208
|
+
if ($isTableRowNode(currentRow)) {
|
|
209
|
+
const currentIsHeader = isHeaderRow();
|
|
210
|
+
const newHeaderState = currentIsHeader
|
|
211
|
+
? TableCellHeaderStates.NO_STATUS
|
|
212
|
+
: TableCellHeaderStates.ROW;
|
|
213
|
+
for (const cell of currentRow.getChildren()) {
|
|
214
|
+
if ($isTableCellNode(cell)) {
|
|
215
|
+
cell.setHeaderStyles(newHeaderState);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
onClose();
|
|
221
|
+
}, [editor, tableNode, rowIndex, isHeaderRow, onClose]);
|
|
222
|
+
const moveRowUp = useCallback(() => {
|
|
223
|
+
editor.update(() => {
|
|
224
|
+
if (rowIndex === 0)
|
|
225
|
+
return; // Can't move first row up
|
|
226
|
+
const rows = tableNode.getChildren();
|
|
227
|
+
const currentRow = rows[rowIndex];
|
|
228
|
+
const previousRow = rows[rowIndex - 1];
|
|
229
|
+
if ($isTableRowNode(currentRow) && $isTableRowNode(previousRow)) {
|
|
230
|
+
previousRow.insertBefore(currentRow);
|
|
231
|
+
// Handle header state changes
|
|
232
|
+
if (rowIndex === 1) {
|
|
233
|
+
// Moving to first position, make it header
|
|
234
|
+
for (const cell of currentRow.getChildren()) {
|
|
235
|
+
if ($isTableCellNode(cell)) {
|
|
236
|
+
cell.setHeaderStyles(TableCellHeaderStates.ROW);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Remove header from old first row
|
|
240
|
+
for (const cell of previousRow.getChildren()) {
|
|
241
|
+
if ($isTableCellNode(cell)) {
|
|
242
|
+
cell.setHeaderStyles(TableCellHeaderStates.NO_STATUS);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
onClose();
|
|
249
|
+
}, [editor, tableNode, rowIndex, onClose]);
|
|
250
|
+
const moveRowDown = useCallback(() => {
|
|
251
|
+
editor.update(() => {
|
|
252
|
+
const rows = tableNode.getChildren();
|
|
253
|
+
if (rowIndex >= rows.length - 1)
|
|
254
|
+
return; // Can't move last row down
|
|
255
|
+
const currentRow = rows[rowIndex];
|
|
256
|
+
const nextRow = rows[rowIndex + 1];
|
|
257
|
+
if ($isTableRowNode(currentRow) && $isTableRowNode(nextRow)) {
|
|
258
|
+
nextRow.insertAfter(currentRow);
|
|
259
|
+
// Handle header state changes
|
|
260
|
+
if (rowIndex === 0) {
|
|
261
|
+
// Moving away from first position, remove header
|
|
262
|
+
for (const cell of currentRow.getChildren()) {
|
|
263
|
+
if ($isTableCellNode(cell)) {
|
|
264
|
+
cell.setHeaderStyles(TableCellHeaderStates.NO_STATUS);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Make new first row the header
|
|
268
|
+
for (const cell of nextRow.getChildren()) {
|
|
269
|
+
if ($isTableCellNode(cell)) {
|
|
270
|
+
cell.setHeaderStyles(TableCellHeaderStates.ROW);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
onClose();
|
|
277
|
+
}, [editor, tableNode, rowIndex, onClose]);
|
|
278
|
+
const moveColumnLeft = useCallback(() => {
|
|
279
|
+
editor.update(() => {
|
|
280
|
+
if (colIndex === 0)
|
|
281
|
+
return; // Can't move first column left
|
|
282
|
+
const rows = tableNode.getChildren();
|
|
283
|
+
for (const row of rows) {
|
|
284
|
+
if ($isTableRowNode(row)) {
|
|
285
|
+
const cells = row.getChildren();
|
|
286
|
+
const currentCell = cells[colIndex];
|
|
287
|
+
const previousCell = cells[colIndex - 1];
|
|
288
|
+
if (currentCell && previousCell) {
|
|
289
|
+
previousCell.insertBefore(currentCell);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
onClose();
|
|
295
|
+
}, [editor, tableNode, colIndex, onClose]);
|
|
296
|
+
const moveColumnRight = useCallback(() => {
|
|
297
|
+
editor.update(() => {
|
|
298
|
+
const rows = tableNode.getChildren();
|
|
299
|
+
if (rows.length === 0)
|
|
300
|
+
return;
|
|
301
|
+
const firstRow = rows[0];
|
|
302
|
+
if (!$isTableRowNode(firstRow))
|
|
303
|
+
return;
|
|
304
|
+
if (colIndex >= firstRow.getChildren().length - 1)
|
|
305
|
+
return; // Can't move last column right
|
|
306
|
+
for (const row of rows) {
|
|
307
|
+
if ($isTableRowNode(row)) {
|
|
308
|
+
const cells = row.getChildren();
|
|
309
|
+
const currentCell = cells[colIndex];
|
|
310
|
+
const nextCell = cells[colIndex + 1];
|
|
311
|
+
if (currentCell && nextCell) {
|
|
312
|
+
nextCell.insertAfter(currentCell);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
onClose();
|
|
318
|
+
}, [editor, tableNode, colIndex, onClose]);
|
|
319
|
+
const deleteTable = useCallback(() => {
|
|
320
|
+
editor.update(() => {
|
|
321
|
+
tableNode.remove();
|
|
322
|
+
});
|
|
323
|
+
onClose();
|
|
324
|
+
}, [editor, tableNode, onClose]);
|
|
325
|
+
return (_jsxs("div", { ref: menuRef, className: "table-actions-menu", style: {
|
|
326
|
+
position: 'fixed',
|
|
327
|
+
top: position.top,
|
|
328
|
+
left: position.left,
|
|
329
|
+
}, children: [_jsxs("div", { className: "table-actions-section", children: [_jsx("div", { className: "table-actions-header", children: "Row" }), _jsxs("button", { className: "table-actions-button", onClick: addRowAbove, children: [_jsx("span", { className: "table-actions-icon", children: "\u2191" }), "Add row above"] }), _jsxs("button", { className: "table-actions-button", onClick: addRowBelow, children: [_jsx("span", { className: "table-actions-icon", children: "\u2193" }), "Add row below"] }), _jsxs("button", { className: "table-actions-button", onClick: moveRowUp, children: [_jsx("span", { className: "table-actions-icon", children: "\u2B06" }), "Move row up"] }), _jsxs("button", { className: "table-actions-button", onClick: moveRowDown, children: [_jsx("span", { className: "table-actions-icon", children: "\u2B07" }), "Move row down"] }), _jsxs("button", { className: "table-actions-button", onClick: toggleHeaderRow, children: [_jsx("span", { className: "table-actions-icon", children: isHeaderRow() ? '☐' : '☑' }), isHeaderRow() ? 'Remove header' : 'Make header'] }), _jsxs("button", { className: "table-actions-button table-actions-danger", onClick: deleteRow, children: [_jsx("span", { className: "table-actions-icon", children: "\u2715" }), "Delete row"] })] }), _jsx("div", { className: "table-actions-divider" }), _jsxs("div", { className: "table-actions-section", children: [_jsx("div", { className: "table-actions-header", children: "Column" }), _jsxs("button", { className: "table-actions-button", onClick: addColumnLeft, children: [_jsx("span", { className: "table-actions-icon", children: "\u2190" }), "Add column left"] }), _jsxs("button", { className: "table-actions-button", onClick: addColumnRight, children: [_jsx("span", { className: "table-actions-icon", children: "\u2192" }), "Add column right"] }), _jsxs("button", { className: "table-actions-button", onClick: moveColumnLeft, children: [_jsx("span", { className: "table-actions-icon", children: "\u2B05" }), "Move column left"] }), _jsxs("button", { className: "table-actions-button", onClick: moveColumnRight, children: [_jsx("span", { className: "table-actions-icon", children: "\u27A1" }), "Move column right"] }), _jsxs("button", { className: "table-actions-button table-actions-danger", onClick: deleteColumn, children: [_jsx("span", { className: "table-actions-icon", children: "\u2715" }), "Delete column"] })] }), _jsx("div", { className: "table-actions-divider" }), _jsx("div", { className: "table-actions-section", children: _jsxs("button", { className: "table-actions-button table-actions-danger", onClick: deleteTable, children: [_jsx("span", { className: "table-actions-icon", children: "\uD83D\uDDD1" }), "Delete table"] }) })] }));
|
|
330
|
+
}
|
|
331
|
+
export function TableActionsPlugin() {
|
|
332
|
+
const [editor] = useLexicalComposerContext();
|
|
333
|
+
const [menuState, setMenuState] = useState(null);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const handleContextMenu = (e) => {
|
|
336
|
+
const target = e.target;
|
|
337
|
+
const cellElement = target.closest('td, th');
|
|
338
|
+
if (!cellElement)
|
|
339
|
+
return;
|
|
340
|
+
// Find the table element
|
|
341
|
+
const tableElement = cellElement.closest('table');
|
|
342
|
+
if (!tableElement)
|
|
343
|
+
return;
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
// Find the row and column indices from the DOM
|
|
346
|
+
const rowElement = cellElement.closest('tr');
|
|
347
|
+
if (!rowElement)
|
|
348
|
+
return;
|
|
349
|
+
const rows = Array.from(tableElement.querySelectorAll(':scope > tbody > tr, :scope > tr'));
|
|
350
|
+
const rowIndex = rows.indexOf(rowElement);
|
|
351
|
+
const cells = Array.from(rowElement.querySelectorAll(':scope > td, :scope > th'));
|
|
352
|
+
const colIndex = cells.indexOf(cellElement);
|
|
353
|
+
if (rowIndex === -1 || colIndex === -1)
|
|
354
|
+
return;
|
|
355
|
+
// Find which table this is by checking all tables in the editor
|
|
356
|
+
const editorElement = editor.getRootElement();
|
|
357
|
+
if (!editorElement)
|
|
358
|
+
return;
|
|
359
|
+
const allTables = Array.from(editorElement.querySelectorAll('table'));
|
|
360
|
+
const tableIndex = allTables.indexOf(tableElement);
|
|
361
|
+
if (tableIndex === -1)
|
|
362
|
+
return;
|
|
363
|
+
editor.getEditorState().read(() => {
|
|
364
|
+
// Find all table nodes in the editor
|
|
365
|
+
const root = $getRoot();
|
|
366
|
+
const tableNodes = [];
|
|
367
|
+
for (const child of root.getChildren()) {
|
|
368
|
+
if ($isTableNode(child)) {
|
|
369
|
+
tableNodes.push(child);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const tableNode = tableNodes[tableIndex];
|
|
373
|
+
if (!tableNode)
|
|
374
|
+
return;
|
|
375
|
+
setMenuState({
|
|
376
|
+
tableNode,
|
|
377
|
+
rowIndex,
|
|
378
|
+
colIndex,
|
|
379
|
+
position: {
|
|
380
|
+
top: e.clientY,
|
|
381
|
+
left: e.clientX,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
const editorElement = editor.getRootElement();
|
|
387
|
+
if (editorElement) {
|
|
388
|
+
editorElement.addEventListener('contextmenu', handleContextMenu);
|
|
389
|
+
return () => editorElement.removeEventListener('contextmenu', handleContextMenu);
|
|
390
|
+
}
|
|
391
|
+
}, [editor]);
|
|
392
|
+
const handleClose = useCallback(() => {
|
|
393
|
+
setMenuState(null);
|
|
394
|
+
}, []);
|
|
395
|
+
if (!menuState)
|
|
396
|
+
return null;
|
|
397
|
+
return (_jsx(TableActionsMenu, { tableNode: menuState.tableNode, rowIndex: menuState.rowIndex, colIndex: menuState.colIndex, position: menuState.position, onClose: handleClose }));
|
|
398
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function TogglePlugin(): null;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { $getNodeByKey, $getRoot, COMMAND_PRIORITY_LOW, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, $getSelection, $isRangeSelection, } from 'lexical';
|
|
4
|
+
import { $isToggleContainerNode, $isToggleTitleNode, $isToggleContentNode, } from './nodes/index.js';
|
|
5
|
+
export function TogglePlugin() {
|
|
6
|
+
const [editor] = useLexicalComposerContext();
|
|
7
|
+
// Handle click on toggle arrow to expand/collapse
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const handleClick = (event) => {
|
|
10
|
+
const target = event.target;
|
|
11
|
+
// Check if clicked on toggle-title
|
|
12
|
+
const toggleTitle = target.closest('.toggle-title');
|
|
13
|
+
if (!toggleTitle)
|
|
14
|
+
return;
|
|
15
|
+
// Check if the click is on the arrow area (first 32px)
|
|
16
|
+
const rect = toggleTitle.getBoundingClientRect();
|
|
17
|
+
const clickX = event.clientX - rect.left;
|
|
18
|
+
// Only toggle on arrow click (first 32px) to allow editing title text
|
|
19
|
+
if (clickX > 32)
|
|
20
|
+
return;
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
// Find the toggle container DOM element
|
|
24
|
+
const container = toggleTitle.closest('.toggle-container');
|
|
25
|
+
if (!container)
|
|
26
|
+
return;
|
|
27
|
+
// Find the corresponding Lexical node by traversing editor state
|
|
28
|
+
editor.update(() => {
|
|
29
|
+
const root = $getRoot();
|
|
30
|
+
// Walk through all nodes to find toggle containers
|
|
31
|
+
const findToggleContainer = (element) => {
|
|
32
|
+
// Try to get the node key from the element
|
|
33
|
+
const key = element.getAttribute('data-lexical-node-key');
|
|
34
|
+
if (key) {
|
|
35
|
+
const node = $getNodeByKey(key);
|
|
36
|
+
if ($isToggleContainerNode(node)) {
|
|
37
|
+
return node;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Fallback: search for the toggle container by matching DOM elements
|
|
41
|
+
for (const child of root.getChildren()) {
|
|
42
|
+
if ($isToggleContainerNode(child)) {
|
|
43
|
+
const domElement = editor.getElementByKey(child.getKey());
|
|
44
|
+
if (domElement === container) {
|
|
45
|
+
return child;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
const toggleNode = findToggleContainer(container);
|
|
52
|
+
if (toggleNode) {
|
|
53
|
+
toggleNode.toggleOpen();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const rootElement = editor.getRootElement();
|
|
58
|
+
if (rootElement) {
|
|
59
|
+
rootElement.addEventListener('click', handleClick);
|
|
60
|
+
return () => {
|
|
61
|
+
rootElement.removeEventListener('click', handleClick);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}, [editor]);
|
|
65
|
+
// Handle arrow key navigation between title and content
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const handleArrowDown = () => {
|
|
68
|
+
const selection = $getSelection();
|
|
69
|
+
if (!$isRangeSelection(selection))
|
|
70
|
+
return false;
|
|
71
|
+
const anchorNode = selection.anchor.getNode();
|
|
72
|
+
// Check if we're in a paragraph that's inside a toggle title
|
|
73
|
+
let titleNode = null;
|
|
74
|
+
let node = anchorNode;
|
|
75
|
+
while (node) {
|
|
76
|
+
const parent = node.getParent();
|
|
77
|
+
if ($isToggleTitleNode(parent)) {
|
|
78
|
+
titleNode = parent;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if ($isToggleTitleNode(node)) {
|
|
82
|
+
titleNode = node;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
node = parent;
|
|
86
|
+
}
|
|
87
|
+
if (titleNode) {
|
|
88
|
+
const containerNode = titleNode.getParent();
|
|
89
|
+
if ($isToggleContainerNode(containerNode)) {
|
|
90
|
+
// Open if closed
|
|
91
|
+
if (!containerNode.getOpen()) {
|
|
92
|
+
containerNode.setOpen(true);
|
|
93
|
+
}
|
|
94
|
+
// Find content node
|
|
95
|
+
const children = containerNode.getChildren();
|
|
96
|
+
const contentNode = children.find($isToggleContentNode);
|
|
97
|
+
if (contentNode) {
|
|
98
|
+
const firstChild = contentNode.getFirstChild();
|
|
99
|
+
if (firstChild) {
|
|
100
|
+
firstChild.selectStart();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
const handleArrowUp = () => {
|
|
109
|
+
const selection = $getSelection();
|
|
110
|
+
if (!$isRangeSelection(selection))
|
|
111
|
+
return false;
|
|
112
|
+
const anchorNode = selection.anchor.getNode();
|
|
113
|
+
// Check if we're in a paragraph that's inside toggle content, at the start
|
|
114
|
+
let contentNode = null;
|
|
115
|
+
let node = anchorNode;
|
|
116
|
+
while (node) {
|
|
117
|
+
const parent = node.getParent();
|
|
118
|
+
if ($isToggleContentNode(parent)) {
|
|
119
|
+
contentNode = parent;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if ($isToggleContentNode(node)) {
|
|
123
|
+
contentNode = node;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
node = parent;
|
|
127
|
+
}
|
|
128
|
+
if (contentNode && selection.anchor.offset === 0) {
|
|
129
|
+
const containerNode = contentNode.getParent();
|
|
130
|
+
const firstContentChild = contentNode.getFirstChild();
|
|
131
|
+
// Only move if we're at the very start of the first child
|
|
132
|
+
if ($isToggleContainerNode(containerNode) && firstContentChild) {
|
|
133
|
+
// Check if anchor is at start of first element
|
|
134
|
+
const anchorTop = anchorNode.getTopLevelElement?.() || anchorNode.getParent();
|
|
135
|
+
if (anchorTop === firstContentChild || anchorNode === firstContentChild) {
|
|
136
|
+
// Find title node
|
|
137
|
+
const children = containerNode.getChildren();
|
|
138
|
+
const titleNode = children.find($isToggleTitleNode);
|
|
139
|
+
if (titleNode) {
|
|
140
|
+
const titleParagraph = titleNode.getFirstChild();
|
|
141
|
+
if (titleParagraph) {
|
|
142
|
+
titleParagraph.selectEnd();
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
const removeDown = editor.registerCommand(KEY_ARROW_DOWN_COMMAND, handleArrowDown, COMMAND_PRIORITY_LOW);
|
|
152
|
+
const removeUp = editor.registerCommand(KEY_ARROW_UP_COMMAND, handleArrowUp, COMMAND_PRIORITY_LOW);
|
|
153
|
+
return () => {
|
|
154
|
+
removeDown();
|
|
155
|
+
removeUp();
|
|
156
|
+
};
|
|
157
|
+
}, [editor]);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One toolbar-surfaced annotation kind's affordance, plain data only (FR-001/FR-009). */
|
|
2
|
+
export interface ToolbarAnnotationAffordance {
|
|
3
|
+
kind: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
interface ToolbarProps {
|
|
7
|
+
annotationAffordances?: ToolbarAnnotationAffordance[];
|
|
8
|
+
}
|
|
9
|
+
export declare function Toolbar({ annotationAffordances }: ToolbarProps): import("react").JSX.Element | null;
|
|
10
|
+
export {};
|