@diagrammo/dgmo 0.8.20 → 0.8.22
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/AGENTS.md +2 -1
- package/README.md +1 -0
- package/dist/cli.cjs +142 -90
- package/dist/editor.cjs +30 -4
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +30 -4
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +25 -3
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +25 -3
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +21201 -12886
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +646 -89
- package/dist/index.d.ts +646 -89
- package/dist/index.js +21178 -12889
- package/dist/index.js.map +1 -1
- package/docs/guide/chart-mindmap.md +198 -0
- package/docs/guide/chart-sequence.md +23 -1
- package/docs/guide/chart-sitemap.md +18 -1
- package/docs/guide/chart-tech-radar.md +219 -0
- package/docs/guide/chart-wireframe.md +100 -0
- package/docs/guide/index.md +8 -0
- package/docs/guide/registry.json +1 -0
- package/docs/language-reference.md +249 -4
- package/gallery/fixtures/boxes-and-lines.dgmo +10 -3
- package/gallery/fixtures/c4-full.dgmo +2 -2
- package/gallery/fixtures/cycle/ooda-loop.dgmo +25 -0
- package/gallery/fixtures/cycle/pdca-circle-nodes.dgmo +12 -0
- package/gallery/fixtures/cycle/pdca-minimal.dgmo +6 -0
- package/gallery/fixtures/cycle/sprint-cycle-span.dgmo +17 -0
- package/gallery/fixtures/gantt-full.dgmo +2 -2
- package/gallery/fixtures/gantt.dgmo +2 -2
- package/gallery/fixtures/infra-full.dgmo +2 -2
- package/gallery/fixtures/infra.dgmo +1 -1
- package/gallery/fixtures/sequence-tags-protocols.dgmo +2 -2
- package/gallery/fixtures/sequence-tags.dgmo +2 -2
- package/gallery/fixtures/tech-radar-dense.dgmo +77 -0
- package/gallery/fixtures/tech-radar.dgmo +36 -0
- package/gallery/fixtures/timeline.dgmo +1 -1
- package/package.json +1 -1
- package/src/boxes-and-lines/collapse.ts +21 -3
- package/src/boxes-and-lines/layout.ts +360 -42
- package/src/boxes-and-lines/parser.ts +94 -11
- package/src/boxes-and-lines/renderer.ts +371 -114
- package/src/boxes-and-lines/types.ts +2 -1
- package/src/c4/layout.ts +8 -8
- package/src/c4/parser.ts +35 -2
- package/src/c4/renderer.ts +19 -3
- package/src/c4/types.ts +1 -0
- package/src/chart.ts +14 -7
- package/src/completion.ts +253 -0
- package/src/cycle/layout.ts +732 -0
- package/src/cycle/parser.ts +352 -0
- package/src/cycle/renderer.ts +539 -0
- package/src/cycle/types.ts +77 -0
- package/src/d3.ts +240 -40
- package/src/dgmo-router.ts +15 -0
- package/src/echarts.ts +7 -4
- package/src/editor/dgmo.grammar +5 -1
- package/src/editor/dgmo.grammar.js +1 -1
- package/src/editor/keywords.ts +26 -0
- package/src/gantt/parser.ts +2 -8
- package/src/graph/flowchart-parser.ts +15 -21
- package/src/graph/layout.ts +73 -9
- package/src/graph/state-collapse.ts +78 -0
- package/src/graph/state-parser.ts +5 -10
- package/src/graph/state-renderer.ts +139 -34
- package/src/index.ts +78 -0
- package/src/infra/layout.ts +218 -74
- package/src/infra/parser.ts +30 -6
- package/src/infra/renderer.ts +14 -8
- package/src/infra/types.ts +10 -3
- package/src/journey-map/layout.ts +386 -0
- package/src/journey-map/parser.ts +540 -0
- package/src/journey-map/renderer.ts +1456 -0
- package/src/journey-map/types.ts +47 -0
- package/src/kanban/parser.ts +3 -10
- package/src/kanban/renderer.ts +325 -63
- package/src/mindmap/collapse.ts +88 -0
- package/src/mindmap/layout.ts +605 -0
- package/src/mindmap/parser.ts +373 -0
- package/src/mindmap/renderer.ts +544 -0
- package/src/mindmap/text-wrap.ts +217 -0
- package/src/mindmap/types.ts +55 -0
- package/src/org/parser.ts +2 -6
- package/src/render.ts +18 -21
- package/src/sequence/renderer.ts +273 -56
- package/src/sharing.ts +3 -0
- package/src/sitemap/layout.ts +56 -18
- package/src/sitemap/parser.ts +26 -17
- package/src/sitemap/renderer.ts +34 -0
- package/src/sitemap/types.ts +1 -0
- package/src/tech-radar/index.ts +14 -0
- package/src/tech-radar/interactive.ts +1058 -0
- package/src/tech-radar/layout.ts +190 -0
- package/src/tech-radar/parser.ts +385 -0
- package/src/tech-radar/renderer.ts +1159 -0
- package/src/tech-radar/shared.ts +187 -0
- package/src/tech-radar/types.ts +81 -0
- package/src/utils/description-helpers.ts +33 -0
- package/src/utils/export-container.ts +3 -2
- package/src/utils/legend-d3.ts +1 -0
- package/src/utils/legend-layout.ts +5 -3
- package/src/utils/parsing.ts +48 -7
- package/src/utils/tag-groups.ts +46 -60
- package/src/wireframe/layout.ts +460 -0
- package/src/wireframe/parser.ts +956 -0
- package/src/wireframe/renderer.ts +1293 -0
- package/src/wireframe/types.ts +110 -0
|
@@ -5,7 +5,7 @@ export interface BLNode {
|
|
|
5
5
|
label: string;
|
|
6
6
|
lineNumber: number;
|
|
7
7
|
metadata: Record<string, string>;
|
|
8
|
-
description?: string;
|
|
8
|
+
description?: string[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface BLEdge {
|
|
@@ -22,6 +22,7 @@ export interface BLGroup {
|
|
|
22
22
|
children: string[];
|
|
23
23
|
lineNumber: number;
|
|
24
24
|
metadata: Record<string, string>;
|
|
25
|
+
parentGroup?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface ParsedBoxesAndLines {
|
package/src/c4/layout.ts
CHANGED
|
@@ -689,7 +689,7 @@ export function computeC4NodeDimensions(
|
|
|
689
689
|
// (no type label — containers are the default in container view)
|
|
690
690
|
let height = CARD_V_PAD + NAME_HEIGHT;
|
|
691
691
|
|
|
692
|
-
const desc = el.
|
|
692
|
+
const desc = el.description?.join('\n');
|
|
693
693
|
if (desc) {
|
|
694
694
|
const contentWidth = width - CARD_H_PAD * 2;
|
|
695
695
|
const lines = wrapText(desc, contentWidth, DESC_CHAR_WIDTH);
|
|
@@ -719,7 +719,7 @@ export function computeC4NodeDimensions(
|
|
|
719
719
|
// Context card layout: type + name | divider | description
|
|
720
720
|
let height = CARD_V_PAD + TYPE_LABEL_HEIGHT + DIVIDER_GAP + NAME_HEIGHT;
|
|
721
721
|
|
|
722
|
-
const desc = el.
|
|
722
|
+
const desc = el.description?.join('\n');
|
|
723
723
|
if (desc) {
|
|
724
724
|
const contentWidth = width - CARD_H_PAD * 2;
|
|
725
725
|
const lines = wrapText(desc, contentWidth, DESC_CHAR_WIDTH);
|
|
@@ -887,7 +887,7 @@ export function layoutC4Context(
|
|
|
887
887
|
id: el.name,
|
|
888
888
|
name: el.name,
|
|
889
889
|
type: el.type as 'person' | 'system',
|
|
890
|
-
description: el.
|
|
890
|
+
description: el.description?.join('\n'),
|
|
891
891
|
metadata: el.metadata,
|
|
892
892
|
lineNumber: el.lineNumber,
|
|
893
893
|
color,
|
|
@@ -1241,7 +1241,7 @@ export function layoutC4Containers(
|
|
|
1241
1241
|
id: el.name,
|
|
1242
1242
|
name: el.name,
|
|
1243
1243
|
type: 'container',
|
|
1244
|
-
description: el.
|
|
1244
|
+
description: el.description?.join('\n'),
|
|
1245
1245
|
metadata: el.metadata,
|
|
1246
1246
|
lineNumber: el.lineNumber,
|
|
1247
1247
|
color,
|
|
@@ -1267,7 +1267,7 @@ export function layoutC4Containers(
|
|
|
1267
1267
|
id: el.name,
|
|
1268
1268
|
name: el.name,
|
|
1269
1269
|
type: el.type as 'person' | 'system',
|
|
1270
|
-
description: el.
|
|
1270
|
+
description: el.description?.join('\n'),
|
|
1271
1271
|
metadata: el.metadata,
|
|
1272
1272
|
lineNumber: el.lineNumber,
|
|
1273
1273
|
color,
|
|
@@ -1787,7 +1787,7 @@ export function layoutC4Components(
|
|
|
1787
1787
|
id: el.name,
|
|
1788
1788
|
name: el.name,
|
|
1789
1789
|
type: 'component',
|
|
1790
|
-
description: el.
|
|
1790
|
+
description: el.description?.join('\n'),
|
|
1791
1791
|
metadata: el.metadata,
|
|
1792
1792
|
lineNumber: el.lineNumber,
|
|
1793
1793
|
color,
|
|
@@ -1813,7 +1813,7 @@ export function layoutC4Components(
|
|
|
1813
1813
|
id: el.name,
|
|
1814
1814
|
name: el.name,
|
|
1815
1815
|
type: el.type as 'person' | 'system' | 'container',
|
|
1816
|
-
description: el.
|
|
1816
|
+
description: el.description?.join('\n'),
|
|
1817
1817
|
metadata: el.metadata,
|
|
1818
1818
|
lineNumber: el.lineNumber,
|
|
1819
1819
|
color,
|
|
@@ -2236,7 +2236,7 @@ export function layoutC4Deployment(
|
|
|
2236
2236
|
id: r.element.name,
|
|
2237
2237
|
name: r.element.name,
|
|
2238
2238
|
type: 'container',
|
|
2239
|
-
description: r.element.
|
|
2239
|
+
description: r.element.description?.join('\n'),
|
|
2240
2240
|
metadata: r.element.metadata,
|
|
2241
2241
|
lineNumber: r.element.lineNumber,
|
|
2242
2242
|
color,
|
package/src/c4/parser.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
parseFirstLine,
|
|
21
21
|
OPTION_NOCOLON_RE,
|
|
22
22
|
} from '../utils/parsing';
|
|
23
|
+
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
23
24
|
import type {
|
|
24
25
|
ParsedC4,
|
|
25
26
|
C4Element,
|
|
@@ -695,11 +696,20 @@ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
|
|
|
695
696
|
explicitShape ??
|
|
696
697
|
inferC4Shape(namePart, metadata.tech ?? metadata.technology);
|
|
697
698
|
|
|
699
|
+
// Extract description from pipe metadata into dedicated field
|
|
700
|
+
let isADescription: string[] | undefined;
|
|
701
|
+
if ('description' in metadata) {
|
|
702
|
+
const descVal = metadata['description'].trim();
|
|
703
|
+
if (descVal) isADescription = [descVal];
|
|
704
|
+
delete metadata['description'];
|
|
705
|
+
}
|
|
706
|
+
|
|
698
707
|
const element: C4Element = {
|
|
699
708
|
name: namePart,
|
|
700
709
|
type: elementType,
|
|
701
710
|
shape,
|
|
702
711
|
metadata,
|
|
712
|
+
description: isADescription,
|
|
703
713
|
children: [],
|
|
704
714
|
groups: [],
|
|
705
715
|
relationships: [],
|
|
@@ -762,11 +772,20 @@ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
|
|
|
762
772
|
explicitShape ??
|
|
763
773
|
inferC4Shape(namePart, metadata.tech ?? metadata.technology);
|
|
764
774
|
|
|
775
|
+
// Extract description from pipe metadata into dedicated field
|
|
776
|
+
let prefixDescription: string[] | undefined;
|
|
777
|
+
if ('description' in metadata) {
|
|
778
|
+
const descVal = metadata['description'].trim();
|
|
779
|
+
if (descVal) prefixDescription = [descVal];
|
|
780
|
+
delete metadata['description'];
|
|
781
|
+
}
|
|
782
|
+
|
|
765
783
|
const element: C4Element = {
|
|
766
784
|
name: namePart,
|
|
767
785
|
type: elementType,
|
|
768
786
|
shape,
|
|
769
787
|
metadata,
|
|
788
|
+
description: prefixDescription,
|
|
770
789
|
children: [],
|
|
771
790
|
groups: [],
|
|
772
791
|
relationships: [],
|
|
@@ -805,6 +824,15 @@ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
|
|
|
805
824
|
|
|
806
825
|
const key = aliasMap.get(rawKey) ?? rawKey;
|
|
807
826
|
const value = metadataMatch[2].trim();
|
|
827
|
+
|
|
828
|
+
// Extract description into dedicated field
|
|
829
|
+
if (key === 'description') {
|
|
830
|
+
if (!parentEntry.element.description)
|
|
831
|
+
parentEntry.element.description = [];
|
|
832
|
+
parentEntry.element.description.push(value);
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
|
|
808
836
|
parentEntry.element.metadata[key] = value;
|
|
809
837
|
continue;
|
|
810
838
|
}
|
|
@@ -821,9 +849,14 @@ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
|
|
|
821
849
|
}
|
|
822
850
|
}
|
|
823
851
|
|
|
824
|
-
// If inside a parent,
|
|
852
|
+
// If inside a parent, try as keyword-based or keywordless description
|
|
825
853
|
const parent = findParentElement(indent, stack);
|
|
826
|
-
if (
|
|
854
|
+
if (parent) {
|
|
855
|
+
const descResult = tryStripDescriptionKeyword(trimmed);
|
|
856
|
+
const descText = descResult.isKeyword ? descResult.text : trimmed;
|
|
857
|
+
if (!parent.element.description) parent.element.description = [];
|
|
858
|
+
parent.element.description.push(descText);
|
|
859
|
+
} else {
|
|
827
860
|
pushError(lineNumber, `Unexpected content: "${trimmed}"`);
|
|
828
861
|
}
|
|
829
862
|
}
|
package/src/c4/renderer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { FONT_FAMILY } from '../fonts';
|
|
|
8
8
|
import type { PaletteColors } from '../palettes';
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import { renderInlineText } from '../utils/inline-markdown';
|
|
11
|
+
import { preprocessDescriptionLine } from '../utils/description-helpers';
|
|
11
12
|
import type { ParsedC4 } from './types';
|
|
12
13
|
import type { C4LayoutResult, C4LayoutEdge } from './layout';
|
|
13
14
|
import { parseC4 } from './parser';
|
|
@@ -572,7 +573,12 @@ export function renderC4Context(
|
|
|
572
573
|
.attr('dominant-baseline', 'central')
|
|
573
574
|
.attr('fill', palette.textMuted)
|
|
574
575
|
.attr('font-size', DESC_FONT_SIZE);
|
|
575
|
-
renderInlineText(
|
|
576
|
+
renderInlineText(
|
|
577
|
+
textEl,
|
|
578
|
+
preprocessDescriptionLine(line),
|
|
579
|
+
palette,
|
|
580
|
+
DESC_FONT_SIZE
|
|
581
|
+
);
|
|
576
582
|
yPos += DESC_LINE_HEIGHT;
|
|
577
583
|
}
|
|
578
584
|
}
|
|
@@ -1641,7 +1647,12 @@ export function renderC4Containers(
|
|
|
1641
1647
|
.attr('dominant-baseline', 'central')
|
|
1642
1648
|
.attr('fill', palette.textMuted)
|
|
1643
1649
|
.attr('font-size', DESC_FONT_SIZE);
|
|
1644
|
-
renderInlineText(
|
|
1650
|
+
renderInlineText(
|
|
1651
|
+
textEl,
|
|
1652
|
+
preprocessDescriptionLine(line),
|
|
1653
|
+
palette,
|
|
1654
|
+
DESC_FONT_SIZE
|
|
1655
|
+
);
|
|
1645
1656
|
yPos += DESC_LINE_HEIGHT;
|
|
1646
1657
|
}
|
|
1647
1658
|
}
|
|
@@ -1720,7 +1731,12 @@ export function renderC4Containers(
|
|
|
1720
1731
|
.attr('dominant-baseline', 'central')
|
|
1721
1732
|
.attr('fill', palette.textMuted)
|
|
1722
1733
|
.attr('font-size', DESC_FONT_SIZE);
|
|
1723
|
-
renderInlineText(
|
|
1734
|
+
renderInlineText(
|
|
1735
|
+
textEl,
|
|
1736
|
+
preprocessDescriptionLine(line),
|
|
1737
|
+
palette,
|
|
1738
|
+
DESC_FONT_SIZE
|
|
1739
|
+
);
|
|
1724
1740
|
yPos += DESC_LINE_HEIGHT;
|
|
1725
1741
|
}
|
|
1726
1742
|
}
|
package/src/c4/types.ts
CHANGED
package/src/chart.ts
CHANGED
|
@@ -64,6 +64,7 @@ import type { PaletteColors } from './palettes';
|
|
|
64
64
|
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
65
65
|
import {
|
|
66
66
|
extractColor,
|
|
67
|
+
normalizeNumericToken,
|
|
67
68
|
parseFirstLine,
|
|
68
69
|
parseSeriesNames,
|
|
69
70
|
} from './utils/parsing';
|
|
@@ -528,7 +529,8 @@ export function parseDataRowValues(
|
|
|
528
529
|
// Find how many trailing comma-separated parts are numeric
|
|
529
530
|
let numericCount = 0;
|
|
530
531
|
for (let j = commaParts.length - 1; j >= 0; j--) {
|
|
531
|
-
const part =
|
|
532
|
+
const part =
|
|
533
|
+
normalizeNumericToken(commaParts[j].trim()) ?? commaParts[j].trim();
|
|
532
534
|
if (part && !isNaN(parseFloat(part)) && isFinite(Number(part))) {
|
|
533
535
|
numericCount++;
|
|
534
536
|
} else {
|
|
@@ -545,7 +547,9 @@ export function parseDataRowValues(
|
|
|
545
547
|
// Split firstPart from the right: last space-separated token must be numeric
|
|
546
548
|
const lastSpaceIdx = firstPart.lastIndexOf(' ');
|
|
547
549
|
if (lastSpaceIdx >= 0) {
|
|
548
|
-
const
|
|
550
|
+
const rawFirstVal = firstPart.substring(lastSpaceIdx + 1).trim();
|
|
551
|
+
const possibleFirstVal =
|
|
552
|
+
normalizeNumericToken(rawFirstVal) ?? rawFirstVal;
|
|
549
553
|
if (
|
|
550
554
|
possibleFirstVal &&
|
|
551
555
|
!isNaN(parseFloat(possibleFirstVal)) &&
|
|
@@ -555,7 +559,8 @@ export function parseDataRowValues(
|
|
|
555
559
|
if (label) {
|
|
556
560
|
const values = [parseFloat(possibleFirstVal)];
|
|
557
561
|
for (const p of extraValueParts) {
|
|
558
|
-
|
|
562
|
+
const normP = normalizeNumericToken(p.trim()) ?? p.trim();
|
|
563
|
+
values.push(parseFloat(normP));
|
|
559
564
|
}
|
|
560
565
|
return { label, values };
|
|
561
566
|
}
|
|
@@ -577,8 +582,9 @@ export function parseDataRowValues(
|
|
|
577
582
|
let idx = tokens.length - 1;
|
|
578
583
|
while (idx >= 1 && values.length < limit) {
|
|
579
584
|
const tok = tokens[idx];
|
|
580
|
-
const
|
|
581
|
-
|
|
585
|
+
const normTok = normalizeNumericToken(tok) ?? tok;
|
|
586
|
+
const num = parseFloat(normTok);
|
|
587
|
+
if (isNaN(num) || !isFinite(Number(normTok))) break;
|
|
582
588
|
values.unshift(num);
|
|
583
589
|
idx--;
|
|
584
590
|
}
|
|
@@ -590,8 +596,9 @@ export function parseDataRowValues(
|
|
|
590
596
|
|
|
591
597
|
// Single-value mode: only the last space-separated token
|
|
592
598
|
const lastToken = tokens[tokens.length - 1];
|
|
593
|
-
const
|
|
594
|
-
|
|
599
|
+
const normalizedLast = normalizeNumericToken(lastToken) ?? lastToken;
|
|
600
|
+
const num = parseFloat(normalizedLast);
|
|
601
|
+
if (isNaN(num) || !isFinite(Number(normalizedLast))) return null;
|
|
595
602
|
|
|
596
603
|
const label = tokens.slice(0, -1).join(' ');
|
|
597
604
|
if (!label) return null;
|
package/src/completion.ts
CHANGED
|
@@ -335,6 +335,52 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
|
|
|
335
335
|
hide: { description: 'Hide tag:value pairs' },
|
|
336
336
|
}),
|
|
337
337
|
],
|
|
338
|
+
[
|
|
339
|
+
'mindmap',
|
|
340
|
+
withGlobals({
|
|
341
|
+
'hide-descriptions': { description: 'Hide node descriptions' },
|
|
342
|
+
'active-tag': { description: 'Active tag group name' },
|
|
343
|
+
}),
|
|
344
|
+
],
|
|
345
|
+
[
|
|
346
|
+
'wireframe',
|
|
347
|
+
withGlobals({
|
|
348
|
+
mobile: { description: 'Use mobile (narrow vertical) layout' },
|
|
349
|
+
'active-tag': { description: 'Active tag group name' },
|
|
350
|
+
}),
|
|
351
|
+
],
|
|
352
|
+
[
|
|
353
|
+
'tech-radar',
|
|
354
|
+
withGlobals({
|
|
355
|
+
rings: { description: 'Ring names block (innermost to outermost)' },
|
|
356
|
+
quadrant: {
|
|
357
|
+
description:
|
|
358
|
+
'Quadrant position (top-left, top-right, bottom-left, bottom-right)',
|
|
359
|
+
},
|
|
360
|
+
ring: { description: 'Ring assignment for a blip' },
|
|
361
|
+
trend: { description: 'Blip trend (new, up, down, stable)' },
|
|
362
|
+
color: { description: 'Override quadrant color' },
|
|
363
|
+
}),
|
|
364
|
+
],
|
|
365
|
+
[
|
|
366
|
+
'cycle',
|
|
367
|
+
withGlobals({
|
|
368
|
+
'direction-counterclockwise': {
|
|
369
|
+
description: 'Reverse cycle direction to counterclockwise',
|
|
370
|
+
},
|
|
371
|
+
'hide-descriptions': { description: 'Hide node and edge descriptions' },
|
|
372
|
+
'circle-nodes': {
|
|
373
|
+
description: 'Render nodes as circles instead of rectangles',
|
|
374
|
+
},
|
|
375
|
+
}),
|
|
376
|
+
],
|
|
377
|
+
[
|
|
378
|
+
'journey-map',
|
|
379
|
+
withGlobals({
|
|
380
|
+
'no-legend': { description: 'Hide the score legend' },
|
|
381
|
+
persona: { description: 'Define the journey persona' },
|
|
382
|
+
}),
|
|
383
|
+
],
|
|
338
384
|
]);
|
|
339
385
|
|
|
340
386
|
// ============================================================
|
|
@@ -378,6 +424,11 @@ const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
|
|
|
378
424
|
infra: 'Infrastructure diagram',
|
|
379
425
|
gantt: 'Gantt chart',
|
|
380
426
|
'boxes-and-lines': 'Boxes and lines diagram',
|
|
427
|
+
mindmap: 'Mindmap diagram',
|
|
428
|
+
wireframe: 'UI wireframe diagram',
|
|
429
|
+
'tech-radar': 'Technology adoption radar (ThoughtWorks style)',
|
|
430
|
+
cycle: 'Cycle diagram (circular process flow)',
|
|
431
|
+
'journey-map': 'User journey map with emotion curve',
|
|
381
432
|
};
|
|
382
433
|
|
|
383
434
|
/** All chart types with descriptions, for chart type autocomplete. Excludes `multi-line` alias. */
|
|
@@ -511,6 +562,48 @@ export const PIPE_METADATA = new Map<
|
|
|
511
562
|
edge: {},
|
|
512
563
|
},
|
|
513
564
|
],
|
|
565
|
+
[
|
|
566
|
+
'mindmap',
|
|
567
|
+
{
|
|
568
|
+
node: {
|
|
569
|
+
description: { description: 'Node description text' },
|
|
570
|
+
collapsed: { description: 'Collapse node subtree by default' },
|
|
571
|
+
},
|
|
572
|
+
edge: {},
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
[
|
|
576
|
+
'tech-radar',
|
|
577
|
+
{
|
|
578
|
+
node: {
|
|
579
|
+
quadrant: {
|
|
580
|
+
description: 'Quadrant position',
|
|
581
|
+
values: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
582
|
+
},
|
|
583
|
+
ring: { description: 'Ring assignment for blip' },
|
|
584
|
+
trend: {
|
|
585
|
+
description: 'Blip trend indicator',
|
|
586
|
+
values: ['new', 'up', 'down', 'stable'],
|
|
587
|
+
},
|
|
588
|
+
color: { description: 'Override quadrant color' },
|
|
589
|
+
},
|
|
590
|
+
edge: {},
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
[
|
|
594
|
+
'cycle',
|
|
595
|
+
{
|
|
596
|
+
node: {
|
|
597
|
+
color: { description: 'Node fill color (palette name)' },
|
|
598
|
+
span: { description: 'Relative arc distance to next node' },
|
|
599
|
+
description: { description: 'Node description text' },
|
|
600
|
+
},
|
|
601
|
+
edge: {
|
|
602
|
+
color: { description: 'Edge stroke color (palette name)' },
|
|
603
|
+
width: { description: 'Edge stroke width in pixels' },
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
],
|
|
514
607
|
]);
|
|
515
608
|
|
|
516
609
|
// ============================================================
|
|
@@ -1009,3 +1102,163 @@ registerExtractor('sitemap', extractSitemapSymbols);
|
|
|
1009
1102
|
registerExtractor('c4', extractC4Symbols);
|
|
1010
1103
|
registerExtractor('gantt', extractGanttSymbols);
|
|
1011
1104
|
registerExtractor('boxes-and-lines', extractBoxesAndLinesSymbols);
|
|
1105
|
+
registerExtractor('tech-radar', extractTechRadarSymbols);
|
|
1106
|
+
registerExtractor('cycle', extractCycleSymbols);
|
|
1107
|
+
registerExtractor('journey-map', extractJourneyMapSymbols);
|
|
1108
|
+
|
|
1109
|
+
function extractTechRadarSymbols(docText: string): DiagramSymbols {
|
|
1110
|
+
const entities: string[] = [];
|
|
1111
|
+
const keywords: string[] = [
|
|
1112
|
+
'rings',
|
|
1113
|
+
'quadrant',
|
|
1114
|
+
'ring',
|
|
1115
|
+
'trend',
|
|
1116
|
+
'new',
|
|
1117
|
+
'up',
|
|
1118
|
+
'down',
|
|
1119
|
+
'stable',
|
|
1120
|
+
'top-left',
|
|
1121
|
+
'top-right',
|
|
1122
|
+
'bottom-left',
|
|
1123
|
+
'bottom-right',
|
|
1124
|
+
'alias',
|
|
1125
|
+
'aka',
|
|
1126
|
+
'color',
|
|
1127
|
+
];
|
|
1128
|
+
|
|
1129
|
+
// Extract ring names and aliases from the rings block
|
|
1130
|
+
const lines = docText.split('\n');
|
|
1131
|
+
let inRings = false;
|
|
1132
|
+
for (const line of lines) {
|
|
1133
|
+
const trimmed = line.trim();
|
|
1134
|
+
if (trimmed.toLowerCase() === 'rings') {
|
|
1135
|
+
inRings = true;
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (inRings) {
|
|
1139
|
+
if (!trimmed || (line[0] !== ' ' && line[0] !== '\t')) {
|
|
1140
|
+
inRings = false;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
// Parse ring name (and alias)
|
|
1144
|
+
const aliasMatch = trimmed.match(/^(.+?)\s+(?:alias|aka)\s+(\S+)\s*$/i);
|
|
1145
|
+
if (aliasMatch) {
|
|
1146
|
+
entities.push(aliasMatch[1].trim());
|
|
1147
|
+
entities.push(aliasMatch[2].trim());
|
|
1148
|
+
} else {
|
|
1149
|
+
entities.push(trimmed);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return { kind: 'tech-radar', entities, keywords };
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// ============================================================
|
|
1158
|
+
// Cycle extractor
|
|
1159
|
+
// ============================================================
|
|
1160
|
+
|
|
1161
|
+
function extractCycleSymbols(docText: string): DiagramSymbols {
|
|
1162
|
+
const lines = docText.split('\n');
|
|
1163
|
+
const entities: string[] = [];
|
|
1164
|
+
let pastFirstLine = false;
|
|
1165
|
+
|
|
1166
|
+
for (const line of lines) {
|
|
1167
|
+
const trimmed = line.trim();
|
|
1168
|
+
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
1169
|
+
|
|
1170
|
+
if (!pastFirstLine) {
|
|
1171
|
+
pastFirstLine = true;
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Skip directives/metadata
|
|
1176
|
+
const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
|
|
1177
|
+
if (METADATA_KEY_SET.has(firstToken)) continue;
|
|
1178
|
+
if (
|
|
1179
|
+
firstToken === 'direction-counterclockwise' ||
|
|
1180
|
+
firstToken === 'circle-nodes' ||
|
|
1181
|
+
firstToken === 'hide-descriptions'
|
|
1182
|
+
)
|
|
1183
|
+
continue;
|
|
1184
|
+
|
|
1185
|
+
// Skip indented lines (descriptions, edges)
|
|
1186
|
+
if (line[0] === ' ' || line[0] === '\t') continue;
|
|
1187
|
+
|
|
1188
|
+
// Node label (strip pipe metadata)
|
|
1189
|
+
const label = trimmed.split('|')[0].trim();
|
|
1190
|
+
if (label && !entities.includes(label)) entities.push(label);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return {
|
|
1194
|
+
kind: 'cycle',
|
|
1195
|
+
entities,
|
|
1196
|
+
keywords: [
|
|
1197
|
+
'direction-counterclockwise',
|
|
1198
|
+
'hide-descriptions',
|
|
1199
|
+
'circle-nodes',
|
|
1200
|
+
],
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function extractJourneyMapSymbols(docText: string): DiagramSymbols {
|
|
1205
|
+
const lines = docText.split('\n');
|
|
1206
|
+
const entities: string[] = [];
|
|
1207
|
+
let pastFirstLine = false;
|
|
1208
|
+
|
|
1209
|
+
for (const line of lines) {
|
|
1210
|
+
const trimmed = line.trim();
|
|
1211
|
+
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
1212
|
+
|
|
1213
|
+
if (!pastFirstLine) {
|
|
1214
|
+
pastFirstLine = true;
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Skip directives/metadata at indent 0
|
|
1219
|
+
const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
|
|
1220
|
+
if (METADATA_KEY_SET.has(firstToken)) continue;
|
|
1221
|
+
if (
|
|
1222
|
+
firstToken === 'persona' ||
|
|
1223
|
+
firstToken === 'tag' ||
|
|
1224
|
+
firstToken === 'no-legend'
|
|
1225
|
+
)
|
|
1226
|
+
continue;
|
|
1227
|
+
|
|
1228
|
+
const isIndented = line[0] === ' ' || line[0] === '\t';
|
|
1229
|
+
|
|
1230
|
+
// Skip deep-indented lines (annotations, descriptions under steps)
|
|
1231
|
+
// but keep singly-indented lines (steps within phases)
|
|
1232
|
+
if (isIndented) {
|
|
1233
|
+
// Annotation/description keywords — skip
|
|
1234
|
+
if (/^(pain|opportunity|thought|description)\s*:/i.test(trimmed))
|
|
1235
|
+
continue;
|
|
1236
|
+
// Tag group entries — skip
|
|
1237
|
+
if (/^\S+\([^)]+\)/.test(trimmed)) continue;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Phase header
|
|
1241
|
+
const phaseMatch = trimmed.match(/^\[(.+?)\]$/);
|
|
1242
|
+
if (phaseMatch) {
|
|
1243
|
+
entities.push(phaseMatch[1].trim());
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Step label (strip pipe metadata) — works for both indent 0 and indented steps
|
|
1248
|
+
const label = trimmed.split('|')[0].trim();
|
|
1249
|
+
if (label && !entities.includes(label)) entities.push(label);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return {
|
|
1253
|
+
kind: 'journey-map',
|
|
1254
|
+
entities,
|
|
1255
|
+
keywords: [
|
|
1256
|
+
'persona',
|
|
1257
|
+
'no-legend',
|
|
1258
|
+
'pain',
|
|
1259
|
+
'opportunity',
|
|
1260
|
+
'thought',
|
|
1261
|
+
'description',
|
|
1262
|
+
],
|
|
1263
|
+
};
|
|
1264
|
+
}
|