@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,284 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * C4Component - React component for rendering and editing C4 architecture diagrams
4
+ *
5
+ * Uses the canonical React C4Renderer for diagram display.
6
+ * Provides editing UI (textarea overlay, click-to-edit) for the Lexical editor.
7
+ * Supports manual layout mode for drag-and-drop positioning of elements.
8
+ */
9
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
10
+ import { mergeRegister } from '@lexical/utils';
11
+ import { $getNodeByKey, $getSelection, $isNodeSelection, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from 'lexical';
12
+ import { Move, RotateCcw } from 'lucide-react';
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+ import { parseC4, validateC4 } from '../c4/parser.js';
15
+ import { layoutC4Diagram } from '../c4/layout.js';
16
+ import { C4Renderer, C4ErrorDisplay } from '../c4/renderer.js';
17
+ import { C4InteractiveRenderer } from '../c4/C4InteractiveRenderer.js';
18
+ import { $isC4Node } from './C4Node.js';
19
+ import { DiagramContextMenu, useDiagramContextMenu } from './DiagramContextMenu.js';
20
+ function C4DiagramDisplay({ code, nodeKey, onDoubleClick, isEditable, }) {
21
+ const [editor] = useLexicalComposerContext();
22
+ const containerRef = useRef(null);
23
+ const [isDarkMode, setIsDarkMode] = useState(false);
24
+ const [showToolbar, setShowToolbar] = useState(false);
25
+ const [isEditingLayout, setIsEditingLayout] = useState(false);
26
+ const contextMenu = useDiagramContextMenu();
27
+ const previousCodeRef = useRef(code);
28
+ // Read manual layout from the node
29
+ const [manualLayout, setManualLayout] = useState(() => {
30
+ return editor.getEditorState().read(() => {
31
+ const node = $getNodeByKey(nodeKey);
32
+ if ($isC4Node(node)) {
33
+ return node.getManualLayout();
34
+ }
35
+ return undefined;
36
+ });
37
+ });
38
+ // Sync manual layout from node when it changes externally (e.g., undo/redo)
39
+ useEffect(() => {
40
+ return editor.registerUpdateListener(({ editorState }) => {
41
+ editorState.read(() => {
42
+ const node = $getNodeByKey(nodeKey);
43
+ if ($isC4Node(node)) {
44
+ const nodeLayout = node.getManualLayout();
45
+ setManualLayout(nodeLayout);
46
+ }
47
+ });
48
+ });
49
+ }, [editor, nodeKey]);
50
+ // Detect theme using MutationObserver
51
+ useEffect(() => {
52
+ const detectTheme = () => {
53
+ const isDark = document.documentElement.classList.contains('dark');
54
+ setIsDarkMode(isDark);
55
+ };
56
+ detectTheme();
57
+ const observer = new MutationObserver(detectTheme);
58
+ observer.observe(document.documentElement, {
59
+ attributes: true,
60
+ attributeFilter: ['class'],
61
+ });
62
+ return () => observer.disconnect();
63
+ }, []);
64
+ // Parse, validate, and layout the diagram
65
+ const parseResult = useMemo(() => {
66
+ const pr = parseC4(code);
67
+ if (pr.diagram) {
68
+ const validationErrors = validateC4(pr.diagram);
69
+ pr.errors.push(...validationErrors);
70
+ }
71
+ return pr;
72
+ }, [code]);
73
+ const layout = useMemo(() => {
74
+ if (!parseResult.diagram || parseResult.errors.length > 0)
75
+ return null;
76
+ const positions = manualLayout?.positions;
77
+ return layoutC4Diagram(parseResult.diagram, undefined, positions);
78
+ }, [parseResult, manualLayout]);
79
+ // TASK 7: Clear manual layout when DSL code changes (elements added/removed)
80
+ useEffect(() => {
81
+ if (manualLayout && code !== previousCodeRef.current && parseResult.diagram) {
82
+ const newElementIds = new Set(parseResult.diagram.elements.map(e => e.id));
83
+ const oldElementIds = new Set(Object.keys(manualLayout.positions).filter(id => !id.startsWith('__')));
84
+ // Check if elements were added or removed
85
+ const hasChanges = newElementIds.size !== oldElementIds.size ||
86
+ [...newElementIds].some(id => !oldElementIds.has(id)) ||
87
+ [...oldElementIds].some(id => !newElementIds.has(id));
88
+ if (hasChanges) {
89
+ // Clear manual layout
90
+ editor.update(() => {
91
+ const node = $getNodeByKey(nodeKey);
92
+ if ($isC4Node(node)) {
93
+ node.setManualLayout(undefined);
94
+ }
95
+ });
96
+ setIsEditingLayout(false);
97
+ }
98
+ }
99
+ previousCodeRef.current = code;
100
+ }, [code, manualLayout, parseResult.diagram, editor, nodeKey]);
101
+ // TASK 6: Handle position changes with Lexical undo integration
102
+ // The update listener above will sync manualLayout state from the node,
103
+ // so we only need to write to the Lexical node here.
104
+ const handlePositionChange = useCallback((newPositions) => {
105
+ editor.update(() => {
106
+ const node = $getNodeByKey(nodeKey);
107
+ if ($isC4Node(node)) {
108
+ node.setManualLayout({ positions: newPositions });
109
+ }
110
+ });
111
+ }, [editor, nodeKey]);
112
+ // Handle reset to auto layout
113
+ const handleResetLayout = useCallback(() => {
114
+ editor.update(() => {
115
+ const node = $getNodeByKey(nodeKey);
116
+ if ($isC4Node(node)) {
117
+ node.setManualLayout(undefined);
118
+ }
119
+ });
120
+ setManualLayout(undefined);
121
+ setIsEditingLayout(false);
122
+ }, [editor, nodeKey]);
123
+ // Exit edit mode when clicking outside or pressing Escape
124
+ useEffect(() => {
125
+ if (!isEditingLayout)
126
+ return;
127
+ const handleKeyDown = (e) => {
128
+ if (e.key === 'Escape') {
129
+ setIsEditingLayout(false);
130
+ }
131
+ };
132
+ const handleMouseDown = (e) => {
133
+ const container = containerRef.current;
134
+ if (!container)
135
+ return;
136
+ if (e.target instanceof Node && !container.contains(e.target)) {
137
+ setIsEditingLayout(false);
138
+ }
139
+ };
140
+ window.addEventListener('keydown', handleKeyDown);
141
+ document.addEventListener('mousedown', handleMouseDown);
142
+ return () => {
143
+ window.removeEventListener('keydown', handleKeyDown);
144
+ document.removeEventListener('mousedown', handleMouseDown);
145
+ };
146
+ }, [isEditingLayout]);
147
+ if (!layout || !parseResult.diagram) {
148
+ return _jsx(C4ErrorDisplay, { errors: parseResult.errors, isDarkMode: isDarkMode });
149
+ }
150
+ const manualPositions = manualLayout?.positions ?? {};
151
+ return (_jsxs("div", { ref: containerRef, role: "button", tabIndex: 0, onDoubleClick: isEditingLayout ? undefined : onDoubleClick, onContextMenu: (e) => contextMenu.show(e, containerRef), onKeyDown: (e) => {
152
+ if (!isEditingLayout && (e.key === 'Enter' || e.key === ' ')) {
153
+ e.preventDefault();
154
+ onDoubleClick();
155
+ }
156
+ }, onMouseEnter: () => setShowToolbar(true), onMouseLeave: () => setShowToolbar(false), className: "c4-renderer c4-diagram-container", style: {
157
+ display: 'flex',
158
+ justifyContent: 'center',
159
+ padding: '1rem',
160
+ cursor: isEditingLayout ? 'default' : 'pointer',
161
+ minHeight: '100px',
162
+ overflowX: 'auto',
163
+ position: 'relative',
164
+ }, children: [isEditable && (showToolbar || isEditingLayout) && (_jsxs("div", { style: {
165
+ position: 'absolute',
166
+ top: '8px',
167
+ right: '8px',
168
+ display: 'flex',
169
+ gap: '4px',
170
+ zIndex: 10,
171
+ }, children: [_jsx("button", { type: "button", onClick: (e) => {
172
+ e.stopPropagation();
173
+ setIsEditingLayout(!isEditingLayout);
174
+ }, title: isEditingLayout ? 'Exit layout mode' : 'Edit layout', "aria-label": isEditingLayout ? 'Exit layout mode' : 'Edit layout', style: {
175
+ width: '28px',
176
+ height: '28px',
177
+ display: 'flex',
178
+ alignItems: 'center',
179
+ justifyContent: 'center',
180
+ borderRadius: '4px',
181
+ border: 'none',
182
+ cursor: 'pointer',
183
+ backgroundColor: isEditingLayout
184
+ ? 'var(--color-primary-100, rgba(59, 130, 246, 0.1))'
185
+ : 'var(--color-muted-100, rgba(128, 128, 128, 0.1))',
186
+ color: isEditingLayout
187
+ ? 'var(--color-primary, #3b82f6)'
188
+ : 'var(--color-muted-foreground, #6b7280)',
189
+ }, children: _jsx(Move, { size: 16 }) }), manualLayout && (_jsx("button", { type: "button", onClick: (e) => {
190
+ e.stopPropagation();
191
+ handleResetLayout();
192
+ }, title: "Reset to auto layout", "aria-label": "Reset to auto layout", style: {
193
+ width: '28px',
194
+ height: '28px',
195
+ display: 'flex',
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ borderRadius: '4px',
199
+ border: 'none',
200
+ cursor: 'pointer',
201
+ backgroundColor: 'var(--color-muted-100, rgba(128, 128, 128, 0.1))',
202
+ color: 'var(--color-muted-foreground, #6b7280)',
203
+ }, children: _jsx(RotateCcw, { size: 16 }) }))] })), isEditingLayout && parseResult.diagram ? (_jsx(C4InteractiveRenderer, { diagram: parseResult.diagram, isDarkMode: isDarkMode, isEditMode: isEditingLayout, manualPositions: manualPositions, onPositionChange: handlePositionChange })) : (_jsx(C4Renderer, { layout: layout, isDarkMode: isDarkMode })), _jsx(DiagramContextMenu, { ...contextMenu.props, onEditText: isEditable ? onDoubleClick : undefined, onEditLayout: isEditable ? () => setIsEditingLayout(prev => !prev) : undefined, onResetLayout: isEditable ? handleResetLayout : undefined, isEditingLayout: isEditingLayout, hasManualLayout: !!manualLayout })] }));
204
+ }
205
+ // =============================================================================
206
+ // C4 EDITOR
207
+ // =============================================================================
208
+ function C4Editor({ code, setCode, onClose, }) {
209
+ const textareaRef = useRef(null);
210
+ useEffect(() => {
211
+ textareaRef.current?.focus();
212
+ }, []);
213
+ const handleKeyDown = (e) => {
214
+ if (e.key === 'Escape') {
215
+ e.preventDefault();
216
+ onClose();
217
+ }
218
+ };
219
+ return (_jsxs("div", { className: "c4-editor-container", children: [_jsxs("div", { className: "c4-editor-header", children: [_jsx("span", { className: "c4-editor-label", children: "```c4" }), _jsx("button", { className: "c4-editor-close", onClick: onClose, title: "Close editor (Esc)", children: "\u2715" })] }), _jsx("textarea", { ref: textareaRef, className: "c4-editor-textarea", value: code, onChange: (e) => setCode(e.target.value), onKeyDown: handleKeyDown, placeholder: "Enter C4 diagram code...", spellCheck: false }), _jsx("div", { className: "c4-editor-footer", children: _jsx("span", { className: "c4-editor-label", children: "```" }) })] }));
220
+ }
221
+ export default function C4Component({ code, nodeKey, }) {
222
+ const [editor] = useLexicalComposerContext();
223
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
224
+ const [codeValue, setCodeValue] = useState(code);
225
+ const [showEditor, setShowEditor] = useState(false);
226
+ // Listen for editable changes
227
+ useEffect(() => {
228
+ return editor.registerEditableListener((editable) => {
229
+ setIsEditable(editable);
230
+ });
231
+ }, [editor]);
232
+ const onHide = useCallback(() => {
233
+ setShowEditor(false);
234
+ editor.update(() => {
235
+ const node = $getNodeByKey(nodeKey);
236
+ if ($isC4Node(node)) {
237
+ node.setCode(codeValue);
238
+ }
239
+ });
240
+ }, [editor, codeValue, nodeKey]);
241
+ useEffect(() => {
242
+ if (!showEditor && codeValue !== code) {
243
+ setCodeValue(code);
244
+ }
245
+ }, [showEditor, code, codeValue]);
246
+ useEffect(() => {
247
+ if (!isEditable) {
248
+ return;
249
+ }
250
+ if (showEditor) {
251
+ return mergeRegister(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
252
+ // Don't close on selection change while editing
253
+ return false;
254
+ }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
255
+ if (showEditor) {
256
+ onHide();
257
+ return true;
258
+ }
259
+ return false;
260
+ }, COMMAND_PRIORITY_HIGH));
261
+ }
262
+ else {
263
+ return editor.registerUpdateListener(({ editorState }) => {
264
+ const isSelected = editorState.read(() => {
265
+ const selection = $getSelection();
266
+ return ($isNodeSelection(selection) &&
267
+ selection.has(nodeKey) &&
268
+ selection.getNodes().length === 1);
269
+ });
270
+ if (isSelected) {
271
+ setShowEditor(true);
272
+ }
273
+ });
274
+ }
275
+ }, [editor, nodeKey, onHide, showEditor, isEditable]);
276
+ if (showEditor && isEditable) {
277
+ return (_jsx(C4Editor, { code: codeValue, setCode: setCodeValue, onClose: onHide }));
278
+ }
279
+ return (_jsx(C4DiagramDisplay, { code: codeValue, nodeKey: nodeKey, isEditable: isEditable, onDoubleClick: () => {
280
+ if (isEditable) {
281
+ setShowEditor(true);
282
+ }
283
+ } }));
284
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * C4Node - Renders C4 architecture diagrams
3
+ * Uses DecoratorNode pattern similar to MermaidNode
4
+ */
5
+ import { DecoratorNode, DOMConversionMap, DOMExportOutput, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical';
6
+ import type { ManualLayout } from '../c4/types.js';
7
+ export type SerializedC4Node = Spread<{
8
+ code: string;
9
+ manualLayout?: ManualLayout;
10
+ }, SerializedLexicalNode>;
11
+ export declare class C4Node extends DecoratorNode<JSX.Element> {
12
+ __code: string;
13
+ __manualLayout: ManualLayout | undefined;
14
+ static getType(): string;
15
+ static clone(node: C4Node): C4Node;
16
+ constructor(code: string, manualLayout?: ManualLayout, key?: NodeKey);
17
+ static importJSON(serializedNode: SerializedC4Node): C4Node;
18
+ exportJSON(): SerializedC4Node;
19
+ createDOM(): HTMLElement;
20
+ exportDOM(): DOMExportOutput;
21
+ static importDOM(): DOMConversionMap | null;
22
+ updateDOM(): boolean;
23
+ isInline(): boolean;
24
+ getTextContent(): string;
25
+ getCode(): string;
26
+ setCode(code: string): void;
27
+ getManualLayout(): ManualLayout | undefined;
28
+ setManualLayout(layout: ManualLayout | undefined): void;
29
+ decorate(): JSX.Element;
30
+ }
31
+ export declare function $createC4Node(code?: string, manualLayout?: ManualLayout): C4Node;
32
+ export declare function $isC4Node(node: LexicalNode | null | undefined): node is C4Node;
@@ -0,0 +1,112 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ /**
3
+ * C4Node - Renders C4 architecture diagrams
4
+ * Uses DecoratorNode pattern similar to MermaidNode
5
+ */
6
+ import { DecoratorNode, $applyNodeReplacement, } from 'lexical';
7
+ import { createElement, lazy, Suspense } from 'react';
8
+ const C4Component = lazy(() => import('./C4Component.js'));
9
+ function $convertC4Element(domNode) {
10
+ const code = domNode.getAttribute('data-lexical-c4');
11
+ if (code) {
12
+ // Decode from base64
13
+ const decodedCode = atob(code);
14
+ const node = $createC4Node(decodedCode);
15
+ return { node };
16
+ }
17
+ return null;
18
+ }
19
+ export class C4Node extends DecoratorNode {
20
+ __code;
21
+ __manualLayout;
22
+ static getType() {
23
+ return 'c4';
24
+ }
25
+ static clone(node) {
26
+ const layout = node.__manualLayout
27
+ ? { positions: { ...node.__manualLayout.positions } }
28
+ : undefined;
29
+ return new C4Node(node.__code, layout, node.__key);
30
+ }
31
+ constructor(code, manualLayout, key) {
32
+ super(key);
33
+ this.__code = code;
34
+ this.__manualLayout = manualLayout;
35
+ }
36
+ static importJSON(serializedNode) {
37
+ return $createC4Node(serializedNode.code, serializedNode.manualLayout);
38
+ }
39
+ exportJSON() {
40
+ const json = {
41
+ type: 'c4',
42
+ version: 1,
43
+ code: this.getCode(),
44
+ };
45
+ if (this.__manualLayout) {
46
+ json.manualLayout = this.__manualLayout;
47
+ }
48
+ return json;
49
+ }
50
+ createDOM() {
51
+ const element = document.createElement('div');
52
+ element.className = 'editor-c4';
53
+ return element;
54
+ }
55
+ exportDOM() {
56
+ const element = document.createElement('div');
57
+ // Encode as base64 to avoid issues with special characters
58
+ element.setAttribute('data-lexical-c4', btoa(this.__code));
59
+ element.className = 'c4-export';
60
+ element.textContent = this.__code;
61
+ return { element };
62
+ }
63
+ static importDOM() {
64
+ return {
65
+ div: (domNode) => {
66
+ if (!domNode.hasAttribute('data-lexical-c4')) {
67
+ return null;
68
+ }
69
+ return {
70
+ conversion: $convertC4Element,
71
+ priority: 2,
72
+ };
73
+ },
74
+ };
75
+ }
76
+ updateDOM() {
77
+ return false;
78
+ }
79
+ isInline() {
80
+ return false;
81
+ }
82
+ getTextContent() {
83
+ return this.__code;
84
+ }
85
+ getCode() {
86
+ return this.__code;
87
+ }
88
+ setCode(code) {
89
+ const writable = this.getWritable();
90
+ writable.__code = code;
91
+ }
92
+ getManualLayout() {
93
+ return this.__manualLayout;
94
+ }
95
+ setManualLayout(layout) {
96
+ const writable = this.getWritable();
97
+ writable.__manualLayout = layout;
98
+ }
99
+ decorate() {
100
+ return createElement(Suspense, { fallback: createElement('div', { className: 'c4-loading' }, 'Loading diagram...') }, createElement(C4Component, {
101
+ code: this.__code,
102
+ nodeKey: this.__key,
103
+ }));
104
+ }
105
+ }
106
+ export function $createC4Node(code = '', manualLayout) {
107
+ const c4Node = new C4Node(code, manualLayout);
108
+ return $applyNodeReplacement(c4Node);
109
+ }
110
+ export function $isC4Node(node) {
111
+ return node instanceof C4Node;
112
+ }
@@ -0,0 +1,23 @@
1
+ import { DOMConversionMap, DOMExportOutput, EditorConfig, ElementNode, LexicalNode, NodeKey, SerializedElementNode, Spread } from 'lexical';
2
+ export type CalloutType = 'note' | 'tip' | 'warning' | 'important' | 'caution';
3
+ export type SerializedCalloutNode = Spread<{
4
+ calloutType: CalloutType;
5
+ }, SerializedElementNode>;
6
+ export declare class CalloutNode extends ElementNode {
7
+ __calloutType: CalloutType;
8
+ static getType(): string;
9
+ static clone(node: CalloutNode): CalloutNode;
10
+ constructor(calloutType: CalloutType, key?: NodeKey);
11
+ getCalloutType(): CalloutType;
12
+ setCalloutType(calloutType: CalloutType): void;
13
+ createDOM(config: EditorConfig): HTMLElement;
14
+ updateDOM(prevNode: CalloutNode, dom: HTMLElement): boolean;
15
+ static importDOM(): DOMConversionMap | null;
16
+ exportDOM(): DOMExportOutput;
17
+ static importJSON(serializedNode: SerializedCalloutNode): CalloutNode;
18
+ exportJSON(): SerializedCalloutNode;
19
+ canBeEmpty(): boolean;
20
+ isShadowRoot(): boolean;
21
+ }
22
+ export declare function $createCalloutNode(calloutType: CalloutType, content?: string): CalloutNode;
23
+ export declare function $isCalloutNode(node: LexicalNode | null | undefined): node is CalloutNode;
@@ -0,0 +1,94 @@
1
+ import { $createParagraphNode, $createTextNode, ElementNode, } from 'lexical';
2
+ export class CalloutNode extends ElementNode {
3
+ __calloutType;
4
+ static getType() {
5
+ return 'callout';
6
+ }
7
+ static clone(node) {
8
+ return new CalloutNode(node.__calloutType, node.__key);
9
+ }
10
+ constructor(calloutType, key) {
11
+ super(key);
12
+ this.__calloutType = calloutType;
13
+ }
14
+ getCalloutType() {
15
+ return this.__calloutType;
16
+ }
17
+ setCalloutType(calloutType) {
18
+ const writable = this.getWritable();
19
+ writable.__calloutType = calloutType;
20
+ }
21
+ createDOM(config) {
22
+ void config;
23
+ const element = document.createElement('div');
24
+ element.className = `callout callout-${this.__calloutType}`;
25
+ element.setAttribute('data-callout-type', this.__calloutType);
26
+ return element;
27
+ }
28
+ updateDOM(prevNode, dom) {
29
+ if (prevNode.__calloutType !== this.__calloutType) {
30
+ dom.className = `callout callout-${this.__calloutType}`;
31
+ dom.setAttribute('data-callout-type', this.__calloutType);
32
+ }
33
+ return false;
34
+ }
35
+ static importDOM() {
36
+ return {
37
+ div: (domNode) => {
38
+ if (domNode.classList.contains('callout')) {
39
+ return {
40
+ conversion: convertCalloutElement,
41
+ priority: 1,
42
+ };
43
+ }
44
+ return null;
45
+ },
46
+ };
47
+ }
48
+ exportDOM() {
49
+ const element = document.createElement('div');
50
+ element.className = `callout callout-${this.__calloutType}`;
51
+ element.setAttribute('data-callout-type', this.__calloutType);
52
+ return { element };
53
+ }
54
+ static importJSON(serializedNode) {
55
+ void serializedNode;
56
+ const node = $createCalloutNode(serializedNode.calloutType);
57
+ return node;
58
+ }
59
+ exportJSON() {
60
+ return {
61
+ ...super.exportJSON(),
62
+ type: 'callout',
63
+ calloutType: this.__calloutType,
64
+ version: 1,
65
+ };
66
+ }
67
+ // Allow block-level content inside callouts
68
+ canBeEmpty() {
69
+ return false;
70
+ }
71
+ isShadowRoot() {
72
+ return true;
73
+ }
74
+ }
75
+ function convertCalloutElement(domNode) {
76
+ const calloutType = (domNode.getAttribute('data-callout-type') || 'note');
77
+ const node = $createCalloutNode(calloutType);
78
+ return { node };
79
+ }
80
+ export function $createCalloutNode(calloutType, content) {
81
+ const node = new CalloutNode(calloutType);
82
+ // If content is provided, create a paragraph with text inside
83
+ if (content !== undefined) {
84
+ const paragraph = $createParagraphNode();
85
+ if (content) {
86
+ paragraph.append($createTextNode(content));
87
+ }
88
+ node.append(paragraph);
89
+ }
90
+ return node;
91
+ }
92
+ export function $isCalloutNode(node) {
93
+ return node instanceof CalloutNode;
94
+ }
@@ -0,0 +1,57 @@
1
+ import { LinkNode, SerializedLinkNode } from '@lexical/link';
2
+ import { DOMConversionMap, EditorConfig, LexicalNode } from 'lexical';
3
+ export type SerializedCustomLinkNode = SerializedLinkNode & {
4
+ wikiAliasState?: 'empty';
5
+ wikiLinkOrigin?: true;
6
+ };
7
+ /**
8
+ * CustomLinkNode - Extends Lexical's LinkNode to prevent VS Code webview link interception
9
+ *
10
+ * The standard LinkNode renders `<a href="...">` which VS Code webviews intercept and
11
+ * open automatically on click. This custom node renders `<a data-href="...">` instead,
12
+ * allowing us to control navigation via JavaScript (Cmd+click to open).
13
+ */
14
+ export declare class CustomLinkNode extends LinkNode {
15
+ /** @internal */
16
+ __wikiAliasState: 'empty' | null;
17
+ /**
18
+ * Whether this link was parsed from genuine `[[target]]` / `[[target|alias]]`
19
+ * wiki-link syntax, as opposed to a standard `[text](url)` link whose URL
20
+ * merely looks wiki-link-shaped. Export (`convertLinkNode` in
21
+ * `lexicalToMdast.ts`) always promotes a node with this flag set back to
22
+ * wiki-link syntax, regardless of `wikiLinkPromotionMode` — an opted-out
23
+ * host still must not corrupt a document's existing wiki-links (liminis#951).
24
+ * @internal
25
+ */
26
+ __wikiLinkOrigin: boolean;
27
+ constructor(url: string, attributes?: {
28
+ rel?: null | string;
29
+ target?: null | string;
30
+ title?: null | string;
31
+ }, key?: string);
32
+ static getType(): string;
33
+ static clone(node: CustomLinkNode): CustomLinkNode;
34
+ createDOM(config: EditorConfig): HTMLAnchorElement;
35
+ /**
36
+ * Check if this link is a wiki-link (internal markdown file)
37
+ */
38
+ isWikiLink(): boolean;
39
+ /**
40
+ * Check if this link is an external link (http/https/mailto)
41
+ */
42
+ isExternalLink(): boolean;
43
+ updateDOM(prevNode: CustomLinkNode, anchor: HTMLAnchorElement, config: EditorConfig): boolean;
44
+ static importDOM(): DOMConversionMap | null;
45
+ static importJSON(serializedNode: SerializedCustomLinkNode): CustomLinkNode;
46
+ exportJSON(): SerializedCustomLinkNode;
47
+ setWikiAliasState(state: 'empty' | null): void;
48
+ getWikiAliasState(): 'empty' | null;
49
+ setWikiLinkOrigin(origin: boolean): void;
50
+ getWikiLinkOrigin(): boolean;
51
+ }
52
+ export declare function $createCustomLinkNode(url: string, attributes?: {
53
+ rel?: null | string;
54
+ target?: null | string;
55
+ title?: null | string;
56
+ }): CustomLinkNode;
57
+ export declare function $isCustomLinkNode(node: LexicalNode | null | undefined): node is CustomLinkNode;