@diagrammo/dgmo 0.15.0 → 0.16.0
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/README.md +23 -10
- package/dist/advanced.cjs +53094 -0
- package/dist/advanced.d.cts +4690 -0
- package/dist/advanced.d.ts +4690 -0
- package/dist/advanced.js +52849 -0
- package/dist/auto.cjs +2298 -2069
- package/dist/auto.js +132 -109
- package/dist/auto.mjs +2294 -2065
- package/dist/cli.cjs +175 -152
- package/dist/editor.cjs +8 -9
- package/dist/editor.js +8 -9
- package/dist/highlight.cjs +8 -9
- package/dist/highlight.js +8 -9
- package/dist/index.cjs +2281 -2048
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +2276 -2044
- package/dist/internal.cjs +2064 -1831
- package/dist/internal.d.cts +113 -113
- package/dist/internal.d.ts +113 -113
- package/dist/internal.js +2059 -1826
- package/dist/pert.cjs +325 -0
- package/dist/pert.d.cts +542 -0
- package/dist/pert.d.ts +542 -0
- package/dist/pert.js +294 -0
- package/docs/language-reference.md +83 -66
- package/gallery/fixtures/area.dgmo +3 -3
- package/gallery/fixtures/bar-stacked.dgmo +5 -5
- package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
- package/gallery/fixtures/c4-full.dgmo +8 -8
- package/gallery/fixtures/class-full.dgmo +2 -2
- package/gallery/fixtures/doughnut.dgmo +6 -6
- package/gallery/fixtures/flowchart-colors.dgmo +3 -3
- package/gallery/fixtures/function.dgmo +3 -3
- package/gallery/fixtures/gantt-full.dgmo +9 -9
- package/gallery/fixtures/gantt.dgmo +7 -7
- package/gallery/fixtures/infra-full.dgmo +6 -6
- package/gallery/fixtures/infra.dgmo +2 -2
- package/gallery/fixtures/kanban.dgmo +9 -9
- package/gallery/fixtures/line.dgmo +2 -2
- package/gallery/fixtures/multi-line.dgmo +3 -3
- package/gallery/fixtures/org-full.dgmo +6 -6
- package/gallery/fixtures/quadrant.dgmo +2 -2
- package/gallery/fixtures/sankey.dgmo +9 -9
- package/gallery/fixtures/scatter.dgmo +3 -3
- package/gallery/fixtures/sequence-tags-protocols.dgmo +8 -8
- package/gallery/fixtures/sequence-tags.dgmo +7 -7
- package/gallery/fixtures/sitemap-full.dgmo +7 -7
- package/gallery/fixtures/slope.dgmo +5 -5
- package/gallery/fixtures/spr-eras.dgmo +9 -9
- package/gallery/fixtures/timeline.dgmo +3 -3
- package/gallery/fixtures/venn.dgmo +3 -3
- package/package.json +28 -3
- package/src/advanced.ts +730 -0
- package/src/auto/index.ts +14 -13
- package/src/boxes-and-lines/layout.ts +481 -445
- package/src/boxes-and-lines/renderer.ts +5 -1
- package/src/c4/parser.ts +8 -8
- package/src/c4/renderer.ts +15 -8
- package/src/chart-types.ts +0 -5
- package/src/chart.ts +18 -9
- package/src/class/parser.ts +8 -15
- package/src/class/renderer.ts +17 -6
- package/src/cli.ts +15 -13
- package/src/completion-types.ts +28 -0
- package/src/completion.ts +28 -21
- package/src/cycle/layout.ts +2 -2
- package/src/cycle/parser.ts +14 -0
- package/src/cycle/renderer.ts +6 -3
- package/src/d3.ts +1537 -1164
- package/src/echarts.ts +37 -20
- package/src/editor/dgmo.grammar +1 -3
- package/src/editor/dgmo.grammar.js +8 -8
- package/src/editor/dgmo.grammar.terms.js +11 -12
- package/src/editor/highlight-api.ts +0 -1
- package/src/editor/highlight.ts +0 -1
- package/src/er/parser.ts +19 -20
- package/src/er/renderer.ts +20 -8
- package/src/gantt/calculator.ts +1 -11
- package/src/gantt/parser.ts +17 -17
- package/src/gantt/renderer.ts +9 -6
- package/src/graph/flowchart-parser.ts +19 -85
- package/src/graph/flowchart-renderer.ts +4 -9
- package/src/graph/layout.ts +0 -2
- package/src/graph/state-parser.ts +17 -62
- package/src/graph/state-renderer.ts +4 -9
- package/src/index.ts +17 -1
- package/src/infra/parser.ts +40 -30
- package/src/infra/renderer.ts +9 -6
- package/src/internal.ts +9 -721
- package/src/journey-map/parser.ts +10 -3
- package/src/journey-map/renderer.ts +3 -1
- package/src/kanban/parser.ts +12 -8
- package/src/kanban/renderer.ts +3 -1
- package/src/mindmap/layout.ts +1 -1
- package/src/mindmap/parser.ts +3 -3
- package/src/mindmap/renderer.ts +2 -1
- package/src/org/parser.ts +3 -3
- package/src/org/renderer.ts +5 -4
- package/src/pert/layout.ts +1 -1
- package/src/pert/monte-carlo.ts +2 -2
- package/src/pert/parser.ts +10 -10
- package/src/pert/renderer.ts +7 -2
- package/src/pert/types.ts +1 -1
- package/src/pyramid/parser.ts +12 -0
- package/src/raci/parser.ts +44 -14
- package/src/raci/renderer.ts +3 -2
- package/src/raci/types.ts +4 -3
- package/src/ring/parser.ts +12 -0
- package/src/sequence/parser.ts +15 -9
- package/src/sequence/renderer.ts +2 -5
- package/src/sitemap/layout.ts +0 -2
- package/src/sitemap/parser.ts +12 -38
- package/src/sitemap/renderer.ts +13 -13
- package/src/sitemap/types.ts +0 -1
- package/src/tech-radar/interactive.ts +1 -1
- package/src/tech-radar/renderer.ts +6 -4
- package/src/tech-radar/types.ts +2 -0
- package/src/utils/arrows.ts +3 -28
- package/src/utils/legend-d3.ts +12 -6
- package/src/utils/legend-layout.ts +1 -1
- package/src/utils/legend-types.ts +1 -1
- package/src/utils/parsing.ts +64 -35
- package/src/utils/tag-groups.ts +109 -30
- package/src/wireframe/layout.ts +11 -7
- package/src/wireframe/parser.ts +4 -4
- package/src/wireframe/renderer.ts +5 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
2
1
|
import type { DgmoError } from '../diagnostics';
|
|
3
2
|
import type { PaletteColors } from '../palettes';
|
|
4
3
|
import {
|
|
@@ -8,10 +7,9 @@ import {
|
|
|
8
7
|
NAME_DIAGNOSTIC_CODES,
|
|
9
8
|
nameMergedMessage,
|
|
10
9
|
} from '../diagnostics';
|
|
11
|
-
import { parseInArrowLabel
|
|
10
|
+
import { parseInArrowLabel } from '../utils/arrows';
|
|
12
11
|
import {
|
|
13
12
|
measureIndent,
|
|
14
|
-
inferArrowColor,
|
|
15
13
|
parseFirstLine,
|
|
16
14
|
OPTION_NOCOLON_RE,
|
|
17
15
|
ALL_CHART_TYPES,
|
|
@@ -90,9 +88,8 @@ function parseNodeRef(text: string): NodeRef | null {
|
|
|
90
88
|
|
|
91
89
|
/**
|
|
92
90
|
* Split a line into segments around arrow tokens.
|
|
93
|
-
* Arrows: `->`, `-label->`,
|
|
94
|
-
*
|
|
95
|
-
* token is the maximal run of `-+>`).
|
|
91
|
+
* Arrows: `->`, `-label->`, and long-dash variants like `-->`, `--->`,
|
|
92
|
+
* `--foo--->` (TD-9 longest-match: the arrow token is the maximal run of `-+>`).
|
|
96
93
|
*
|
|
97
94
|
* Returns alternating: [nodeText, arrowText, nodeText, arrowText, nodeText, ...]
|
|
98
95
|
* Where arrowText is the synthesized full arrow token like `-yes->` or `->`
|
|
@@ -105,7 +102,6 @@ function splitArrows(line: string): string[] {
|
|
|
105
102
|
start: number;
|
|
106
103
|
end: number;
|
|
107
104
|
label?: string;
|
|
108
|
-
color?: string;
|
|
109
105
|
}[] = [];
|
|
110
106
|
|
|
111
107
|
// Find all arrow tokens. A token is a maximal run of `-+>` (one-or-more
|
|
@@ -132,7 +128,6 @@ function splitArrows(line: string): string[] {
|
|
|
132
128
|
// the label; the full arrow token runs from opening through `>`.
|
|
133
129
|
let arrowStart: number;
|
|
134
130
|
let label: string | undefined;
|
|
135
|
-
let color: string | undefined;
|
|
136
131
|
|
|
137
132
|
let openingStart = -1;
|
|
138
133
|
for (let i = scanFloor; i < runStart; i++) {
|
|
@@ -151,16 +146,9 @@ function splitArrows(line: string): string[] {
|
|
|
151
146
|
while (openingEnd < runStart && line[openingEnd] === '-') openingEnd++;
|
|
152
147
|
|
|
153
148
|
// Label content = everything between opening run and the arrow run.
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
color = colorMatch[1].trim();
|
|
158
|
-
const labelPart = arrowContent.substring(0, colorMatch.index!).trim();
|
|
159
|
-
if (labelPart) label = labelPart;
|
|
160
|
-
} else {
|
|
161
|
-
const labelPart = arrowContent.trim();
|
|
162
|
-
if (labelPart) label = labelPart;
|
|
163
|
-
}
|
|
149
|
+
// Edges have no color slot (§1.7); parens stay literal.
|
|
150
|
+
const labelPart = line.substring(openingEnd, runStart).trim();
|
|
151
|
+
if (labelPart) label = labelPart;
|
|
164
152
|
arrowStart = openingStart;
|
|
165
153
|
} else {
|
|
166
154
|
// No opening dash run found. All absorbed leftward dashes belong to
|
|
@@ -168,7 +156,7 @@ function splitArrows(line: string): string[] {
|
|
|
168
156
|
arrowStart = runStart;
|
|
169
157
|
}
|
|
170
158
|
|
|
171
|
-
arrowPositions.push({ start: arrowStart, end: arrowEnd, label
|
|
159
|
+
arrowPositions.push({ start: arrowStart, end: arrowEnd, label });
|
|
172
160
|
searchFrom = arrowEnd;
|
|
173
161
|
scanFloor = arrowEnd;
|
|
174
162
|
}
|
|
@@ -179,12 +167,9 @@ function splitArrows(line: string): string[] {
|
|
|
179
167
|
|
|
180
168
|
// Build segments.
|
|
181
169
|
//
|
|
182
|
-
// NOTE: the synthesized arrow token is always the short form (
|
|
183
|
-
// `-label
|
|
184
|
-
//
|
|
185
|
-
// dash-length-sensitive edge styling (e.g. Mermaid-style "long arrow"
|
|
186
|
-
// emphasis), thread `arrow.end - arrow.start - label?.length - color?.length`
|
|
187
|
-
// through to ArrowInfo so downstream renderers can honor it.
|
|
170
|
+
// NOTE: the synthesized arrow token is always the short form (`->` or
|
|
171
|
+
// `-label->`). The actual dash run-length (`-->`, `--->`, `---->`) seen
|
|
172
|
+
// in the source is collapsed here.
|
|
188
173
|
let lastIndex = 0;
|
|
189
174
|
for (let i = 0; i < arrowPositions.length; i++) {
|
|
190
175
|
const arrow = arrowPositions[i];
|
|
@@ -192,12 +177,7 @@ function splitArrows(line: string): string[] {
|
|
|
192
177
|
if (beforeText || i === 0) {
|
|
193
178
|
segments.push(beforeText);
|
|
194
179
|
}
|
|
195
|
-
|
|
196
|
-
let arrowToken = '->';
|
|
197
|
-
if (arrow.label && arrow.color)
|
|
198
|
-
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
199
|
-
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
200
|
-
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
180
|
+
const arrowToken = arrow.label ? `-${arrow.label}->` : '->';
|
|
201
181
|
segments.push(arrowToken);
|
|
202
182
|
lastIndex = arrow.end;
|
|
203
183
|
}
|
|
@@ -212,61 +192,23 @@ function splitArrows(line: string): string[] {
|
|
|
212
192
|
|
|
213
193
|
interface ArrowInfo {
|
|
214
194
|
label?: string;
|
|
215
|
-
color?: string;
|
|
216
195
|
}
|
|
217
196
|
|
|
218
197
|
function parseArrowToken(
|
|
219
198
|
token: string,
|
|
220
|
-
|
|
199
|
+
_palette: PaletteColors | undefined,
|
|
221
200
|
lineNumber: number,
|
|
222
201
|
diagnostics: DgmoError[]
|
|
223
202
|
): ArrowInfo {
|
|
224
203
|
if (token === '->') return {};
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
|
|
229
|
-
if (bareParen) {
|
|
230
|
-
const colorName = matchColorParens(bareParen[1]);
|
|
231
|
-
if (colorName) {
|
|
232
|
-
return {
|
|
233
|
-
color: resolveColorWithDiagnostic(
|
|
234
|
-
colorName,
|
|
235
|
-
lineNumber,
|
|
236
|
-
diagnostics,
|
|
237
|
-
palette
|
|
238
|
-
),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
// Unrecognized color name → whole `(X)` is the label (fall through).
|
|
242
|
-
}
|
|
243
|
-
// -label(color)-> or -label->
|
|
244
|
-
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
204
|
+
// Edges have no color slot (spec §1.7 "Edge color is not a feature"). The
|
|
205
|
+
// whole content between `-` and `->` is the label, including parens.
|
|
206
|
+
const m = token.match(/^-(.+?)->$/);
|
|
245
207
|
if (m) {
|
|
246
208
|
const rawLabel = m[1] ?? '';
|
|
247
|
-
// Route label through TD-13/TD-14 validator.
|
|
248
209
|
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
249
210
|
diagnostics.push(...labelResult.diagnostics);
|
|
250
|
-
|
|
251
|
-
let color = m[2]
|
|
252
|
-
? resolveColorWithDiagnostic(
|
|
253
|
-
m[2].trim(),
|
|
254
|
-
lineNumber,
|
|
255
|
-
diagnostics,
|
|
256
|
-
palette
|
|
257
|
-
)
|
|
258
|
-
: undefined;
|
|
259
|
-
if (label && !color) {
|
|
260
|
-
const inferred = inferArrowColor(label);
|
|
261
|
-
if (inferred)
|
|
262
|
-
color = resolveColorWithDiagnostic(
|
|
263
|
-
inferred,
|
|
264
|
-
lineNumber,
|
|
265
|
-
diagnostics,
|
|
266
|
-
palette
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
return { label, color };
|
|
211
|
+
return { label: labelResult.label };
|
|
270
212
|
}
|
|
271
213
|
return {};
|
|
272
214
|
}
|
|
@@ -362,15 +304,13 @@ export function parseFlowchart(
|
|
|
362
304
|
sourceId: string,
|
|
363
305
|
targetId: string,
|
|
364
306
|
lineNumber: number,
|
|
365
|
-
label?: string
|
|
366
|
-
color?: string
|
|
307
|
+
label?: string
|
|
367
308
|
): void {
|
|
368
309
|
const edge: GraphEdge = {
|
|
369
310
|
source: sourceId,
|
|
370
311
|
target: targetId,
|
|
371
312
|
lineNumber,
|
|
372
313
|
...(label && { label }),
|
|
373
|
-
...(color && { color }),
|
|
374
314
|
};
|
|
375
315
|
result.edges.push(edge);
|
|
376
316
|
}
|
|
@@ -471,13 +411,7 @@ export function parseFlowchart(
|
|
|
471
411
|
if (pendingArrow !== null) {
|
|
472
412
|
const sourceId = lastNodeId ?? implicitSourceId;
|
|
473
413
|
if (sourceId) {
|
|
474
|
-
addEdge(
|
|
475
|
-
sourceId,
|
|
476
|
-
node.id,
|
|
477
|
-
lineNumber,
|
|
478
|
-
pendingArrow.label,
|
|
479
|
-
pendingArrow.color
|
|
480
|
-
);
|
|
414
|
+
addEdge(sourceId, node.id, lineNumber, pendingArrow.label);
|
|
481
415
|
}
|
|
482
416
|
pendingArrow = null;
|
|
483
417
|
} else if (lastNodeId === null && implicitSourceId === null) {
|
|
@@ -663,7 +597,7 @@ export function looksLikeFlowchart(content: string): boolean {
|
|
|
663
597
|
// Symbol extraction (for completion API)
|
|
664
598
|
// ============================================================
|
|
665
599
|
|
|
666
|
-
import type { DiagramSymbols } from '../completion';
|
|
600
|
+
import type { DiagramSymbols } from '../completion-types';
|
|
667
601
|
|
|
668
602
|
// Node ID: identifier at line start followed by a shape delimiter or space (arrow line)
|
|
669
603
|
const NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
|
|
@@ -447,11 +447,8 @@ export function renderFlowchart(
|
|
|
447
447
|
.attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
|
|
448
448
|
.attr('fill', palette.textMuted);
|
|
449
449
|
|
|
450
|
-
//
|
|
450
|
+
// Edges have no color slot (§1.7); keep empty set for marker iteration.
|
|
451
451
|
const edgeColors = new Set<string>();
|
|
452
|
-
for (const edge of layout.edges) {
|
|
453
|
-
if (edge.color) edgeColors.add(edge.color);
|
|
454
|
-
}
|
|
455
452
|
for (const color of edgeColors) {
|
|
456
453
|
const id = `fc-arrow-${color.replace('#', '')}`;
|
|
457
454
|
defs
|
|
@@ -569,10 +566,8 @@ export function renderFlowchart(
|
|
|
569
566
|
.attr('class', 'fc-edge-group')
|
|
570
567
|
.attr('data-line-number', String(edge.lineNumber));
|
|
571
568
|
|
|
572
|
-
const edgeColor =
|
|
573
|
-
const markerId =
|
|
574
|
-
? `fc-arrow-${edge.color.replace('#', '')}`
|
|
575
|
-
: 'fc-arrow';
|
|
569
|
+
const edgeColor = palette.textMuted;
|
|
570
|
+
const markerId = 'fc-arrow';
|
|
576
571
|
|
|
577
572
|
const pathD = lineGenerator(edge.points);
|
|
578
573
|
if (pathD) {
|
|
@@ -623,7 +618,7 @@ export function renderFlowchart(
|
|
|
623
618
|
}
|
|
624
619
|
|
|
625
620
|
// Render nodes (top layer)
|
|
626
|
-
const colorOff = graph.options?.color === 'off';
|
|
621
|
+
const colorOff = graph.options?.['color'] === 'off';
|
|
627
622
|
const solid = graph.options?.['solid-fill'] === 'on';
|
|
628
623
|
for (const node of layout.nodes) {
|
|
629
624
|
const nodeG = contentG
|
package/src/graph/layout.ts
CHANGED
|
@@ -25,7 +25,6 @@ export interface LayoutEdge {
|
|
|
25
25
|
target: string;
|
|
26
26
|
points: { x: number; y: number }[];
|
|
27
27
|
label?: string;
|
|
28
|
-
color?: string;
|
|
29
28
|
lineNumber: number;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -179,7 +178,6 @@ export function layoutGraph(
|
|
|
179
178
|
target: edge.target,
|
|
180
179
|
points: edgeData?.points ?? [],
|
|
181
180
|
label: edge.label,
|
|
182
|
-
color: edge.color,
|
|
183
181
|
lineNumber: edge.lineNumber,
|
|
184
182
|
};
|
|
185
183
|
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
NAME_DIAGNOSTIC_CODES,
|
|
9
9
|
nameMergedMessage,
|
|
10
10
|
} from '../diagnostics';
|
|
11
|
-
import { parseInArrowLabel
|
|
11
|
+
import { parseInArrowLabel } from '../utils/arrows';
|
|
12
12
|
import {
|
|
13
13
|
measureIndent,
|
|
14
14
|
parseFirstLine,
|
|
@@ -26,7 +26,10 @@ import type { ParsedGraph, GraphNode, GraphGroup } from './types';
|
|
|
26
26
|
const PSEUDOSTATE_ID = 'pseudostate:[*]';
|
|
27
27
|
const PSEUDOSTATE_LABEL = '[*]';
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// `[Group]` or `[Group] color` (universal §1.5 trailing-token).
|
|
30
|
+
// Color (group 2) must be a recognized lowercase palette word.
|
|
31
|
+
const GROUP_BRACKET_RE =
|
|
32
|
+
/^\[([^\]]+)\](?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/;
|
|
30
33
|
|
|
31
34
|
// ============================================================
|
|
32
35
|
// Arrow splitter
|
|
@@ -36,7 +39,7 @@ const GROUP_BRACKET_RE = /^\[([^\]]+)\](?:\(([^)]+)\))?\s*$/;
|
|
|
36
39
|
* Split a line on `->` arrows, returning alternating segments:
|
|
37
40
|
* [nodeText, arrowToken, nodeText, ...]
|
|
38
41
|
*
|
|
39
|
-
* Arrows: `->`, `-label
|
|
42
|
+
* Arrows: `->`, `-label->`. Edges have no color slot (spec §1.7).
|
|
40
43
|
*/
|
|
41
44
|
function splitArrows(line: string): string[] {
|
|
42
45
|
// Mirrors flowchart-parser.ts splitArrows. TD-9 longest-match: arrow token
|
|
@@ -46,7 +49,6 @@ function splitArrows(line: string): string[] {
|
|
|
46
49
|
start: number;
|
|
47
50
|
end: number;
|
|
48
51
|
label?: string;
|
|
49
|
-
color?: string;
|
|
50
52
|
}[] = [];
|
|
51
53
|
|
|
52
54
|
let searchFrom = 0;
|
|
@@ -61,7 +63,6 @@ function splitArrows(line: string): string[] {
|
|
|
61
63
|
|
|
62
64
|
let arrowStart: number;
|
|
63
65
|
let label: string | undefined;
|
|
64
|
-
let color: string | undefined;
|
|
65
66
|
|
|
66
67
|
let openingStart = -1;
|
|
67
68
|
for (let i = scanFloor; i < runStart; i++) {
|
|
@@ -78,22 +79,14 @@ function splitArrows(line: string): string[] {
|
|
|
78
79
|
let openingEnd = openingStart;
|
|
79
80
|
while (openingEnd < runStart && line[openingEnd] === '-') openingEnd++;
|
|
80
81
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
if (colorMatch) {
|
|
84
|
-
color = colorMatch[1].trim();
|
|
85
|
-
const labelPart = arrowContent.substring(0, colorMatch.index!).trim();
|
|
86
|
-
if (labelPart) label = labelPart;
|
|
87
|
-
} else {
|
|
88
|
-
const labelPart = arrowContent.trim();
|
|
89
|
-
if (labelPart) label = labelPart;
|
|
90
|
-
}
|
|
82
|
+
const labelPart = line.substring(openingEnd, runStart).trim();
|
|
83
|
+
if (labelPart) label = labelPart;
|
|
91
84
|
arrowStart = openingStart;
|
|
92
85
|
} else {
|
|
93
86
|
arrowStart = runStart;
|
|
94
87
|
}
|
|
95
88
|
|
|
96
|
-
arrowPositions.push({ start: arrowStart, end: arrowEnd, label
|
|
89
|
+
arrowPositions.push({ start: arrowStart, end: arrowEnd, label });
|
|
97
90
|
searchFrom = arrowEnd;
|
|
98
91
|
scanFloor = arrowEnd;
|
|
99
92
|
}
|
|
@@ -106,11 +99,7 @@ function splitArrows(line: string): string[] {
|
|
|
106
99
|
const beforeText = line.substring(lastIndex, arrow.start).trim();
|
|
107
100
|
if (beforeText || i === 0) segments.push(beforeText);
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
if (arrow.label && arrow.color)
|
|
111
|
-
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
112
|
-
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
113
|
-
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
102
|
+
const arrowToken = arrow.label ? `-${arrow.label}->` : '->';
|
|
114
103
|
segments.push(arrowToken);
|
|
115
104
|
lastIndex = arrow.end;
|
|
116
105
|
}
|
|
@@ -122,49 +111,23 @@ function splitArrows(line: string): string[] {
|
|
|
122
111
|
|
|
123
112
|
interface ArrowInfo {
|
|
124
113
|
label?: string;
|
|
125
|
-
color?: string;
|
|
126
114
|
}
|
|
127
115
|
|
|
128
116
|
function parseArrowToken(
|
|
129
117
|
token: string,
|
|
130
|
-
|
|
118
|
+
_palette: PaletteColors | undefined,
|
|
131
119
|
lineNumber: number,
|
|
132
120
|
diagnostics: DgmoError[]
|
|
133
121
|
): ArrowInfo {
|
|
134
122
|
if (token === '->') return {};
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
|
|
139
|
-
if (bareParen) {
|
|
140
|
-
const colorName = matchColorParens(bareParen[1]);
|
|
141
|
-
if (colorName) {
|
|
142
|
-
return {
|
|
143
|
-
color: resolveColorWithDiagnostic(
|
|
144
|
-
colorName,
|
|
145
|
-
lineNumber,
|
|
146
|
-
diagnostics,
|
|
147
|
-
palette
|
|
148
|
-
),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
// fall through — whole `(X)` becomes label
|
|
152
|
-
}
|
|
153
|
-
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
123
|
+
// Edges have no color slot (§1.7); arrow content between `-` and `->`
|
|
124
|
+
// is pure label text.
|
|
125
|
+
const m = token.match(/^-(.+?)->$/);
|
|
154
126
|
if (m) {
|
|
155
127
|
const rawLabel = m[1] ?? '';
|
|
156
128
|
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
157
129
|
diagnostics.push(...labelResult.diagnostics);
|
|
158
|
-
|
|
159
|
-
const color = m[2]
|
|
160
|
-
? resolveColorWithDiagnostic(
|
|
161
|
-
m[2].trim(),
|
|
162
|
-
lineNumber,
|
|
163
|
-
diagnostics,
|
|
164
|
-
palette
|
|
165
|
-
)
|
|
166
|
-
: undefined;
|
|
167
|
-
return { label, color };
|
|
130
|
+
return { label: labelResult.label };
|
|
168
131
|
}
|
|
169
132
|
return {};
|
|
170
133
|
}
|
|
@@ -292,15 +255,13 @@ export function parseState(
|
|
|
292
255
|
sourceId: string,
|
|
293
256
|
targetId: string,
|
|
294
257
|
lineNumber: number,
|
|
295
|
-
label?: string
|
|
296
|
-
color?: string
|
|
258
|
+
label?: string
|
|
297
259
|
): void {
|
|
298
260
|
result.edges.push({
|
|
299
261
|
source: sourceId,
|
|
300
262
|
target: targetId,
|
|
301
263
|
lineNumber,
|
|
302
264
|
...(label && { label }),
|
|
303
|
-
...(color && { color }),
|
|
304
265
|
});
|
|
305
266
|
}
|
|
306
267
|
|
|
@@ -494,13 +455,7 @@ export function parseState(
|
|
|
494
455
|
// Use explicit source if available, else implicit from indent
|
|
495
456
|
const sourceId = lastNodeId ?? implicitSourceId;
|
|
496
457
|
if (sourceId) {
|
|
497
|
-
addEdge(
|
|
498
|
-
sourceId,
|
|
499
|
-
node.id,
|
|
500
|
-
lineNumber,
|
|
501
|
-
pendingArrow.label,
|
|
502
|
-
pendingArrow.color
|
|
503
|
-
);
|
|
458
|
+
addEdge(sourceId, node.id, lineNumber, pendingArrow.label);
|
|
504
459
|
}
|
|
505
460
|
pendingArrow = null;
|
|
506
461
|
}
|
|
@@ -144,11 +144,8 @@ export function renderState(
|
|
|
144
144
|
.attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
|
|
145
145
|
.attr('fill', palette.textMuted);
|
|
146
146
|
|
|
147
|
-
//
|
|
147
|
+
// Edges have no color slot (§1.7); keep empty set for marker iteration.
|
|
148
148
|
const edgeColors = new Set<string>();
|
|
149
|
-
for (const edge of layout.edges) {
|
|
150
|
-
if (edge.color) edgeColors.add(edge.color);
|
|
151
|
-
}
|
|
152
149
|
for (const color of edgeColors) {
|
|
153
150
|
const id = `st-arrow-${color.replace('#', '')}`;
|
|
154
151
|
defs
|
|
@@ -345,10 +342,8 @@ export function renderState(
|
|
|
345
342
|
.attr('class', 'st-edge-group')
|
|
346
343
|
.attr('data-line-number', String(edge.lineNumber));
|
|
347
344
|
|
|
348
|
-
const edgeColor =
|
|
349
|
-
const markerId =
|
|
350
|
-
? `st-arrow-${edge.color.replace('#', '')}`
|
|
351
|
-
: 'st-arrow';
|
|
345
|
+
const edgeColor = palette.textMuted;
|
|
346
|
+
const markerId = 'st-arrow';
|
|
352
347
|
|
|
353
348
|
if (edge.source === edge.target) {
|
|
354
349
|
// Self-loop
|
|
@@ -431,7 +426,7 @@ export function renderState(
|
|
|
431
426
|
}
|
|
432
427
|
|
|
433
428
|
// Render nodes (top layer)
|
|
434
|
-
const colorOff = graph.options?.color === 'off';
|
|
429
|
+
const colorOff = graph.options?.['color'] === 'off';
|
|
435
430
|
const solid = graph.options?.['solid-fill'] === 'on';
|
|
436
431
|
for (const node of layout.nodes) {
|
|
437
432
|
const isCollapsedGroup = collapsedGroupIds.has(node.id);
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { render as renderInternal } from './render';
|
|
|
13
13
|
import {
|
|
14
14
|
encodeDiagramUrl as encodeDiagramUrlInternal,
|
|
15
15
|
decodeDiagramUrl as decodeDiagramUrlInternal,
|
|
16
|
+
type CompactViewState,
|
|
16
17
|
} from './sharing';
|
|
17
18
|
import { parseDgmo as validate } from './dgmo-router';
|
|
18
19
|
import { palettes, getPalette } from './palettes';
|
|
@@ -20,6 +21,8 @@ import type { PaletteConfig } from './palettes/types';
|
|
|
20
21
|
import type { Theme } from './themes';
|
|
21
22
|
import { formatDgmoError, type DgmoError } from './diagnostics';
|
|
22
23
|
|
|
24
|
+
export type { CompactViewState } from './sharing';
|
|
25
|
+
|
|
23
26
|
// ============================================================
|
|
24
27
|
// render(text, options?)
|
|
25
28
|
// ============================================================
|
|
@@ -34,6 +37,12 @@ export interface RenderOptions {
|
|
|
34
37
|
* 'throw' — throw an Error with the diagnostics
|
|
35
38
|
*/
|
|
36
39
|
onError?: 'svg' | 'silent' | 'throw';
|
|
40
|
+
/**
|
|
41
|
+
* Pre-applied interactive view state — collapsed sections/columns,
|
|
42
|
+
* active swimlane tag-group, etc. Used to render a specific view
|
|
43
|
+
* non-interactively (server-side render, share-link decode).
|
|
44
|
+
*/
|
|
45
|
+
viewState?: CompactViewState;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export interface RenderResult {
|
|
@@ -65,6 +74,7 @@ export async function render(
|
|
|
65
74
|
const result = await renderInternal(text, {
|
|
66
75
|
theme: options?.theme,
|
|
67
76
|
palette: palette.id,
|
|
77
|
+
viewState: options?.viewState,
|
|
68
78
|
});
|
|
69
79
|
|
|
70
80
|
const errors = result.diagnostics.filter((d) => d.severity === 'error');
|
|
@@ -139,6 +149,11 @@ export interface EncodeDiagramUrlOptions {
|
|
|
139
149
|
palette?: PaletteConfig;
|
|
140
150
|
theme?: Theme;
|
|
141
151
|
filename?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Initial view state to embed in the URL — re-applied when the link is
|
|
154
|
+
* decoded so recipients open the diagram in the same configuration.
|
|
155
|
+
*/
|
|
156
|
+
viewState?: CompactViewState;
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
/**
|
|
@@ -158,6 +173,7 @@ export function encodeDiagramUrl(
|
|
|
158
173
|
palette: options?.palette?.id,
|
|
159
174
|
theme: internalTheme,
|
|
160
175
|
filename: options?.filename,
|
|
176
|
+
viewState: options?.viewState,
|
|
161
177
|
});
|
|
162
178
|
return 'error' in result && result.error ? null : (result.url ?? null);
|
|
163
179
|
}
|
|
@@ -188,7 +204,7 @@ export function decodeDiagramUrl(url: string): DecodedDiagramUrl | null {
|
|
|
188
204
|
// Palettes + themes (namespaces)
|
|
189
205
|
// ============================================================
|
|
190
206
|
|
|
191
|
-
export { palettes } from './palettes';
|
|
207
|
+
export { palettes, getPalette } from './palettes';
|
|
192
208
|
export { themes, type Theme } from './themes';
|
|
193
209
|
|
|
194
210
|
// ============================================================
|
package/src/infra/parser.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
nameMergedMessage,
|
|
15
15
|
} from '../diagnostics';
|
|
16
16
|
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
17
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
18
17
|
import { parseInArrowLabel } from '../utils/arrows';
|
|
19
18
|
import {
|
|
20
19
|
measureIndent,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
OPTION_NOCOLON_RE,
|
|
23
22
|
tryParseSharedOption,
|
|
24
23
|
} from '../utils/parsing';
|
|
24
|
+
import { isRecognizedColorName } from '../colors';
|
|
25
25
|
import { normalizeName, displayName } from '../utils/name-normalize';
|
|
26
26
|
import {
|
|
27
27
|
matchTagBlockHeading,
|
|
@@ -62,9 +62,11 @@ const DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
|
|
|
62
62
|
const GROUP_RE =
|
|
63
63
|
/^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?(?:\|\s*(.+))?$/;
|
|
64
64
|
|
|
65
|
-
// Tag value: Name
|
|
66
|
-
//
|
|
67
|
-
|
|
65
|
+
// Tag value: `Name` or `Name color` (trailing-token color form). Color is
|
|
66
|
+
// extracted via the shared `extractColor` helper at use-site (see
|
|
67
|
+
// `dgmo/src/utils/parsing.ts:extractColor`), not via this regex. This regex
|
|
68
|
+
// just confirms the line shape is a valid tag value (no reserved sigils).
|
|
69
|
+
const TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
|
|
68
70
|
|
|
69
71
|
// Component line. Accepts either a quoted name ("name with | : reserved chars")
|
|
70
72
|
// or a bare name (multi-word allowed; must start with letter/underscore so digit-
|
|
@@ -351,11 +353,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
351
353
|
|
|
352
354
|
// animate (default ON) / no-animate
|
|
353
355
|
if (trimmed === 'animate') {
|
|
354
|
-
result.options
|
|
356
|
+
result.options['animate'] = 'on';
|
|
355
357
|
continue;
|
|
356
358
|
}
|
|
357
359
|
if (trimmed === 'no-animate') {
|
|
358
|
-
result.options
|
|
360
|
+
result.options['animate'] = 'off';
|
|
359
361
|
continue;
|
|
360
362
|
}
|
|
361
363
|
|
|
@@ -445,14 +447,22 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
445
447
|
// Tag value inside tag group — first value is the default unless another is marked `default`
|
|
446
448
|
if (currentTagGroup && indent > 0) {
|
|
447
449
|
const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
450
|
+
// Trailing-token color (universal rule, §1.5): peel off a lowercase
|
|
451
|
+
// recognized color word from the end of the line. Downstream stores
|
|
452
|
+
// the raw color NAME (not the palette hex) so the renderer can resolve
|
|
453
|
+
// against whichever theme/palette is active at render time.
|
|
454
|
+
const lastSpaceIdx = cleanEntry.lastIndexOf(' ');
|
|
455
|
+
let valueName = cleanEntry;
|
|
456
|
+
let rawColor: string | undefined;
|
|
457
|
+
if (lastSpaceIdx > 0) {
|
|
458
|
+
const trailing = cleanEntry.substring(lastSpaceIdx + 1);
|
|
459
|
+
if (isRecognizedColorName(trailing)) {
|
|
460
|
+
rawColor = trailing;
|
|
461
|
+
valueName = cleanEntry.substring(0, lastSpaceIdx).trimEnd();
|
|
455
462
|
}
|
|
463
|
+
}
|
|
464
|
+
const tvMatch = valueName.match(TAG_VALUE_RE);
|
|
465
|
+
if (tvMatch || /^\w+$/.test(valueName)) {
|
|
456
466
|
currentTagGroup.values.push({
|
|
457
467
|
name: valueName,
|
|
458
468
|
color: rawColor,
|
|
@@ -550,11 +560,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
550
560
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
551
561
|
const targetName = pipeMeta.clean || targetRaw;
|
|
552
562
|
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
553
|
-
const split = pipeMeta.tags
|
|
554
|
-
? parseFloat(pipeMeta.tags
|
|
563
|
+
const split = pipeMeta.tags['split']
|
|
564
|
+
? parseFloat(pipeMeta.tags['split'])
|
|
555
565
|
: null;
|
|
556
|
-
const fanoutRaw = pipeMeta.tags
|
|
557
|
-
? parseInt(pipeMeta.tags
|
|
566
|
+
const fanoutRaw = pipeMeta.tags['fanout']
|
|
567
|
+
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
558
568
|
: null;
|
|
559
569
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
560
570
|
warn(
|
|
@@ -588,11 +598,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
588
598
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
589
599
|
const targetName = pipeMeta.clean || targetRaw;
|
|
590
600
|
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
591
|
-
const split = pipeMeta.tags
|
|
592
|
-
? parseFloat(pipeMeta.tags
|
|
601
|
+
const split = pipeMeta.tags['split']
|
|
602
|
+
? parseFloat(pipeMeta.tags['split'])
|
|
593
603
|
: null;
|
|
594
|
-
const fanoutRaw = pipeMeta.tags
|
|
595
|
-
? parseInt(pipeMeta.tags
|
|
604
|
+
const fanoutRaw = pipeMeta.tags['fanout']
|
|
605
|
+
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
596
606
|
: null;
|
|
597
607
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
598
608
|
warn(
|
|
@@ -631,11 +641,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
631
641
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
632
642
|
const targetName = pipeMeta.clean || targetRaw;
|
|
633
643
|
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
634
|
-
const split = pipeMeta.tags
|
|
635
|
-
? parseFloat(pipeMeta.tags
|
|
644
|
+
const split = pipeMeta.tags['split']
|
|
645
|
+
? parseFloat(pipeMeta.tags['split'])
|
|
636
646
|
: null;
|
|
637
|
-
const fanoutRaw = pipeMeta.tags
|
|
638
|
-
? parseInt(pipeMeta.tags
|
|
647
|
+
const fanoutRaw = pipeMeta.tags['fanout']
|
|
648
|
+
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
639
649
|
: null;
|
|
640
650
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
641
651
|
warn(
|
|
@@ -669,11 +679,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
669
679
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
670
680
|
const targetName = pipeMeta.clean || targetRaw;
|
|
671
681
|
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
672
|
-
const split = pipeMeta.tags
|
|
673
|
-
? parseFloat(pipeMeta.tags
|
|
682
|
+
const split = pipeMeta.tags['split']
|
|
683
|
+
? parseFloat(pipeMeta.tags['split'])
|
|
674
684
|
: null;
|
|
675
|
-
const fanoutRaw = pipeMeta.tags
|
|
676
|
-
? parseInt(pipeMeta.tags
|
|
685
|
+
const fanoutRaw = pipeMeta.tags['fanout']
|
|
686
|
+
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
677
687
|
: null;
|
|
678
688
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
679
689
|
warn(
|
|
@@ -892,7 +902,7 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
892
902
|
// Symbol extraction (for completion API)
|
|
893
903
|
// ============================================================
|
|
894
904
|
|
|
895
|
-
import type { DiagramSymbols } from '../completion';
|
|
905
|
+
import type { DiagramSymbols } from '../completion-types';
|
|
896
906
|
|
|
897
907
|
/**
|
|
898
908
|
* Extract component names (entities) from infra document text.
|