@diagrammo/dgmo 0.8.23 → 0.8.25
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.md +60 -72
- package/dist/cli.cjs +119 -114
- package/dist/editor.cjs +0 -2
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +0 -2
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +0 -2
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +0 -2
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +690 -278
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -18
- package/dist/index.d.ts +105 -18
- package/dist/index.js +680 -277
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +348 -51
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +93 -5
- package/dist/internal.d.ts +93 -5
- package/dist/internal.js +334 -38
- package/dist/internal.js.map +1 -1
- package/docs/guide/chart-area.md +17 -17
- package/docs/guide/chart-bar-stacked.md +12 -12
- package/docs/guide/chart-doughnut.md +10 -10
- package/docs/guide/chart-funnel.md +9 -9
- package/docs/guide/chart-heatmap.md +10 -10
- package/docs/guide/chart-kanban.md +2 -0
- package/docs/guide/chart-line.md +19 -19
- package/docs/guide/chart-multi-line.md +16 -16
- package/docs/guide/chart-pie.md +11 -11
- package/docs/guide/chart-polar-area.md +10 -10
- package/docs/guide/chart-radar.md +9 -9
- package/docs/guide/chart-scatter.md +24 -27
- package/docs/guide/index.md +3 -3
- package/docs/language-reference.md +46 -25
- package/fonts/Inter-Bold.ttf +0 -0
- package/fonts/Inter-Regular.ttf +0 -0
- package/fonts/LICENSE-Inter.txt +92 -0
- package/gallery/fixtures/bar-stacked.dgmo +12 -6
- package/gallery/fixtures/heatmap.dgmo +12 -6
- package/gallery/fixtures/multi-line.dgmo +11 -7
- package/gallery/fixtures/quadrant.dgmo +8 -8
- package/gallery/fixtures/scatter.dgmo +12 -12
- package/package.json +4 -2
- package/src/boxes-and-lines/parser.ts +13 -2
- package/src/boxes-and-lines/renderer.ts +22 -13
- package/src/chart-type-scoring.ts +162 -0
- package/src/chart-types.ts +437 -0
- package/src/cli.ts +147 -66
- package/src/completion.ts +0 -4
- package/src/d3.ts +9 -2
- package/src/dgmo-router.ts +85 -130
- package/src/editor/keywords.ts +0 -2
- package/src/fonts.ts +3 -2
- package/src/gantt/parser.ts +5 -1
- package/src/index.ts +24 -1
- package/src/infra/parser.ts +1 -1
- package/src/internal.ts +6 -2
- package/src/journey-map/layout.ts +7 -3
- package/src/journey-map/parser.ts +5 -1
- package/src/kanban/parser.ts +5 -1
- package/src/org/collapse.ts +1 -4
- package/src/org/parser.ts +1 -1
- package/src/org/renderer.ts +26 -17
- package/src/sequence/parser.ts +2 -2
- package/src/sequence/participant-inference.ts +0 -1
- package/src/sequence/renderer.ts +95 -263
- package/src/sharing.ts +0 -1
- package/src/sitemap/parser.ts +1 -1
- package/src/utils/tag-groups.ts +35 -5
- package/src/wireframe/parser.ts +3 -1
|
@@ -121,14 +121,18 @@ export function layoutJourneyMap(
|
|
|
121
121
|
? parsed.phases.flatMap((p) => p.steps)
|
|
122
122
|
: parsed.steps;
|
|
123
123
|
|
|
124
|
-
// Compute step card heights based on content (matches kanban card sizing)
|
|
124
|
+
// Compute step card heights based on content (matches kanban card sizing).
|
|
125
|
+
// Char-width constants MUST match the renderer's wrapText() in renderer.ts
|
|
126
|
+
// (`fontSize * 0.6`) and the title wrap (`TITLE_CHAR_WIDTH`) — otherwise the
|
|
127
|
+
// layout reserves too little vertical space and rendered text overflows.
|
|
125
128
|
const annoIconIndent = ANNO_ICON_SIZE + ANNO_ICON_GAP;
|
|
126
129
|
const annoTextW = STEP_CARD_WIDTH - CARD_PADDING_X * 2 - annoIconIndent;
|
|
127
130
|
const descTextWidth = STEP_CARD_WIDTH - CARD_PADDING_X * 2;
|
|
128
|
-
const
|
|
131
|
+
const FONT_SIZE_META = 10;
|
|
132
|
+
const charWidth = FONT_SIZE_META * 0.6; // matches renderer wrapText()
|
|
129
133
|
|
|
130
134
|
const titleTextWidth = STEP_CARD_WIDTH - CARD_PADDING_X * 2;
|
|
131
|
-
const titleCharWidth = 6.5; //
|
|
135
|
+
const titleCharWidth = 6.5; // matches renderer TITLE_CHAR_WIDTH (FONT_SIZE_STEP 12px)
|
|
132
136
|
const TITLE_LINE_HEIGHT = 16;
|
|
133
137
|
|
|
134
138
|
const stepHeights = allSteps.map((step) => {
|
|
@@ -408,7 +408,11 @@ export function parseJourneyMap(
|
|
|
408
408
|
return fail(1, 'No phases or steps found');
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
validateTagGroupNames(result.tagGroups, warn)
|
|
411
|
+
validateTagGroupNames(result.tagGroups, warn, (line, msg) => {
|
|
412
|
+
const diag = makeDgmoError(line, msg);
|
|
413
|
+
result.diagnostics.push(diag);
|
|
414
|
+
if (!result.error) result.error = formatDgmoError(diag);
|
|
415
|
+
});
|
|
412
416
|
|
|
413
417
|
return result;
|
|
414
418
|
}
|
package/src/kanban/parser.ts
CHANGED
|
@@ -373,7 +373,11 @@ export function parseKanban(
|
|
|
373
373
|
return fail(1, 'No columns found. Use [Column Name] to define columns');
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
validateTagGroupNames(result.tagGroups, warn)
|
|
376
|
+
validateTagGroupNames(result.tagGroups, warn, (line, msg) => {
|
|
377
|
+
const diag = makeDgmoError(line, msg);
|
|
378
|
+
result.diagnostics.push(diag);
|
|
379
|
+
if (!result.error) result.error = formatDgmoError(diag);
|
|
380
|
+
});
|
|
377
381
|
|
|
378
382
|
return result;
|
|
379
383
|
}
|
package/src/org/collapse.ts
CHANGED
|
@@ -72,10 +72,7 @@ function computeHiddenCounts(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** Remove children of collapsed nodes on the cloned tree. */
|
|
75
|
-
function pruneCollapsed(
|
|
76
|
-
node: OrgNode,
|
|
77
|
-
collapsedIds: Set<string>
|
|
78
|
-
): void {
|
|
75
|
+
function pruneCollapsed(node: OrgNode, collapsedIds: Set<string>): void {
|
|
79
76
|
for (const child of node.children) {
|
|
80
77
|
pruneCollapsed(child, collapsedIds);
|
|
81
78
|
}
|
package/src/org/parser.ts
CHANGED
|
@@ -344,7 +344,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
|
344
344
|
collectAll(result.roots);
|
|
345
345
|
|
|
346
346
|
validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
|
|
347
|
-
validateTagGroupNames(result.tagGroups, pushWarning);
|
|
347
|
+
validateTagGroupNames(result.tagGroups, pushWarning, pushError);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
if (
|
package/src/org/renderer.ts
CHANGED
|
@@ -138,14 +138,16 @@ export function renderOrg(
|
|
|
138
138
|
const titleReserve = fixedTitle ? TITLE_HEIGHT : 0;
|
|
139
139
|
|
|
140
140
|
// Ancestor breadcrumb trail (focus mode) — rendered inside the scaled group
|
|
141
|
-
const hasAncestorTrail =
|
|
141
|
+
const hasAncestorTrail =
|
|
142
|
+
!exportDims && ancestorPath && ancestorPath.length > 0;
|
|
142
143
|
const ancestorTrailHeight = hasAncestorTrail
|
|
143
144
|
? ancestorPath.length * ANCESTOR_ROW_HEIGHT + ANCESTOR_TRAIL_BOTTOM_GAP
|
|
144
145
|
: 0;
|
|
145
146
|
|
|
146
147
|
// Compute scale to fit diagram in viewport
|
|
147
148
|
const diagramW = layout.width;
|
|
148
|
-
let diagramH =
|
|
149
|
+
let diagramH =
|
|
150
|
+
layout.height + (fixedTitle ? 0 : titleOffset) + ancestorTrailHeight;
|
|
149
151
|
if (fixedLegend) {
|
|
150
152
|
// Remove the legend space from diagram height — legend is rendered separately
|
|
151
153
|
diagramH -= layoutLegendShift;
|
|
@@ -594,15 +596,17 @@ export function renderOrg(
|
|
|
594
596
|
: rootContainer
|
|
595
597
|
? rootContainer.x + rootContainer.width / 2
|
|
596
598
|
: null;
|
|
597
|
-
const rootTopY = rootNode
|
|
599
|
+
const rootTopY = rootNode
|
|
600
|
+
? rootNode.y
|
|
601
|
+
: rootContainer
|
|
602
|
+
? rootContainer.y
|
|
603
|
+
: null;
|
|
598
604
|
if (rootCenterX !== null && rootTopY !== null) {
|
|
599
605
|
// Trail connects directly to the top edge of the root node.
|
|
600
606
|
// The last ancestor dot sits ANCESTOR_TRAIL_BOTTOM_GAP above the root.
|
|
601
607
|
const trailBottomY = rootTopY - ANCESTOR_TRAIL_BOTTOM_GAP;
|
|
602
608
|
|
|
603
|
-
const trailG = contentG
|
|
604
|
-
.append('g')
|
|
605
|
-
.attr('class', 'org-ancestor-trail');
|
|
609
|
+
const trailG = contentG.append('g').attr('class', 'org-ancestor-trail');
|
|
606
610
|
|
|
607
611
|
const count = ancestorPath!.length;
|
|
608
612
|
|
|
@@ -631,13 +635,14 @@ export function renderOrg(
|
|
|
631
635
|
const dotY = dotPositions[i];
|
|
632
636
|
|
|
633
637
|
// Resolve color from tag groups (same logic as node cards)
|
|
634
|
-
const resolvedColor =
|
|
635
|
-
??
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
638
|
+
const resolvedColor =
|
|
639
|
+
ancestor.color ??
|
|
640
|
+
resolveTagColor(
|
|
641
|
+
ancestor.metadata,
|
|
642
|
+
parsed.tagGroups,
|
|
643
|
+
activeTagGroup ?? null,
|
|
644
|
+
ancestor.isContainer
|
|
645
|
+
);
|
|
641
646
|
const dotColor = resolvedColor ?? palette.textMuted;
|
|
642
647
|
|
|
643
648
|
const rowG = trailG
|
|
@@ -676,14 +681,18 @@ export function renderOrg(
|
|
|
676
681
|
// Hover effect
|
|
677
682
|
rowG
|
|
678
683
|
.on('mouseenter', function () {
|
|
679
|
-
d3Selection
|
|
684
|
+
d3Selection
|
|
685
|
+
.select(this)
|
|
686
|
+
.select('circle')
|
|
680
687
|
.attr('r', ANCESTOR_DOT_R + 1);
|
|
681
688
|
d3Selection.select(this).select('text').attr('fill', palette.text);
|
|
682
689
|
})
|
|
683
690
|
.on('mouseleave', function () {
|
|
684
|
-
d3Selection.select(this).select('circle')
|
|
685
|
-
|
|
686
|
-
|
|
691
|
+
d3Selection.select(this).select('circle').attr('r', ANCESTOR_DOT_R);
|
|
692
|
+
d3Selection
|
|
693
|
+
.select(this)
|
|
694
|
+
.select('text')
|
|
695
|
+
.attr('fill', palette.textMuted);
|
|
687
696
|
});
|
|
688
697
|
}
|
|
689
698
|
}
|
package/src/sequence/parser.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
const KNOWN_SEQ_OPTIONS = new Set(['active-tag']);
|
|
27
27
|
|
|
28
28
|
/** Known sequence-diagram boolean options (bare keyword or `no-` prefix). */
|
|
29
|
-
const KNOWN_SEQ_BOOLEANS = new Set(['activations'
|
|
29
|
+
const KNOWN_SEQ_BOOLEANS = new Set(['activations']);
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Participant types that can be declared via "Name is a type" syntax.
|
|
@@ -1345,7 +1345,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
1345
1345
|
entities.push({ metadata: g.metadata, lineNumber: g.lineNumber });
|
|
1346
1346
|
}
|
|
1347
1347
|
validateTagValues(entities, result.tagGroups, pushWarning, suggest);
|
|
1348
|
-
validateTagGroupNames(result.tagGroups, pushWarning);
|
|
1348
|
+
validateTagGroupNames(result.tagGroups, pushWarning, pushError);
|
|
1349
1349
|
}
|
|
1350
1350
|
|
|
1351
1351
|
return result;
|
|
@@ -185,7 +185,6 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
185
185
|
{ pattern: /^Admin$/i, type: 'actor' },
|
|
186
186
|
{ pattern: /^User$/i, type: 'actor' },
|
|
187
187
|
{ pattern: /^Customer$/i, type: 'actor' },
|
|
188
|
-
{ pattern: /^Client$/i, type: 'actor' },
|
|
189
188
|
{ pattern: /^Agent$/i, type: 'actor' },
|
|
190
189
|
{ pattern: /^Person$/i, type: 'actor' },
|
|
191
190
|
{ pattern: /^Buyer$/i, type: 'actor' },
|
package/src/sequence/renderer.ts
CHANGED
|
@@ -62,8 +62,6 @@ const NOTE_CHAR_W = 6;
|
|
|
62
62
|
const NOTE_CHARS_PER_LINE = Math.floor(
|
|
63
63
|
(NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W
|
|
64
64
|
);
|
|
65
|
-
const COLLAPSED_NOTE_H = 20;
|
|
66
|
-
const COLLAPSED_NOTE_W = 40;
|
|
67
65
|
const ACTIVATION_WIDTH = 10;
|
|
68
66
|
const SELF_CALL_HEIGHT = 25;
|
|
69
67
|
const SELF_CALL_WIDTH = 30;
|
|
@@ -557,13 +555,8 @@ export interface SectionMessageGroup {
|
|
|
557
555
|
export interface SequenceRenderOptions {
|
|
558
556
|
collapsedSections?: Set<number>; // keyed by section lineNumber
|
|
559
557
|
collapsedGroups?: Set<number>; // keyed by group lineNumber
|
|
560
|
-
expandedNoteLines?: Set<number>; // keyed by note lineNumber; undefined = all expanded (CLI default)
|
|
561
558
|
exportWidth?: number; // Explicit width for CLI/export rendering (bypasses getBoundingClientRect)
|
|
562
559
|
activeTagGroup?: string | null; // Active tag group name for tag-driven recoloring; null = explicitly none
|
|
563
|
-
expandAllNotes?: boolean; // Whether the "Expand Notes" toggle is active
|
|
564
|
-
onExpandAllNotes?: (expand: boolean) => void; // Toggle all notes expanded/collapsed
|
|
565
|
-
controlsExpanded?: boolean; // Controls group expanded state (managed by React)
|
|
566
|
-
onToggleControlsExpand?: () => void; // Callback to toggle controls group
|
|
567
560
|
}
|
|
568
561
|
|
|
569
562
|
/**
|
|
@@ -960,15 +953,6 @@ export function renderSequenceDiagram(
|
|
|
960
953
|
const collapsedGroupIds = collapsed?.collapsedGroupIds ?? new Map();
|
|
961
954
|
|
|
962
955
|
const collapsedSections = options?.collapsedSections;
|
|
963
|
-
const expandedNoteLines = options?.expandedNoteLines;
|
|
964
|
-
const collapseNotesDisabled =
|
|
965
|
-
parsedOptions['collapse-notes']?.toLowerCase() === 'no';
|
|
966
|
-
// A note is expanded if: expandedNoteLines is undefined (CLI/export),
|
|
967
|
-
// collapse-notes: no is set, or the note's lineNumber is in the set.
|
|
968
|
-
const isNoteExpanded = (note: SequenceNote): boolean =>
|
|
969
|
-
expandedNoteLines === undefined ||
|
|
970
|
-
collapseNotesDisabled ||
|
|
971
|
-
expandedNoteLines.has(note.lineNumber);
|
|
972
956
|
|
|
973
957
|
const sourceParticipants = collapsed
|
|
974
958
|
? collapsed.participants
|
|
@@ -1232,9 +1216,7 @@ export function renderSequenceDiagram(
|
|
|
1232
1216
|
const note = els[j] as SequenceNote;
|
|
1233
1217
|
const sc = isNoteAfterSelfCall(note);
|
|
1234
1218
|
const maxW = noteEffectiveMaxW(note.participantId, note.position, sc);
|
|
1235
|
-
const noteH =
|
|
1236
|
-
? computeNoteHeight(note.text, charsForWidth(maxW))
|
|
1237
|
-
: COLLAPSED_NOTE_H;
|
|
1219
|
+
const noteH = computeNoteHeight(note.text, charsForWidth(maxW));
|
|
1238
1220
|
totalExtent += noteH + NOTE_OFFSET_BELOW;
|
|
1239
1221
|
j++;
|
|
1240
1222
|
}
|
|
@@ -1500,9 +1482,10 @@ export function renderSequenceDiagram(
|
|
|
1500
1482
|
prevNote.position,
|
|
1501
1483
|
isNoteAfterSelfCall(prevNote)
|
|
1502
1484
|
);
|
|
1503
|
-
const prevNoteH =
|
|
1504
|
-
|
|
1505
|
-
|
|
1485
|
+
const prevNoteH = computeNoteHeight(
|
|
1486
|
+
prevNote.text,
|
|
1487
|
+
charsForWidth(prevMaxW)
|
|
1488
|
+
);
|
|
1506
1489
|
noteTopY = prevNoteY + prevNoteH + NOTE_OFFSET_BELOW;
|
|
1507
1490
|
} else {
|
|
1508
1491
|
// First note after a message — use larger offset after self-calls
|
|
@@ -1539,9 +1522,7 @@ export function renderSequenceDiagram(
|
|
|
1539
1522
|
note.position,
|
|
1540
1523
|
isNoteAfterSelfCall(note)
|
|
1541
1524
|
);
|
|
1542
|
-
const noteH =
|
|
1543
|
-
? computeNoteHeight(note.text, charsForWidth(maxW))
|
|
1544
|
-
: COLLAPSED_NOTE_H;
|
|
1525
|
+
const noteH = computeNoteHeight(note.text, charsForWidth(maxW));
|
|
1545
1526
|
contentBottomY = Math.max(
|
|
1546
1527
|
contentBottomY,
|
|
1547
1528
|
noteTopY + noteH + NOTE_TRAILING_GAP
|
|
@@ -1736,32 +1717,6 @@ export function renderSequenceDiagram(
|
|
|
1736
1717
|
}
|
|
1737
1718
|
}
|
|
1738
1719
|
|
|
1739
|
-
// Collect all note line numbers (for controls group visibility + "all expanded" check)
|
|
1740
|
-
const allNoteLineNumbers: number[] = [];
|
|
1741
|
-
const collectNoteLines = (els: SequenceElement[]): void => {
|
|
1742
|
-
for (const el of els) {
|
|
1743
|
-
if (isSequenceNote(el)) {
|
|
1744
|
-
allNoteLineNumbers.push(el.lineNumber);
|
|
1745
|
-
} else if (isSequenceBlock(el)) {
|
|
1746
|
-
collectNoteLines(el.children);
|
|
1747
|
-
if ('elseChildren' in el) collectNoteLines(el.elseChildren);
|
|
1748
|
-
if ('branches' in el && Array.isArray(el.branches)) {
|
|
1749
|
-
for (const branch of el.branches) {
|
|
1750
|
-
collectNoteLines(branch.children);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
};
|
|
1756
|
-
collectNoteLines(elements);
|
|
1757
|
-
|
|
1758
|
-
// Show controls group only in interactive mode (expandedNoteLines defined)
|
|
1759
|
-
// when notes exist and collapse-notes is not disabled
|
|
1760
|
-
const showNotesControl =
|
|
1761
|
-
allNoteLineNumbers.length > 0 &&
|
|
1762
|
-
!collapseNotesDisabled &&
|
|
1763
|
-
expandedNoteLines !== undefined;
|
|
1764
|
-
|
|
1765
1720
|
const hasTagGroups = parsed.tagGroups.length > 0;
|
|
1766
1721
|
|
|
1767
1722
|
// Build set of collapsed group names for drill-bar rendering
|
|
@@ -2632,8 +2587,6 @@ export function renderSequenceDiagram(
|
|
|
2632
2587
|
? mix(palette.surface, palette.bg, 50)
|
|
2633
2588
|
: mix(palette.bg, palette.surface, 15);
|
|
2634
2589
|
|
|
2635
|
-
const collapsedNoteFill = mix(palette.textMuted, palette.bg, 15);
|
|
2636
|
-
|
|
2637
2590
|
const renderNoteElements = (els: SequenceElement[]): void => {
|
|
2638
2591
|
for (const el of els) {
|
|
2639
2592
|
if (isSequenceNote(el)) {
|
|
@@ -2642,168 +2595,98 @@ export function renderSequenceDiagram(
|
|
|
2642
2595
|
const noteTopY = noteYMap.get(el);
|
|
2643
2596
|
if (noteTopY === undefined) continue;
|
|
2644
2597
|
|
|
2645
|
-
const expanded = isNoteExpanded(el);
|
|
2646
2598
|
const isRight = el.position === 'right';
|
|
2599
|
+
const afterSelfCall = isNoteAfterSelfCall(el);
|
|
2600
|
+
const maxW = noteEffectiveMaxW(
|
|
2601
|
+
el.participantId,
|
|
2602
|
+
el.position,
|
|
2603
|
+
afterSelfCall
|
|
2604
|
+
);
|
|
2605
|
+
const maxChars = charsForWidth(maxW);
|
|
2606
|
+
const wrappedLines = wrapTextLines(el.text, maxChars);
|
|
2607
|
+
const noteH = wrappedLines.length * NOTE_LINE_H + NOTE_PAD_V * 2;
|
|
2608
|
+
const maxLineLen = Math.max(...wrappedLines.map((l) => l.length));
|
|
2609
|
+
const noteW = Math.min(
|
|
2610
|
+
maxW,
|
|
2611
|
+
Math.max(80, maxLineLen * NOTE_CHAR_W + NOTE_PAD_H * 2 + NOTE_FOLD)
|
|
2612
|
+
);
|
|
2613
|
+
// Shift notes past self-call loopback when applicable
|
|
2614
|
+
const rightOffset =
|
|
2615
|
+
afterSelfCall && isRight
|
|
2616
|
+
? ACTIVATION_WIDTH / 2 + SELF_CALL_WIDTH + NOTE_GAP
|
|
2617
|
+
: ACTIVATION_WIDTH + NOTE_GAP;
|
|
2618
|
+
const noteX = isRight
|
|
2619
|
+
? px + rightOffset
|
|
2620
|
+
: px - ACTIVATION_WIDTH - NOTE_GAP - noteW;
|
|
2621
|
+
|
|
2622
|
+
const noteG = svg
|
|
2623
|
+
.append('g')
|
|
2624
|
+
.attr('class', 'note')
|
|
2625
|
+
.attr('data-note-toggle', '')
|
|
2626
|
+
.attr('data-line-number', String(el.lineNumber))
|
|
2627
|
+
.attr('data-line-end', String(el.endLineNumber));
|
|
2628
|
+
|
|
2629
|
+
// Folded-corner path
|
|
2630
|
+
noteG
|
|
2631
|
+
.append('path')
|
|
2632
|
+
.attr(
|
|
2633
|
+
'd',
|
|
2634
|
+
[
|
|
2635
|
+
`M ${noteX} ${noteTopY}`,
|
|
2636
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2637
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2638
|
+
`L ${noteX + noteW} ${noteTopY + noteH}`,
|
|
2639
|
+
`L ${noteX} ${noteTopY + noteH}`,
|
|
2640
|
+
'Z',
|
|
2641
|
+
].join(' ')
|
|
2642
|
+
)
|
|
2643
|
+
.attr('fill', noteFill)
|
|
2644
|
+
.attr('stroke', palette.textMuted)
|
|
2645
|
+
.attr('stroke-width', 0.75)
|
|
2646
|
+
.attr('class', 'note-box');
|
|
2647
2647
|
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
.
|
|
2675
|
-
.attr('
|
|
2676
|
-
.attr('
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
// Folded-corner path
|
|
2681
|
-
noteG
|
|
2682
|
-
.append('path')
|
|
2683
|
-
.attr(
|
|
2684
|
-
'd',
|
|
2685
|
-
[
|
|
2686
|
-
`M ${noteX} ${noteTopY}`,
|
|
2687
|
-
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2688
|
-
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2689
|
-
`L ${noteX + noteW} ${noteTopY + noteH}`,
|
|
2690
|
-
`L ${noteX} ${noteTopY + noteH}`,
|
|
2691
|
-
'Z',
|
|
2692
|
-
].join(' ')
|
|
2693
|
-
)
|
|
2694
|
-
.attr('fill', noteFill)
|
|
2695
|
-
.attr('stroke', palette.textMuted)
|
|
2696
|
-
.attr('stroke-width', 0.75)
|
|
2697
|
-
.attr('class', 'note-box');
|
|
2698
|
-
|
|
2699
|
-
// Fold triangle
|
|
2700
|
-
noteG
|
|
2701
|
-
.append('path')
|
|
2702
|
-
.attr(
|
|
2703
|
-
'd',
|
|
2704
|
-
[
|
|
2705
|
-
`M ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2706
|
-
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY + NOTE_FOLD}`,
|
|
2707
|
-
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2708
|
-
].join(' ')
|
|
2709
|
-
)
|
|
2710
|
-
.attr('fill', 'none')
|
|
2711
|
-
.attr('stroke', palette.textMuted)
|
|
2712
|
-
.attr('stroke-width', 0.75)
|
|
2713
|
-
.attr('class', 'note-fold');
|
|
2714
|
-
|
|
2715
|
-
// Render text with inline markdown
|
|
2716
|
-
wrappedLines.forEach((line, li) => {
|
|
2717
|
-
const textY = noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
2718
|
-
const isBullet = line.startsWith('- ');
|
|
2719
|
-
const bulletIndent = isBullet ? 10 : 0;
|
|
2720
|
-
const displayLine = isBullet ? line.slice(2) : line;
|
|
2721
|
-
const textEl = noteG
|
|
2648
|
+
// Fold triangle
|
|
2649
|
+
noteG
|
|
2650
|
+
.append('path')
|
|
2651
|
+
.attr(
|
|
2652
|
+
'd',
|
|
2653
|
+
[
|
|
2654
|
+
`M ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2655
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY + NOTE_FOLD}`,
|
|
2656
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2657
|
+
].join(' ')
|
|
2658
|
+
)
|
|
2659
|
+
.attr('fill', 'none')
|
|
2660
|
+
.attr('stroke', palette.textMuted)
|
|
2661
|
+
.attr('stroke-width', 0.75)
|
|
2662
|
+
.attr('class', 'note-fold');
|
|
2663
|
+
|
|
2664
|
+
// Render text with inline markdown
|
|
2665
|
+
wrappedLines.forEach((line, li) => {
|
|
2666
|
+
const textY = noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
2667
|
+
const isBullet = line.startsWith('- ');
|
|
2668
|
+
const bulletIndent = isBullet ? 10 : 0;
|
|
2669
|
+
const displayLine = isBullet ? line.slice(2) : line;
|
|
2670
|
+
const textEl = noteG
|
|
2671
|
+
.append('text')
|
|
2672
|
+
.attr('x', noteX + NOTE_PAD_H + bulletIndent)
|
|
2673
|
+
.attr('y', textY)
|
|
2674
|
+
.attr('fill', palette.text)
|
|
2675
|
+
.attr('font-size', NOTE_FONT_SIZE)
|
|
2676
|
+
.attr('class', 'note-text');
|
|
2677
|
+
|
|
2678
|
+
if (isBullet) {
|
|
2679
|
+
noteG
|
|
2722
2680
|
.append('text')
|
|
2723
|
-
.attr('x', noteX + NOTE_PAD_H
|
|
2681
|
+
.attr('x', noteX + NOTE_PAD_H)
|
|
2724
2682
|
.attr('y', textY)
|
|
2725
2683
|
.attr('fill', palette.text)
|
|
2726
2684
|
.attr('font-size', NOTE_FONT_SIZE)
|
|
2727
|
-
.
|
|
2728
|
-
|
|
2729
|
-
if (isBullet) {
|
|
2730
|
-
noteG
|
|
2731
|
-
.append('text')
|
|
2732
|
-
.attr('x', noteX + NOTE_PAD_H)
|
|
2733
|
-
.attr('y', textY)
|
|
2734
|
-
.attr('fill', palette.text)
|
|
2735
|
-
.attr('font-size', NOTE_FONT_SIZE)
|
|
2736
|
-
.text('\u2022');
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
renderInlineText(textEl, displayLine, palette, NOTE_FONT_SIZE);
|
|
2740
|
-
});
|
|
2741
|
-
} else {
|
|
2742
|
-
// --- Collapsed note: compact indicator ---
|
|
2743
|
-
const cFold = 6;
|
|
2744
|
-
const afterSelfCallC = isNoteAfterSelfCall(el);
|
|
2745
|
-
const rightOffsetC =
|
|
2746
|
-
afterSelfCallC && isRight
|
|
2747
|
-
? ACTIVATION_WIDTH / 2 + SELF_CALL_WIDTH + NOTE_GAP
|
|
2748
|
-
: ACTIVATION_WIDTH + NOTE_GAP;
|
|
2749
|
-
const noteX = isRight
|
|
2750
|
-
? px + rightOffsetC
|
|
2751
|
-
: px - ACTIVATION_WIDTH - NOTE_GAP - COLLAPSED_NOTE_W;
|
|
2752
|
-
|
|
2753
|
-
const noteG = svg
|
|
2754
|
-
.append('g')
|
|
2755
|
-
.attr('class', 'note note-collapsed')
|
|
2756
|
-
.attr('data-note-toggle', '')
|
|
2757
|
-
.attr('data-line-number', String(el.lineNumber))
|
|
2758
|
-
.attr('data-line-end', String(el.endLineNumber))
|
|
2759
|
-
.style('cursor', 'pointer');
|
|
2760
|
-
|
|
2761
|
-
// Small folded-corner rectangle
|
|
2762
|
-
noteG
|
|
2763
|
-
.append('path')
|
|
2764
|
-
.attr(
|
|
2765
|
-
'd',
|
|
2766
|
-
[
|
|
2767
|
-
`M ${noteX} ${noteTopY}`,
|
|
2768
|
-
`L ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY}`,
|
|
2769
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + cFold}`,
|
|
2770
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + COLLAPSED_NOTE_H}`,
|
|
2771
|
-
`L ${noteX} ${noteTopY + COLLAPSED_NOTE_H}`,
|
|
2772
|
-
'Z',
|
|
2773
|
-
].join(' ')
|
|
2774
|
-
)
|
|
2775
|
-
.attr('fill', collapsedNoteFill)
|
|
2776
|
-
.attr('stroke', palette.border)
|
|
2777
|
-
.attr('stroke-width', 0.75)
|
|
2778
|
-
.attr('class', 'note-box');
|
|
2779
|
-
|
|
2780
|
-
// Fold triangle
|
|
2781
|
-
noteG
|
|
2782
|
-
.append('path')
|
|
2783
|
-
.attr(
|
|
2784
|
-
'd',
|
|
2785
|
-
[
|
|
2786
|
-
`M ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY}`,
|
|
2787
|
-
`L ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY + cFold}`,
|
|
2788
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + cFold}`,
|
|
2789
|
-
].join(' ')
|
|
2790
|
-
)
|
|
2791
|
-
.attr('fill', 'none')
|
|
2792
|
-
.attr('stroke', palette.border)
|
|
2793
|
-
.attr('stroke-width', 0.75)
|
|
2794
|
-
.attr('class', 'note-fold');
|
|
2685
|
+
.text('\u2022');
|
|
2686
|
+
}
|
|
2795
2687
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
.append('text')
|
|
2799
|
-
.attr('x', noteX + COLLAPSED_NOTE_W / 2)
|
|
2800
|
-
.attr('y', noteTopY + COLLAPSED_NOTE_H / 2 + 3)
|
|
2801
|
-
.attr('text-anchor', 'middle')
|
|
2802
|
-
.attr('fill', palette.textMuted)
|
|
2803
|
-
.attr('font-size', 9)
|
|
2804
|
-
.attr('class', 'note-text')
|
|
2805
|
-
.text('\u2026');
|
|
2806
|
-
}
|
|
2688
|
+
renderInlineText(textEl, displayLine, palette, NOTE_FONT_SIZE);
|
|
2689
|
+
});
|
|
2807
2690
|
} else if (isSequenceBlock(el)) {
|
|
2808
2691
|
renderNoteElements(el.children);
|
|
2809
2692
|
if (el.elseIfBranches) {
|
|
@@ -2822,9 +2705,7 @@ export function renderSequenceDiagram(
|
|
|
2822
2705
|
|
|
2823
2706
|
// Render legend LAST so it sits on top of all other SVG elements
|
|
2824
2707
|
// (group boxes, lifelines, participants, etc.) and can receive clicks.
|
|
2825
|
-
if (hasTagGroups
|
|
2826
|
-
const controlsExpanded = options?.controlsExpanded ?? false;
|
|
2827
|
-
|
|
2708
|
+
if (hasTagGroups) {
|
|
2828
2709
|
const legendY = TOP_MARGIN + titleOffset;
|
|
2829
2710
|
const resolvedGroups = parsed.tagGroups
|
|
2830
2711
|
.filter((tg) => tg.entries.length > 0)
|
|
@@ -2836,41 +2717,17 @@ export function renderSequenceDiagram(
|
|
|
2836
2717
|
})),
|
|
2837
2718
|
}));
|
|
2838
2719
|
|
|
2839
|
-
const allExpanded = showNotesControl && (options?.expandAllNotes ?? false);
|
|
2840
|
-
|
|
2841
|
-
const controlsGroup = showNotesControl
|
|
2842
|
-
? {
|
|
2843
|
-
toggles: [
|
|
2844
|
-
{
|
|
2845
|
-
id: 'expand-all-notes',
|
|
2846
|
-
type: 'toggle' as const,
|
|
2847
|
-
label: 'Expand Notes',
|
|
2848
|
-
active: allExpanded,
|
|
2849
|
-
onToggle: () => {},
|
|
2850
|
-
},
|
|
2851
|
-
],
|
|
2852
|
-
}
|
|
2853
|
-
: undefined;
|
|
2854
|
-
|
|
2855
2720
|
const legendConfig: LegendConfig = {
|
|
2856
2721
|
groups: resolvedGroups,
|
|
2857
2722
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
2858
2723
|
mode: 'fixed',
|
|
2859
|
-
controlsGroup,
|
|
2860
2724
|
};
|
|
2861
2725
|
const legendState: LegendState = {
|
|
2862
2726
|
activeGroup: activeTagGroup ?? null,
|
|
2863
|
-
controlsExpanded,
|
|
2727
|
+
controlsExpanded: false,
|
|
2864
2728
|
};
|
|
2865
2729
|
|
|
2866
|
-
const legendCallbacks: LegendCallbacks = {
|
|
2867
|
-
onControlsExpand: () => {
|
|
2868
|
-
options?.onToggleControlsExpand?.();
|
|
2869
|
-
},
|
|
2870
|
-
onControlsToggle: (_toggleId: string, active: boolean) => {
|
|
2871
|
-
options?.onExpandAllNotes?.(active);
|
|
2872
|
-
},
|
|
2873
|
-
};
|
|
2730
|
+
const legendCallbacks: LegendCallbacks = {};
|
|
2874
2731
|
|
|
2875
2732
|
const legendG = svg
|
|
2876
2733
|
.append('g')
|
|
@@ -2891,7 +2748,7 @@ export function renderSequenceDiagram(
|
|
|
2891
2748
|
/**
|
|
2892
2749
|
* Build a mapping from each note's lineNumber to the lineNumber of its
|
|
2893
2750
|
* associated message (the last message before the note in document order).
|
|
2894
|
-
* Used by the app to
|
|
2751
|
+
* Used by the app to highlight the associated message when cursor is on a note.
|
|
2895
2752
|
*/
|
|
2896
2753
|
export function buildNoteMessageMap(
|
|
2897
2754
|
elements: SequenceElement[]
|
|
@@ -2924,31 +2781,6 @@ export function buildNoteMessageMap(
|
|
|
2924
2781
|
return map;
|
|
2925
2782
|
}
|
|
2926
2783
|
|
|
2927
|
-
/**
|
|
2928
|
-
* Collect all note line numbers from a sequence diagram's elements.
|
|
2929
|
-
* Used by the app to compute the "expand all" set.
|
|
2930
|
-
*/
|
|
2931
|
-
export function collectNoteLineNumbers(elements: SequenceElement[]): number[] {
|
|
2932
|
-
const result: number[] = [];
|
|
2933
|
-
const walk = (els: SequenceElement[]): void => {
|
|
2934
|
-
for (const el of els) {
|
|
2935
|
-
if (isSequenceNote(el)) {
|
|
2936
|
-
result.push(el.lineNumber);
|
|
2937
|
-
} else if (isSequenceBlock(el)) {
|
|
2938
|
-
walk(el.children);
|
|
2939
|
-
if (el.elseIfBranches) {
|
|
2940
|
-
for (const branch of el.elseIfBranches) {
|
|
2941
|
-
walk(branch.children);
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
walk(el.elseChildren);
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
};
|
|
2948
|
-
walk(elements);
|
|
2949
|
-
return result;
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
2784
|
function renderParticipant(
|
|
2953
2785
|
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>,
|
|
2954
2786
|
participant: SequenceParticipant,
|
package/src/sharing.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface CompactViewState {
|
|
|
21
21
|
rm?: string; // render mode override
|
|
22
22
|
htv?: Record<string, string[]>; // hidden tag values
|
|
23
23
|
ha?: string[]; // hidden attributes
|
|
24
|
-
enl?: number[]; // expanded note lines (sequence)
|
|
25
24
|
sem?: boolean; // semantic colors (ER)
|
|
26
25
|
cm?: boolean; // compact meta (kanban)
|
|
27
26
|
c4l?: string; // C4 level
|
package/src/sitemap/parser.ts
CHANGED
|
@@ -509,7 +509,7 @@ export function parseSitemap(
|
|
|
509
509
|
};
|
|
510
510
|
collectAll(result.roots);
|
|
511
511
|
validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
|
|
512
|
-
validateTagGroupNames(result.tagGroups, pushWarning);
|
|
512
|
+
validateTagGroupNames(result.tagGroups, pushWarning, pushError);
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
if (
|