@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,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C4-PlantUML Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses C4-PlantUML macro syntax into a C4 diagram AST.
|
|
5
|
+
* Supports: Person, System, Container, Component (and _Ext, Db, Queue variants),
|
|
6
|
+
* System_Boundary, Container_Boundary, Rel (and directional variants), BiRel.
|
|
7
|
+
*
|
|
8
|
+
* Strips: @startuml/@enduml, !include, SHOW_LEGEND(), LAYOUT_* directives.
|
|
9
|
+
*/
|
|
10
|
+
import type { C4Diagram, ParseError, ParseResult } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Parse C4-PlantUML code into a C4 diagram AST.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseC4(code: string): ParseResult;
|
|
15
|
+
/**
|
|
16
|
+
* Validate that a C4 diagram is well-formed.
|
|
17
|
+
* Checks for duplicate IDs, missing relationship targets, etc.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateC4(diagram: C4Diagram): ParseError[];
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C4-PlantUML Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses C4-PlantUML macro syntax into a C4 diagram AST.
|
|
5
|
+
* Supports: Person, System, Container, Component (and _Ext, Db, Queue variants),
|
|
6
|
+
* System_Boundary, Container_Boundary, Rel (and directional variants), BiRel.
|
|
7
|
+
*
|
|
8
|
+
* Strips: @startuml/@enduml, !include, SHOW_LEGEND(), LAYOUT_* directives.
|
|
9
|
+
*/
|
|
10
|
+
const ELEMENT_MACROS = {
|
|
11
|
+
// Person
|
|
12
|
+
Person: { type: 'person', external: false, shape: 'rectangle', argStyle: 'system', boundary: false },
|
|
13
|
+
Person_Ext: { type: 'person', external: true, shape: 'rectangle', argStyle: 'system', boundary: false },
|
|
14
|
+
// System
|
|
15
|
+
System: { type: 'system', external: false, shape: 'rectangle', argStyle: 'system', boundary: false },
|
|
16
|
+
System_Ext: { type: 'system', external: true, shape: 'rectangle', argStyle: 'system', boundary: false },
|
|
17
|
+
SystemDb: { type: 'system', external: false, shape: 'cylinder', argStyle: 'system', boundary: false },
|
|
18
|
+
SystemDb_Ext: { type: 'system', external: true, shape: 'cylinder', argStyle: 'system', boundary: false },
|
|
19
|
+
SystemQueue: { type: 'system', external: false, shape: 'queue', argStyle: 'system', boundary: false },
|
|
20
|
+
SystemQueue_Ext: { type: 'system', external: true, shape: 'queue', argStyle: 'system', boundary: false },
|
|
21
|
+
// Container
|
|
22
|
+
Container: { type: 'container', external: false, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
23
|
+
Container_Ext: { type: 'container', external: true, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
24
|
+
ContainerDb: { type: 'container', external: false, shape: 'cylinder', argStyle: 'detail', boundary: false },
|
|
25
|
+
ContainerDb_Ext: { type: 'container', external: true, shape: 'cylinder', argStyle: 'detail', boundary: false },
|
|
26
|
+
ContainerQueue: { type: 'container', external: false, shape: 'queue', argStyle: 'detail', boundary: false },
|
|
27
|
+
ContainerQueue_Ext: { type: 'container', external: true, shape: 'queue', argStyle: 'detail', boundary: false },
|
|
28
|
+
// Component
|
|
29
|
+
Component: { type: 'component', external: false, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
30
|
+
Component_Ext: { type: 'component', external: true, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
31
|
+
ComponentDb: { type: 'component', external: false, shape: 'cylinder', argStyle: 'detail', boundary: false },
|
|
32
|
+
ComponentDb_Ext: { type: 'component', external: true, shape: 'cylinder', argStyle: 'detail', boundary: false },
|
|
33
|
+
ComponentQueue: { type: 'component', external: false, shape: 'queue', argStyle: 'detail', boundary: false },
|
|
34
|
+
ComponentQueue_Ext: { type: 'component', external: true, shape: 'queue', argStyle: 'detail', boundary: false },
|
|
35
|
+
// Boundaries
|
|
36
|
+
System_Boundary: { type: 'system', external: false, shape: 'rectangle', argStyle: 'system', boundary: true },
|
|
37
|
+
Container_Boundary: { type: 'container', external: false, shape: 'rectangle', argStyle: 'system', boundary: true },
|
|
38
|
+
Enterprise_Boundary: { type: 'system', external: false, shape: 'rectangle', argStyle: 'system', boundary: true },
|
|
39
|
+
Boundary: { type: 'system', external: false, shape: 'rectangle', argStyle: 'system', boundary: true },
|
|
40
|
+
// Deployment nodes (C4 deployment diagrams)
|
|
41
|
+
Deployment_Node: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
42
|
+
Deployment_Node_L: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
43
|
+
Deployment_Node_R: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
44
|
+
Node: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
45
|
+
Node_L: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
46
|
+
Node_R: { type: 'system', external: false, shape: 'rectangle', argStyle: 'detail', boundary: true },
|
|
47
|
+
// Infrastructure nodes (C4 deployment diagrams)
|
|
48
|
+
InfrastructureNode: { type: 'component', external: false, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
49
|
+
InfrastructureNode_Ext: { type: 'component', external: true, shape: 'rectangle', argStyle: 'detail', boundary: false },
|
|
50
|
+
};
|
|
51
|
+
const REL_MACROS = new Set([
|
|
52
|
+
'Rel', 'Rel_Back', 'Rel_Neighbor',
|
|
53
|
+
'Rel_D', 'Rel_U', 'Rel_L', 'Rel_R',
|
|
54
|
+
'BiRel', 'BiRel_D', 'BiRel_U', 'BiRel_L', 'BiRel_R',
|
|
55
|
+
]);
|
|
56
|
+
/** Directives and macros to silently skip */
|
|
57
|
+
const SKIP_PREFIXES = ['@startuml', '@enduml', '!include', '!define', 'SHOW_', 'HIDE_', 'title '];
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// LINE-BASED PARSER
|
|
60
|
+
// =============================================================================
|
|
61
|
+
/**
|
|
62
|
+
* Parse C4-PlantUML code into a C4 diagram AST.
|
|
63
|
+
*/
|
|
64
|
+
export function parseC4(code) {
|
|
65
|
+
const parser = new C4PlantUMLParser(code);
|
|
66
|
+
return parser.parse();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate that a C4 diagram is well-formed.
|
|
70
|
+
* Checks for duplicate IDs, missing relationship targets, etc.
|
|
71
|
+
*/
|
|
72
|
+
export function validateC4(diagram) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
const elementIds = new Set();
|
|
75
|
+
for (const element of diagram.elements) {
|
|
76
|
+
if (elementIds.has(element.id)) {
|
|
77
|
+
errors.push({
|
|
78
|
+
message: `Duplicate element ID: '${element.id}'`,
|
|
79
|
+
line: 0,
|
|
80
|
+
column: 0,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
elementIds.add(element.id);
|
|
84
|
+
}
|
|
85
|
+
for (const rel of diagram.relationships) {
|
|
86
|
+
if (!elementIds.has(rel.sourceId)) {
|
|
87
|
+
errors.push({
|
|
88
|
+
message: `Relationship source '${rel.sourceId}' not found`,
|
|
89
|
+
line: 0,
|
|
90
|
+
column: 0,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (!elementIds.has(rel.targetId)) {
|
|
94
|
+
errors.push({
|
|
95
|
+
message: `Relationship target '${rel.targetId}' not found`,
|
|
96
|
+
line: 0,
|
|
97
|
+
column: 0,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return errors;
|
|
102
|
+
}
|
|
103
|
+
class C4PlantUMLParser {
|
|
104
|
+
lines;
|
|
105
|
+
lineNum = 0;
|
|
106
|
+
elements = [];
|
|
107
|
+
relationships = [];
|
|
108
|
+
errors = [];
|
|
109
|
+
direction;
|
|
110
|
+
constructor(code) {
|
|
111
|
+
this.lines = code.split('\n');
|
|
112
|
+
}
|
|
113
|
+
parse() {
|
|
114
|
+
this.parseLines(undefined);
|
|
115
|
+
const diagram = {
|
|
116
|
+
elements: this.elements,
|
|
117
|
+
relationships: this.relationships,
|
|
118
|
+
direction: this.direction,
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
diagram: this.errors.length === 0 ? diagram : null,
|
|
122
|
+
errors: this.errors,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse lines at the current level. If parentId is set, we're inside a boundary.
|
|
127
|
+
* Stops when we hit a closing brace or run out of lines.
|
|
128
|
+
*/
|
|
129
|
+
parseLines(parentId) {
|
|
130
|
+
while (this.lineNum < this.lines.length) {
|
|
131
|
+
const rawLine = this.lines[this.lineNum];
|
|
132
|
+
const line = this.stripComment(rawLine).trim();
|
|
133
|
+
// Empty or comment-only line
|
|
134
|
+
if (line === '') {
|
|
135
|
+
this.lineNum++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Closing brace — return to parent
|
|
139
|
+
if (line === '}' || line.startsWith('}')) {
|
|
140
|
+
this.lineNum++;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Parse LAYOUT directives
|
|
144
|
+
if (line.startsWith('LAYOUT_TOP_DOWN')) {
|
|
145
|
+
this.direction = 'down';
|
|
146
|
+
this.lineNum++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (line.startsWith('LAYOUT_LEFT_RIGHT')) {
|
|
150
|
+
this.direction = 'right';
|
|
151
|
+
this.lineNum++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Skip other directives
|
|
155
|
+
if (this.isSkippable(line)) {
|
|
156
|
+
this.lineNum++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Extract the macro name (first word before '(')
|
|
160
|
+
const macroMatch = /^([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line);
|
|
161
|
+
if (!macroMatch) {
|
|
162
|
+
// Unknown line
|
|
163
|
+
this.errors.push({
|
|
164
|
+
message: `Unrecognized syntax: '${line.slice(0, 50)}'`,
|
|
165
|
+
line: this.lineNum + 1,
|
|
166
|
+
column: 1,
|
|
167
|
+
});
|
|
168
|
+
this.lineNum++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const macroName = macroMatch[1];
|
|
172
|
+
// Element macro
|
|
173
|
+
if (macroName in ELEMENT_MACROS) {
|
|
174
|
+
this.parseElementMacro(macroName, line, parentId);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// Relationship macro
|
|
178
|
+
if (REL_MACROS.has(macroName)) {
|
|
179
|
+
this.parseRelMacro(line);
|
|
180
|
+
this.lineNum++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// Unknown macro — skip
|
|
184
|
+
this.errors.push({
|
|
185
|
+
message: `Unknown macro: '${macroName}'`,
|
|
186
|
+
line: this.lineNum + 1,
|
|
187
|
+
column: 1,
|
|
188
|
+
});
|
|
189
|
+
this.lineNum++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Parse an element macro like Person(alias, "Label", "Description")
|
|
194
|
+
* or a boundary like System_Boundary(alias, "Label") { ... }
|
|
195
|
+
*/
|
|
196
|
+
parseElementMacro(macroName, line, parentId) {
|
|
197
|
+
const info = ELEMENT_MACROS[macroName];
|
|
198
|
+
const currentLine = this.lineNum + 1;
|
|
199
|
+
// Extract arguments from parentheses
|
|
200
|
+
const args = this.extractArgs(line, currentLine);
|
|
201
|
+
if (args === null) {
|
|
202
|
+
this.lineNum++;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// First arg is always the alias (unquoted identifier)
|
|
206
|
+
const alias = args[0]?.replace(/^"|"$/g, '');
|
|
207
|
+
if (!alias) {
|
|
208
|
+
this.errors.push({
|
|
209
|
+
message: `${macroName}: missing alias (first argument)`,
|
|
210
|
+
line: currentLine,
|
|
211
|
+
column: 1,
|
|
212
|
+
});
|
|
213
|
+
this.lineNum++;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Build properties from remaining args
|
|
217
|
+
const properties = {};
|
|
218
|
+
let name;
|
|
219
|
+
if (info.external)
|
|
220
|
+
properties.external = true;
|
|
221
|
+
if (info.shape !== 'rectangle')
|
|
222
|
+
properties.shape = info.shape;
|
|
223
|
+
if (info.boundary)
|
|
224
|
+
properties.style = 'boundary';
|
|
225
|
+
if (info.argStyle === 'system') {
|
|
226
|
+
// (alias, "label", "description")
|
|
227
|
+
name = this.unquote(args[1] ?? '') || alias;
|
|
228
|
+
const desc = this.unquote(args[2] ?? '');
|
|
229
|
+
if (desc)
|
|
230
|
+
properties.description = desc;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// (alias, "label", "technology", "description")
|
|
234
|
+
name = this.unquote(args[1] ?? '') || alias;
|
|
235
|
+
const tech = this.unquote(args[2] ?? '');
|
|
236
|
+
const desc = this.unquote(args[3] ?? '');
|
|
237
|
+
if (tech)
|
|
238
|
+
properties.tech = tech;
|
|
239
|
+
if (desc)
|
|
240
|
+
properties.description = desc;
|
|
241
|
+
}
|
|
242
|
+
// Check for $tags and other named params
|
|
243
|
+
for (const arg of args) {
|
|
244
|
+
if (arg.startsWith('$tags=')) {
|
|
245
|
+
// Could be used for styling in the future
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const element = {
|
|
249
|
+
type: info.type,
|
|
250
|
+
id: alias,
|
|
251
|
+
name,
|
|
252
|
+
properties,
|
|
253
|
+
children: [],
|
|
254
|
+
parent: parentId,
|
|
255
|
+
};
|
|
256
|
+
this.elements.push(element);
|
|
257
|
+
// Check if this line ends with { (boundary with children)
|
|
258
|
+
const hasBrace = line.includes('{') && !line.includes('}');
|
|
259
|
+
if (hasBrace) {
|
|
260
|
+
this.lineNum++;
|
|
261
|
+
// Parse children
|
|
262
|
+
const childrenBefore = this.elements.length;
|
|
263
|
+
this.parseLines(alias);
|
|
264
|
+
// Link children
|
|
265
|
+
for (let i = childrenBefore; i < this.elements.length; i++) {
|
|
266
|
+
if (this.elements[i].parent === alias) {
|
|
267
|
+
element.children.push(this.elements[i]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
this.lineNum++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Parse a relationship macro like Rel(from, to, "label", "tech")
|
|
277
|
+
*/
|
|
278
|
+
parseRelMacro(line) {
|
|
279
|
+
const currentLine = this.lineNum + 1;
|
|
280
|
+
const args = this.extractArgs(line, currentLine);
|
|
281
|
+
if (args === null)
|
|
282
|
+
return;
|
|
283
|
+
if (args.length < 3) {
|
|
284
|
+
this.errors.push({
|
|
285
|
+
message: `Relationship requires at least 3 arguments (from, to, label)`,
|
|
286
|
+
line: currentLine,
|
|
287
|
+
column: 1,
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const sourceId = args[0].replace(/^"|"$/g, '');
|
|
292
|
+
const targetId = args[1].replace(/^"|"$/g, '');
|
|
293
|
+
const label = this.unquote(args[2]);
|
|
294
|
+
const tech = this.unquote(args[3] ?? '');
|
|
295
|
+
const fullLabel = tech ? `${label} [${tech}]` : label;
|
|
296
|
+
this.relationships.push({
|
|
297
|
+
sourceId,
|
|
298
|
+
targetId,
|
|
299
|
+
label: fullLabel,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Extract comma-separated arguments from a macro call.
|
|
304
|
+
* Handles quoted strings with commas inside them.
|
|
305
|
+
*/
|
|
306
|
+
extractArgs(line, lineNum) {
|
|
307
|
+
// Find opening paren
|
|
308
|
+
const openIdx = line.indexOf('(');
|
|
309
|
+
if (openIdx === -1) {
|
|
310
|
+
this.errors.push({
|
|
311
|
+
message: `Expected '(' in macro call`,
|
|
312
|
+
line: lineNum,
|
|
313
|
+
column: 1,
|
|
314
|
+
});
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
// Find matching closing paren (handle nested parens in edge cases)
|
|
318
|
+
let depth = 0;
|
|
319
|
+
let closeIdx = -1;
|
|
320
|
+
for (let i = openIdx; i < line.length; i++) {
|
|
321
|
+
if (line[i] === '(')
|
|
322
|
+
depth++;
|
|
323
|
+
else if (line[i] === ')') {
|
|
324
|
+
depth--;
|
|
325
|
+
if (depth === 0) {
|
|
326
|
+
closeIdx = i;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (closeIdx === -1) {
|
|
332
|
+
this.errors.push({
|
|
333
|
+
message: `Unmatched '(' — missing closing ')'`,
|
|
334
|
+
line: lineNum,
|
|
335
|
+
column: openIdx + 1,
|
|
336
|
+
});
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const inner = line.slice(openIdx + 1, closeIdx);
|
|
340
|
+
return this.splitArgs(inner);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Split a comma-separated argument string, respecting quoted strings.
|
|
344
|
+
*/
|
|
345
|
+
splitArgs(inner) {
|
|
346
|
+
const args = [];
|
|
347
|
+
let current = '';
|
|
348
|
+
let inQuote = false;
|
|
349
|
+
let escaped = false;
|
|
350
|
+
for (const char of inner) {
|
|
351
|
+
if (escaped) {
|
|
352
|
+
current += char;
|
|
353
|
+
escaped = false;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (char === '\\') {
|
|
357
|
+
escaped = true;
|
|
358
|
+
current += char;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (char === '"') {
|
|
362
|
+
inQuote = !inQuote;
|
|
363
|
+
current += char;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (char === ',' && !inQuote) {
|
|
367
|
+
const trimmed = current.trim();
|
|
368
|
+
if (trimmed)
|
|
369
|
+
args.push(trimmed);
|
|
370
|
+
current = '';
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
current += char;
|
|
374
|
+
}
|
|
375
|
+
const trimmed = current.trim();
|
|
376
|
+
if (trimmed)
|
|
377
|
+
args.push(trimmed);
|
|
378
|
+
return args;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Remove surrounding quotes from a string argument.
|
|
382
|
+
*/
|
|
383
|
+
unquote(s) {
|
|
384
|
+
s = s.trim();
|
|
385
|
+
if (s.startsWith('"') && s.endsWith('"')) {
|
|
386
|
+
return s.slice(1, -1);
|
|
387
|
+
}
|
|
388
|
+
return s;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Strip single-line comment (C4-PlantUML uses ' for comments)
|
|
392
|
+
*/
|
|
393
|
+
stripComment(line) {
|
|
394
|
+
let inQuote = false;
|
|
395
|
+
for (let i = 0; i < line.length; i++) {
|
|
396
|
+
if (line[i] === '"')
|
|
397
|
+
inQuote = !inQuote;
|
|
398
|
+
if (line[i] === '\'' && !inQuote) {
|
|
399
|
+
return line.slice(0, i);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return line;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Check if a line should be silently skipped (directives, etc.)
|
|
406
|
+
*/
|
|
407
|
+
isSkippable(line) {
|
|
408
|
+
return SKIP_PREFIXES.some((p) => line.startsWith(p));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless C4 SVG rendering — pure .ts wrapper (no JSX).
|
|
3
|
+
*
|
|
4
|
+
* Bridges the React C4Renderer to Node.js contexts (Electron main process)
|
|
5
|
+
* by using renderToStaticMarkup + createElement instead of JSX.
|
|
6
|
+
*
|
|
7
|
+
* Used by the app_render_c4_diagram MCP tool for Confluence publishing.
|
|
8
|
+
*/
|
|
9
|
+
import type { ParseError } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse C4 code and render to SVG string in one call.
|
|
12
|
+
*
|
|
13
|
+
* @param code - C4 DSL source code
|
|
14
|
+
* @param isDarkMode - Whether to render in dark mode
|
|
15
|
+
* @param manualPositions - Optional manual positions for elements (bypasses dagre auto-layout)
|
|
16
|
+
* @returns svg string and any parse/validation errors
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderC4DiagramToSVG(code: string, isDarkMode?: boolean, manualPositions?: Record<string, {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}>): {
|
|
22
|
+
svg: string;
|
|
23
|
+
errors: ParseError[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless C4 SVG rendering — pure .ts wrapper (no JSX).
|
|
3
|
+
*
|
|
4
|
+
* Bridges the React C4Renderer to Node.js contexts (Electron main process)
|
|
5
|
+
* by using renderToStaticMarkup + createElement instead of JSX.
|
|
6
|
+
*
|
|
7
|
+
* Used by the app_render_c4_diagram MCP tool for Confluence publishing.
|
|
8
|
+
*/
|
|
9
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
10
|
+
import { createElement } from 'react';
|
|
11
|
+
import { C4Renderer } from './renderer.js';
|
|
12
|
+
import { parseC4, validateC4 } from './parser.js';
|
|
13
|
+
import { layoutC4Diagram } from './layout.js';
|
|
14
|
+
/**
|
|
15
|
+
* Parse C4 code and render to SVG string in one call.
|
|
16
|
+
*
|
|
17
|
+
* @param code - C4 DSL source code
|
|
18
|
+
* @param isDarkMode - Whether to render in dark mode
|
|
19
|
+
* @param manualPositions - Optional manual positions for elements (bypasses dagre auto-layout)
|
|
20
|
+
* @returns svg string and any parse/validation errors
|
|
21
|
+
*/
|
|
22
|
+
export function renderC4DiagramToSVG(code, isDarkMode = false, manualPositions) {
|
|
23
|
+
const parseResult = parseC4(code);
|
|
24
|
+
if (parseResult.diagram) {
|
|
25
|
+
const validationErrors = validateC4(parseResult.diagram);
|
|
26
|
+
parseResult.errors.push(...validationErrors);
|
|
27
|
+
}
|
|
28
|
+
if (!parseResult.diagram || parseResult.errors.length > 0) {
|
|
29
|
+
return { svg: '', errors: parseResult.errors };
|
|
30
|
+
}
|
|
31
|
+
const layout = layoutC4Diagram(parseResult.diagram, undefined, manualPositions);
|
|
32
|
+
const svg = renderToStaticMarkup(createElement(C4Renderer, { layout, isDarkMode }));
|
|
33
|
+
return { svg, errors: [] };
|
|
34
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C4 SVG Renderer (Canonical)
|
|
3
|
+
*
|
|
4
|
+
* Pure React component that renders C4 architecture diagrams as SVG.
|
|
5
|
+
* This is the single authoritative renderer — used in the editor (via C4Node)
|
|
6
|
+
* and for headless SVG generation (via renderToStaticMarkup for publishing).
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - All C4 element types (person, system, container, component)
|
|
10
|
+
* - All shapes (rectangle, cylinder, queue)
|
|
11
|
+
* - Boundary nesting with alternating fills
|
|
12
|
+
* - Edge labels with line clipping, rotation, and multi-line support
|
|
13
|
+
* - Step number labels (circled) and legend ref labels (squared)
|
|
14
|
+
* - Smart legend placement for long/parallel labels
|
|
15
|
+
* - Light and dark theme support
|
|
16
|
+
*/
|
|
17
|
+
import type { LayoutResult } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Compute legend placement and dimensions for a layout.
|
|
20
|
+
* Exported for use by C4InteractiveRenderer to create a legend hit area.
|
|
21
|
+
* Returns null if the layout has no legend entries.
|
|
22
|
+
*/
|
|
23
|
+
export declare function computeLegendInfo(layout: LayoutResult): {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
} | null;
|
|
29
|
+
export interface C4RendererProps {
|
|
30
|
+
/** Layout result from the layout engine */
|
|
31
|
+
layout: LayoutResult;
|
|
32
|
+
/** Whether dark mode is enabled */
|
|
33
|
+
isDarkMode: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* C4 Diagram SVG Renderer.
|
|
37
|
+
*
|
|
38
|
+
* Pure component: takes layout + theme, returns SVG.
|
|
39
|
+
* Used directly in the editor and via renderToStaticMarkup for publishing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function C4Renderer({ layout, isDarkMode }: C4RendererProps): JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* Renders C4 diagram content (nodes, edges, legend) as SVG group elements.
|
|
44
|
+
* Exported for use by C4InteractiveRenderer to embed diagram content
|
|
45
|
+
* inside a custom SVG wrapper without depending on C4Renderer's DOM structure.
|
|
46
|
+
*/
|
|
47
|
+
export interface C4RendererContentProps extends C4RendererProps {
|
|
48
|
+
/** Override legend position (for manual layout mode) */
|
|
49
|
+
legendPositionOverride?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
} | null;
|
|
53
|
+
}
|
|
54
|
+
export declare function C4RendererContent({ layout, isDarkMode, legendPositionOverride }: C4RendererContentProps): JSX.Element;
|
|
55
|
+
export interface C4ErrorDisplayProps {
|
|
56
|
+
errors: {
|
|
57
|
+
message: string;
|
|
58
|
+
line: number;
|
|
59
|
+
column: number;
|
|
60
|
+
}[];
|
|
61
|
+
isDarkMode: boolean;
|
|
62
|
+
}
|
|
63
|
+
export declare function C4ErrorDisplay({ errors, isDarkMode }: C4ErrorDisplayProps): JSX.Element;
|
|
64
|
+
export default C4Renderer;
|