@diagrammo/dgmo 0.2.6 → 0.2.8
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 +213 -57
- package/dist/cli.cjs +91 -85
- package/dist/index.cjs +1362 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -2
- package/dist/index.d.ts +108 -2
- package/dist/index.js +1357 -193
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/d3.ts +59 -1
- package/src/dgmo-router.ts +5 -1
- package/src/echarts.ts +2 -2
- package/src/graph/flowchart-parser.ts +499 -0
- package/src/graph/flowchart-renderer.ts +503 -0
- package/src/graph/layout.ts +222 -0
- package/src/graph/types.ts +44 -0
- package/src/index.ts +24 -0
- package/src/sequence/parser.ts +221 -37
- package/src/sequence/renderer.ts +342 -16
package/src/sequence/renderer.ts
CHANGED
|
@@ -11,9 +11,10 @@ import type {
|
|
|
11
11
|
SequenceElement,
|
|
12
12
|
SequenceGroup,
|
|
13
13
|
SequenceMessage,
|
|
14
|
+
SequenceNote,
|
|
14
15
|
SequenceParticipant,
|
|
15
16
|
} from './parser';
|
|
16
|
-
import { isSequenceBlock, isSequenceSection } from './parser';
|
|
17
|
+
import { isSequenceBlock, isSequenceSection, isSequenceNote } from './parser';
|
|
17
18
|
|
|
18
19
|
// ============================================================
|
|
19
20
|
// Layout Constants
|
|
@@ -30,6 +31,64 @@ const MESSAGE_START_OFFSET = 30;
|
|
|
30
31
|
const LIFELINE_TAIL = 30;
|
|
31
32
|
const ARROWHEAD_SIZE = 8;
|
|
32
33
|
|
|
34
|
+
// Note rendering constants
|
|
35
|
+
const NOTE_MAX_W = 200;
|
|
36
|
+
const NOTE_FOLD = 10;
|
|
37
|
+
const NOTE_PAD_H = 8;
|
|
38
|
+
const NOTE_PAD_V = 6;
|
|
39
|
+
const NOTE_FONT_SIZE = 10;
|
|
40
|
+
const NOTE_LINE_H = 14;
|
|
41
|
+
const NOTE_GAP = 15;
|
|
42
|
+
const NOTE_CHAR_W = 6;
|
|
43
|
+
const NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
|
|
44
|
+
|
|
45
|
+
interface InlineSpan {
|
|
46
|
+
text: string;
|
|
47
|
+
bold?: boolean;
|
|
48
|
+
italic?: boolean;
|
|
49
|
+
code?: boolean;
|
|
50
|
+
href?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseInlineMarkdown(text: string): InlineSpan[] {
|
|
54
|
+
const spans: InlineSpan[] = [];
|
|
55
|
+
const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|([^*_`[]+)/g;
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = regex.exec(text)) !== null) {
|
|
58
|
+
if (match[1]) spans.push({ text: match[1], bold: true }); // **bold**
|
|
59
|
+
else if (match[2]) spans.push({ text: match[2], bold: true }); // __bold__
|
|
60
|
+
else if (match[3]) spans.push({ text: match[3], italic: true }); // *italic*
|
|
61
|
+
else if (match[4]) spans.push({ text: match[4], italic: true }); // _italic_
|
|
62
|
+
else if (match[5]) spans.push({ text: match[5], code: true }); // `code`
|
|
63
|
+
else if (match[6]) spans.push({ text: match[6], href: match[7] }); // [text](url)
|
|
64
|
+
else if (match[8]) spans.push({ text: match[8] });
|
|
65
|
+
}
|
|
66
|
+
return spans;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function wrapTextLines(text: string, maxChars: number): string[] {
|
|
70
|
+
const rawLines = text.split('\n');
|
|
71
|
+
const wrapped: string[] = [];
|
|
72
|
+
for (const line of rawLines) {
|
|
73
|
+
if (line.length <= maxChars) {
|
|
74
|
+
wrapped.push(line);
|
|
75
|
+
} else {
|
|
76
|
+
const words = line.split(' ');
|
|
77
|
+
let current = '';
|
|
78
|
+
for (const word of words) {
|
|
79
|
+
if (current && (current + ' ' + word).length > maxChars) {
|
|
80
|
+
wrapped.push(current);
|
|
81
|
+
current = word;
|
|
82
|
+
} else {
|
|
83
|
+
current = current ? current + ' ' + word : word;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (current) wrapped.push(current);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return wrapped;
|
|
90
|
+
}
|
|
91
|
+
|
|
33
92
|
// Mix two hex colors in sRGB: pct% of a, rest of b
|
|
34
93
|
function mix(a: string, b: string, pct: number): string {
|
|
35
94
|
const parse = (h: string) => {
|
|
@@ -440,11 +499,16 @@ export function groupMessagesBySection(
|
|
|
440
499
|
...collectIndices(el.children),
|
|
441
500
|
...collectIndices(el.elseChildren)
|
|
442
501
|
);
|
|
443
|
-
|
|
444
|
-
|
|
502
|
+
if (el.elseIfBranches) {
|
|
503
|
+
for (const branch of el.elseIfBranches) {
|
|
504
|
+
indices.push(...collectIndices(branch.children));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} else if (isSequenceSection(el) || isSequenceNote(el)) {
|
|
508
|
+
// Sections and notes inside blocks are not messages — skip
|
|
445
509
|
continue;
|
|
446
510
|
} else {
|
|
447
|
-
const idx = messages.indexOf(el);
|
|
511
|
+
const idx = messages.indexOf(el as SequenceMessage);
|
|
448
512
|
if (idx >= 0) indices.push(idx);
|
|
449
513
|
}
|
|
450
514
|
}
|
|
@@ -460,8 +524,8 @@ export function groupMessagesBySection(
|
|
|
460
524
|
// Collect messages from this element into the current group
|
|
461
525
|
if (isSequenceBlock(el)) {
|
|
462
526
|
currentGroup.messageIndices.push(...collectIndices([el]));
|
|
463
|
-
} else {
|
|
464
|
-
const idx = messages.indexOf(el);
|
|
527
|
+
} else if (!isSequenceNote(el)) {
|
|
528
|
+
const idx = messages.indexOf(el as SequenceMessage);
|
|
465
529
|
if (idx >= 0) currentGroup.messageIndices.push(idx);
|
|
466
530
|
}
|
|
467
531
|
}
|
|
@@ -793,8 +857,16 @@ export function renderSequenceDiagram(
|
|
|
793
857
|
if (isSequenceBlock(el)) {
|
|
794
858
|
const idx = findFirstMsgIndex(el.children);
|
|
795
859
|
if (idx >= 0) return idx;
|
|
796
|
-
|
|
797
|
-
|
|
860
|
+
if (el.elseIfBranches) {
|
|
861
|
+
for (const branch of el.elseIfBranches) {
|
|
862
|
+
const branchIdx = findFirstMsgIndex(branch.children);
|
|
863
|
+
if (branchIdx >= 0) return branchIdx;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const elseIdx = findFirstMsgIndex(el.elseChildren);
|
|
867
|
+
if (elseIdx >= 0) return elseIdx;
|
|
868
|
+
} else if (!isSequenceSection(el) && !isSequenceNote(el)) {
|
|
869
|
+
const idx = messages.indexOf(el as SequenceMessage);
|
|
798
870
|
if (idx >= 0 && !hiddenMsgIndices.has(idx)) return idx;
|
|
799
871
|
}
|
|
800
872
|
}
|
|
@@ -820,6 +892,14 @@ export function renderSequenceDiagram(
|
|
|
820
892
|
const firstIdx = findFirstMsgIndex(el.children);
|
|
821
893
|
if (firstIdx >= 0) addExtra(firstIdx, BLOCK_HEADER_SPACE);
|
|
822
894
|
|
|
895
|
+
if (el.elseIfBranches) {
|
|
896
|
+
for (const branch of el.elseIfBranches) {
|
|
897
|
+
const firstBranchIdx = findFirstMsgIndex(branch.children);
|
|
898
|
+
if (firstBranchIdx >= 0) addExtra(firstBranchIdx, BLOCK_HEADER_SPACE);
|
|
899
|
+
markBlockSpacing(branch.children);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
823
903
|
const firstElseIdx = findFirstMsgIndex(el.elseChildren);
|
|
824
904
|
if (firstElseIdx >= 0) addExtra(firstElseIdx, BLOCK_HEADER_SPACE);
|
|
825
905
|
|
|
@@ -837,6 +917,38 @@ export function renderSequenceDiagram(
|
|
|
837
917
|
markBlockSpacing(elements);
|
|
838
918
|
}
|
|
839
919
|
|
|
920
|
+
// Note spacing — add vertical room after messages that have notes attached
|
|
921
|
+
const NOTE_OFFSET_BELOW = 16; // gap between message arrow and top of note box
|
|
922
|
+
const computeNoteHeight = (text: string): number => {
|
|
923
|
+
const lines = wrapTextLines(text, NOTE_CHARS_PER_LINE);
|
|
924
|
+
return lines.length * NOTE_LINE_H + NOTE_PAD_V * 2;
|
|
925
|
+
};
|
|
926
|
+
const markNoteSpacing = (els: SequenceElement[]): void => {
|
|
927
|
+
for (let i = 0; i < els.length; i++) {
|
|
928
|
+
const el = els[i];
|
|
929
|
+
if (isSequenceNote(el)) {
|
|
930
|
+
const noteH = computeNoteHeight(el.text);
|
|
931
|
+
// Find the next non-note element after this note
|
|
932
|
+
const nextIdx =
|
|
933
|
+
i + 1 < els.length ? findFirstMsgIndex([els[i + 1]]) : -1;
|
|
934
|
+
if (nextIdx >= 0) {
|
|
935
|
+
addExtra(nextIdx, noteH + NOTE_OFFSET_BELOW);
|
|
936
|
+
}
|
|
937
|
+
} else if (isSequenceBlock(el)) {
|
|
938
|
+
markNoteSpacing(el.children);
|
|
939
|
+
if (el.elseIfBranches) {
|
|
940
|
+
for (const branch of el.elseIfBranches) {
|
|
941
|
+
markNoteSpacing(branch.children);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
markNoteSpacing(el.elseChildren);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
if (elements && elements.length > 0) {
|
|
949
|
+
markNoteSpacing(elements);
|
|
950
|
+
}
|
|
951
|
+
|
|
840
952
|
// --- Section-aware Y layout ---
|
|
841
953
|
// Sections get their own Y positions computed from content above them (not anchored
|
|
842
954
|
// to messages below). This ensures toggling collapse/expand doesn't move the divider.
|
|
@@ -856,16 +968,28 @@ export function renderSequenceDiagram(
|
|
|
856
968
|
for (const child of block.children) {
|
|
857
969
|
if (isSequenceBlock(child)) {
|
|
858
970
|
indices.push(...collectMsgIndicesFromBlock(child));
|
|
859
|
-
} else if (!isSequenceSection(child)) {
|
|
860
|
-
const idx = messages.indexOf(child);
|
|
971
|
+
} else if (!isSequenceSection(child) && !isSequenceNote(child)) {
|
|
972
|
+
const idx = messages.indexOf(child as SequenceMessage);
|
|
861
973
|
if (idx >= 0) indices.push(idx);
|
|
862
974
|
}
|
|
863
975
|
}
|
|
976
|
+
if (block.elseIfBranches) {
|
|
977
|
+
for (const branch of block.elseIfBranches) {
|
|
978
|
+
for (const child of branch.children) {
|
|
979
|
+
if (isSequenceBlock(child)) {
|
|
980
|
+
indices.push(...collectMsgIndicesFromBlock(child));
|
|
981
|
+
} else if (!isSequenceSection(child) && !isSequenceNote(child)) {
|
|
982
|
+
const idx = messages.indexOf(child as SequenceMessage);
|
|
983
|
+
if (idx >= 0) indices.push(idx);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
864
988
|
for (const child of block.elseChildren) {
|
|
865
989
|
if (isSequenceBlock(child)) {
|
|
866
990
|
indices.push(...collectMsgIndicesFromBlock(child));
|
|
867
|
-
} else if (!isSequenceSection(child)) {
|
|
868
|
-
const idx = messages.indexOf(child);
|
|
991
|
+
} else if (!isSequenceSection(child) && !isSequenceNote(child)) {
|
|
992
|
+
const idx = messages.indexOf(child as SequenceMessage);
|
|
869
993
|
if (idx >= 0) indices.push(idx);
|
|
870
994
|
}
|
|
871
995
|
}
|
|
@@ -881,7 +1005,7 @@ export function renderSequenceDiagram(
|
|
|
881
1005
|
} else if (isSequenceBlock(el)) {
|
|
882
1006
|
currentTarget.push(...collectMsgIndicesFromBlock(el));
|
|
883
1007
|
} else {
|
|
884
|
-
const idx = messages.indexOf(el);
|
|
1008
|
+
const idx = messages.indexOf(el as SequenceMessage);
|
|
885
1009
|
if (idx >= 0) currentTarget.push(idx);
|
|
886
1010
|
}
|
|
887
1011
|
}
|
|
@@ -1212,8 +1336,13 @@ export function renderSequenceDiagram(
|
|
|
1212
1336
|
...collectMsgIndices(el.children),
|
|
1213
1337
|
...collectMsgIndices(el.elseChildren)
|
|
1214
1338
|
);
|
|
1215
|
-
|
|
1216
|
-
|
|
1339
|
+
if (el.elseIfBranches) {
|
|
1340
|
+
for (const branch of el.elseIfBranches) {
|
|
1341
|
+
indices.push(...collectMsgIndices(branch.children));
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
} else if (!isSequenceSection(el) && !isSequenceNote(el)) {
|
|
1345
|
+
const idx = messages.indexOf(el as SequenceMessage);
|
|
1217
1346
|
if (idx >= 0) indices.push(idx);
|
|
1218
1347
|
}
|
|
1219
1348
|
}
|
|
@@ -1242,8 +1371,21 @@ export function renderSequenceDiagram(
|
|
|
1242
1371
|
if (!isSequenceBlock(el)) continue;
|
|
1243
1372
|
|
|
1244
1373
|
const ifIndices = collectMsgIndices(el.children);
|
|
1374
|
+
const elseIfBranchData: { label: string; indices: number[] }[] = [];
|
|
1375
|
+
if (el.elseIfBranches) {
|
|
1376
|
+
for (const branch of el.elseIfBranches) {
|
|
1377
|
+
elseIfBranchData.push({
|
|
1378
|
+
label: branch.label,
|
|
1379
|
+
indices: collectMsgIndices(branch.children),
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1245
1383
|
const elseIndices = collectMsgIndices(el.elseChildren);
|
|
1246
|
-
const allIndices = [
|
|
1384
|
+
const allIndices = [
|
|
1385
|
+
...ifIndices,
|
|
1386
|
+
...elseIfBranchData.flatMap((b) => b.indices),
|
|
1387
|
+
...elseIndices,
|
|
1388
|
+
];
|
|
1247
1389
|
if (allIndices.length === 0) continue;
|
|
1248
1390
|
|
|
1249
1391
|
// Find render step range
|
|
@@ -1308,6 +1450,34 @@ export function renderSequenceDiagram(
|
|
|
1308
1450
|
blockLine: el.lineNumber,
|
|
1309
1451
|
});
|
|
1310
1452
|
|
|
1453
|
+
// Else-if dividers
|
|
1454
|
+
for (const branchData of elseIfBranchData) {
|
|
1455
|
+
if (branchData.indices.length > 0) {
|
|
1456
|
+
let firstBranchStep = Infinity;
|
|
1457
|
+
for (const mi of branchData.indices) {
|
|
1458
|
+
const first = msgToFirstStep.get(mi);
|
|
1459
|
+
if (first !== undefined)
|
|
1460
|
+
firstBranchStep = Math.min(firstBranchStep, first);
|
|
1461
|
+
}
|
|
1462
|
+
if (firstBranchStep < Infinity) {
|
|
1463
|
+
const dividerY = stepY(firstBranchStep) - stepSpacing / 2;
|
|
1464
|
+
deferredLines.push({
|
|
1465
|
+
x1: frameX,
|
|
1466
|
+
y1: dividerY,
|
|
1467
|
+
x2: frameX + frameW,
|
|
1468
|
+
y2: dividerY,
|
|
1469
|
+
});
|
|
1470
|
+
deferredLabels.push({
|
|
1471
|
+
x: frameX + 6,
|
|
1472
|
+
y: dividerY + 14,
|
|
1473
|
+
text: `else if ${branchData.label}`,
|
|
1474
|
+
bold: false,
|
|
1475
|
+
italic: true,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1311
1481
|
// Else divider
|
|
1312
1482
|
if (elseIndices.length > 0) {
|
|
1313
1483
|
let firstElseStep = Infinity;
|
|
@@ -1336,6 +1506,11 @@ export function renderSequenceDiagram(
|
|
|
1336
1506
|
|
|
1337
1507
|
// Recurse into nested blocks
|
|
1338
1508
|
renderBlockFrames(el.children, depth + 1);
|
|
1509
|
+
if (el.elseIfBranches) {
|
|
1510
|
+
for (const branch of el.elseIfBranches) {
|
|
1511
|
+
renderBlockFrames(branch.children, depth + 1);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1339
1514
|
renderBlockFrames(el.elseChildren, depth + 1);
|
|
1340
1515
|
}
|
|
1341
1516
|
};
|
|
@@ -1681,6 +1856,157 @@ export function renderSequenceDiagram(
|
|
|
1681
1856
|
}
|
|
1682
1857
|
}
|
|
1683
1858
|
});
|
|
1859
|
+
|
|
1860
|
+
// Render notes — folded-corner boxes attached to participant lifelines
|
|
1861
|
+
const noteFill = isDark
|
|
1862
|
+
? mix(palette.surface, palette.bg, 50)
|
|
1863
|
+
: mix(palette.bg, palette.surface, 15);
|
|
1864
|
+
|
|
1865
|
+
const findAssociatedStep = (note: SequenceNote): number => {
|
|
1866
|
+
let bestMsgIndex = -1;
|
|
1867
|
+
let bestLine = -1;
|
|
1868
|
+
for (let mi = 0; mi < messages.length; mi++) {
|
|
1869
|
+
if (
|
|
1870
|
+
messages[mi].lineNumber < note.lineNumber &&
|
|
1871
|
+
messages[mi].lineNumber > bestLine &&
|
|
1872
|
+
!hiddenMsgIndices.has(mi)
|
|
1873
|
+
) {
|
|
1874
|
+
bestLine = messages[mi].lineNumber;
|
|
1875
|
+
bestMsgIndex = mi;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (bestMsgIndex < 0) return -1;
|
|
1879
|
+
return msgToFirstStep.get(bestMsgIndex) ?? -1;
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
const renderNoteElements = (els: SequenceElement[]): void => {
|
|
1883
|
+
for (const el of els) {
|
|
1884
|
+
if (isSequenceNote(el)) {
|
|
1885
|
+
const px = participantX.get(el.participantId);
|
|
1886
|
+
if (px === undefined) continue;
|
|
1887
|
+
const si = findAssociatedStep(el);
|
|
1888
|
+
if (si < 0) continue;
|
|
1889
|
+
const noteY = stepY(si);
|
|
1890
|
+
|
|
1891
|
+
const wrappedLines = wrapTextLines(el.text, NOTE_CHARS_PER_LINE);
|
|
1892
|
+
const noteH = wrappedLines.length * NOTE_LINE_H + NOTE_PAD_V * 2;
|
|
1893
|
+
const maxLineLen = Math.max(...wrappedLines.map((l) => l.length));
|
|
1894
|
+
const noteW = Math.min(
|
|
1895
|
+
NOTE_MAX_W,
|
|
1896
|
+
Math.max(80, maxLineLen * NOTE_CHAR_W + NOTE_PAD_H * 2 + NOTE_FOLD)
|
|
1897
|
+
);
|
|
1898
|
+
const isRight = el.position === 'right';
|
|
1899
|
+
const noteX = isRight
|
|
1900
|
+
? px + ACTIVATION_WIDTH + NOTE_GAP
|
|
1901
|
+
: px - ACTIVATION_WIDTH - NOTE_GAP - noteW;
|
|
1902
|
+
const noteTopY = noteY + NOTE_OFFSET_BELOW;
|
|
1903
|
+
|
|
1904
|
+
// Wrap in <g> with data attributes for toggle support
|
|
1905
|
+
const noteG = svg
|
|
1906
|
+
.append('g')
|
|
1907
|
+
.attr('class', 'note')
|
|
1908
|
+
.attr('data-note-toggle', '')
|
|
1909
|
+
.attr('data-line-number', String(el.lineNumber))
|
|
1910
|
+
.attr('data-line-end', String(el.endLineNumber));
|
|
1911
|
+
|
|
1912
|
+
// Folded-corner path
|
|
1913
|
+
noteG
|
|
1914
|
+
.append('path')
|
|
1915
|
+
.attr(
|
|
1916
|
+
'd',
|
|
1917
|
+
[
|
|
1918
|
+
`M ${noteX} ${noteTopY}`,
|
|
1919
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
1920
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
1921
|
+
`L ${noteX + noteW} ${noteTopY + noteH}`,
|
|
1922
|
+
`L ${noteX} ${noteTopY + noteH}`,
|
|
1923
|
+
'Z',
|
|
1924
|
+
].join(' ')
|
|
1925
|
+
)
|
|
1926
|
+
.attr('fill', noteFill)
|
|
1927
|
+
.attr('stroke', palette.textMuted)
|
|
1928
|
+
.attr('stroke-width', 0.75)
|
|
1929
|
+
.attr('class', 'note-box');
|
|
1930
|
+
|
|
1931
|
+
// Fold triangle
|
|
1932
|
+
noteG
|
|
1933
|
+
.append('path')
|
|
1934
|
+
.attr(
|
|
1935
|
+
'd',
|
|
1936
|
+
[
|
|
1937
|
+
`M ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
1938
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY + NOTE_FOLD}`,
|
|
1939
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
1940
|
+
].join(' ')
|
|
1941
|
+
)
|
|
1942
|
+
.attr('fill', 'none')
|
|
1943
|
+
.attr('stroke', palette.textMuted)
|
|
1944
|
+
.attr('stroke-width', 0.75)
|
|
1945
|
+
.attr('class', 'note-fold');
|
|
1946
|
+
|
|
1947
|
+
// Render text with inline markdown
|
|
1948
|
+
wrappedLines.forEach((line, li) => {
|
|
1949
|
+
const textY =
|
|
1950
|
+
noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
1951
|
+
const isBullet = line.startsWith('- ');
|
|
1952
|
+
const bulletIndent = isBullet ? 10 : 0;
|
|
1953
|
+
const displayLine = isBullet ? line.slice(2) : line;
|
|
1954
|
+
const textEl = noteG
|
|
1955
|
+
.append('text')
|
|
1956
|
+
.attr('x', noteX + NOTE_PAD_H + bulletIndent)
|
|
1957
|
+
.attr('y', textY)
|
|
1958
|
+
.attr('fill', palette.text)
|
|
1959
|
+
.attr('font-size', NOTE_FONT_SIZE)
|
|
1960
|
+
.attr('class', 'note-text');
|
|
1961
|
+
|
|
1962
|
+
if (isBullet) {
|
|
1963
|
+
noteG
|
|
1964
|
+
.append('text')
|
|
1965
|
+
.attr('x', noteX + NOTE_PAD_H)
|
|
1966
|
+
.attr('y', textY)
|
|
1967
|
+
.attr('fill', palette.text)
|
|
1968
|
+
.attr('font-size', NOTE_FONT_SIZE)
|
|
1969
|
+
.text('\u2022');
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
const spans = parseInlineMarkdown(displayLine);
|
|
1973
|
+
for (const span of spans) {
|
|
1974
|
+
if (span.href) {
|
|
1975
|
+
const a = textEl
|
|
1976
|
+
.append('a')
|
|
1977
|
+
.attr('href', span.href);
|
|
1978
|
+
a.append('tspan')
|
|
1979
|
+
.text(span.text)
|
|
1980
|
+
.attr('fill', palette.primary)
|
|
1981
|
+
.style('text-decoration', 'underline');
|
|
1982
|
+
} else {
|
|
1983
|
+
const tspan = textEl
|
|
1984
|
+
.append('tspan')
|
|
1985
|
+
.text(span.text);
|
|
1986
|
+
if (span.bold) tspan.attr('font-weight', 'bold');
|
|
1987
|
+
if (span.italic) tspan.attr('font-style', 'italic');
|
|
1988
|
+
if (span.code)
|
|
1989
|
+
tspan
|
|
1990
|
+
.attr('font-family', 'monospace')
|
|
1991
|
+
.attr('font-size', NOTE_FONT_SIZE - 1);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
} else if (isSequenceBlock(el)) {
|
|
1996
|
+
renderNoteElements(el.children);
|
|
1997
|
+
if (el.elseIfBranches) {
|
|
1998
|
+
for (const branch of el.elseIfBranches) {
|
|
1999
|
+
renderNoteElements(branch.children);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
renderNoteElements(el.elseChildren);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
if (elements && elements.length > 0) {
|
|
2008
|
+
renderNoteElements(elements);
|
|
2009
|
+
}
|
|
1684
2010
|
}
|
|
1685
2011
|
|
|
1686
2012
|
function renderParticipant(
|