@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.
Files changed (219) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +376 -0
  3. package/dist/annotations/anchor-align.d.ts +50 -0
  4. package/dist/annotations/anchor-align.js +74 -0
  5. package/dist/annotations/anchor-model.d.ts +81 -0
  6. package/dist/annotations/anchor-model.js +80 -0
  7. package/dist/annotations/anchor-resolver.d.ts +92 -0
  8. package/dist/annotations/anchor-resolver.js +231 -0
  9. package/dist/annotations/block-structure.d.ts +65 -0
  10. package/dist/annotations/block-structure.js +113 -0
  11. package/dist/annotations/types.d.ts +159 -0
  12. package/dist/annotations/types.js +50 -0
  13. package/dist/annotations.d.ts +25 -0
  14. package/dist/annotations.js +27 -0
  15. package/dist/app/App.d.ts +64 -0
  16. package/dist/app/App.js +180 -0
  17. package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
  18. package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
  19. package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
  20. package/dist/app/editor/AnchorScrollPlugin.js +134 -0
  21. package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
  22. package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
  23. package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
  24. package/dist/app/editor/AnnotationPlugin.js +135 -0
  25. package/dist/app/editor/AnnotationSurface.d.ts +34 -0
  26. package/dist/app/editor/AnnotationSurface.js +115 -0
  27. package/dist/app/editor/AssetContext.d.ts +20 -0
  28. package/dist/app/editor/AssetContext.js +81 -0
  29. package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
  30. package/dist/app/editor/BlockClickPlugin.js +126 -0
  31. package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
  32. package/dist/app/editor/CodeBlockPlugin.js +219 -0
  33. package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
  34. package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
  35. package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
  36. package/dist/app/editor/DragHandlePlugin.js +300 -0
  37. package/dist/app/editor/Editor.d.ts +84 -0
  38. package/dist/app/editor/Editor.js +539 -0
  39. package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
  40. package/dist/app/editor/FrontmatterPlugin.js +301 -0
  41. package/dist/app/editor/ImageModal.d.ts +13 -0
  42. package/dist/app/editor/ImageModal.js +267 -0
  43. package/dist/app/editor/ImagePlugin.d.ts +3 -0
  44. package/dist/app/editor/ImagePlugin.js +187 -0
  45. package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
  46. package/dist/app/editor/LinkClickPlugin.js +90 -0
  47. package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
  48. package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
  49. package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
  50. package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
  51. package/dist/app/editor/SearchPlugin.d.ts +1 -0
  52. package/dist/app/editor/SearchPlugin.js +379 -0
  53. package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
  54. package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
  55. package/dist/app/editor/SlashMenu.d.ts +11 -0
  56. package/dist/app/editor/SlashMenu.js +494 -0
  57. package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
  58. package/dist/app/editor/SlashMenuPlugin.js +168 -0
  59. package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
  60. package/dist/app/editor/TableActionsPlugin.js +398 -0
  61. package/dist/app/editor/TogglePlugin.d.ts +1 -0
  62. package/dist/app/editor/TogglePlugin.js +159 -0
  63. package/dist/app/editor/Toolbar.d.ts +10 -0
  64. package/dist/app/editor/Toolbar.js +263 -0
  65. package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
  66. package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
  67. package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
  68. package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
  69. package/dist/app/editor/annotation-marks.d.ts +177 -0
  70. package/dist/app/editor/annotation-marks.js +750 -0
  71. package/dist/app/editor/annotationCommands.d.ts +13 -0
  72. package/dist/app/editor/annotationCommands.js +11 -0
  73. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
  74. package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
  75. package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
  76. package/dist/app/editor/c4/edge-clipping.js +139 -0
  77. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
  78. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
  79. package/dist/app/editor/c4/layout.d.ts +25 -0
  80. package/dist/app/editor/c4/layout.js +839 -0
  81. package/dist/app/editor/c4/parser.d.ts +19 -0
  82. package/dist/app/editor/c4/parser.js +410 -0
  83. package/dist/app/editor/c4/render-to-string.d.ts +24 -0
  84. package/dist/app/editor/c4/render-to-string.js +34 -0
  85. package/dist/app/editor/c4/renderer.d.ts +64 -0
  86. package/dist/app/editor/c4/renderer.js +569 -0
  87. package/dist/app/editor/c4/types.d.ts +203 -0
  88. package/dist/app/editor/c4/types.js +43 -0
  89. package/dist/app/editor/correction-yaml.d.ts +10 -0
  90. package/dist/app/editor/correction-yaml.js +40 -0
  91. package/dist/app/editor/editorNodes.d.ts +9 -0
  92. package/dist/app/editor/editorNodes.js +36 -0
  93. package/dist/app/editor/index.d.ts +1 -0
  94. package/dist/app/editor/index.js +1 -0
  95. package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
  96. package/dist/app/editor/mark-overlap-resolver.js +45 -0
  97. package/dist/app/editor/nodes/C4Component.d.ts +14 -0
  98. package/dist/app/editor/nodes/C4Component.js +284 -0
  99. package/dist/app/editor/nodes/C4Node.d.ts +32 -0
  100. package/dist/app/editor/nodes/C4Node.js +112 -0
  101. package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
  102. package/dist/app/editor/nodes/CalloutNode.js +94 -0
  103. package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
  104. package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
  105. package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
  106. package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
  107. package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
  108. package/dist/app/editor/nodes/CustomListNode.js +54 -0
  109. package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
  110. package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
  111. package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
  112. package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
  113. package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
  114. package/dist/app/editor/nodes/EquationComponent.js +130 -0
  115. package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
  116. package/dist/app/editor/nodes/EquationNode.js +179 -0
  117. package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
  118. package/dist/app/editor/nodes/FootnoteNode.js +151 -0
  119. package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
  120. package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
  121. package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
  122. package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
  123. package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
  124. package/dist/app/editor/nodes/HtmlNode.js +121 -0
  125. package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
  126. package/dist/app/editor/nodes/ImageComponent.js +228 -0
  127. package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
  128. package/dist/app/editor/nodes/ImageNode.js +183 -0
  129. package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
  130. package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
  131. package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
  132. package/dist/app/editor/nodes/MermaidComponent.js +216 -0
  133. package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
  134. package/dist/app/editor/nodes/MermaidNode.js +96 -0
  135. package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
  136. package/dist/app/editor/nodes/ToggleNode.js +237 -0
  137. package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
  138. package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
  139. package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
  140. package/dist/app/editor/nodes/diagram-utils.js +29 -0
  141. package/dist/app/editor/nodes/index.d.ts +30 -0
  142. package/dist/app/editor/nodes/index.js +17 -0
  143. package/dist/app/editor/utils.d.ts +2 -0
  144. package/dist/app/editor/utils.js +17 -0
  145. package/dist/app/mapper/index.d.ts +3 -0
  146. package/dist/app/mapper/index.js +2 -0
  147. package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
  148. package/dist/app/mapper/lexicalToMdast.js +1937 -0
  149. package/dist/app/mapper/mdastToLexical.d.ts +31 -0
  150. package/dist/app/mapper/mdastToLexical.js +1075 -0
  151. package/dist/contract.d.ts +8 -0
  152. package/dist/contract.js +8 -0
  153. package/dist/headless.d.ts +40 -0
  154. package/dist/headless.js +45 -0
  155. package/dist/host/context.d.ts +20 -0
  156. package/dist/host/context.js +24 -0
  157. package/dist/host/defaults.d.ts +21 -0
  158. package/dist/host/defaults.js +47 -0
  159. package/dist/host/messages.d.ts +26 -0
  160. package/dist/host/messages.js +47 -0
  161. package/dist/host/types.d.ts +75 -0
  162. package/dist/host/types.js +13 -0
  163. package/dist/index.d.ts +35 -0
  164. package/dist/index.js +51 -0
  165. package/dist/markdown/annotate-sentinels.d.ts +42 -0
  166. package/dist/markdown/annotate-sentinels.js +86 -0
  167. package/dist/markdown/parse.d.ts +56 -0
  168. package/dist/markdown/parse.js +516 -0
  169. package/dist/markdown/stringify.d.ts +7 -0
  170. package/dist/markdown/stringify.js +439 -0
  171. package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
  172. package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
  173. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
  174. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
  175. package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
  176. package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
  177. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
  178. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
  179. package/dist/markdown.d.ts +28 -0
  180. package/dist/markdown.js +44 -0
  181. package/dist/mathjax-config.d.ts +89 -0
  182. package/dist/mathjax-config.js +133 -0
  183. package/dist/nodes.d.ts +34 -0
  184. package/dist/nodes.js +34 -0
  185. package/dist/stores/correctionStore.d.ts +15 -0
  186. package/dist/stores/correctionStore.js +12 -0
  187. package/dist/stores/editorStore.d.ts +16 -0
  188. package/dist/stores/editorStore.js +27 -0
  189. package/dist/styles.css +2477 -0
  190. package/dist/types.d.ts +296 -0
  191. package/dist/types.js +91 -0
  192. package/dist/utils/file-types.d.ts +10 -0
  193. package/dist/utils/file-types.js +46 -0
  194. package/docs/annotations.md +134 -0
  195. package/docs/decisions/README.md +72 -0
  196. package/docs/decisions/adr-002.md +27 -0
  197. package/docs/decisions/adr-007.md +27 -0
  198. package/docs/decisions/adr-010.md +26 -0
  199. package/docs/decisions/adr-024.md +124 -0
  200. package/docs/decisions/adr-024b.md +0 -0
  201. package/docs/decisions/adr-025.md +103 -0
  202. package/docs/decisions/adr-027.md +66 -0
  203. package/docs/decisions/adr-057.md +67 -0
  204. package/docs/decisions/adr-070.md +131 -0
  205. package/docs/decisions/adr-075.md +410 -0
  206. package/docs/decisions/adr-076.md +183 -0
  207. package/docs/decisions/adr-077.md +410 -0
  208. package/docs/decisions/adr-078.md +243 -0
  209. package/docs/decisions/adr-079.md +167 -0
  210. package/docs/decisions/adr-080.md +325 -0
  211. package/docs/decisions/adr-081.md +108 -0
  212. package/docs/decisions/adr-082.md +123 -0
  213. package/docs/decisions/adr-083.md +204 -0
  214. package/docs/decisions/adr-084.md +179 -0
  215. package/docs/editor-api.md +106 -0
  216. package/docs/markdown-pipeline.md +282 -0
  217. package/docs/provenance.md +106 -0
  218. package/docs/zusammen-editor-capability-map.md +232 -0
  219. package/package.json +175 -0
