@diagrammo/dgmo 0.8.3 → 0.8.5
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/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +452 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +188 -185
- package/dist/editor.cjs +338 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +307 -0
- package/dist/editor.js.map +1 -0
- package/dist/highlight.cjs +560 -0
- package/dist/highlight.cjs.map +1 -0
- package/dist/highlight.d.cts +32 -0
- package/dist/highlight.d.ts +32 -0
- package/dist/highlight.js +530 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.cjs +3467 -1078
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +3466 -1078
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +46 -37
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +9 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +71 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +100 -55
- package/src/chart.ts +91 -28
- package/src/class/parser.ts +41 -12
- package/src/cli.ts +211 -62
- package/src/completion.ts +378 -183
- package/src/d3.ts +1044 -303
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +69 -23
- package/src/echarts.ts +646 -153
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight-api.ts +444 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +222 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +48 -14
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +197 -71
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +46 -25
- package/src/graph/state-parser.ts +47 -17
- package/src/index.ts +96 -31
- package/src/infra/parser.ts +157 -53
- package/src/infra/renderer.ts +723 -271
- package/src/initiative-status/parser.ts +138 -44
- package/src/kanban/parser.ts +25 -14
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +69 -22
- package/src/palettes/index.ts +3 -2
- package/src/sequence/parser.ts +193 -61
- package/src/sitemap/parser.ts +65 -29
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +75 -31
package/src/org/layout.ts
CHANGED
|
@@ -6,7 +6,11 @@ import { hierarchy, tree } from 'd3-hierarchy';
|
|
|
6
6
|
import type { ParsedOrg, OrgNode } from './parser';
|
|
7
7
|
import type { TagGroup } from '../utils/tag-groups';
|
|
8
8
|
import { resolveTagColor, injectDefaultTagMetadata } from '../utils/tag-groups';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
LEGEND_PILL_FONT_SIZE,
|
|
11
|
+
LEGEND_ENTRY_FONT_SIZE,
|
|
12
|
+
measureLegendText,
|
|
13
|
+
} from '../utils/legend-constants';
|
|
10
14
|
|
|
11
15
|
// ============================================================
|
|
12
16
|
// Types
|
|
@@ -98,9 +102,7 @@ const CONTAINER_LABEL_HEIGHT = 28;
|
|
|
98
102
|
const CONTAINER_META_LINE_HEIGHT = 16;
|
|
99
103
|
const STACK_V_GAP = 20;
|
|
100
104
|
|
|
101
|
-
|
|
102
105
|
// Legend (kanban-style pills)
|
|
103
|
-
const LEGEND_GAP = 30;
|
|
104
106
|
const LEGEND_HEIGHT = 28;
|
|
105
107
|
const LEGEND_PILL_PAD = 16;
|
|
106
108
|
const LEGEND_CAPSULE_PAD = 4;
|
|
@@ -116,10 +118,14 @@ const LEGEND_EYE_GAP = 6;
|
|
|
116
118
|
// ============================================================
|
|
117
119
|
|
|
118
120
|
/** Count all non-container descendants recursively, including hidden (collapsed) ones. */
|
|
119
|
-
function countDescendantNodes(
|
|
121
|
+
function countDescendantNodes(
|
|
122
|
+
node: OrgNode,
|
|
123
|
+
hiddenCounts?: Map<string, number>
|
|
124
|
+
): number {
|
|
120
125
|
let count = 0;
|
|
121
126
|
for (const child of node.children) {
|
|
122
|
-
count +=
|
|
127
|
+
count +=
|
|
128
|
+
(child.isContainer ? 0 : 1) + countDescendantNodes(child, hiddenCounts);
|
|
123
129
|
const hc = hiddenCounts?.get(child.id);
|
|
124
130
|
if (hc) count += hc;
|
|
125
131
|
}
|
|
@@ -152,13 +158,18 @@ function computeCardWidth(label: string, meta: Record<string, string>): number {
|
|
|
152
158
|
if (lineChars > maxChars) maxChars = lineChars;
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
return Math.max(
|
|
161
|
+
return Math.max(
|
|
162
|
+
MIN_CARD_WIDTH,
|
|
163
|
+
Math.ceil(maxChars * CHAR_WIDTH) + CARD_H_PAD * 2
|
|
164
|
+
);
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
function computeCardHeight(meta: Record<string, string>): number {
|
|
159
168
|
const metaCount = Object.keys(meta).length;
|
|
160
169
|
if (metaCount === 0) return HEADER_HEIGHT + CARD_V_PAD;
|
|
161
|
-
return
|
|
170
|
+
return (
|
|
171
|
+
HEADER_HEIGHT + SEPARATOR_GAP + metaCount * META_LINE_HEIGHT + CARD_V_PAD
|
|
172
|
+
);
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
// ============================================================
|
|
@@ -172,7 +183,12 @@ function resolveNodeColor(
|
|
|
172
183
|
): string | undefined {
|
|
173
184
|
// Explicit inline (color) always wins — handled before tag resolution
|
|
174
185
|
if (node.color) return node.color;
|
|
175
|
-
return resolveTagColor(
|
|
186
|
+
return resolveTagColor(
|
|
187
|
+
node.metadata,
|
|
188
|
+
tagGroups,
|
|
189
|
+
activeGroupName,
|
|
190
|
+
node.isContainer
|
|
191
|
+
);
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
// ============================================================
|
|
@@ -204,7 +220,13 @@ function buildTreeNodes(
|
|
|
204
220
|
}
|
|
205
221
|
return {
|
|
206
222
|
orgNode,
|
|
207
|
-
children: buildTreeNodes(
|
|
223
|
+
children: buildTreeNodes(
|
|
224
|
+
orgNode.children,
|
|
225
|
+
hiddenCounts,
|
|
226
|
+
hiddenAttributes,
|
|
227
|
+
subNodeLabel,
|
|
228
|
+
showSubNodeCount
|
|
229
|
+
),
|
|
208
230
|
width: computeCardWidth(orgNode.label, meta),
|
|
209
231
|
height: computeCardHeight(meta),
|
|
210
232
|
};
|
|
@@ -279,7 +301,8 @@ function computeLegendGroups(
|
|
|
279
301
|
if (visibleEntries.length === 0) continue;
|
|
280
302
|
|
|
281
303
|
// Pill label shows just the group name (alias is for DSL shorthand only)
|
|
282
|
-
const pillWidth =
|
|
304
|
+
const pillWidth =
|
|
305
|
+
measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
|
|
283
306
|
const minPillWidth = pillWidth;
|
|
284
307
|
|
|
285
308
|
// Capsule: pad + pill + gap + entries + pad
|
|
@@ -318,10 +341,7 @@ function computeLegendGroups(
|
|
|
318
341
|
* Inject default tag group values into non-container node metadata.
|
|
319
342
|
* Delegates to shared `injectDefaultTagMetadata` with org-specific skip logic.
|
|
320
343
|
*/
|
|
321
|
-
function injectDefaultMetadata(
|
|
322
|
-
roots: OrgNode[],
|
|
323
|
-
tagGroups: TagGroup[]
|
|
324
|
-
): void {
|
|
344
|
+
function injectDefaultMetadata(roots: OrgNode[], tagGroups: TagGroup[]): void {
|
|
325
345
|
// Flatten all nodes (recursive) for the shared utility
|
|
326
346
|
const allNodes: OrgNode[] = [];
|
|
327
347
|
const collect = (node: OrgNode) => {
|
|
@@ -349,7 +369,14 @@ export function layoutOrg(
|
|
|
349
369
|
const showEyeIcons = hiddenAttributes !== undefined;
|
|
350
370
|
const legendGroups = computeLegendGroups(parsed.tagGroups, showEyeIcons);
|
|
351
371
|
if (legendGroups.length === 0) {
|
|
352
|
-
return {
|
|
372
|
+
return {
|
|
373
|
+
nodes: [],
|
|
374
|
+
edges: [],
|
|
375
|
+
containers: [],
|
|
376
|
+
legend: [],
|
|
377
|
+
width: 0,
|
|
378
|
+
height: 0,
|
|
379
|
+
};
|
|
353
380
|
}
|
|
354
381
|
|
|
355
382
|
// Legend-only mode: stack groups vertically, all expanded
|
|
@@ -377,8 +404,16 @@ export function layoutOrg(
|
|
|
377
404
|
|
|
378
405
|
// Build tree structure
|
|
379
406
|
const subNodeLabel = parsed.options['sub-node-label'] ?? undefined;
|
|
380
|
-
const showSubNodeCount = ['yes', 'on'].includes(
|
|
381
|
-
|
|
407
|
+
const showSubNodeCount = ['yes', 'on'].includes(
|
|
408
|
+
parsed.options['show-sub-node-count']?.toLowerCase() ?? ''
|
|
409
|
+
);
|
|
410
|
+
const treeNodes = buildTreeNodes(
|
|
411
|
+
parsed.roots,
|
|
412
|
+
hiddenCounts,
|
|
413
|
+
hiddenAttributes,
|
|
414
|
+
subNodeLabel,
|
|
415
|
+
showSubNodeCount
|
|
416
|
+
);
|
|
382
417
|
|
|
383
418
|
// Single root or virtual root for multiple roots
|
|
384
419
|
let root: TreeNode;
|
|
@@ -492,9 +527,9 @@ export function layoutOrg(
|
|
|
492
527
|
// Y positions so each level's gap is based on the actual max height at that
|
|
493
528
|
// level rather than the global max.
|
|
494
529
|
{
|
|
495
|
-
const descendants = h
|
|
496
|
-
(
|
|
497
|
-
|
|
530
|
+
const descendants = h
|
|
531
|
+
.descendants()
|
|
532
|
+
.filter((d) => d.data.orgNode.id !== '__virtual_root__');
|
|
498
533
|
|
|
499
534
|
// Collect max actual card height per depth level.
|
|
500
535
|
// Exclude __stack_ placeholders — their aggregate height (multiple
|
|
@@ -597,7 +632,7 @@ export function layoutOrg(
|
|
|
597
632
|
// D3 uses uniform nodeSize so narrow stacks get the same gap as wide
|
|
598
633
|
// subtrees. Process bottom-up so inner subtrees are compact first.
|
|
599
634
|
{
|
|
600
|
-
type HNode =
|
|
635
|
+
type HNode = typeof h;
|
|
601
636
|
const subtreeExtent = (node: HNode): { minX: number; maxX: number } => {
|
|
602
637
|
// Start with this node's own card/header bounds
|
|
603
638
|
let min = node.x! - node.data.width / 2;
|
|
@@ -652,8 +687,7 @@ export function layoutOrg(
|
|
|
652
687
|
positions[i] = prevRight + H_GAP - extents[i].relLeft;
|
|
653
688
|
}
|
|
654
689
|
|
|
655
|
-
const newCenter =
|
|
656
|
-
(positions[0] + positions[positions.length - 1]) / 2;
|
|
690
|
+
const newCenter = (positions[0] + positions[positions.length - 1]) / 2;
|
|
657
691
|
const centerShift = currentCenter - newCenter;
|
|
658
692
|
|
|
659
693
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -761,7 +795,11 @@ export function layoutOrg(
|
|
|
761
795
|
for (const ec of expandedChildren) {
|
|
762
796
|
const hc = hiddenCounts?.get(ec.orgNode.id);
|
|
763
797
|
const meta = filterMetadata(ec.orgNode.metadata, hiddenAttributes);
|
|
764
|
-
if (
|
|
798
|
+
if (
|
|
799
|
+
!ec.orgNode.isContainer &&
|
|
800
|
+
showSubNodeCount &&
|
|
801
|
+
!(hc != null && hc > 0)
|
|
802
|
+
) {
|
|
765
803
|
const count = countDescendantNodes(ec.orgNode, hiddenCounts);
|
|
766
804
|
if (count > 0) meta[subNodeKey] = String(count);
|
|
767
805
|
}
|
|
@@ -771,13 +809,18 @@ export function layoutOrg(
|
|
|
771
809
|
metadata: meta,
|
|
772
810
|
isContainer: ec.orgNode.isContainer,
|
|
773
811
|
lineNumber: ec.orgNode.lineNumber,
|
|
774
|
-
color: resolveNodeColor(
|
|
812
|
+
color: resolveNodeColor(
|
|
813
|
+
ec.orgNode,
|
|
814
|
+
parsed.tagGroups,
|
|
815
|
+
activeTagGroup ?? null
|
|
816
|
+
),
|
|
775
817
|
x: ec.cx + offsetX,
|
|
776
818
|
y: ec.cy + offsetY,
|
|
777
819
|
width: ec.width,
|
|
778
820
|
height: ec.height,
|
|
779
821
|
hiddenCount: hc,
|
|
780
|
-
hasChildren:
|
|
822
|
+
hasChildren:
|
|
823
|
+
ec.orgNode.children.length > 0 || (hc != null && hc > 0) || undefined,
|
|
781
824
|
});
|
|
782
825
|
}
|
|
783
826
|
|
|
@@ -813,13 +856,20 @@ export function layoutOrg(
|
|
|
813
856
|
metadata: nodeMeta,
|
|
814
857
|
isContainer: orgNode.isContainer,
|
|
815
858
|
lineNumber: orgNode.lineNumber,
|
|
816
|
-
color: resolveNodeColor(
|
|
859
|
+
color: resolveNodeColor(
|
|
860
|
+
orgNode,
|
|
861
|
+
parsed.tagGroups,
|
|
862
|
+
activeTagGroup ?? null
|
|
863
|
+
),
|
|
817
864
|
x,
|
|
818
865
|
y,
|
|
819
866
|
width: w,
|
|
820
867
|
height: ht,
|
|
821
868
|
hiddenCount: hc,
|
|
822
|
-
hasChildren:
|
|
869
|
+
hasChildren:
|
|
870
|
+
(d.children != null && d.children.length > 0) ||
|
|
871
|
+
(hc != null && hc > 0) ||
|
|
872
|
+
undefined,
|
|
823
873
|
});
|
|
824
874
|
|
|
825
875
|
// Collect children per parent for bus-style edge generation
|
|
@@ -912,11 +962,12 @@ export function layoutOrg(
|
|
|
912
962
|
|
|
913
963
|
// Compute container bounds from d3 hierarchy (bottom-up so inner
|
|
914
964
|
// container boxes are available when computing outer containers)
|
|
915
|
-
const allContainerNodes = h
|
|
916
|
-
(
|
|
917
|
-
|
|
918
|
-
d
|
|
919
|
-
|
|
965
|
+
const allContainerNodes = h
|
|
966
|
+
.descendants()
|
|
967
|
+
.filter(
|
|
968
|
+
(d) =>
|
|
969
|
+
d.data.orgNode.id !== '__virtual_root__' && d.data.orgNode.isContainer
|
|
970
|
+
);
|
|
920
971
|
|
|
921
972
|
// Map from node ID to computed visual bounds (offset-space)
|
|
922
973
|
const containerBoundsMap = new Map<
|
|
@@ -938,7 +989,10 @@ export function layoutOrg(
|
|
|
938
989
|
const labelHeight =
|
|
939
990
|
CONTAINER_LABEL_HEIGHT + metaCount * CONTAINER_META_LINE_HEIGHT;
|
|
940
991
|
const boxWidth = d.data.width;
|
|
941
|
-
const boxHeight = Math.max(
|
|
992
|
+
const boxHeight = Math.max(
|
|
993
|
+
labelHeight + CONTAINER_PAD_BOTTOM,
|
|
994
|
+
EMPTY_CONTAINER_MIN_HEIGHT
|
|
995
|
+
);
|
|
942
996
|
const boxX = cx - boxWidth / 2;
|
|
943
997
|
const boxY = cy;
|
|
944
998
|
|
|
@@ -955,7 +1009,11 @@ export function layoutOrg(
|
|
|
955
1009
|
nodeId: d.data.orgNode.id,
|
|
956
1010
|
label: d.data.orgNode.label,
|
|
957
1011
|
lineNumber: d.data.orgNode.lineNumber,
|
|
958
|
-
color: resolveNodeColor(
|
|
1012
|
+
color: resolveNodeColor(
|
|
1013
|
+
d.data.orgNode,
|
|
1014
|
+
parsed.tagGroups,
|
|
1015
|
+
activeTagGroup ?? null
|
|
1016
|
+
),
|
|
959
1017
|
metadata: cMeta,
|
|
960
1018
|
x: boxX,
|
|
961
1019
|
y: boxY,
|
|
@@ -975,7 +1033,7 @@ export function layoutOrg(
|
|
|
975
1033
|
|
|
976
1034
|
for (const d of containerCandidates) {
|
|
977
1035
|
// Collect all descendants (not just direct children)
|
|
978
|
-
const allDesc: typeof d[] = [];
|
|
1036
|
+
const allDesc: (typeof d)[] = [];
|
|
979
1037
|
const collectDesc = (node: typeof d) => {
|
|
980
1038
|
if (node.children) {
|
|
981
1039
|
for (const child of node.children) {
|
|
@@ -1044,9 +1102,7 @@ export function layoutOrg(
|
|
|
1044
1102
|
const finalBoxWidth = Math.max(contentWidth, d.data.width);
|
|
1045
1103
|
// Center the box if the label is wider than the content
|
|
1046
1104
|
const centeredBoxX =
|
|
1047
|
-
finalBoxWidth > contentWidth
|
|
1048
|
-
? containerX - finalBoxWidth / 2
|
|
1049
|
-
: boxX;
|
|
1105
|
+
finalBoxWidth > contentWidth ? containerX - finalBoxWidth / 2 : boxX;
|
|
1050
1106
|
|
|
1051
1107
|
// Store bounds for parent containers to reference
|
|
1052
1108
|
containerBoundsMap.set(d.data.orgNode.id, {
|
|
@@ -1062,7 +1118,11 @@ export function layoutOrg(
|
|
|
1062
1118
|
nodeId: d.data.orgNode.id,
|
|
1063
1119
|
label: d.data.orgNode.label,
|
|
1064
1120
|
lineNumber: d.data.orgNode.lineNumber,
|
|
1065
|
-
color: resolveNodeColor(
|
|
1121
|
+
color: resolveNodeColor(
|
|
1122
|
+
d.data.orgNode,
|
|
1123
|
+
parsed.tagGroups,
|
|
1124
|
+
activeTagGroup ?? null
|
|
1125
|
+
),
|
|
1066
1126
|
metadata: cMeta2,
|
|
1067
1127
|
x: centeredBoxX,
|
|
1068
1128
|
y: boxY,
|
|
@@ -1111,16 +1171,23 @@ export function layoutOrg(
|
|
|
1111
1171
|
|
|
1112
1172
|
// Compute legend for tag groups
|
|
1113
1173
|
const showEyeIcons = hiddenAttributes !== undefined;
|
|
1114
|
-
const legendGroups = computeLegendGroups(
|
|
1174
|
+
const legendGroups = computeLegendGroups(
|
|
1175
|
+
parsed.tagGroups,
|
|
1176
|
+
showEyeIcons,
|
|
1177
|
+
usedValuesByGroup
|
|
1178
|
+
);
|
|
1115
1179
|
let finalWidth = totalWidth;
|
|
1116
1180
|
let finalHeight = totalHeight;
|
|
1117
1181
|
|
|
1118
1182
|
// When a tag group is active, only that group is laid out (full size).
|
|
1119
1183
|
// When none is active, all groups are laid out minified — unless
|
|
1120
1184
|
// expandAllLegend is set (export mode), which shows all groups expanded.
|
|
1121
|
-
const visibleGroups =
|
|
1122
|
-
|
|
1123
|
-
|
|
1185
|
+
const visibleGroups =
|
|
1186
|
+
activeTagGroup != null
|
|
1187
|
+
? legendGroups.filter(
|
|
1188
|
+
(g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()
|
|
1189
|
+
)
|
|
1190
|
+
: legendGroups;
|
|
1124
1191
|
const allExpanded = expandAllLegend && activeTagGroup == null;
|
|
1125
1192
|
const effectiveW = (g: OrgLegendGroup) =>
|
|
1126
1193
|
activeTagGroup != null || allExpanded ? g.width : g.minifiedWidth;
|
package/src/org/parser.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { PaletteColors } from '../palettes';
|
|
2
2
|
import type { DgmoError } from '../diagnostics';
|
|
3
3
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
4
|
-
import type { TagGroup
|
|
5
|
-
import {
|
|
4
|
+
import type { TagGroup } from '../utils/tag-groups';
|
|
5
|
+
import {
|
|
6
|
+
isTagBlockHeading,
|
|
7
|
+
matchTagBlockHeading,
|
|
8
|
+
validateTagValues,
|
|
9
|
+
} from '../utils/tag-groups';
|
|
6
10
|
import {
|
|
7
11
|
measureIndent,
|
|
8
12
|
extractColor,
|
|
@@ -46,13 +50,12 @@ const METADATA_RE = /^([^:]+):\s*(.+)$/;
|
|
|
46
50
|
|
|
47
51
|
/** Known org chart options (key-value). */
|
|
48
52
|
const KNOWN_OPTIONS = new Set([
|
|
49
|
-
'sub-node-label',
|
|
50
|
-
|
|
51
|
-
/** Known org chart boolean options (bare keyword = on). */
|
|
52
|
-
const KNOWN_BOOLEANS = new Set([
|
|
53
|
+
'sub-node-label',
|
|
54
|
+
'hide',
|
|
53
55
|
'show-sub-node-count',
|
|
54
|
-
'direction-tb',
|
|
55
56
|
]);
|
|
57
|
+
/** Known org chart boolean options (bare keyword = on). */
|
|
58
|
+
const KNOWN_BOOLEANS = new Set(['show-sub-node-count', 'direction-tb']);
|
|
56
59
|
|
|
57
60
|
// ============================================================
|
|
58
61
|
// Inference
|
|
@@ -72,10 +75,7 @@ export function looksLikeOrg(content: string): boolean {
|
|
|
72
75
|
// Parser
|
|
73
76
|
// ============================================================
|
|
74
77
|
|
|
75
|
-
export function parseOrg(
|
|
76
|
-
content: string,
|
|
77
|
-
palette?: PaletteColors
|
|
78
|
-
): ParsedOrg {
|
|
78
|
+
export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
79
79
|
const result: ParsedOrg = {
|
|
80
80
|
title: null,
|
|
81
81
|
titleLineNumber: null,
|
|
@@ -148,7 +148,22 @@ export function parseOrg(
|
|
|
148
148
|
const firstLine = parseFirstLine(trimmed);
|
|
149
149
|
if (firstLine) {
|
|
150
150
|
if (firstLine.chartType !== 'org') {
|
|
151
|
-
const allTypes = [
|
|
151
|
+
const allTypes = [
|
|
152
|
+
'org',
|
|
153
|
+
'class',
|
|
154
|
+
'flowchart',
|
|
155
|
+
'sequence',
|
|
156
|
+
'er',
|
|
157
|
+
'bar',
|
|
158
|
+
'line',
|
|
159
|
+
'pie',
|
|
160
|
+
'scatter',
|
|
161
|
+
'sankey',
|
|
162
|
+
'venn',
|
|
163
|
+
'timeline',
|
|
164
|
+
'arc',
|
|
165
|
+
'slope',
|
|
166
|
+
];
|
|
152
167
|
let msg = `Expected chart type "org", got "${firstLine.chartType}"`;
|
|
153
168
|
const hint = suggest(firstLine.chartType, allTypes);
|
|
154
169
|
if (hint) msg += `. ${hint}`;
|
|
@@ -177,7 +192,10 @@ export function parseOrg(
|
|
|
177
192
|
lineNumber,
|
|
178
193
|
};
|
|
179
194
|
if (tagBlockMatch.alias) {
|
|
180
|
-
aliasMap.set(
|
|
195
|
+
aliasMap.set(
|
|
196
|
+
tagBlockMatch.alias.toLowerCase(),
|
|
197
|
+
tagBlockMatch.name.toLowerCase()
|
|
198
|
+
);
|
|
181
199
|
}
|
|
182
200
|
result.tagGroups.push(currentTagGroup);
|
|
183
201
|
continue;
|
|
@@ -208,7 +226,10 @@ export function parseOrg(
|
|
|
208
226
|
if (indent > 0) {
|
|
209
227
|
const { label, color } = extractColor(trimmed, palette);
|
|
210
228
|
if (!color) {
|
|
211
|
-
pushError(
|
|
229
|
+
pushError(
|
|
230
|
+
lineNumber,
|
|
231
|
+
`Expected 'Value(color)' in tag group '${currentTagGroup.name}'`
|
|
232
|
+
);
|
|
212
233
|
continue;
|
|
213
234
|
}
|
|
214
235
|
// First entry is the default
|
|
@@ -223,7 +244,7 @@ export function parseOrg(
|
|
|
223
244
|
continue;
|
|
224
245
|
}
|
|
225
246
|
// Non-indented line after tag group — fall through to content parsing
|
|
226
|
-
currentTagGroup = null;
|
|
247
|
+
currentTagGroup = null; // eslint-disable-line no-useless-assignment
|
|
227
248
|
}
|
|
228
249
|
|
|
229
250
|
// --- Org content phase ---
|
|
@@ -238,7 +259,9 @@ export function parseOrg(
|
|
|
238
259
|
// Check for metadata syntax: key: value
|
|
239
260
|
// Lines containing '|' are pipe-delimited nodes (e.g. "Alice | role: Engineer"),
|
|
240
261
|
// not metadata — skip the metadata regex for them.
|
|
241
|
-
const metadataMatch = trimmed.includes('|')
|
|
262
|
+
const metadataMatch = trimmed.includes('|')
|
|
263
|
+
? null
|
|
264
|
+
: trimmed.match(METADATA_RE);
|
|
242
265
|
|
|
243
266
|
if (containerMatch) {
|
|
244
267
|
// It's a container node
|
|
@@ -277,14 +300,30 @@ export function parseOrg(
|
|
|
277
300
|
// Otherwise it's an orphan metadata error
|
|
278
301
|
if (indent === 0) {
|
|
279
302
|
// Treat as a node label (e.g., "Dr. Smith: Surgeon" is a valid name)
|
|
280
|
-
const node = parseNodeLabel(
|
|
303
|
+
const node = parseNodeLabel(
|
|
304
|
+
trimmed,
|
|
305
|
+
indent,
|
|
306
|
+
lineNumber,
|
|
307
|
+
palette,
|
|
308
|
+
++nodeCounter,
|
|
309
|
+
aliasMap,
|
|
310
|
+
pushWarning
|
|
311
|
+
);
|
|
281
312
|
attachNode(node, indent, indentStack, result);
|
|
282
313
|
} else {
|
|
283
314
|
pushError(lineNumber, 'Metadata has no parent node');
|
|
284
315
|
}
|
|
285
316
|
} else {
|
|
286
317
|
// It's a node label — possibly with single-line pipe-delimited metadata
|
|
287
|
-
const node = parseNodeLabel(
|
|
318
|
+
const node = parseNodeLabel(
|
|
319
|
+
trimmed,
|
|
320
|
+
indent,
|
|
321
|
+
lineNumber,
|
|
322
|
+
palette,
|
|
323
|
+
++nodeCounter,
|
|
324
|
+
aliasMap,
|
|
325
|
+
pushWarning
|
|
326
|
+
);
|
|
288
327
|
attachNode(node, indent, indentStack, result);
|
|
289
328
|
}
|
|
290
329
|
}
|
|
@@ -304,7 +343,11 @@ export function parseOrg(
|
|
|
304
343
|
validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
|
|
305
344
|
}
|
|
306
345
|
|
|
307
|
-
if (
|
|
346
|
+
if (
|
|
347
|
+
result.roots.length === 0 &&
|
|
348
|
+
result.tagGroups.length === 0 &&
|
|
349
|
+
!result.error
|
|
350
|
+
) {
|
|
308
351
|
const diag = makeDgmoError(1, 'No nodes found in org chart');
|
|
309
352
|
result.diagnostics.push(diag);
|
|
310
353
|
result.error = formatDgmoError(diag);
|
|
@@ -324,15 +367,19 @@ function parseNodeLabel(
|
|
|
324
367
|
palette: PaletteColors | undefined,
|
|
325
368
|
counter: number,
|
|
326
369
|
aliasMap: Map<string, string> = new Map(),
|
|
327
|
-
warnFn?: (line: number, msg: string) => void
|
|
370
|
+
warnFn?: (line: number, msg: string) => void
|
|
328
371
|
): OrgNode {
|
|
329
372
|
// Check for single-line compact metadata: "Alice Park | role: Senior, location: NY"
|
|
330
373
|
const segments = trimmed.split('|').map((s) => s.trim());
|
|
331
374
|
|
|
332
|
-
|
|
375
|
+
const rawLabel = segments[0];
|
|
333
376
|
const { label, color } = extractColor(rawLabel, palette);
|
|
334
377
|
|
|
335
|
-
const metadata = parsePipeMetadata(
|
|
378
|
+
const metadata = parsePipeMetadata(
|
|
379
|
+
segments,
|
|
380
|
+
aliasMap,
|
|
381
|
+
warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_ERROR) : undefined
|
|
382
|
+
);
|
|
336
383
|
|
|
337
384
|
return {
|
|
338
385
|
id: `node-${counter}`,
|
package/src/palettes/index.ts
CHANGED
|
@@ -25,14 +25,15 @@ export {
|
|
|
25
25
|
// Re-export palette definitions (alphabetical)
|
|
26
26
|
export { boldPalette } from './bold';
|
|
27
27
|
export { catppuccinPalette } from './catppuccin';
|
|
28
|
-
export { draculaPalette } from './dracula';
|
|
29
28
|
export { gruvboxPalette } from './gruvbox';
|
|
30
|
-
export { monokaiPalette } from './monokai';
|
|
31
29
|
export { nordPalette } from './nord';
|
|
32
30
|
export { oneDarkPalette } from './one-dark';
|
|
33
31
|
export { rosePinePalette } from './rose-pine';
|
|
34
32
|
export { solarizedPalette } from './solarized';
|
|
35
33
|
export { tokyoNightPalette } from './tokyo-night';
|
|
36
34
|
|
|
35
|
+
export { draculaPalette } from './dracula';
|
|
36
|
+
export { monokaiPalette } from './monokai';
|
|
37
|
+
|
|
37
38
|
// Re-export Mermaid bridge
|
|
38
39
|
export { buildMermaidThemeVars, buildThemeCSS } from './mermaid-bridge';
|