@diagrammo/dgmo 0.2.7 → 0.2.9
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/dist/cli.cjs +87 -87
- package/dist/index.cjs +176 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +176 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chart.ts +2 -0
- package/src/d3.ts +118 -9
- package/src/echarts.ts +2 -0
- package/src/graph/flowchart-parser.ts +1 -0
- package/src/graph/flowchart-renderer.ts +15 -4
- package/src/graph/types.ts +1 -0
- package/src/sequence/parser.ts +12 -17
- package/src/sequence/renderer.ts +64 -27
package/package.json
CHANGED
package/src/chart.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface ChartDataPoint {
|
|
|
23
23
|
export interface ParsedChart {
|
|
24
24
|
type: ChartType;
|
|
25
25
|
title?: string;
|
|
26
|
+
titleLineNumber?: number;
|
|
26
27
|
series?: string;
|
|
27
28
|
xlabel?: string;
|
|
28
29
|
ylabel?: string;
|
|
@@ -120,6 +121,7 @@ export function parseChart(
|
|
|
120
121
|
|
|
121
122
|
if (key === 'title') {
|
|
122
123
|
result.title = value;
|
|
124
|
+
result.titleLineNumber = lineNumber;
|
|
123
125
|
continue;
|
|
124
126
|
}
|
|
125
127
|
|
package/src/d3.ts
CHANGED
|
@@ -139,6 +139,7 @@ export interface D3ExportDimensions {
|
|
|
139
139
|
export interface ParsedD3 {
|
|
140
140
|
type: D3ChartType | null;
|
|
141
141
|
title: string | null;
|
|
142
|
+
titleLineNumber: number | null;
|
|
142
143
|
orientation: 'horizontal' | 'vertical';
|
|
143
144
|
periods: string[];
|
|
144
145
|
data: D3DataItem[];
|
|
@@ -265,6 +266,7 @@ export function parseD3(content: string, palette?: PaletteColors): ParsedD3 {
|
|
|
265
266
|
const result: ParsedD3 = {
|
|
266
267
|
type: null,
|
|
267
268
|
title: null,
|
|
269
|
+
titleLineNumber: null,
|
|
268
270
|
orientation: 'horizontal',
|
|
269
271
|
periods: [],
|
|
270
272
|
data: [],
|
|
@@ -609,6 +611,7 @@ export function parseD3(content: string, palette?: PaletteColors): ParsedD3 {
|
|
|
609
611
|
|
|
610
612
|
if (key === 'title') {
|
|
611
613
|
result.title = line.substring(colonIndex + 1).trim();
|
|
614
|
+
result.titleLineNumber = lineNumber;
|
|
612
615
|
if (result.type === 'quadrant') {
|
|
613
616
|
result.quadrantTitleLineNumber = lineNumber;
|
|
614
617
|
}
|
|
@@ -1120,15 +1123,27 @@ export function renderSlopeChart(
|
|
|
1120
1123
|
|
|
1121
1124
|
// Title
|
|
1122
1125
|
if (title) {
|
|
1123
|
-
svg
|
|
1126
|
+
const titleEl = svg
|
|
1124
1127
|
.append('text')
|
|
1128
|
+
.attr('class', 'chart-title')
|
|
1125
1129
|
.attr('x', width / 2)
|
|
1126
1130
|
.attr('y', 30)
|
|
1127
1131
|
.attr('text-anchor', 'middle')
|
|
1128
1132
|
.attr('fill', textColor)
|
|
1129
1133
|
.attr('font-size', '20px')
|
|
1130
1134
|
.attr('font-weight', '700')
|
|
1135
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1131
1136
|
.text(title);
|
|
1137
|
+
|
|
1138
|
+
if (parsed.titleLineNumber) {
|
|
1139
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1140
|
+
if (onClickItem) {
|
|
1141
|
+
titleEl
|
|
1142
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1143
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1144
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1132
1147
|
}
|
|
1133
1148
|
|
|
1134
1149
|
// Period column headers
|
|
@@ -1501,15 +1516,27 @@ export function renderArcDiagram(
|
|
|
1501
1516
|
|
|
1502
1517
|
// Title
|
|
1503
1518
|
if (title) {
|
|
1504
|
-
svg
|
|
1519
|
+
const titleEl = svg
|
|
1505
1520
|
.append('text')
|
|
1521
|
+
.attr('class', 'chart-title')
|
|
1506
1522
|
.attr('x', width / 2)
|
|
1507
1523
|
.attr('y', 30)
|
|
1508
1524
|
.attr('text-anchor', 'middle')
|
|
1509
1525
|
.attr('fill', textColor)
|
|
1510
1526
|
.attr('font-size', '20px')
|
|
1511
1527
|
.attr('font-weight', '700')
|
|
1528
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1512
1529
|
.text(title);
|
|
1530
|
+
|
|
1531
|
+
if (parsed.titleLineNumber) {
|
|
1532
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1533
|
+
if (onClickItem) {
|
|
1534
|
+
titleEl
|
|
1535
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1536
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1537
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1513
1540
|
}
|
|
1514
1541
|
|
|
1515
1542
|
// Build adjacency map for hover interactions
|
|
@@ -2745,15 +2772,27 @@ export function renderTimeline(
|
|
|
2745
2772
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2746
2773
|
|
|
2747
2774
|
if (title) {
|
|
2748
|
-
svg
|
|
2775
|
+
const titleEl = svg
|
|
2749
2776
|
.append('text')
|
|
2777
|
+
.attr('class', 'chart-title')
|
|
2750
2778
|
.attr('x', width / 2)
|
|
2751
2779
|
.attr('y', 30)
|
|
2752
2780
|
.attr('text-anchor', 'middle')
|
|
2753
2781
|
.attr('fill', textColor)
|
|
2754
2782
|
.attr('font-size', '20px')
|
|
2755
2783
|
.attr('font-weight', '700')
|
|
2784
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2756
2785
|
.text(title);
|
|
2786
|
+
|
|
2787
|
+
if (parsed.titleLineNumber) {
|
|
2788
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
2789
|
+
if (onClickItem) {
|
|
2790
|
+
titleEl
|
|
2791
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
2792
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
2793
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2757
2796
|
}
|
|
2758
2797
|
|
|
2759
2798
|
renderEras(
|
|
@@ -2936,15 +2975,27 @@ export function renderTimeline(
|
|
|
2936
2975
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2937
2976
|
|
|
2938
2977
|
if (title) {
|
|
2939
|
-
svg
|
|
2978
|
+
const titleEl = svg
|
|
2940
2979
|
.append('text')
|
|
2980
|
+
.attr('class', 'chart-title')
|
|
2941
2981
|
.attr('x', width / 2)
|
|
2942
2982
|
.attr('y', 30)
|
|
2943
2983
|
.attr('text-anchor', 'middle')
|
|
2944
2984
|
.attr('fill', textColor)
|
|
2945
2985
|
.attr('font-size', '20px')
|
|
2946
2986
|
.attr('font-weight', '700')
|
|
2987
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2947
2988
|
.text(title);
|
|
2989
|
+
|
|
2990
|
+
if (parsed.titleLineNumber) {
|
|
2991
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
2992
|
+
if (onClickItem) {
|
|
2993
|
+
titleEl
|
|
2994
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
2995
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
2996
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2948
2999
|
}
|
|
2949
3000
|
|
|
2950
3001
|
renderEras(
|
|
@@ -3188,15 +3239,27 @@ export function renderTimeline(
|
|
|
3188
3239
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3189
3240
|
|
|
3190
3241
|
if (title) {
|
|
3191
|
-
svg
|
|
3242
|
+
const titleEl = svg
|
|
3192
3243
|
.append('text')
|
|
3244
|
+
.attr('class', 'chart-title')
|
|
3193
3245
|
.attr('x', width / 2)
|
|
3194
3246
|
.attr('y', 30)
|
|
3195
3247
|
.attr('text-anchor', 'middle')
|
|
3196
3248
|
.attr('fill', textColor)
|
|
3197
3249
|
.attr('font-size', '20px')
|
|
3198
3250
|
.attr('font-weight', '700')
|
|
3251
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3199
3252
|
.text(title);
|
|
3253
|
+
|
|
3254
|
+
if (parsed.titleLineNumber) {
|
|
3255
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3256
|
+
if (onClickItem) {
|
|
3257
|
+
titleEl
|
|
3258
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3259
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3260
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3200
3263
|
}
|
|
3201
3264
|
|
|
3202
3265
|
renderEras(
|
|
@@ -3472,15 +3535,27 @@ export function renderTimeline(
|
|
|
3472
3535
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3473
3536
|
|
|
3474
3537
|
if (title) {
|
|
3475
|
-
svg
|
|
3538
|
+
const titleEl = svg
|
|
3476
3539
|
.append('text')
|
|
3540
|
+
.attr('class', 'chart-title')
|
|
3477
3541
|
.attr('x', width / 2)
|
|
3478
3542
|
.attr('y', 30)
|
|
3479
3543
|
.attr('text-anchor', 'middle')
|
|
3480
3544
|
.attr('fill', textColor)
|
|
3481
3545
|
.attr('font-size', '20px')
|
|
3482
3546
|
.attr('font-weight', '700')
|
|
3547
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3483
3548
|
.text(title);
|
|
3549
|
+
|
|
3550
|
+
if (parsed.titleLineNumber) {
|
|
3551
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3552
|
+
if (onClickItem) {
|
|
3553
|
+
titleEl
|
|
3554
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3555
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3556
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3484
3559
|
}
|
|
3485
3560
|
|
|
3486
3561
|
renderEras(
|
|
@@ -3767,15 +3842,27 @@ export function renderWordCloud(
|
|
|
3767
3842
|
.style('background', bgColor);
|
|
3768
3843
|
|
|
3769
3844
|
if (title) {
|
|
3770
|
-
svg
|
|
3845
|
+
const titleEl = svg
|
|
3771
3846
|
.append('text')
|
|
3847
|
+
.attr('class', 'chart-title')
|
|
3772
3848
|
.attr('x', width / 2)
|
|
3773
3849
|
.attr('y', 30)
|
|
3774
3850
|
.attr('text-anchor', 'middle')
|
|
3775
3851
|
.attr('fill', textColor)
|
|
3776
3852
|
.attr('font-size', '20px')
|
|
3777
3853
|
.attr('font-weight', '700')
|
|
3854
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3778
3855
|
.text(title);
|
|
3856
|
+
|
|
3857
|
+
if (parsed.titleLineNumber) {
|
|
3858
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3859
|
+
if (onClickItem) {
|
|
3860
|
+
titleEl
|
|
3861
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3862
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3863
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3779
3866
|
}
|
|
3780
3867
|
|
|
3781
3868
|
const g = svg
|
|
@@ -3872,8 +3959,9 @@ function renderWordCloudAsync(
|
|
|
3872
3959
|
.style('background', bgColor);
|
|
3873
3960
|
|
|
3874
3961
|
if (title) {
|
|
3875
|
-
svg
|
|
3962
|
+
const titleEl = svg
|
|
3876
3963
|
.append('text')
|
|
3964
|
+
.attr('class', 'chart-title')
|
|
3877
3965
|
.attr('x', width / 2)
|
|
3878
3966
|
.attr('y', 30)
|
|
3879
3967
|
.attr('text-anchor', 'middle')
|
|
@@ -3881,6 +3969,10 @@ function renderWordCloudAsync(
|
|
|
3881
3969
|
.attr('font-size', '20px')
|
|
3882
3970
|
.attr('font-weight', '700')
|
|
3883
3971
|
.text(title);
|
|
3972
|
+
|
|
3973
|
+
if (parsed.titleLineNumber) {
|
|
3974
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3975
|
+
}
|
|
3884
3976
|
}
|
|
3885
3977
|
|
|
3886
3978
|
const g = svg
|
|
@@ -4217,15 +4309,27 @@ export function renderVenn(
|
|
|
4217
4309
|
|
|
4218
4310
|
// Title
|
|
4219
4311
|
if (title) {
|
|
4220
|
-
svg
|
|
4312
|
+
const titleEl = svg
|
|
4221
4313
|
.append('text')
|
|
4314
|
+
.attr('class', 'chart-title')
|
|
4222
4315
|
.attr('x', width / 2)
|
|
4223
4316
|
.attr('y', 30)
|
|
4224
4317
|
.attr('text-anchor', 'middle')
|
|
4225
4318
|
.attr('fill', textColor)
|
|
4226
4319
|
.attr('font-size', '20px')
|
|
4227
4320
|
.attr('font-weight', '700')
|
|
4321
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
4228
4322
|
.text(title);
|
|
4323
|
+
|
|
4324
|
+
if (parsed.titleLineNumber) {
|
|
4325
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4326
|
+
if (onClickItem) {
|
|
4327
|
+
titleEl
|
|
4328
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
4329
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
4330
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4229
4333
|
}
|
|
4230
4334
|
|
|
4231
4335
|
// ── Clip-path definitions ──
|
|
@@ -4593,6 +4697,7 @@ export function renderQuadrant(
|
|
|
4593
4697
|
if (title) {
|
|
4594
4698
|
const titleText = svg
|
|
4595
4699
|
.append('text')
|
|
4700
|
+
.attr('class', 'chart-title')
|
|
4596
4701
|
.attr('x', width / 2)
|
|
4597
4702
|
.attr('y', 30)
|
|
4598
4703
|
.attr('text-anchor', 'middle')
|
|
@@ -4605,6 +4710,10 @@ export function renderQuadrant(
|
|
|
4605
4710
|
)
|
|
4606
4711
|
.text(title);
|
|
4607
4712
|
|
|
4713
|
+
if (quadrantTitleLineNumber) {
|
|
4714
|
+
titleText.attr('data-line-number', quadrantTitleLineNumber);
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4608
4717
|
if (onClickItem && quadrantTitleLineNumber) {
|
|
4609
4718
|
titleText
|
|
4610
4719
|
.on('click', () => onClickItem(quadrantTitleLineNumber))
|
package/src/echarts.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface ParsedHeatmapRow {
|
|
|
54
54
|
export interface ParsedEChart {
|
|
55
55
|
type: EChartsChartType;
|
|
56
56
|
title?: string;
|
|
57
|
+
titleLineNumber?: number;
|
|
57
58
|
series?: string;
|
|
58
59
|
seriesNames?: string[];
|
|
59
60
|
seriesNameColors?: (string | undefined)[];
|
|
@@ -174,6 +175,7 @@ export function parseEChart(
|
|
|
174
175
|
|
|
175
176
|
if (key === 'title') {
|
|
176
177
|
result.title = value;
|
|
178
|
+
result.titleLineNumber = lineNumber;
|
|
177
179
|
continue;
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -257,7 +257,6 @@ export function renderFlowchart(
|
|
|
257
257
|
.append('svg')
|
|
258
258
|
.attr('width', width)
|
|
259
259
|
.attr('height', height)
|
|
260
|
-
.style('background', palette.bg)
|
|
261
260
|
.style('font-family', FONT_FAMILY);
|
|
262
261
|
|
|
263
262
|
// Defs: arrowhead markers
|
|
@@ -305,7 +304,7 @@ export function renderFlowchart(
|
|
|
305
304
|
|
|
306
305
|
// Title
|
|
307
306
|
if (graph.title) {
|
|
308
|
-
mainG
|
|
307
|
+
const titleEl = mainG
|
|
309
308
|
.append('text')
|
|
310
309
|
.attr('x', diagramW / 2)
|
|
311
310
|
.attr('y', TITLE_FONT_SIZE)
|
|
@@ -313,8 +312,19 @@ export function renderFlowchart(
|
|
|
313
312
|
.attr('fill', palette.text)
|
|
314
313
|
.attr('font-size', TITLE_FONT_SIZE)
|
|
315
314
|
.attr('font-weight', 'bold')
|
|
316
|
-
.attr('class', 'fc-title')
|
|
315
|
+
.attr('class', 'fc-title chart-title')
|
|
316
|
+
.style('cursor', onClickItem && graph.titleLineNumber ? 'pointer' : 'default')
|
|
317
317
|
.text(graph.title);
|
|
318
|
+
|
|
319
|
+
if (graph.titleLineNumber) {
|
|
320
|
+
titleEl.attr('data-line-number', graph.titleLineNumber);
|
|
321
|
+
if (onClickItem) {
|
|
322
|
+
titleEl
|
|
323
|
+
.on('click', () => onClickItem(graph.titleLineNumber!))
|
|
324
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
325
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
318
328
|
}
|
|
319
329
|
|
|
320
330
|
// Content group (offset by title)
|
|
@@ -425,7 +435,8 @@ export function renderFlowchart(
|
|
|
425
435
|
.append('g')
|
|
426
436
|
.attr('transform', `translate(${node.x}, ${node.y})`)
|
|
427
437
|
.attr('class', 'fc-node')
|
|
428
|
-
.attr('data-line-number', String(node.lineNumber))
|
|
438
|
+
.attr('data-line-number', String(node.lineNumber))
|
|
439
|
+
.attr('data-node-id', node.id);
|
|
429
440
|
|
|
430
441
|
if (onClickItem) {
|
|
431
442
|
nodeG.style('cursor', 'pointer').on('click', () => {
|
package/src/graph/types.ts
CHANGED
package/src/sequence/parser.ts
CHANGED
|
@@ -97,6 +97,7 @@ export interface SequenceNote {
|
|
|
97
97
|
position: 'right' | 'left';
|
|
98
98
|
participantId: string;
|
|
99
99
|
lineNumber: number;
|
|
100
|
+
endLineNumber: number;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export type SequenceElement =
|
|
@@ -132,6 +133,7 @@ export interface SequenceGroup {
|
|
|
132
133
|
*/
|
|
133
134
|
export interface ParsedSequenceDgmo {
|
|
134
135
|
title: string | null;
|
|
136
|
+
titleLineNumber: number | null;
|
|
135
137
|
participants: SequenceParticipant[];
|
|
136
138
|
messages: SequenceMessage[];
|
|
137
139
|
elements: SequenceElement[];
|
|
@@ -165,7 +167,7 @@ const UML_RETURN_PATTERN = /^(\w+\([^)]*\))\s*:\s*(.+)$/;
|
|
|
165
167
|
|
|
166
168
|
// Note patterns — "note: text", "note right of API: text", "note left of User"
|
|
167
169
|
const NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
|
|
168
|
-
const NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+(
|
|
170
|
+
const NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+([^\s:]+))?\s*:?\s*$/i;
|
|
169
171
|
|
|
170
172
|
/**
|
|
171
173
|
* Extract return label from a message label string.
|
|
@@ -226,6 +228,7 @@ function measureIndent(line: string): number {
|
|
|
226
228
|
export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
227
229
|
const result: ParsedSequenceDgmo = {
|
|
228
230
|
title: null,
|
|
231
|
+
titleLineNumber: null,
|
|
229
232
|
participants: [],
|
|
230
233
|
messages: [],
|
|
231
234
|
elements: [],
|
|
@@ -366,6 +369,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
366
369
|
|
|
367
370
|
if (key === 'title') {
|
|
368
371
|
result.title = value;
|
|
372
|
+
result.titleLineNumber = lineNumber;
|
|
369
373
|
continue;
|
|
370
374
|
}
|
|
371
375
|
|
|
@@ -647,15 +651,11 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
647
651
|
(noteSingleMatch[1]?.toLowerCase() as 'right' | 'left') || 'right';
|
|
648
652
|
let noteParticipant = noteSingleMatch[2] || null;
|
|
649
653
|
if (!noteParticipant) {
|
|
650
|
-
if (!lastMsgFrom)
|
|
651
|
-
result.error = `Line ${lineNumber}: note requires a preceding message`;
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
654
|
+
if (!lastMsgFrom) continue; // incomplete — skip during live typing
|
|
654
655
|
noteParticipant = lastMsgFrom;
|
|
655
656
|
}
|
|
656
657
|
if (!result.participants.some((p) => p.id === noteParticipant)) {
|
|
657
|
-
|
|
658
|
-
return result;
|
|
658
|
+
continue; // unknown participant — skip during live typing
|
|
659
659
|
}
|
|
660
660
|
const note: SequenceNote = {
|
|
661
661
|
kind: 'note',
|
|
@@ -663,6 +663,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
663
663
|
position: notePosition,
|
|
664
664
|
participantId: noteParticipant,
|
|
665
665
|
lineNumber,
|
|
666
|
+
endLineNumber: lineNumber,
|
|
666
667
|
};
|
|
667
668
|
currentContainer().push(note);
|
|
668
669
|
continue;
|
|
@@ -675,15 +676,11 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
675
676
|
(noteMultiMatch[1]?.toLowerCase() as 'right' | 'left') || 'right';
|
|
676
677
|
let noteParticipant = noteMultiMatch[2] || null;
|
|
677
678
|
if (!noteParticipant) {
|
|
678
|
-
if (!lastMsgFrom)
|
|
679
|
-
result.error = `Line ${lineNumber}: note requires a preceding message`;
|
|
680
|
-
return result;
|
|
681
|
-
}
|
|
679
|
+
if (!lastMsgFrom) continue; // incomplete — skip during live typing
|
|
682
680
|
noteParticipant = lastMsgFrom;
|
|
683
681
|
}
|
|
684
682
|
if (!result.participants.some((p) => p.id === noteParticipant)) {
|
|
685
|
-
|
|
686
|
-
return result;
|
|
683
|
+
continue; // unknown participant — skip during live typing
|
|
687
684
|
}
|
|
688
685
|
// Collect indented body lines
|
|
689
686
|
const noteLines: string[] = [];
|
|
@@ -696,16 +693,14 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
696
693
|
noteLines.push(nextTrimmed);
|
|
697
694
|
i++;
|
|
698
695
|
}
|
|
699
|
-
if (noteLines.length === 0)
|
|
700
|
-
result.error = `Line ${lineNumber}: multi-line note has no content — add indented lines or use 'note: text'`;
|
|
701
|
-
return result;
|
|
702
|
-
}
|
|
696
|
+
if (noteLines.length === 0) continue; // no body yet — skip during live typing
|
|
703
697
|
const note: SequenceNote = {
|
|
704
698
|
kind: 'note',
|
|
705
699
|
text: noteLines.join('\n'),
|
|
706
700
|
position: notePosition,
|
|
707
701
|
participantId: noteParticipant,
|
|
708
702
|
lineNumber,
|
|
703
|
+
endLineNumber: i + 1, // i has advanced past the body lines (1-based)
|
|
709
704
|
};
|
|
710
705
|
currentContainer().push(note);
|
|
711
706
|
continue;
|
package/src/sequence/renderer.ts
CHANGED
|
@@ -52,14 +52,16 @@ interface InlineSpan {
|
|
|
52
52
|
|
|
53
53
|
function parseInlineMarkdown(text: string): InlineSpan[] {
|
|
54
54
|
const spans: InlineSpan[] = [];
|
|
55
|
-
const regex = /\*\*(.+?)
|
|
55
|
+
const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|([^*_`[]+)/g;
|
|
56
56
|
let match;
|
|
57
57
|
while ((match = regex.exec(text)) !== null) {
|
|
58
|
-
if (match[1]) spans.push({ text: match[1], bold: true });
|
|
59
|
-
else if (match[2]) spans.push({ text: match[2],
|
|
60
|
-
else if (match[3]) spans.push({ text: match[3],
|
|
61
|
-
else if (match[4]) spans.push({ text: match[4],
|
|
62
|
-
else if (match[
|
|
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] });
|
|
63
65
|
}
|
|
64
66
|
return spans;
|
|
65
67
|
}
|
|
@@ -915,6 +917,38 @@ export function renderSequenceDiagram(
|
|
|
915
917
|
markBlockSpacing(elements);
|
|
916
918
|
}
|
|
917
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
|
+
|
|
918
952
|
// --- Section-aware Y layout ---
|
|
919
953
|
// Sections get their own Y positions computed from content above them (not anchored
|
|
920
954
|
// to messages below). This ensures toggling collapse/expand doesn't move the divider.
|
|
@@ -1195,8 +1229,9 @@ export function renderSequenceDiagram(
|
|
|
1195
1229
|
|
|
1196
1230
|
// Render title
|
|
1197
1231
|
if (title) {
|
|
1198
|
-
svg
|
|
1232
|
+
const titleEl = svg
|
|
1199
1233
|
.append('text')
|
|
1234
|
+
.attr('class', 'chart-title')
|
|
1200
1235
|
.attr('x', svgWidth / 2)
|
|
1201
1236
|
.attr('y', 30)
|
|
1202
1237
|
.attr('text-anchor', 'middle')
|
|
@@ -1204,6 +1239,10 @@ export function renderSequenceDiagram(
|
|
|
1204
1239
|
.attr('font-size', 20)
|
|
1205
1240
|
.attr('font-weight', 'bold')
|
|
1206
1241
|
.text(title);
|
|
1242
|
+
|
|
1243
|
+
if (parsed.titleLineNumber) {
|
|
1244
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1245
|
+
}
|
|
1207
1246
|
}
|
|
1208
1247
|
|
|
1209
1248
|
// Render group boxes (behind participant shapes)
|
|
@@ -1865,14 +1904,15 @@ export function renderSequenceDiagram(
|
|
|
1865
1904
|
const noteX = isRight
|
|
1866
1905
|
? px + ACTIVATION_WIDTH + NOTE_GAP
|
|
1867
1906
|
: px - ACTIVATION_WIDTH - NOTE_GAP - noteW;
|
|
1868
|
-
const noteTopY = noteY
|
|
1907
|
+
const noteTopY = noteY + NOTE_OFFSET_BELOW;
|
|
1869
1908
|
|
|
1870
1909
|
// Wrap in <g> with data attributes for toggle support
|
|
1871
1910
|
const noteG = svg
|
|
1872
1911
|
.append('g')
|
|
1873
1912
|
.attr('class', 'note')
|
|
1874
1913
|
.attr('data-note-toggle', '')
|
|
1875
|
-
.attr('data-line-number', String(el.lineNumber))
|
|
1914
|
+
.attr('data-line-number', String(el.lineNumber))
|
|
1915
|
+
.attr('data-line-end', String(el.endLineNumber));
|
|
1876
1916
|
|
|
1877
1917
|
// Folded-corner path
|
|
1878
1918
|
noteG
|
|
@@ -1909,35 +1949,32 @@ export function renderSequenceDiagram(
|
|
|
1909
1949
|
.attr('stroke-width', 0.75)
|
|
1910
1950
|
.attr('class', 'note-fold');
|
|
1911
1951
|
|
|
1912
|
-
// Dashed connector to lifeline
|
|
1913
|
-
const connectorNoteX = isRight ? noteX : noteX + noteW;
|
|
1914
|
-
const connectorLifeX = isRight
|
|
1915
|
-
? px + ACTIVATION_WIDTH / 2
|
|
1916
|
-
: px - ACTIVATION_WIDTH / 2;
|
|
1917
|
-
noteG
|
|
1918
|
-
.append('line')
|
|
1919
|
-
.attr('x1', connectorNoteX)
|
|
1920
|
-
.attr('y1', noteY)
|
|
1921
|
-
.attr('x2', connectorLifeX)
|
|
1922
|
-
.attr('y2', noteY)
|
|
1923
|
-
.attr('stroke', palette.textMuted)
|
|
1924
|
-
.attr('stroke-width', 0.75)
|
|
1925
|
-
.attr('stroke-dasharray', '3 2')
|
|
1926
|
-
.attr('class', 'note-connector');
|
|
1927
|
-
|
|
1928
1952
|
// Render text with inline markdown
|
|
1929
1953
|
wrappedLines.forEach((line, li) => {
|
|
1930
1954
|
const textY =
|
|
1931
1955
|
noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
1956
|
+
const isBullet = line.startsWith('- ');
|
|
1957
|
+
const bulletIndent = isBullet ? 10 : 0;
|
|
1958
|
+
const displayLine = isBullet ? line.slice(2) : line;
|
|
1932
1959
|
const textEl = noteG
|
|
1933
1960
|
.append('text')
|
|
1934
|
-
.attr('x', noteX + NOTE_PAD_H)
|
|
1961
|
+
.attr('x', noteX + NOTE_PAD_H + bulletIndent)
|
|
1935
1962
|
.attr('y', textY)
|
|
1936
1963
|
.attr('fill', palette.text)
|
|
1937
1964
|
.attr('font-size', NOTE_FONT_SIZE)
|
|
1938
1965
|
.attr('class', 'note-text');
|
|
1939
1966
|
|
|
1940
|
-
|
|
1967
|
+
if (isBullet) {
|
|
1968
|
+
noteG
|
|
1969
|
+
.append('text')
|
|
1970
|
+
.attr('x', noteX + NOTE_PAD_H)
|
|
1971
|
+
.attr('y', textY)
|
|
1972
|
+
.attr('fill', palette.text)
|
|
1973
|
+
.attr('font-size', NOTE_FONT_SIZE)
|
|
1974
|
+
.text('\u2022');
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
const spans = parseInlineMarkdown(displayLine);
|
|
1941
1978
|
for (const span of spans) {
|
|
1942
1979
|
if (span.href) {
|
|
1943
1980
|
const a = textEl
|