@diagrammo/dgmo 0.2.7 → 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/dist/cli.cjs +87 -87
- package/dist/index.cjs +56 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +56 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/sequence/parser.ts +9 -17
- package/src/sequence/renderer.ts +58 -26
package/package.json
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 =
|
|
@@ -165,7 +166,7 @@ const UML_RETURN_PATTERN = /^(\w+\([^)]*\))\s*:\s*(.+)$/;
|
|
|
165
166
|
|
|
166
167
|
// Note patterns — "note: text", "note right of API: text", "note left of User"
|
|
167
168
|
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+(
|
|
169
|
+
const NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+([^\s:]+))?\s*:?\s*$/i;
|
|
169
170
|
|
|
170
171
|
/**
|
|
171
172
|
* Extract return label from a message label string.
|
|
@@ -647,15 +648,11 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
647
648
|
(noteSingleMatch[1]?.toLowerCase() as 'right' | 'left') || 'right';
|
|
648
649
|
let noteParticipant = noteSingleMatch[2] || null;
|
|
649
650
|
if (!noteParticipant) {
|
|
650
|
-
if (!lastMsgFrom)
|
|
651
|
-
result.error = `Line ${lineNumber}: note requires a preceding message`;
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
651
|
+
if (!lastMsgFrom) continue; // incomplete — skip during live typing
|
|
654
652
|
noteParticipant = lastMsgFrom;
|
|
655
653
|
}
|
|
656
654
|
if (!result.participants.some((p) => p.id === noteParticipant)) {
|
|
657
|
-
|
|
658
|
-
return result;
|
|
655
|
+
continue; // unknown participant — skip during live typing
|
|
659
656
|
}
|
|
660
657
|
const note: SequenceNote = {
|
|
661
658
|
kind: 'note',
|
|
@@ -663,6 +660,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
663
660
|
position: notePosition,
|
|
664
661
|
participantId: noteParticipant,
|
|
665
662
|
lineNumber,
|
|
663
|
+
endLineNumber: lineNumber,
|
|
666
664
|
};
|
|
667
665
|
currentContainer().push(note);
|
|
668
666
|
continue;
|
|
@@ -675,15 +673,11 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
675
673
|
(noteMultiMatch[1]?.toLowerCase() as 'right' | 'left') || 'right';
|
|
676
674
|
let noteParticipant = noteMultiMatch[2] || null;
|
|
677
675
|
if (!noteParticipant) {
|
|
678
|
-
if (!lastMsgFrom)
|
|
679
|
-
result.error = `Line ${lineNumber}: note requires a preceding message`;
|
|
680
|
-
return result;
|
|
681
|
-
}
|
|
676
|
+
if (!lastMsgFrom) continue; // incomplete — skip during live typing
|
|
682
677
|
noteParticipant = lastMsgFrom;
|
|
683
678
|
}
|
|
684
679
|
if (!result.participants.some((p) => p.id === noteParticipant)) {
|
|
685
|
-
|
|
686
|
-
return result;
|
|
680
|
+
continue; // unknown participant — skip during live typing
|
|
687
681
|
}
|
|
688
682
|
// Collect indented body lines
|
|
689
683
|
const noteLines: string[] = [];
|
|
@@ -696,16 +690,14 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
696
690
|
noteLines.push(nextTrimmed);
|
|
697
691
|
i++;
|
|
698
692
|
}
|
|
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
|
-
}
|
|
693
|
+
if (noteLines.length === 0) continue; // no body yet — skip during live typing
|
|
703
694
|
const note: SequenceNote = {
|
|
704
695
|
kind: 'note',
|
|
705
696
|
text: noteLines.join('\n'),
|
|
706
697
|
position: notePosition,
|
|
707
698
|
participantId: noteParticipant,
|
|
708
699
|
lineNumber,
|
|
700
|
+
endLineNumber: i + 1, // i has advanced past the body lines (1-based)
|
|
709
701
|
};
|
|
710
702
|
currentContainer().push(note);
|
|
711
703
|
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.
|
|
@@ -1865,14 +1899,15 @@ export function renderSequenceDiagram(
|
|
|
1865
1899
|
const noteX = isRight
|
|
1866
1900
|
? px + ACTIVATION_WIDTH + NOTE_GAP
|
|
1867
1901
|
: px - ACTIVATION_WIDTH - NOTE_GAP - noteW;
|
|
1868
|
-
const noteTopY = noteY
|
|
1902
|
+
const noteTopY = noteY + NOTE_OFFSET_BELOW;
|
|
1869
1903
|
|
|
1870
1904
|
// Wrap in <g> with data attributes for toggle support
|
|
1871
1905
|
const noteG = svg
|
|
1872
1906
|
.append('g')
|
|
1873
1907
|
.attr('class', 'note')
|
|
1874
1908
|
.attr('data-note-toggle', '')
|
|
1875
|
-
.attr('data-line-number', String(el.lineNumber))
|
|
1909
|
+
.attr('data-line-number', String(el.lineNumber))
|
|
1910
|
+
.attr('data-line-end', String(el.endLineNumber));
|
|
1876
1911
|
|
|
1877
1912
|
// Folded-corner path
|
|
1878
1913
|
noteG
|
|
@@ -1909,35 +1944,32 @@ export function renderSequenceDiagram(
|
|
|
1909
1944
|
.attr('stroke-width', 0.75)
|
|
1910
1945
|
.attr('class', 'note-fold');
|
|
1911
1946
|
|
|
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
1947
|
// Render text with inline markdown
|
|
1929
1948
|
wrappedLines.forEach((line, li) => {
|
|
1930
1949
|
const textY =
|
|
1931
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;
|
|
1932
1954
|
const textEl = noteG
|
|
1933
1955
|
.append('text')
|
|
1934
|
-
.attr('x', noteX + NOTE_PAD_H)
|
|
1956
|
+
.attr('x', noteX + NOTE_PAD_H + bulletIndent)
|
|
1935
1957
|
.attr('y', textY)
|
|
1936
1958
|
.attr('fill', palette.text)
|
|
1937
1959
|
.attr('font-size', NOTE_FONT_SIZE)
|
|
1938
1960
|
.attr('class', 'note-text');
|
|
1939
1961
|
|
|
1940
|
-
|
|
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);
|
|
1941
1973
|
for (const span of spans) {
|
|
1942
1974
|
if (span.href) {
|
|
1943
1975
|
const a = textEl
|