@@ -0,0 +1,216 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * MermaidComponent - React component for rendering and editing Mermaid diagrams
4
+ * Uses Shadow DOM for style isolation
5
+ */
6
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
7
+ import { mergeRegister } from '@lexical/utils';
8
+ import { $getNodeByKey, $getSelection, $isNodeSelection, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from 'lexical';
9
+ import { useCallback, useEffect, useRef, useState } from 'react';
10
+ import mermaid from 'mermaid';
11
+ import { useHostMessages } from '../../../host/messages.js';
12
+ import { $isMermaidNode } from './MermaidNode.js';
13
+ import { DiagramContextMenu, useDiagramContextMenu } from './DiagramContextMenu.js';
14
+ // Initialize mermaid with default config
15
+ mermaid.initialize({
16
+ startOnLoad: false,
17
+ theme: 'dark',
18
+ securityLevel: 'strict',
19
+ fontFamily: 'inherit',
20
+ });
21
+ // Mermaid Renderer component using Shadow DOM
22
+ function MermaidRenderer({ code, onDoubleClick, }) {
23
+ const containerRef = useRef(null);
24
+ const shadowRootRef = useRef(null);
25
+ const renderIdRef = useRef(0);
26
+ const linkHandlerRef = useRef(null);
27
+ const contextMenu = useDiagramContextMenu();
28
+ const { openLink } = useHostMessages();
29
+ useEffect(() => {
30
+ const container = containerRef.current;
31
+ if (container === null)
32
+ return;
33
+ // Create shadow root if it doesn't exist
34
+ if (!shadowRootRef.current) {
35
+ shadowRootRef.current = container.attachShadow({ mode: 'open' });
36
+ // Add basic styles for the shadow DOM
37
+ const style = document.createElement('style');
38
+ style.textContent = `
39
+ :host {
40
+ display: block;
41
+ width: 100%;
42
+ }
43
+ .mermaid-container {
44
+ display: flex;
45
+ justify-content: center;
46
+ padding: 1rem;
47
+ background: rgba(255, 255, 255, 0.05);
48
+ border-radius: 8px;
49
+ overflow-x: auto;
50
+ }
51
+ .mermaid-container svg {
52
+ max-width: 100%;
53
+ height: auto;
54
+ }
55
+ .mermaid-error {
56
+ color: #ff6b6b;
57
+ padding: 1rem;
58
+ font-family: monospace;
59
+ white-space: pre-wrap;
60
+ }
61
+ `;
62
+ shadowRootRef.current.appendChild(style);
63
+ // Intercept link clicks inside Mermaid SVGs and open externally
64
+ const handleLinkClick = (event) => {
65
+ const target = event.target;
66
+ const anchor = target?.closest?.('a');
67
+ if (!anchor)
68
+ return;
69
+ const href = anchor.getAttribute('href') ||
70
+ anchor.getAttribute('xlink:href') ||
71
+ anchor.href?.baseVal;
72
+ if (!href)
73
+ return;
74
+ event.preventDefault();
75
+ event.stopPropagation();
76
+ openLink(href);
77
+ };
78
+ linkHandlerRef.current = handleLinkClick;
79
+ shadowRootRef.current.addEventListener('click', handleLinkClick);
80
+ const handlePointerMove = (event) => {
81
+ const target = event.target;
82
+ const anchor = target?.closest?.('a');
83
+ container.style.cursor = anchor ? 'pointer' : 'default';
84
+ };
85
+ const handlePointerLeave = () => {
86
+ container.style.cursor = 'default';
87
+ };
88
+ shadowRootRef.current.addEventListener('mousemove', handlePointerMove);
89
+ shadowRootRef.current.addEventListener('mouseleave', handlePointerLeave);
90
+ }
91
+ const shadowRoot = shadowRootRef.current;
92
+ // Clear previous content (except style)
93
+ const existingContainer = shadowRoot.querySelector('.mermaid-container, .mermaid-error');
94
+ if (existingContainer) {
95
+ existingContainer.remove();
96
+ }
97
+ // Render the diagram
98
+ const renderDiagram = async () => {
99
+ const currentRenderId = ++renderIdRef.current;
100
+ try {
101
+ // Validate syntax first
102
+ await mermaid.parse(code);
103
+ // Check if this render is still current
104
+ if (currentRenderId !== renderIdRef.current)
105
+ return;
106
+ // Generate unique ID for this render
107
+ const id = `mermaid-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
108
+ // Render the diagram
109
+ const { svg } = await mermaid.render(id, code);
110
+ // Check again if this render is still current
111
+ if (currentRenderId !== renderIdRef.current)
112
+ return;
113
+ // Create container and add SVG
114
+ const renderContainer = document.createElement('div');
115
+ renderContainer.className = 'mermaid-container';
116
+ renderContainer.innerHTML = svg;
117
+ shadowRoot.appendChild(renderContainer);
118
+ }
119
+ catch (e) {
120
+ if (currentRenderId !== renderIdRef.current)
121
+ return;
122
+ const errorMessage = e instanceof Error ? e.message : 'Failed to render diagram';
123
+ // Show error in shadow DOM
124
+ const errorContainer = document.createElement('div');
125
+ errorContainer.className = 'mermaid-error';
126
+ errorContainer.textContent = `Mermaid Error: ${errorMessage}`;
127
+ shadowRoot.appendChild(errorContainer);
128
+ }
129
+ };
130
+ void renderDiagram();
131
+ }, [code, openLink]);
132
+ return (_jsxs(_Fragment, { children: [_jsx("div", { role: "button", tabIndex: -1, onDoubleClick: onDoubleClick, onContextMenu: (e) => contextMenu.show(e, containerRef), ref: containerRef, className: "mermaid-renderer", style: {
133
+ display: 'block',
134
+ cursor: 'pointer',
135
+ minHeight: '100px',
136
+ } }), _jsx(DiagramContextMenu, { ...contextMenu.props })] }));
137
+ }
138
+ // Mermaid Editor component
139
+ function MermaidEditor({ code, setCode, onClose, }) {
140
+ const textareaRef = useRef(null);
141
+ useEffect(() => {
142
+ // Focus textarea on mount
143
+ textareaRef.current?.focus();
144
+ }, []);
145
+ const handleKeyDown = (e) => {
146
+ if (e.key === 'Escape') {
147
+ e.preventDefault();
148
+ onClose();
149
+ }
150
+ };
151
+ return (_jsxs("div", { className: "mermaid-editor-container", children: [_jsxs("div", { className: "mermaid-editor-header", children: [_jsx("span", { className: "mermaid-editor-label", children: "```mermaid" }), _jsx("button", { className: "mermaid-editor-close", onClick: onClose, title: "Close editor (Esc)", children: "\u2715" })] }), _jsx("textarea", { ref: textareaRef, className: "mermaid-editor-textarea", value: code, onChange: (e) => setCode(e.target.value), onKeyDown: handleKeyDown, placeholder: "Enter Mermaid diagram code...", spellCheck: false }), _jsx("div", { className: "mermaid-editor-footer", children: _jsx("span", { className: "mermaid-editor-label", children: "```" }) })] }));
152
+ }
153
+ export default function MermaidComponent({ code, nodeKey, }) {
154
+ const [editor] = useLexicalComposerContext();
155
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
156
+ const [codeValue, setCodeValue] = useState(code);
157
+ const [showEditor, setShowEditor] = useState(false);
158
+ // Listen for editable changes
159
+ useEffect(() => {
160
+ return editor.registerEditableListener((editable) => {
161
+ setIsEditable(editable);
162
+ });
163
+ }, [editor]);
164
+ const onHide = useCallback(() => {
165
+ setShowEditor(false);
166
+ editor.update(() => {
167
+ const node = $getNodeByKey(nodeKey);
168
+ if ($isMermaidNode(node)) {
169
+ node.setCode(codeValue);
170
+ }
171
+ });
172
+ }, [editor, codeValue, nodeKey]);
173
+ useEffect(() => {
174
+ if (!showEditor && codeValue !== code) {
175
+ setCodeValue(code);
176
+ }
177
+ }, [showEditor, code, codeValue]);
178
+ useEffect(() => {
179
+ if (!isEditable) {
180
+ return;
181
+ }
182
+ if (showEditor) {
183
+ return mergeRegister(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
184
+ // Don't close on selection change while editing
185
+ return false;
186
+ }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
187
+ if (showEditor) {
188
+ onHide();
189
+ return true;
190
+ }
191
+ return false;
192
+ }, COMMAND_PRIORITY_HIGH));
193
+ }
194
+ else {
195
+ return editor.registerUpdateListener(({ editorState }) => {
196
+ const isSelected = editorState.read(() => {
197
+ const selection = $getSelection();
198
+ return ($isNodeSelection(selection) &&
199
+ selection.has(nodeKey) &&
200
+ selection.getNodes().length === 1);
201
+ });
202
+ if (isSelected) {
203
+ setShowEditor(true);
204
+ }
205
+ });
206
+ }
207
+ }, [editor, nodeKey, onHide, showEditor, isEditable]);
208
+ if (showEditor && isEditable) {
209
+ return (_jsx(MermaidEditor, { code: codeValue, setCode: setCodeValue, onClose: onHide }));
210
+ }
211
+ return (_jsx(MermaidRenderer, { code: codeValue, onDoubleClick: () => {
212
+ if (isEditable) {
213
+ setShowEditor(true);
214
+ }
215
+ } }));
216
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MermaidNode - Renders Mermaid diagrams
3
+ * Uses DecoratorNode pattern similar to EquationNode
4
+ */
5
+ import { DecoratorNode, DOMConversionMap, DOMExportOutput, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical';
6
+ export type SerializedMermaidNode = Spread<{
7
+ code: string;
8
+ }, SerializedLexicalNode>;
9
+ export declare class MermaidNode extends DecoratorNode<JSX.Element> {
10
+ __code: string;
11
+ static getType(): string;
12
+ static clone(node: MermaidNode): MermaidNode;
13
+ constructor(code: string, key?: NodeKey);
14
+ static importJSON(serializedNode: SerializedMermaidNode): MermaidNode;
15
+ exportJSON(): SerializedMermaidNode;
16
+ createDOM(): HTMLElement;
17
+ exportDOM(): DOMExportOutput;
18
+ static importDOM(): DOMConversionMap | null;
19
+ updateDOM(): boolean;
20
+ isInline(): boolean;
21
+ getTextContent(): string;
22
+ getCode(): string;
23
+ setCode(code: string): void;
24
+ decorate(): JSX.Element;
25
+ }
26
+ export declare function $createMermaidNode(code?: string): MermaidNode;
27
+ export declare function $isMermaidNode(node: LexicalNode | null | undefined): node is MermaidNode;
@@ -0,0 +1,96 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ /**
3
+ * MermaidNode - Renders Mermaid diagrams
4
+ * Uses DecoratorNode pattern similar to EquationNode
5
+ */
6
+ import { DecoratorNode, $applyNodeReplacement, } from 'lexical';
7
+ import { createElement, lazy, Suspense } from 'react';
8
+ const MermaidComponent = lazy(() => import('./MermaidComponent.js'));
9
+ function $convertMermaidElement(domNode) {
10
+ const code = domNode.getAttribute('data-lexical-mermaid');
11
+ if (code) {
12
+ // Decode from base64
13
+ const decodedCode = atob(code);
14
+ const node = $createMermaidNode(decodedCode);
15
+ return { node };
16
+ }
17
+ return null;
18
+ }
19
+ export class MermaidNode extends DecoratorNode {
20
+ __code;
21
+ static getType() {
22
+ return 'mermaid';
23
+ }
24
+ static clone(node) {
25
+ return new MermaidNode(node.__code, node.__key);
26
+ }
27
+ constructor(code, key) {
28
+ super(key);
29
+ this.__code = code;
30
+ }
31
+ static importJSON(serializedNode) {
32
+ return $createMermaidNode(serializedNode.code);
33
+ }
34
+ exportJSON() {
35
+ return {
36
+ type: 'mermaid',
37
+ version: 1,
38
+ code: this.getCode(),
39
+ };
40
+ }
41
+ createDOM() {
42
+ const element = document.createElement('div');
43
+ element.className = 'editor-mermaid';
44
+ return element;
45
+ }
46
+ exportDOM() {
47
+ const element = document.createElement('div');
48
+ // Encode as base64 to avoid issues with special characters
49
+ element.setAttribute('data-lexical-mermaid', btoa(this.__code));
50
+ element.className = 'mermaid-export';
51
+ element.textContent = this.__code;
52
+ return { element };
53
+ }
54
+ static importDOM() {
55
+ return {
56
+ div: (domNode) => {
57
+ if (!domNode.hasAttribute('data-lexical-mermaid')) {
58
+ return null;
59
+ }
60
+ return {
61
+ conversion: $convertMermaidElement,
62
+ priority: 2,
63
+ };
64
+ },
65
+ };
66
+ }
67
+ updateDOM() {
68
+ return false;
69
+ }
70
+ isInline() {
71
+ return false;
72
+ }
73
+ getTextContent() {
74
+ return this.__code;
75
+ }
76
+ getCode() {
77
+ return this.__code;
78
+ }
79
+ setCode(code) {
80
+ const writable = this.getWritable();
81
+ writable.__code = code;
82
+ }
83
+ decorate() {
84
+ return createElement(Suspense, { fallback: createElement('div', { className: 'mermaid-loading' }, 'Loading diagram...') }, createElement(MermaidComponent, {
85
+ code: this.__code,
86
+ nodeKey: this.__key,
87
+ }));
88
+ }
89
+ }
90
+ export function $createMermaidNode(code = '') {
91
+ const mermaidNode = new MermaidNode(code);
92
+ return $applyNodeReplacement(mermaidNode);
93
+ }
94
+ export function $isMermaidNode(node) {
95
+ return node instanceof MermaidNode;
96
+ }
@@ -0,0 +1,56 @@
1
+ import { DOMConversionMap, DOMExportOutput, EditorConfig, ElementNode, LexicalNode, NodeKey, RangeSelection, SerializedElementNode, Spread } from 'lexical';
2
+ export type SerializedToggleContainerNode = Spread<{
3
+ open: boolean;
4
+ }, SerializedElementNode>;
5
+ export declare class ToggleContainerNode extends ElementNode {
6
+ __open: boolean;
7
+ static getType(): string;
8
+ static clone(node: ToggleContainerNode): ToggleContainerNode;
9
+ constructor(open?: boolean, key?: NodeKey);
10
+ createDOM(config: EditorConfig): HTMLElement;
11
+ updateDOM(prevNode: ToggleContainerNode, dom: HTMLElement): boolean;
12
+ static importDOM(): DOMConversionMap | null;
13
+ static importJSON(serializedNode: SerializedToggleContainerNode): ToggleContainerNode;
14
+ exportDOM(): DOMExportOutput;
15
+ exportJSON(): SerializedToggleContainerNode;
16
+ setOpen(open: boolean): void;
17
+ getOpen(): boolean;
18
+ toggleOpen(): void;
19
+ isShadowRoot(): boolean;
20
+ canBeEmpty(): boolean;
21
+ }
22
+ export declare function $createToggleContainerNode(open?: boolean): ToggleContainerNode;
23
+ export declare function $isToggleContainerNode(node: LexicalNode | null | undefined): node is ToggleContainerNode;
24
+ export type SerializedToggleTitleNode = SerializedElementNode;
25
+ export declare class ToggleTitleNode extends ElementNode {
26
+ static getType(): string;
27
+ static clone(node: ToggleTitleNode): ToggleTitleNode;
28
+ createDOM(config: EditorConfig): HTMLElement;
29
+ updateDOM(): boolean;
30
+ static importDOM(): DOMConversionMap | null;
31
+ static importJSON(serializedNode: SerializedToggleTitleNode): ToggleTitleNode;
32
+ exportDOM(): DOMExportOutput;
33
+ exportJSON(): SerializedToggleTitleNode;
34
+ collapseAtStart(_selection: RangeSelection): boolean;
35
+ insertNewAfter(_: RangeSelection, restoreSelection?: boolean): null | ElementNode;
36
+ }
37
+ export declare function $createToggleTitleNode(): ToggleTitleNode;
38
+ export declare function $isToggleTitleNode(node: LexicalNode | null | undefined): node is ToggleTitleNode;
39
+ export type SerializedToggleContentNode = SerializedElementNode;
40
+ export declare class ToggleContentNode extends ElementNode {
41
+ static getType(): string;
42
+ static clone(node: ToggleContentNode): ToggleContentNode;
43
+ createDOM(config: EditorConfig): HTMLElement;
44
+ updateDOM(): boolean;
45
+ static importDOM(): DOMConversionMap | null;
46
+ static importJSON(serializedNode: SerializedToggleContentNode): ToggleContentNode;
47
+ exportDOM(): DOMExportOutput;
48
+ exportJSON(): SerializedToggleContentNode;
49
+ isShadowRoot(): boolean;
50
+ }
51
+ export declare function $createToggleContentNode(): ToggleContentNode;
52
+ export declare function $isToggleContentNode(node: LexicalNode | null | undefined): node is ToggleContentNode;
53
+ export declare function $createToggleNode(summaryText?: string, contentText?: string, open?: boolean): ToggleContainerNode;
54
+ export declare function $isToggleNode(node: LexicalNode | null | undefined): node is ToggleContainerNode;
55
+ export type ToggleNode = ToggleContainerNode;
56
+ export type SerializedToggleNode = SerializedToggleContainerNode;
@@ -0,0 +1,237 @@
1
+ import { $createParagraphNode, $createTextNode, ElementNode, } from 'lexical';
2
+ export class ToggleContainerNode extends ElementNode {
3
+ __open;
4
+ static getType() {
5
+ return 'toggle-container';
6
+ }
7
+ static clone(node) {
8
+ return new ToggleContainerNode(node.__open, node.__key);
9
+ }
10
+ constructor(open = false, key) {
11
+ super(key);
12
+ this.__open = open;
13
+ }
14
+ createDOM(config) {
15
+ void config;
16
+ const dom = document.createElement('div');
17
+ dom.classList.add('toggle-container');
18
+ if (this.__open) {
19
+ dom.classList.add('toggle-container--open');
20
+ }
21
+ return dom;
22
+ }
23
+ updateDOM(prevNode, dom) {
24
+ if (prevNode.__open !== this.__open) {
25
+ dom.classList.toggle('toggle-container--open', this.__open);
26
+ }
27
+ return false;
28
+ }
29
+ static importDOM() {
30
+ return {
31
+ details: (domNode) => {
32
+ void domNode;
33
+ return {
34
+ conversion: convertDetailsElement,
35
+ priority: 1,
36
+ };
37
+ },
38
+ };
39
+ }
40
+ static importJSON(serializedNode) {
41
+ void serializedNode;
42
+ const node = $createToggleContainerNode(serializedNode.open);
43
+ return node;
44
+ }
45
+ exportDOM() {
46
+ const element = document.createElement('details');
47
+ if (this.__open) {
48
+ element.setAttribute('open', '');
49
+ }
50
+ return { element };
51
+ }
52
+ exportJSON() {
53
+ return {
54
+ ...super.exportJSON(),
55
+ type: 'toggle-container',
56
+ open: this.__open,
57
+ version: 1,
58
+ };
59
+ }
60
+ setOpen(open) {
61
+ const writable = this.getWritable();
62
+ writable.__open = open;
63
+ }
64
+ getOpen() {
65
+ return this.__open;
66
+ }
67
+ toggleOpen() {
68
+ this.setOpen(!this.getOpen());
69
+ }
70
+ // This prevents direct selection in the container
71
+ isShadowRoot() {
72
+ return true;
73
+ }
74
+ canBeEmpty() {
75
+ return false;
76
+ }
77
+ }
78
+ function convertDetailsElement(domNode) {
79
+ const isOpen = domNode.hasAttribute('open');
80
+ const node = $createToggleContainerNode(isOpen);
81
+ return { node };
82
+ }
83
+ export function $createToggleContainerNode(open = false) {
84
+ return new ToggleContainerNode(open);
85
+ }
86
+ export function $isToggleContainerNode(node) {
87
+ return node instanceof ToggleContainerNode;
88
+ }
89
+ export class ToggleTitleNode extends ElementNode {
90
+ static getType() {
91
+ return 'toggle-title';
92
+ }
93
+ static clone(node) {
94
+ return new ToggleTitleNode(node.__key);
95
+ }
96
+ createDOM(config) {
97
+ void config;
98
+ const dom = document.createElement('div');
99
+ dom.classList.add('toggle-title');
100
+ return dom;
101
+ }
102
+ updateDOM() {
103
+ return false;
104
+ }
105
+ static importDOM() {
106
+ return {
107
+ summary: () => ({
108
+ conversion: convertSummaryElement,
109
+ priority: 1,
110
+ }),
111
+ };
112
+ }
113
+ static importJSON(serializedNode) {
114
+ void serializedNode;
115
+ return $createToggleTitleNode();
116
+ }
117
+ exportDOM() {
118
+ const element = document.createElement('summary');
119
+ return { element };
120
+ }
121
+ exportJSON() {
122
+ return {
123
+ ...super.exportJSON(),
124
+ type: 'toggle-title',
125
+ version: 1,
126
+ };
127
+ }
128
+ // Collapse at start collapses the entire toggle
129
+ collapseAtStart(_selection) {
130
+ this.getParentOrThrow().insertBefore(this);
131
+ return true;
132
+ }
133
+ // Prevent inserting nodes that aren't allowed
134
+ insertNewAfter(_, restoreSelection) {
135
+ void restoreSelection;
136
+ const containerNode = this.getParentOrThrow();
137
+ if (!$isToggleContainerNode(containerNode)) {
138
+ throw new Error('ToggleTitleNode must be a child of ToggleContainerNode');
139
+ }
140
+ // If closed, open it
141
+ if (!containerNode.getOpen()) {
142
+ containerNode.toggleOpen();
143
+ }
144
+ // Find or create content node
145
+ const children = containerNode.getChildren();
146
+ const contentNode = children.find($isToggleContentNode);
147
+ if (contentNode) {
148
+ const firstChild = contentNode.getFirstChild();
149
+ if (firstChild) {
150
+ firstChild.selectStart();
151
+ }
152
+ else {
153
+ const paragraph = $createParagraphNode();
154
+ contentNode.append(paragraph);
155
+ paragraph.selectStart();
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ }
161
+ function convertSummaryElement() {
162
+ const node = $createToggleTitleNode();
163
+ return { node };
164
+ }
165
+ export function $createToggleTitleNode() {
166
+ return new ToggleTitleNode();
167
+ }
168
+ export function $isToggleTitleNode(node) {
169
+ return node instanceof ToggleTitleNode;
170
+ }
171
+ export class ToggleContentNode extends ElementNode {
172
+ static getType() {
173
+ return 'toggle-content';
174
+ }
175
+ static clone(node) {
176
+ return new ToggleContentNode(node.__key);
177
+ }
178
+ createDOM(config) {
179
+ void config;
180
+ const dom = document.createElement('div');
181
+ dom.classList.add('toggle-content-area');
182
+ return dom;
183
+ }
184
+ updateDOM() {
185
+ return false;
186
+ }
187
+ static importDOM() {
188
+ return null;
189
+ }
190
+ static importJSON(serializedNode) {
191
+ void serializedNode;
192
+ return $createToggleContentNode();
193
+ }
194
+ exportDOM() {
195
+ const element = document.createElement('div');
196
+ return { element };
197
+ }
198
+ exportJSON() {
199
+ return {
200
+ ...super.exportJSON(),
201
+ type: 'toggle-content',
202
+ version: 1,
203
+ };
204
+ }
205
+ isShadowRoot() {
206
+ return true;
207
+ }
208
+ }
209
+ export function $createToggleContentNode() {
210
+ return new ToggleContentNode();
211
+ }
212
+ export function $isToggleContentNode(node) {
213
+ return node instanceof ToggleContentNode;
214
+ }
215
+ // ==================== Helper Functions ====================
216
+ export function $createToggleNode(summaryText = 'Toggle', contentText = '', open = false) {
217
+ const container = $createToggleContainerNode(open);
218
+ const title = $createToggleTitleNode();
219
+ const titleParagraph = $createParagraphNode();
220
+ if (summaryText) {
221
+ titleParagraph.append($createTextNode(summaryText));
222
+ }
223
+ title.append(titleParagraph);
224
+ const content = $createToggleContentNode();
225
+ const contentParagraph = $createParagraphNode();
226
+ if (contentText) {
227
+ contentParagraph.append($createTextNode(contentText));
228
+ }
229
+ content.append(contentParagraph);
230
+ container.append(title);
231
+ container.append(content);
232
+ return container;
233
+ }
234
+ // Legacy compatibility - now just checks for container
235
+ export function $isToggleNode(node) {
236
+ return $isToggleContainerNode(node);
237
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared "Copy image to clipboard" for diagram nodes (C4, Mermaid).
3
+ *
4
+ * Converts the diagram SVG to a high-res PNG and writes it to the
5
+ * clipboard. Uses devicePixelRatio for crisp retina output.
6
+ */
7
+ /**
8
+ * Copy the diagram to the clipboard as a high-res PNG.
9
+ * Returns true on success, false on failure.
10
+ */
11
+ export declare function copyDiagramToClipboard(container: HTMLElement): Promise<boolean>;