@pixerate/editor 0.54.0 → 0.56.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/dist/agent/index.cjs +24 -23
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +9 -8
- package/dist/agent/index.js.map +1 -1
- package/dist/animation/index.cjs +1 -1
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +2 -1
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation/index.js +1 -1
- package/dist/animation-editor/index.cjs +2221 -0
- package/dist/animation-editor/index.cjs.map +1 -0
- package/dist/animation-editor/index.d.cts +1117 -0
- package/dist/animation-editor/index.d.ts +1117 -0
- package/dist/animation-editor/index.js +2221 -0
- package/dist/animation-editor/index.js.map +1 -0
- package/dist/bezier-BfwIbVTl.d.cts +548 -0
- package/dist/bezier-DYBvLgUH.d.ts +548 -0
- package/dist/canvas/index.cjs +2740 -580
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +402 -6
- package/dist/canvas/index.d.ts +402 -6
- package/dist/canvas/index.js +2735 -575
- package/dist/canvas/index.js.map +1 -1
- package/dist/chart/index.cjs +33 -31
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.d.cts +4 -2
- package/dist/chart/index.d.ts +4 -2
- package/dist/chart/index.js +21 -19
- package/dist/chart/index.js.map +1 -1
- package/dist/{chunk-C3JHII3M.cjs → chunk-3I6V7PI4.cjs} +45 -45
- package/dist/{chunk-C3JHII3M.cjs.map → chunk-3I6V7PI4.cjs.map} +1 -1
- package/dist/{chunk-P43HUQRX.cjs → chunk-AHCENCU4.cjs} +12 -10
- package/dist/chunk-AHCENCU4.cjs.map +1 -0
- package/dist/{chunk-TO22LX34.js → chunk-GTMJ4Y3S.js} +2208 -2920
- package/dist/chunk-GTMJ4Y3S.js.map +1 -0
- package/dist/chunk-II77MLHN.cjs +11106 -0
- package/dist/chunk-II77MLHN.cjs.map +1 -0
- package/dist/{chunk-OIKYP7XC.js → chunk-LMUDHBWR.js} +2 -2
- package/dist/chunk-MLREO6J3.cjs +737 -0
- package/dist/chunk-MLREO6J3.cjs.map +1 -0
- package/dist/chunk-NONDTTH7.js +5979 -0
- package/dist/chunk-NONDTTH7.js.map +1 -0
- package/dist/{chunk-YNSK4MGT.cjs → chunk-O2UH7LZE.cjs} +1549 -2261
- package/dist/chunk-O2UH7LZE.cjs.map +1 -0
- package/dist/{chunk-VODBAO32.js → chunk-OEFCLWZ3.js} +4 -1425
- package/dist/chunk-OEFCLWZ3.js.map +1 -0
- package/dist/{chunk-NK3WL5TJ.cjs → chunk-P2CTDJNF.cjs} +27 -1448
- package/dist/chunk-P2CTDJNF.cjs.map +1 -0
- package/dist/chunk-TKPPIVW7.cjs +1443 -0
- package/dist/chunk-TKPPIVW7.cjs.map +1 -0
- package/dist/chunk-VWZB6BCY.js +737 -0
- package/dist/chunk-VWZB6BCY.js.map +1 -0
- package/dist/chunk-WJSCGBJ2.js +11106 -0
- package/dist/chunk-WJSCGBJ2.js.map +1 -0
- package/dist/{chunk-RIXTW3C4.js → chunk-WLVZ235X.js} +5 -3
- package/dist/{chunk-RIXTW3C4.js.map → chunk-WLVZ235X.js.map} +1 -1
- package/dist/chunk-XCXQDPID.cjs +5979 -0
- package/dist/chunk-XCXQDPID.cjs.map +1 -0
- package/dist/chunk-ZOH6X3VR.js +1443 -0
- package/dist/chunk-ZOH6X3VR.js.map +1 -0
- package/dist/color-E9pwm--w.d.cts +33 -0
- package/dist/color-E9pwm--w.d.ts +33 -0
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/{controller-DscWQbjO.d.ts → controller-D2rj5umx.d.ts} +2 -362
- package/dist/{controller-CX0QfSv0.d.cts → controller-R2biKk_E.d.cts} +2 -362
- package/dist/diagram/index.cjs +1481 -50
- package/dist/diagram/index.cjs.map +1 -1
- package/dist/diagram/index.d.cts +1757 -15
- package/dist/diagram/index.d.ts +1757 -15
- package/dist/diagram/index.js +1469 -38
- package/dist/diagram/index.js.map +1 -1
- package/dist/extensions/index.cjs +5 -4
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +4 -3
- package/dist/extensions/index.d.ts +4 -3
- package/dist/extensions/index.js +4 -3
- package/dist/{index-DIWB6xMf.d.cts → index-BcHyG0Wq.d.cts} +2 -1
- package/dist/{index-CALDD35B.d.ts → index-CTCc3m3C.d.ts} +2 -1
- package/dist/index.cjs +10 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/pathfinder-BZL8aSu0.d.cts +469 -0
- package/dist/pathfinder-CNtsbCRK.d.ts +469 -0
- package/dist/{player-DSMjdgQ9.d.cts → player-Dg0FT0UI.d.cts} +1 -33
- package/dist/{player-DdfVrKWq.d.ts → player-W-5DX3Go.d.ts} +1 -33
- package/dist/svg-CLg2zqZA.d.cts +847 -0
- package/dist/svg-CPK82TtU.d.ts +847 -0
- package/dist/types-BRp-6zkY.d.cts +362 -0
- package/dist/types-BRp-6zkY.d.ts +362 -0
- package/dist/{types-BULrFyuQ.d.cts → types-DYkjhzdz.d.cts} +1 -1
- package/dist/{types-BULrFyuQ.d.ts → types-DYkjhzdz.d.ts} +1 -1
- package/dist/vector/index.cjs +1328 -6752
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +6 -846
- package/dist/vector/index.d.ts +6 -846
- package/dist/vector/index.js +645 -6069
- package/dist/vector/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-H5K77C7Y.cjs +0 -3848
- package/dist/chunk-H5K77C7Y.cjs.map +0 -1
- package/dist/chunk-NK3WL5TJ.cjs.map +0 -1
- package/dist/chunk-P43HUQRX.cjs.map +0 -1
- package/dist/chunk-TO22LX34.js.map +0 -1
- package/dist/chunk-VODBAO32.js.map +0 -1
- package/dist/chunk-WWJNC7SX.js +0 -3848
- package/dist/chunk-WWJNC7SX.js.map +0 -1
- package/dist/chunk-YNSK4MGT.cjs.map +0 -1
- package/dist/pathfinder-B9xh_pCZ.d.ts +0 -1013
- package/dist/pathfinder-QdG70sr9.d.cts +0 -1013
- /package/dist/{chunk-OIKYP7XC.js.map → chunk-LMUDHBWR.js.map} +0 -0
package/dist/chunk-WWJNC7SX.js
DELETED
|
@@ -1,3848 +0,0 @@
|
|
|
1
|
-
// src/diagram/source/lines.ts
|
|
2
|
-
var HEADER_PATTERNS = [
|
|
3
|
-
[/^\s*(flowchart|graph)(?:-elk)?\b/i, "flowchart"],
|
|
4
|
-
[/^\s*stateDiagram(?:-v2)?\b/i, "state"],
|
|
5
|
-
[/^\s*classDiagram(?:-v2)?\b/i, "class"],
|
|
6
|
-
[/^\s*erDiagram\b/i, "er"],
|
|
7
|
-
[/^\s*requirementDiagram\b/i, "requirement"],
|
|
8
|
-
[/^\s*mindmap\b/i, "mindmap"],
|
|
9
|
-
[/^\s*sequenceDiagram\b/i, "sequence"],
|
|
10
|
-
[/^\s*gantt\b/i, "gantt"],
|
|
11
|
-
[/^\s*pie\b/i, "pie"],
|
|
12
|
-
[/^\s*journey\b/i, "journey"],
|
|
13
|
-
[/^\s*timeline\b/i, "timeline"],
|
|
14
|
-
[/^\s*gitGraph\b/i, "gitGraph"],
|
|
15
|
-
[/^\s*C4(Context|Container|Component|Dynamic|Deployment)\b/, "c4"],
|
|
16
|
-
[/^\s*quadrantChart\b/i, "quadrant"],
|
|
17
|
-
[/^\s*sankey(?:-beta)?\b/i, "sankey"],
|
|
18
|
-
[/^\s*xychart(?:-beta)?\b/i, "xychart"],
|
|
19
|
-
[/^\s*block(?:-beta)?\b/i, "block"],
|
|
20
|
-
[/^\s*architecture(?:-beta)?\b/i, "architecture"],
|
|
21
|
-
[/^\s*packet(?:-beta)?\b/i, "packet"],
|
|
22
|
-
[/^\s*kanban\b/i, "kanban"],
|
|
23
|
-
[/^\s*radar(?:-beta)?\b/i, "radar"],
|
|
24
|
-
[/^\s*treemap(?:-beta)?\b/i, "treemap"],
|
|
25
|
-
[/^\s*info\b/i, "info"]
|
|
26
|
-
];
|
|
27
|
-
function detectFamilyFromHeader(header) {
|
|
28
|
-
for (const [re, family] of HEADER_PATTERNS) {
|
|
29
|
-
if (re.test(header)) return family;
|
|
30
|
-
}
|
|
31
|
-
return "unknown";
|
|
32
|
-
}
|
|
33
|
-
function isCommentLine(line) {
|
|
34
|
-
const t = line.trim();
|
|
35
|
-
return t.startsWith("%%") && !t.startsWith("%%{");
|
|
36
|
-
}
|
|
37
|
-
function isDirectiveLine(line) {
|
|
38
|
-
return /^\s*%%\{.*\}%%\s*$/.test(line);
|
|
39
|
-
}
|
|
40
|
-
function splitSource(source) {
|
|
41
|
-
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
42
|
-
const rawLines = source.split(/\r\n|\n/);
|
|
43
|
-
let end = rawLines.length;
|
|
44
|
-
while (end > 0 && rawLines[end - 1].trim() === "") end--;
|
|
45
|
-
const trailingLines = rawLines.slice(end);
|
|
46
|
-
const trailing = trailingLines.length ? eol + trailingLines.join(eol) : "";
|
|
47
|
-
const lines = rawLines.slice(0, end);
|
|
48
|
-
const preambleLines = [];
|
|
49
|
-
let i = 0;
|
|
50
|
-
let frontmatter;
|
|
51
|
-
if (lines[0] !== void 0 && /^---\s*$/.test(lines[0])) {
|
|
52
|
-
preambleLines.push(lines[0]);
|
|
53
|
-
i = 1;
|
|
54
|
-
const fm = [];
|
|
55
|
-
while (i < lines.length && !/^---\s*$/.test(lines[i])) {
|
|
56
|
-
fm.push(lines[i]);
|
|
57
|
-
preambleLines.push(lines[i]);
|
|
58
|
-
i++;
|
|
59
|
-
}
|
|
60
|
-
if (i < lines.length) {
|
|
61
|
-
preambleLines.push(lines[i]);
|
|
62
|
-
i++;
|
|
63
|
-
}
|
|
64
|
-
frontmatter = parseSimpleYaml(fm);
|
|
65
|
-
}
|
|
66
|
-
while (i < lines.length) {
|
|
67
|
-
const line = lines[i];
|
|
68
|
-
if (line.trim() === "" || isDirectiveLine(line) || isCommentLine(line)) {
|
|
69
|
-
preambleLines.push(line);
|
|
70
|
-
i++;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
const headerIndex = i;
|
|
76
|
-
const header = lines[i] ?? "";
|
|
77
|
-
const bodyLines = lines.slice(i + 1);
|
|
78
|
-
return {
|
|
79
|
-
eol,
|
|
80
|
-
preamble: { lines: preambleLines, frontmatter },
|
|
81
|
-
header,
|
|
82
|
-
headerIndex,
|
|
83
|
-
bodyLines,
|
|
84
|
-
trailing
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function parseSimpleYaml(lines) {
|
|
88
|
-
const out = {};
|
|
89
|
-
let currentKey = null;
|
|
90
|
-
let nested = null;
|
|
91
|
-
for (const raw of lines) {
|
|
92
|
-
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
93
|
-
const indent = raw.length - raw.trimStart().length;
|
|
94
|
-
const m = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(raw.trim());
|
|
95
|
-
if (!m) continue;
|
|
96
|
-
const [, key, valueRaw] = m;
|
|
97
|
-
const value = parseYamlScalar(valueRaw);
|
|
98
|
-
if (indent === 0) {
|
|
99
|
-
currentKey = key;
|
|
100
|
-
if (valueRaw.trim() === "") {
|
|
101
|
-
nested = {};
|
|
102
|
-
out[key] = nested;
|
|
103
|
-
} else {
|
|
104
|
-
nested = null;
|
|
105
|
-
out[key] = value;
|
|
106
|
-
}
|
|
107
|
-
} else if (nested && currentKey) {
|
|
108
|
-
nested[key] = value;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
function parseYamlScalar(v) {
|
|
114
|
-
const t = v.trim();
|
|
115
|
-
if (t === "") return "";
|
|
116
|
-
if (t.startsWith("{") || t.startsWith("[")) {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(t);
|
|
119
|
-
} catch {
|
|
120
|
-
return t;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (/^(true|false)$/i.test(t)) return t.toLowerCase() === "true";
|
|
124
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
|
|
125
|
-
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
|
126
|
-
return t.slice(1, -1);
|
|
127
|
-
}
|
|
128
|
-
return t;
|
|
129
|
-
}
|
|
130
|
-
function splitStatements(line) {
|
|
131
|
-
const parts = [];
|
|
132
|
-
let depth = 0;
|
|
133
|
-
let quote = null;
|
|
134
|
-
let cur = "";
|
|
135
|
-
for (let i = 0; i < line.length; i++) {
|
|
136
|
-
const ch = line[i];
|
|
137
|
-
if (quote) {
|
|
138
|
-
cur += ch;
|
|
139
|
-
if (ch === quote) quote = null;
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (ch === '"' || ch === "`") {
|
|
143
|
-
quote = ch;
|
|
144
|
-
cur += ch;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (ch === "[" || ch === "(" || ch === "{") depth++;
|
|
148
|
-
else if (ch === "]" || ch === ")" || ch === "}") depth = Math.max(0, depth - 1);
|
|
149
|
-
if (ch === ";" && depth === 0) {
|
|
150
|
-
parts.push(cur);
|
|
151
|
-
cur = "";
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
cur += ch;
|
|
155
|
-
}
|
|
156
|
-
parts.push(cur);
|
|
157
|
-
return parts;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// src/diagram/source/metadata.ts
|
|
161
|
-
var META_COMMENT_PREFIX = "%% @pixerate ";
|
|
162
|
-
var META_FRONTMATTER_KEY = "pixerate";
|
|
163
|
-
var LEGACY_FRONTMATTER_KEY = "ursula";
|
|
164
|
-
var META_LINE = /^\s*%%\s*@(?:pixerate|ursula)\s+(\{.*\})\s*$/;
|
|
165
|
-
var LEGACY_LAYOUT_LINE = /^\s*%%\s*@layout\b\s*(.*?)\s*$/;
|
|
166
|
-
function emptyMeta() {
|
|
167
|
-
return { v: 1 };
|
|
168
|
-
}
|
|
169
|
-
function extractMetaFromBody(bodyLines) {
|
|
170
|
-
let meta = null;
|
|
171
|
-
let legacy = null;
|
|
172
|
-
const rest = [];
|
|
173
|
-
for (const line of bodyLines) {
|
|
174
|
-
const m = META_LINE.exec(line);
|
|
175
|
-
if (m) {
|
|
176
|
-
const parsed = safeParseMeta(m[1]);
|
|
177
|
-
if (parsed) meta = mergeMeta(meta ?? emptyMeta(), parsed);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const lm = LEGACY_LAYOUT_LINE.exec(line);
|
|
181
|
-
if (lm) {
|
|
182
|
-
const parsed = safeParseMeta(`{"layout":${lm[1] || "null"}}`);
|
|
183
|
-
if (parsed) legacy = mergeMeta(legacy ?? emptyMeta(), parsed);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
rest.push(line);
|
|
187
|
-
}
|
|
188
|
-
if (legacy) meta = meta ? mergeMeta(legacy, meta) : legacy;
|
|
189
|
-
return { bodyLines: rest, meta };
|
|
190
|
-
}
|
|
191
|
-
function extractMetaFromFrontmatter(preamble) {
|
|
192
|
-
const value = preamble.frontmatter?.[META_FRONTMATTER_KEY] ?? preamble.frontmatter?.[LEGACY_FRONTMATTER_KEY];
|
|
193
|
-
if (!value) return null;
|
|
194
|
-
if (typeof value === "string") return safeParseMeta(value);
|
|
195
|
-
if (typeof value === "object") return normaliseMeta(value);
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
function safeParseMeta(json) {
|
|
199
|
-
try {
|
|
200
|
-
const value = JSON.parse(json);
|
|
201
|
-
return normaliseMeta(value);
|
|
202
|
-
} catch {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function normaliseMeta(value) {
|
|
207
|
-
const meta = { v: 1 };
|
|
208
|
-
if (value.layout && typeof value.layout === "object") {
|
|
209
|
-
const layout = {};
|
|
210
|
-
for (const [id, pos] of Object.entries(value.layout)) {
|
|
211
|
-
if (Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number")) {
|
|
212
|
-
layout[id] = [pos[0], pos[1]];
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
meta.layout = layout;
|
|
216
|
-
}
|
|
217
|
-
if (value.sizes && typeof value.sizes === "object") {
|
|
218
|
-
const sizes = {};
|
|
219
|
-
for (const [id, size] of Object.entries(value.sizes)) {
|
|
220
|
-
if (Array.isArray(size) && size.length === 2 && size.every((n) => typeof n === "number")) {
|
|
221
|
-
sizes[id] = [size[0], size[1]];
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
meta.sizes = sizes;
|
|
225
|
-
}
|
|
226
|
-
if (value.viewport && typeof value.viewport === "object") {
|
|
227
|
-
const vp = value.viewport;
|
|
228
|
-
if (typeof vp.x === "number" && typeof vp.y === "number" && typeof vp.zoom === "number") {
|
|
229
|
-
meta.viewport = { x: vp.x, y: vp.y, zoom: vp.zoom };
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (value.ext && typeof value.ext === "object") meta.ext = value.ext;
|
|
233
|
-
return meta;
|
|
234
|
-
}
|
|
235
|
-
function mergeMeta(a, b) {
|
|
236
|
-
return {
|
|
237
|
-
v: 1,
|
|
238
|
-
layout: a.layout || b.layout ? { ...a.layout, ...b.layout } : void 0,
|
|
239
|
-
sizes: a.sizes || b.sizes ? { ...a.sizes, ...b.sizes } : void 0,
|
|
240
|
-
viewport: b.viewport ?? a.viewport,
|
|
241
|
-
ext: a.ext || b.ext ? { ...a.ext, ...b.ext } : void 0
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function isMetaEmpty(meta) {
|
|
245
|
-
const hasLayout = !!meta.layout && Object.keys(meta.layout).length > 0;
|
|
246
|
-
const hasSizes = !!meta.sizes && Object.keys(meta.sizes).length > 0;
|
|
247
|
-
const hasExt = !!meta.ext && Object.keys(meta.ext).length > 0;
|
|
248
|
-
return !hasLayout && !hasSizes && !meta.viewport && !hasExt;
|
|
249
|
-
}
|
|
250
|
-
function serializeMeta(meta) {
|
|
251
|
-
const out = { v: 1 };
|
|
252
|
-
if (meta.layout && Object.keys(meta.layout).length) {
|
|
253
|
-
const layout = {};
|
|
254
|
-
for (const id of Object.keys(meta.layout).sort()) {
|
|
255
|
-
const [x, y] = meta.layout[id];
|
|
256
|
-
layout[id] = [Math.round(x), Math.round(y)];
|
|
257
|
-
}
|
|
258
|
-
out.layout = layout;
|
|
259
|
-
}
|
|
260
|
-
if (meta.sizes && Object.keys(meta.sizes).length) {
|
|
261
|
-
const sizes = {};
|
|
262
|
-
for (const id of Object.keys(meta.sizes).sort()) {
|
|
263
|
-
const [w, h] = meta.sizes[id];
|
|
264
|
-
sizes[id] = [Math.round(w), Math.round(h)];
|
|
265
|
-
}
|
|
266
|
-
out.sizes = sizes;
|
|
267
|
-
}
|
|
268
|
-
if (meta.viewport) {
|
|
269
|
-
out.viewport = {
|
|
270
|
-
x: Math.round(meta.viewport.x),
|
|
271
|
-
y: Math.round(meta.viewport.y),
|
|
272
|
-
zoom: Math.round(meta.viewport.zoom * 1e3) / 1e3
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
if (meta.ext && Object.keys(meta.ext).length) out.ext = meta.ext;
|
|
276
|
-
return JSON.stringify(out);
|
|
277
|
-
}
|
|
278
|
-
function metaCommentLine(meta, indent = " ") {
|
|
279
|
-
return `${indent}${META_COMMENT_PREFIX}${serializeMeta(meta)}`;
|
|
280
|
-
}
|
|
281
|
-
function writeMetaToPreamble(preamble, meta) {
|
|
282
|
-
const lines = [...preamble.lines];
|
|
283
|
-
const hasFm = lines[0] !== void 0 && /^---\s*$/.test(lines[0]);
|
|
284
|
-
const entry = meta && !isMetaEmpty(meta) ? `${META_FRONTMATTER_KEY}: ${serializeMeta(meta)}` : null;
|
|
285
|
-
if (!hasFm) {
|
|
286
|
-
if (!entry) return lines;
|
|
287
|
-
return ["---", entry, "---", ...lines];
|
|
288
|
-
}
|
|
289
|
-
const close = lines.findIndex((l, i) => i > 0 && /^---\s*$/.test(l));
|
|
290
|
-
const fmEnd = close === -1 ? lines.length : close;
|
|
291
|
-
const existing = lines.findIndex(
|
|
292
|
-
(l, i) => i > 0 && i < fmEnd && new RegExp(`^(?:${META_FRONTMATTER_KEY}|${LEGACY_FRONTMATTER_KEY})\\s*:`).test(l)
|
|
293
|
-
);
|
|
294
|
-
if (existing !== -1) {
|
|
295
|
-
if (entry) lines[existing] = entry;
|
|
296
|
-
else lines.splice(existing, 1);
|
|
297
|
-
} else if (entry) {
|
|
298
|
-
lines.splice(fmEnd, 0, entry);
|
|
299
|
-
}
|
|
300
|
-
if (!entry) {
|
|
301
|
-
const close2 = lines.findIndex((l, i) => i > 0 && /^---\s*$/.test(l));
|
|
302
|
-
if (close2 === 1) lines.splice(0, 2);
|
|
303
|
-
}
|
|
304
|
-
return lines;
|
|
305
|
-
}
|
|
306
|
-
var FRONTMATTER_META_FAMILIES = /* @__PURE__ */ new Set(["requirement"]);
|
|
307
|
-
function defaultCarrierFor(family) {
|
|
308
|
-
return FRONTMATTER_META_FAMILIES.has(family) ? "frontmatter" : "comment";
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// src/diagram/flowchart/edges.ts
|
|
312
|
-
var START_HEADS = {
|
|
313
|
-
"<": "arrow_point",
|
|
314
|
-
x: "arrow_cross",
|
|
315
|
-
o: "arrow_circle"
|
|
316
|
-
};
|
|
317
|
-
var END_HEADS = {
|
|
318
|
-
">": "arrow_point",
|
|
319
|
-
x: "arrow_cross",
|
|
320
|
-
o: "arrow_circle"
|
|
321
|
-
};
|
|
322
|
-
var EDGE_ID = /^([A-Za-z0-9_À--]+)@(?=[<xo]?[-=~.])/;
|
|
323
|
-
var PLAIN = /^([<xo](?=[-=~]))?(-{2,}|={2,}|-\.+-|~{3,})([>xo])?/;
|
|
324
|
-
var TEXT_OPEN = /^([<xo](?=[-=]))?(--|-\.|==)(?=\s)/;
|
|
325
|
-
var TEXT_CLOSE = /\s+(-{2,}|\.+-|={2,})([>xo])?(?=$|\s|\||[A-Za-z0-9_À-([{>"`&])/;
|
|
326
|
-
function patternOf(body) {
|
|
327
|
-
if (body.startsWith("~")) return "invisible";
|
|
328
|
-
if (body.startsWith("=")) return "thick";
|
|
329
|
-
if (body.includes(".")) return "dotted";
|
|
330
|
-
return "solid";
|
|
331
|
-
}
|
|
332
|
-
function lengthOf(body, hasEndHead) {
|
|
333
|
-
if (body.startsWith("~")) return Math.max(1, body.length - 2);
|
|
334
|
-
if (body.includes(".")) return Math.max(1, (body.match(/\./g) ?? []).length);
|
|
335
|
-
return Math.max(1, body.length - (hasEndHead ? 1 : 2));
|
|
336
|
-
}
|
|
337
|
-
function closerLength(closer, hasEndHead) {
|
|
338
|
-
if (closer.includes(".")) return Math.max(1, (closer.match(/\./g) ?? []).length);
|
|
339
|
-
return Math.max(1, closer.length - (hasEndHead ? 1 : 2));
|
|
340
|
-
}
|
|
341
|
-
function parseLink(input) {
|
|
342
|
-
let pos = 0;
|
|
343
|
-
let id;
|
|
344
|
-
const idm = EDGE_ID.exec(input);
|
|
345
|
-
if (idm) {
|
|
346
|
-
id = idm[1];
|
|
347
|
-
pos += idm[0].length;
|
|
348
|
-
}
|
|
349
|
-
const rest = input.slice(pos);
|
|
350
|
-
const tm = TEXT_OPEN.exec(rest);
|
|
351
|
-
if (tm) {
|
|
352
|
-
const afterOpen = rest.slice(tm[0].length);
|
|
353
|
-
const cm = TEXT_CLOSE.exec(afterOpen);
|
|
354
|
-
if (cm && cm.index > 0) {
|
|
355
|
-
const label = afterOpen.slice(0, cm.index).trim();
|
|
356
|
-
if (label && !/^[-=.>|]/.test(label)) {
|
|
357
|
-
const closer = cm[1];
|
|
358
|
-
const endHead2 = cm[2];
|
|
359
|
-
const consumed2 = pos + tm[0].length + cm.index + cm[0].length;
|
|
360
|
-
const body2 = tm[2];
|
|
361
|
-
const result2 = {
|
|
362
|
-
id,
|
|
363
|
-
pattern: patternOf(body2 === "-." ? "-.-" : body2),
|
|
364
|
-
arrowStart: tm[1] ? START_HEADS[tm[1]] : "none",
|
|
365
|
-
arrowEnd: endHead2 ? END_HEADS[endHead2] : "none",
|
|
366
|
-
length: closerLength(closer, !!endHead2),
|
|
367
|
-
label: unquoteLabel(label),
|
|
368
|
-
labelStyle: "inline",
|
|
369
|
-
consumed: consumed2,
|
|
370
|
-
raw: input.slice(0, consumed2)
|
|
371
|
-
};
|
|
372
|
-
return withPipeLabel(input, result2);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
const pm = PLAIN.exec(rest);
|
|
377
|
-
if (!pm) return null;
|
|
378
|
-
const body = pm[2];
|
|
379
|
-
const endHead = pm[3];
|
|
380
|
-
const consumed = pos + pm[0].length;
|
|
381
|
-
const result = {
|
|
382
|
-
id,
|
|
383
|
-
pattern: patternOf(body),
|
|
384
|
-
arrowStart: pm[1] ? START_HEADS[pm[1]] : "none",
|
|
385
|
-
arrowEnd: endHead ? END_HEADS[endHead] : "none",
|
|
386
|
-
length: lengthOf(body, !!endHead),
|
|
387
|
-
consumed,
|
|
388
|
-
raw: input.slice(0, consumed)
|
|
389
|
-
};
|
|
390
|
-
return withPipeLabel(input, result);
|
|
391
|
-
}
|
|
392
|
-
function withPipeLabel(input, link) {
|
|
393
|
-
const after = input.slice(link.consumed);
|
|
394
|
-
const m = /^\s*\|(\s*"[^"]*"\s*)\|/.exec(after) ?? /^\s*\|([^|]*)\|/.exec(after);
|
|
395
|
-
if (!m) return link;
|
|
396
|
-
const consumed = link.consumed + m[0].length;
|
|
397
|
-
return {
|
|
398
|
-
...link,
|
|
399
|
-
label: unquoteLabel(m[1].trim()),
|
|
400
|
-
labelStyle: link.label ? link.labelStyle : "pipe",
|
|
401
|
-
consumed,
|
|
402
|
-
raw: input.slice(0, consumed)
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
function unquoteLabel(text) {
|
|
406
|
-
const t = text.trim();
|
|
407
|
-
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
|
|
408
|
-
return t;
|
|
409
|
-
}
|
|
410
|
-
var NEEDS_QUOTE = /[[\]{}()|<>"#;&%]|^\s|\s$|^-|^=|^\./;
|
|
411
|
-
function quoteLabelIfNeeded(label) {
|
|
412
|
-
if (label === "") return '""';
|
|
413
|
-
if (label.startsWith("`") && label.endsWith("`")) return `"${label}"`;
|
|
414
|
-
if (NEEDS_QUOTE.test(label)) {
|
|
415
|
-
return `"${label.replace(/"/g, "#quot;")}"`;
|
|
416
|
-
}
|
|
417
|
-
return label;
|
|
418
|
-
}
|
|
419
|
-
var START_CHAR = {
|
|
420
|
-
arrow_point: "<",
|
|
421
|
-
arrow_cross: "x",
|
|
422
|
-
arrow_circle: "o"
|
|
423
|
-
};
|
|
424
|
-
var END_CHAR = {
|
|
425
|
-
arrow_point: ">",
|
|
426
|
-
arrow_cross: "x",
|
|
427
|
-
arrow_circle: "o"
|
|
428
|
-
};
|
|
429
|
-
function formatLink(spec) {
|
|
430
|
-
const start = START_CHAR[spec.arrowStart] ?? "";
|
|
431
|
-
const end = END_CHAR[spec.arrowEnd] ?? "";
|
|
432
|
-
const len = Math.max(1, spec.length);
|
|
433
|
-
const label = spec.label?.trim();
|
|
434
|
-
const body = (closing) => {
|
|
435
|
-
switch (spec.pattern) {
|
|
436
|
-
case "invisible":
|
|
437
|
-
return "~".repeat(len + 2);
|
|
438
|
-
case "thick":
|
|
439
|
-
return "=".repeat(end ? len + 1 : len + 2);
|
|
440
|
-
case "dotted":
|
|
441
|
-
return closing ? `${".".repeat(len)}-` : `-${".".repeat(len)}-`;
|
|
442
|
-
default:
|
|
443
|
-
return "-".repeat(end ? len + 1 : len + 2);
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
const prefix = spec.id ? `${spec.id}@` : "";
|
|
447
|
-
if (label && spec.labelStyle === "inline" && spec.pattern !== "invisible") {
|
|
448
|
-
const open = spec.pattern === "thick" ? "==" : spec.pattern === "dotted" ? "-." : "--";
|
|
449
|
-
return `${prefix}${start}${open} ${quoteLabelIfNeeded(label)} ${body(true)}${end}`;
|
|
450
|
-
}
|
|
451
|
-
const token = `${prefix}${start}${body(false)}${end}`;
|
|
452
|
-
if (label) return `${token}|${quoteLabelIfNeeded(label)}|`;
|
|
453
|
-
return token;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// src/diagram/flowchart/shapes.ts
|
|
457
|
-
var FLOWCHART_SHAPES = [
|
|
458
|
-
{
|
|
459
|
-
name: "rect",
|
|
460
|
-
title: "Rectangle",
|
|
461
|
-
aliases: ["rectangle", "proc", "process", "squareRect", "square"],
|
|
462
|
-
bracket: { open: "[", close: "]" },
|
|
463
|
-
category: "basic"
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
name: "rounded",
|
|
467
|
-
title: "Rounded rectangle",
|
|
468
|
-
aliases: ["rounded-rectangle", "event", "roundedRect", "round"],
|
|
469
|
-
bracket: { open: "(", close: ")" },
|
|
470
|
-
category: "basic"
|
|
471
|
-
},
|
|
472
|
-
{
|
|
473
|
-
name: "stadium",
|
|
474
|
-
title: "Stadium",
|
|
475
|
-
aliases: ["pill", "terminal"],
|
|
476
|
-
bracket: { open: "([", close: "])" },
|
|
477
|
-
category: "basic"
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
name: "subproc",
|
|
481
|
-
title: "Subprocess",
|
|
482
|
-
aliases: ["subprocess", "subroutine", "framed-rectangle", "fr-rect"],
|
|
483
|
-
bracket: { open: "[[", close: "]]" },
|
|
484
|
-
category: "process"
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
name: "cyl",
|
|
488
|
-
title: "Cylinder (database)",
|
|
489
|
-
aliases: ["cylinder", "database", "db"],
|
|
490
|
-
bracket: { open: "[(", close: ")]" },
|
|
491
|
-
category: "data"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
name: "circle",
|
|
495
|
-
title: "Circle",
|
|
496
|
-
aliases: ["circ"],
|
|
497
|
-
bracket: { open: "((", close: "))" },
|
|
498
|
-
category: "basic"
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: "odd",
|
|
502
|
-
title: "Asymmetric",
|
|
503
|
-
aliases: ["asymmetric", "rect_left_inv_arrow"],
|
|
504
|
-
bracket: { open: ">", close: "]" },
|
|
505
|
-
category: "misc"
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
name: "diam",
|
|
509
|
-
title: "Diamond (decision)",
|
|
510
|
-
aliases: ["diamond", "decision", "question"],
|
|
511
|
-
bracket: { open: "{", close: "}" },
|
|
512
|
-
category: "control"
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
name: "hex",
|
|
516
|
-
title: "Hexagon (prepare)",
|
|
517
|
-
aliases: ["hexagon", "prepare"],
|
|
518
|
-
bracket: { open: "{{", close: "}}" },
|
|
519
|
-
category: "process"
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
name: "lean-r",
|
|
523
|
-
title: "Parallelogram (lean right)",
|
|
524
|
-
aliases: ["lean-right", "in-out", "lean_right"],
|
|
525
|
-
bracket: { open: "[/", close: "/]" },
|
|
526
|
-
category: "data"
|
|
527
|
-
},
|
|
528
|
-
{
|
|
529
|
-
name: "lean-l",
|
|
530
|
-
title: "Parallelogram (lean left)",
|
|
531
|
-
aliases: ["lean-left", "out-in", "lean_left"],
|
|
532
|
-
bracket: { open: "[\\", close: "\\]" },
|
|
533
|
-
category: "data"
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
name: "trap-b",
|
|
537
|
-
title: "Trapezoid (base bottom)",
|
|
538
|
-
aliases: ["trapezoid-bottom", "priority", "trapezoid"],
|
|
539
|
-
bracket: { open: "[/", close: "\\]" },
|
|
540
|
-
category: "process"
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: "trap-t",
|
|
544
|
-
title: "Trapezoid (base top)",
|
|
545
|
-
aliases: ["trapezoid-top", "manual", "inv_trapezoid"],
|
|
546
|
-
bracket: { open: "[\\", close: "/]" },
|
|
547
|
-
category: "process"
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
name: "dbl-circ",
|
|
551
|
-
title: "Double circle",
|
|
552
|
-
aliases: ["double-circle", "doublecircle"],
|
|
553
|
-
bracket: { open: "(((", close: ")))" },
|
|
554
|
-
category: "basic"
|
|
555
|
-
},
|
|
556
|
-
// Data-syntax-only shapes
|
|
557
|
-
{ name: "text", title: "Text", aliases: [], category: "misc" },
|
|
558
|
-
{
|
|
559
|
-
name: "card",
|
|
560
|
-
title: "Card (notched rectangle)",
|
|
561
|
-
aliases: ["notched-rectangle", "notch-rect"],
|
|
562
|
-
category: "misc"
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
name: "lin-rect",
|
|
566
|
-
title: "Lined rectangle (shaded process)",
|
|
567
|
-
aliases: ["lined-rectangle", "lin-proc", "lined-process", "shaded-process"],
|
|
568
|
-
category: "process"
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
name: "sm-circ",
|
|
572
|
-
title: "Small circle (start)",
|
|
573
|
-
aliases: ["small-circle", "start"],
|
|
574
|
-
category: "control"
|
|
575
|
-
},
|
|
576
|
-
{
|
|
577
|
-
name: "fr-circ",
|
|
578
|
-
title: "Framed circle (stop)",
|
|
579
|
-
aliases: ["framed-circle", "stop"],
|
|
580
|
-
category: "control"
|
|
581
|
-
},
|
|
582
|
-
{ name: "fork", title: "Fork / join", aliases: ["join"], category: "control" },
|
|
583
|
-
{ name: "hourglass", title: "Hourglass (collate)", aliases: ["collate"], category: "process" },
|
|
584
|
-
{
|
|
585
|
-
name: "brace",
|
|
586
|
-
title: "Comment (left brace)",
|
|
587
|
-
aliases: ["brace-l", "comment"],
|
|
588
|
-
category: "misc"
|
|
589
|
-
},
|
|
590
|
-
{ name: "brace-r", title: "Comment (right brace)", aliases: [], category: "misc" },
|
|
591
|
-
{ name: "braces", title: "Comment (both braces)", aliases: [], category: "misc" },
|
|
592
|
-
{
|
|
593
|
-
name: "bolt",
|
|
594
|
-
title: "Lightning bolt (com link)",
|
|
595
|
-
aliases: ["com-link", "lightning-bolt"],
|
|
596
|
-
category: "misc"
|
|
597
|
-
},
|
|
598
|
-
{ name: "doc", title: "Document", aliases: ["document"], category: "data" },
|
|
599
|
-
{
|
|
600
|
-
name: "delay",
|
|
601
|
-
title: "Delay (half-rounded rectangle)",
|
|
602
|
-
aliases: ["half-rounded-rectangle"],
|
|
603
|
-
category: "process"
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
name: "h-cyl",
|
|
607
|
-
title: "Horizontal cylinder (direct access)",
|
|
608
|
-
aliases: ["das", "horizontal-cylinder"],
|
|
609
|
-
category: "data"
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
name: "lin-cyl",
|
|
613
|
-
title: "Lined cylinder (disk storage)",
|
|
614
|
-
aliases: ["disk", "lined-cylinder"],
|
|
615
|
-
category: "data"
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
name: "curv-trap",
|
|
619
|
-
title: "Curved trapezoid (display)",
|
|
620
|
-
aliases: ["curved-trapezoid", "display"],
|
|
621
|
-
category: "misc"
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
name: "div-rect",
|
|
625
|
-
title: "Divided rectangle",
|
|
626
|
-
aliases: ["div-proc", "divided-process", "divided-rectangle"],
|
|
627
|
-
category: "process"
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
name: "tri",
|
|
631
|
-
title: "Triangle (extract)",
|
|
632
|
-
aliases: ["extract", "triangle"],
|
|
633
|
-
category: "process"
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
name: "win-pane",
|
|
637
|
-
title: "Window pane (internal storage)",
|
|
638
|
-
aliases: ["internal-storage", "window-pane"],
|
|
639
|
-
category: "data"
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
name: "f-circ",
|
|
643
|
-
title: "Filled circle (junction)",
|
|
644
|
-
aliases: ["filled-circle", "junction"],
|
|
645
|
-
category: "control"
|
|
646
|
-
},
|
|
647
|
-
{ name: "lin-doc", title: "Lined document", aliases: ["lined-document"], category: "data" },
|
|
648
|
-
{ name: "notch-rect", title: "Notched rectangle (card)", aliases: [], category: "misc" },
|
|
649
|
-
{ name: "flag", title: "Flag (paper tape)", aliases: ["paper-tape"], category: "data" },
|
|
650
|
-
{
|
|
651
|
-
name: "docs",
|
|
652
|
-
title: "Stacked documents",
|
|
653
|
-
aliases: ["documents", "st-doc", "stacked-document"],
|
|
654
|
-
category: "data"
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
name: "st-rect",
|
|
658
|
-
title: "Stacked rectangles",
|
|
659
|
-
aliases: ["processes", "procs", "stacked-rectangle"],
|
|
660
|
-
category: "process"
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
name: "bow-rect",
|
|
664
|
-
title: "Bow-tie rectangle (stored data)",
|
|
665
|
-
aliases: ["bow-tie-rectangle", "stored-data"],
|
|
666
|
-
category: "data"
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: "cross-circ",
|
|
670
|
-
title: "Crossed circle (summary)",
|
|
671
|
-
aliases: ["crossed-circle", "summary"],
|
|
672
|
-
category: "process"
|
|
673
|
-
},
|
|
674
|
-
{ name: "tag-doc", title: "Tagged document", aliases: ["tagged-document"], category: "data" },
|
|
675
|
-
{
|
|
676
|
-
name: "tag-rect",
|
|
677
|
-
title: "Tagged rectangle",
|
|
678
|
-
aliases: ["tag-proc", "tagged-process", "tagged-rectangle"],
|
|
679
|
-
category: "process"
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
name: "notch-pent",
|
|
683
|
-
title: "Notched pentagon (loop limit)",
|
|
684
|
-
aliases: ["loop-limit", "notched-pentagon"],
|
|
685
|
-
category: "control"
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
name: "flip-tri",
|
|
689
|
-
title: "Flipped triangle (manual file)",
|
|
690
|
-
aliases: ["flipped-triangle", "manual-file"],
|
|
691
|
-
category: "data"
|
|
692
|
-
},
|
|
693
|
-
{
|
|
694
|
-
name: "sl-rect",
|
|
695
|
-
title: "Sloped rectangle (manual input)",
|
|
696
|
-
aliases: ["manual-input", "sloped-rectangle"],
|
|
697
|
-
category: "data"
|
|
698
|
-
},
|
|
699
|
-
{ name: "icon", title: "Icon", aliases: [], category: "misc" },
|
|
700
|
-
{ name: "image", title: "Image", aliases: ["img"], category: "misc" },
|
|
701
|
-
{ name: "anchor", title: "Anchor (invisible)", aliases: [], category: "misc" }
|
|
702
|
-
];
|
|
703
|
-
var BY_NAME = /* @__PURE__ */ new Map();
|
|
704
|
-
for (const def of FLOWCHART_SHAPES) {
|
|
705
|
-
BY_NAME.set(def.name, def);
|
|
706
|
-
for (const alias of def.aliases) BY_NAME.set(alias, def);
|
|
707
|
-
}
|
|
708
|
-
function resolveShape(name) {
|
|
709
|
-
if (!name) return void 0;
|
|
710
|
-
return BY_NAME.get(name) ?? BY_NAME.get(name.toLowerCase());
|
|
711
|
-
}
|
|
712
|
-
function canonicalShape(name) {
|
|
713
|
-
if (!name) return "rect";
|
|
714
|
-
return resolveShape(name)?.name ?? name;
|
|
715
|
-
}
|
|
716
|
-
var BRACKET_OPENERS = Array.from(
|
|
717
|
-
new Set(FLOWCHART_SHAPES.filter((s) => s.bracket).map((s) => s.bracket.open))
|
|
718
|
-
).sort((a, b) => b.length - a.length);
|
|
719
|
-
function closersFor(open) {
|
|
720
|
-
return FLOWCHART_SHAPES.filter((s) => s.bracket?.open === open).map((s) => s.bracket.close).sort((a, b) => b.length - a.length);
|
|
721
|
-
}
|
|
722
|
-
function shapeForBracket(open, close) {
|
|
723
|
-
return FLOWCHART_SHAPES.find((s) => s.bracket?.open === open && s.bracket.close === close);
|
|
724
|
-
}
|
|
725
|
-
var DEFAULT_SHAPE = "rect";
|
|
726
|
-
|
|
727
|
-
// src/diagram/flowchart/statements.ts
|
|
728
|
-
function readId(input, pos) {
|
|
729
|
-
let i = pos;
|
|
730
|
-
while (i < input.length) {
|
|
731
|
-
const ch = input[i];
|
|
732
|
-
const next = input[i + 1];
|
|
733
|
-
if (/[A-Za-z0-9_À-!#$%*+?/\\'`]/.test(ch)) {
|
|
734
|
-
i++;
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
if (ch === "." && next !== "-") {
|
|
738
|
-
i++;
|
|
739
|
-
continue;
|
|
740
|
-
}
|
|
741
|
-
if (ch === "-" && next !== ">" && next !== "-" && next !== "." && next !== void 0 && !/\s/.test(next)) {
|
|
742
|
-
i++;
|
|
743
|
-
continue;
|
|
744
|
-
}
|
|
745
|
-
if (ch === "=" && next !== "=") {
|
|
746
|
-
i++;
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
break;
|
|
750
|
-
}
|
|
751
|
-
return input.slice(pos, i);
|
|
752
|
-
}
|
|
753
|
-
function findCloser(input, pos, closers) {
|
|
754
|
-
let quote = null;
|
|
755
|
-
for (let i = pos; i < input.length; i++) {
|
|
756
|
-
const ch = input[i];
|
|
757
|
-
if (quote) {
|
|
758
|
-
if (ch === quote) quote = null;
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
if (ch === '"') {
|
|
762
|
-
quote = ch;
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
for (const closer of closers) {
|
|
766
|
-
if (input.startsWith(closer, i)) return { index: i, closer };
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return null;
|
|
770
|
-
}
|
|
771
|
-
function readBraceBlock(input, pos) {
|
|
772
|
-
let depth = 0;
|
|
773
|
-
let quote = null;
|
|
774
|
-
for (let i = pos; i < input.length; i++) {
|
|
775
|
-
const ch = input[i];
|
|
776
|
-
if (quote) {
|
|
777
|
-
if (ch === quote) quote = null;
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
if (ch === '"' || ch === "'") {
|
|
781
|
-
quote = ch;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
if (ch === "{") depth++;
|
|
785
|
-
else if (ch === "}") {
|
|
786
|
-
depth--;
|
|
787
|
-
if (depth === 0) return i + 1;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
return -1;
|
|
791
|
-
}
|
|
792
|
-
function parseShapeData(inner) {
|
|
793
|
-
const out = {};
|
|
794
|
-
const parts = [];
|
|
795
|
-
let cur = "";
|
|
796
|
-
let quote = null;
|
|
797
|
-
for (const ch of inner) {
|
|
798
|
-
if (quote) {
|
|
799
|
-
cur += ch;
|
|
800
|
-
if (ch === quote) quote = null;
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
if (ch === '"' || ch === "'") {
|
|
804
|
-
quote = ch;
|
|
805
|
-
cur += ch;
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
if (ch === "," || ch === "\n") {
|
|
809
|
-
parts.push(cur);
|
|
810
|
-
cur = "";
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
cur += ch;
|
|
814
|
-
}
|
|
815
|
-
parts.push(cur);
|
|
816
|
-
for (const part of parts) {
|
|
817
|
-
const m = /^\s*([A-Za-z_][\w-]*)\s*:\s*([\s\S]*?)\s*$/.exec(part);
|
|
818
|
-
if (m) out[m[1]] = m[2];
|
|
819
|
-
}
|
|
820
|
-
return out;
|
|
821
|
-
}
|
|
822
|
-
function unquoteData(value) {
|
|
823
|
-
if (value === void 0) return void 0;
|
|
824
|
-
const t = value.trim();
|
|
825
|
-
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
|
826
|
-
return t.slice(1, -1);
|
|
827
|
-
}
|
|
828
|
-
return t;
|
|
829
|
-
}
|
|
830
|
-
function formatShapeData(data) {
|
|
831
|
-
const entries = Object.entries(data).map(([k, v]) => `${k}: ${v}`);
|
|
832
|
-
return `@{ ${entries.join(", ")} }`;
|
|
833
|
-
}
|
|
834
|
-
function readNode(input, pos) {
|
|
835
|
-
const id = readId(input, pos);
|
|
836
|
-
if (!id) return null;
|
|
837
|
-
let i = pos + id.length;
|
|
838
|
-
const node = { id, classes: [] };
|
|
839
|
-
if (input[i] === "@" && input[i + 1] === "{") {
|
|
840
|
-
const end = readBraceBlock(input, i + 1);
|
|
841
|
-
if (end !== -1) {
|
|
842
|
-
const inner = input.slice(i + 2, end - 1);
|
|
843
|
-
const data = parseShapeData(inner);
|
|
844
|
-
node.form = "data";
|
|
845
|
-
if (data.shape !== void 0) {
|
|
846
|
-
node.shape = canonicalShape(unquoteData(data.shape));
|
|
847
|
-
delete data.shape;
|
|
848
|
-
}
|
|
849
|
-
if (data.label !== void 0) {
|
|
850
|
-
node.label = unquoteData(data.label);
|
|
851
|
-
node.labelQuoted = /^\s*["']/.test(data.label);
|
|
852
|
-
delete data.label;
|
|
853
|
-
}
|
|
854
|
-
if ("img" in data) node.shape = "image";
|
|
855
|
-
else if ("icon" in data && node.shape === void 0) node.shape = "icon";
|
|
856
|
-
node.data = data;
|
|
857
|
-
i = end;
|
|
858
|
-
}
|
|
859
|
-
} else {
|
|
860
|
-
for (const open of BRACKET_OPENERS) {
|
|
861
|
-
if (!input.startsWith(open, i)) continue;
|
|
862
|
-
const closers = closersFor(open);
|
|
863
|
-
const found = findCloser(input, i + open.length, closers);
|
|
864
|
-
if (!found) break;
|
|
865
|
-
const shapeDef = shapeForBracket(open, found.closer);
|
|
866
|
-
if (!shapeDef) break;
|
|
867
|
-
const rawLabel = input.slice(i + open.length, found.index);
|
|
868
|
-
node.shape = shapeDef.name;
|
|
869
|
-
node.form = "bracket";
|
|
870
|
-
node.label = unquoteLabel(rawLabel);
|
|
871
|
-
node.labelQuoted = rawLabel.trim().startsWith('"');
|
|
872
|
-
i = found.index + found.closer.length;
|
|
873
|
-
break;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
while (input.startsWith(":::", i)) {
|
|
877
|
-
const cls = readId(input, i + 3);
|
|
878
|
-
if (!cls) break;
|
|
879
|
-
node.classes.push(cls);
|
|
880
|
-
i += 3 + cls.length;
|
|
881
|
-
}
|
|
882
|
-
return { node, end: i };
|
|
883
|
-
}
|
|
884
|
-
function skipWs(input, pos) {
|
|
885
|
-
let i = pos;
|
|
886
|
-
while (i < input.length && /\s/.test(input[i])) i++;
|
|
887
|
-
return i;
|
|
888
|
-
}
|
|
889
|
-
function parseChain(text) {
|
|
890
|
-
const groups = [];
|
|
891
|
-
const links = [];
|
|
892
|
-
let pos = skipWs(text, 0);
|
|
893
|
-
let group = [];
|
|
894
|
-
const first = readNode(text, pos);
|
|
895
|
-
if (!first) return null;
|
|
896
|
-
group.push(first.node);
|
|
897
|
-
pos = skipWs(text, first.end);
|
|
898
|
-
while (pos < text.length) {
|
|
899
|
-
if (text[pos] === "&") {
|
|
900
|
-
pos = skipWs(text, pos + 1);
|
|
901
|
-
const n = readNode(text, pos);
|
|
902
|
-
if (!n) return null;
|
|
903
|
-
group.push(n.node);
|
|
904
|
-
pos = skipWs(text, n.end);
|
|
905
|
-
continue;
|
|
906
|
-
}
|
|
907
|
-
const link = parseLink(text.slice(pos));
|
|
908
|
-
if (link) {
|
|
909
|
-
groups.push(group);
|
|
910
|
-
group = [];
|
|
911
|
-
links.push({
|
|
912
|
-
id: link.id,
|
|
913
|
-
pattern: link.pattern,
|
|
914
|
-
arrowStart: link.arrowStart,
|
|
915
|
-
arrowEnd: link.arrowEnd,
|
|
916
|
-
length: link.length,
|
|
917
|
-
label: link.label,
|
|
918
|
-
labelStyle: link.labelStyle
|
|
919
|
-
});
|
|
920
|
-
pos = skipWs(text, pos + link.consumed);
|
|
921
|
-
const n = readNode(text, pos);
|
|
922
|
-
if (!n) return null;
|
|
923
|
-
group.push(n.node);
|
|
924
|
-
pos = skipWs(text, n.end);
|
|
925
|
-
continue;
|
|
926
|
-
}
|
|
927
|
-
return null;
|
|
928
|
-
}
|
|
929
|
-
groups.push(group);
|
|
930
|
-
return { groups, links };
|
|
931
|
-
}
|
|
932
|
-
function splitStyles(text) {
|
|
933
|
-
return text.split(",").map((s) => s.trim()).filter(Boolean);
|
|
934
|
-
}
|
|
935
|
-
function parseStatement(line) {
|
|
936
|
-
const indent = line.match(/^\s*/)[0];
|
|
937
|
-
const text = line.trim();
|
|
938
|
-
if (text === "" || text.startsWith("%%")) return { kind: "raw", raw: line };
|
|
939
|
-
let m;
|
|
940
|
-
if (/^end\s*$/i.test(text)) return { kind: "end", raw: line, indent };
|
|
941
|
-
if (m = /^direction\s+(TB|TD|BT|LR|RL)\s*$/i.exec(text)) {
|
|
942
|
-
return { kind: "direction", raw: line, indent, direction: m[1].toUpperCase() };
|
|
943
|
-
}
|
|
944
|
-
if (m = /^subgraph\s+(.*)$/i.exec(text)) {
|
|
945
|
-
const rest = m[1].trim();
|
|
946
|
-
let sm;
|
|
947
|
-
if (sm = /^([^\s["]+)\s*\[(.*)\]\s*$/.exec(rest)) {
|
|
948
|
-
return {
|
|
949
|
-
kind: "subgraph",
|
|
950
|
-
raw: line,
|
|
951
|
-
indent,
|
|
952
|
-
id: sm[1],
|
|
953
|
-
title: unquoteLabel(sm[2]),
|
|
954
|
-
titleForm: "bracket"
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
if (sm = /^"(.*)"\s*$/.exec(rest)) {
|
|
958
|
-
return { kind: "subgraph", raw: line, indent, id: sm[1], title: sm[1], titleForm: "bare" };
|
|
959
|
-
}
|
|
960
|
-
if (/\s/.test(rest)) {
|
|
961
|
-
return { kind: "subgraph", raw: line, indent, id: rest, title: rest, titleForm: "bare" };
|
|
962
|
-
}
|
|
963
|
-
return { kind: "subgraph", raw: line, indent, id: rest, titleForm: "none" };
|
|
964
|
-
}
|
|
965
|
-
if (m = /^classDef\s+([^\s]+)\s+(.*)$/.exec(text)) {
|
|
966
|
-
return {
|
|
967
|
-
kind: "classDef",
|
|
968
|
-
raw: line,
|
|
969
|
-
indent,
|
|
970
|
-
names: m[1].split(","),
|
|
971
|
-
styles: splitStyles(m[2])
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
if (m = /^class\s+([^\s]+)\s+([^\s]+)\s*$/.exec(text)) {
|
|
975
|
-
return { kind: "class", raw: line, indent, ids: m[1].split(","), className: m[2] };
|
|
976
|
-
}
|
|
977
|
-
if (m = /^style\s+([^\s]+)\s+(.*)$/.exec(text)) {
|
|
978
|
-
return { kind: "style", raw: line, indent, id: m[1], styles: splitStyles(m[2]) };
|
|
979
|
-
}
|
|
980
|
-
if (m = /^linkStyle\s+(default|[\d,\s]+)\s+(.*)$/.exec(text)) {
|
|
981
|
-
const targets = m[1] === "default" ? "default" : m[1].split(",").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n));
|
|
982
|
-
return { kind: "linkStyle", raw: line, indent, targets, styles: splitStyles(m[2]) };
|
|
983
|
-
}
|
|
984
|
-
if (m = /^click\s+([^\s]+)\s+(.*)$/.exec(text)) {
|
|
985
|
-
return { kind: "click", raw: line, indent, id: m[1], rest: m[2] };
|
|
986
|
-
}
|
|
987
|
-
if (/^(accTitle|accDescr)\b/.test(text) || /^title\s/.test(text))
|
|
988
|
-
return { kind: "raw", raw: line };
|
|
989
|
-
const chain = parseChain(text);
|
|
990
|
-
if (chain) return { kind: "chain", raw: line, indent, chain };
|
|
991
|
-
return { kind: "raw", raw: line };
|
|
992
|
-
}
|
|
993
|
-
function formatNode(node) {
|
|
994
|
-
let out = node.id;
|
|
995
|
-
const shape = node.shape ? resolveShape(node.shape) : void 0;
|
|
996
|
-
const useData = node.form === "data" || node.shape && !shape?.bracket || node.data && Object.keys(node.data).length;
|
|
997
|
-
if (useData && (node.shape || node.label !== void 0 || node.data && Object.keys(node.data).length)) {
|
|
998
|
-
const data = {};
|
|
999
|
-
const implied = node.shape === "image" && node.data && "img" in node.data || node.shape === "icon" && node.data && "icon" in node.data;
|
|
1000
|
-
if (node.shape && !implied) data.shape = node.shape;
|
|
1001
|
-
if (node.label !== void 0) data.label = JSON.stringify(node.label);
|
|
1002
|
-
Object.assign(data, node.data ?? {});
|
|
1003
|
-
out += formatShapeData(data);
|
|
1004
|
-
} else if (shape?.bracket && (node.label !== void 0 || node.shape !== "rect" || node.form === "bracket")) {
|
|
1005
|
-
const label = node.label ?? node.id;
|
|
1006
|
-
const text = node.labelQuoted ? `"${label.replace(/"/g, "#quot;")}"` : quoteLabelIfNeeded(label);
|
|
1007
|
-
out += `${shape.bracket.open}${text}${shape.bracket.close}`;
|
|
1008
|
-
} else if (node.label !== void 0 && node.label !== node.id) {
|
|
1009
|
-
out += `[${quoteLabelIfNeeded(node.label)}]`;
|
|
1010
|
-
}
|
|
1011
|
-
for (const cls of node.classes) out += `:::${cls}`;
|
|
1012
|
-
return out;
|
|
1013
|
-
}
|
|
1014
|
-
function formatChain(chain) {
|
|
1015
|
-
const parts = [];
|
|
1016
|
-
chain.groups.forEach((group, i) => {
|
|
1017
|
-
parts.push(group.map(formatNode).join(" & "));
|
|
1018
|
-
const link = chain.links[i];
|
|
1019
|
-
if (link) parts.push(formatLink(link));
|
|
1020
|
-
});
|
|
1021
|
-
return parts.join(" ");
|
|
1022
|
-
}
|
|
1023
|
-
function formatStatement(stmt) {
|
|
1024
|
-
if (stmt.kind === "raw" || stmt.kind === "end") return stmt.raw;
|
|
1025
|
-
if (!stmt.dirty) return stmt.raw;
|
|
1026
|
-
switch (stmt.kind) {
|
|
1027
|
-
case "chain":
|
|
1028
|
-
return stmt.indent + formatChain(stmt.chain);
|
|
1029
|
-
case "subgraph": {
|
|
1030
|
-
if (stmt.titleForm === "none" || stmt.title === void 0)
|
|
1031
|
-
return `${stmt.indent}subgraph ${stmt.id}`;
|
|
1032
|
-
if (stmt.titleForm === "bare")
|
|
1033
|
-
return `${stmt.indent}subgraph ${stmt.title.includes(" ") || stmt.title !== stmt.id ? `"${stmt.title}"` : stmt.title}`;
|
|
1034
|
-
return `${stmt.indent}subgraph ${stmt.id} [${quoteLabelIfNeeded(stmt.title)}]`;
|
|
1035
|
-
}
|
|
1036
|
-
case "direction":
|
|
1037
|
-
return `${stmt.indent}direction ${stmt.direction}`;
|
|
1038
|
-
case "classDef":
|
|
1039
|
-
return `${stmt.indent}classDef ${stmt.names.join(",")} ${stmt.styles.join(",")}`;
|
|
1040
|
-
case "class":
|
|
1041
|
-
return `${stmt.indent}class ${stmt.ids.join(",")} ${stmt.className}`;
|
|
1042
|
-
case "style":
|
|
1043
|
-
return `${stmt.indent}style ${stmt.id} ${stmt.styles.join(",")}`;
|
|
1044
|
-
case "linkStyle":
|
|
1045
|
-
return `${stmt.indent}linkStyle ${stmt.targets === "default" ? "default" : stmt.targets.join(",")} ${stmt.styles.join(",")}`;
|
|
1046
|
-
case "click":
|
|
1047
|
-
return `${stmt.indent}click ${stmt.id} ${stmt.rest}`;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
function joinLogicalLines(lines) {
|
|
1051
|
-
const out = [];
|
|
1052
|
-
let i = 0;
|
|
1053
|
-
while (i < lines.length) {
|
|
1054
|
-
let cur = lines[i];
|
|
1055
|
-
const at = cur.indexOf("@{");
|
|
1056
|
-
if (at !== -1 && readBraceBlock(cur, at + 1) === -1) {
|
|
1057
|
-
let j = i + 1;
|
|
1058
|
-
while (j < lines.length) {
|
|
1059
|
-
cur += "\n" + lines[j];
|
|
1060
|
-
j++;
|
|
1061
|
-
if (readBraceBlock(cur, at + 1) !== -1) break;
|
|
1062
|
-
}
|
|
1063
|
-
out.push(cur);
|
|
1064
|
-
i = j;
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
out.push(cur);
|
|
1068
|
-
i++;
|
|
1069
|
-
}
|
|
1070
|
-
return out;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// src/diagram/flowchart/parse.ts
|
|
1074
|
-
function autoEdgeId(source, target, ordinal) {
|
|
1075
|
-
return `L_${source}_${target}_${ordinal}`;
|
|
1076
|
-
}
|
|
1077
|
-
var HEADER_DIRECTION = /^\s*(?:flowchart|graph)(?:-elk)?\s*(TB|TD|BT|LR|RL)?\s*;?\s*$/i;
|
|
1078
|
-
function headerDirection(header) {
|
|
1079
|
-
const m = HEADER_DIRECTION.exec(header);
|
|
1080
|
-
return (m?.[1] ?? "TB").toUpperCase();
|
|
1081
|
-
}
|
|
1082
|
-
function tokenizeBody(bodyLines) {
|
|
1083
|
-
const statements = [];
|
|
1084
|
-
for (const logical of joinLogicalLines(bodyLines)) {
|
|
1085
|
-
if (logical.includes("@{") || !logical.includes(";")) {
|
|
1086
|
-
statements.push(parseStatement(logical));
|
|
1087
|
-
continue;
|
|
1088
|
-
}
|
|
1089
|
-
const segments = splitStatements(logical);
|
|
1090
|
-
const nonEmpty = segments.filter((s) => s.trim() !== "");
|
|
1091
|
-
if (nonEmpty.length <= 1) {
|
|
1092
|
-
const stmt = parseStatement(nonEmpty[0] ?? logical);
|
|
1093
|
-
stmt.raw = logical;
|
|
1094
|
-
statements.push(stmt);
|
|
1095
|
-
continue;
|
|
1096
|
-
}
|
|
1097
|
-
const parsed = nonEmpty.map((seg, i) => parseStatement(i === 0 ? seg : seg.trimStart()));
|
|
1098
|
-
statements.push(...withCompoundMarker(parsed, logical));
|
|
1099
|
-
}
|
|
1100
|
-
return statements;
|
|
1101
|
-
}
|
|
1102
|
-
var COMPOUND = /* @__PURE__ */ Symbol("compound");
|
|
1103
|
-
function withCompoundMarker(parsed, raw) {
|
|
1104
|
-
parsed.forEach((stmt, i) => markCompound(stmt, parsed, i === 0 ? raw : ""));
|
|
1105
|
-
return parsed;
|
|
1106
|
-
}
|
|
1107
|
-
function markCompound(stmt, members, raw) {
|
|
1108
|
-
stmt[COMPOUND] = { members, raw };
|
|
1109
|
-
}
|
|
1110
|
-
function compoundInfo(stmt) {
|
|
1111
|
-
return stmt[COMPOUND];
|
|
1112
|
-
}
|
|
1113
|
-
function buildGraph(statements, direction, title) {
|
|
1114
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
1115
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1116
|
-
const edges = [];
|
|
1117
|
-
const classDefs = {};
|
|
1118
|
-
const diagnostics = [];
|
|
1119
|
-
const stack = [];
|
|
1120
|
-
const membership = /* @__PURE__ */ new Map();
|
|
1121
|
-
const classAssignments = [];
|
|
1122
|
-
const styleAssignments = [];
|
|
1123
|
-
const clicks = [];
|
|
1124
|
-
const linkStyles = [];
|
|
1125
|
-
const dataOnly = [];
|
|
1126
|
-
let ordinal = 0;
|
|
1127
|
-
const touch = (ref) => {
|
|
1128
|
-
let node = nodes.get(ref.id);
|
|
1129
|
-
if (!node) {
|
|
1130
|
-
node = { id: ref.id, label: ref.id, shape: DEFAULT_SHAPE, classes: [], styles: [] };
|
|
1131
|
-
nodes.set(ref.id, node);
|
|
1132
|
-
}
|
|
1133
|
-
if (ref.label !== void 0) node.label = ref.label;
|
|
1134
|
-
if (ref.shape) node.shape = ref.shape;
|
|
1135
|
-
for (const cls of ref.classes) if (!node.classes.includes(cls)) node.classes.push(cls);
|
|
1136
|
-
if (ref.data && Object.keys(ref.data).length) {
|
|
1137
|
-
node.data = { ...node.data ?? {}, ...stripQuotes(ref.data) };
|
|
1138
|
-
}
|
|
1139
|
-
const current = stack[stack.length - 1];
|
|
1140
|
-
if (current) membership.get(current).add(ref.id);
|
|
1141
|
-
return node;
|
|
1142
|
-
};
|
|
1143
|
-
statements.forEach((stmt, index) => {
|
|
1144
|
-
switch (stmt.kind) {
|
|
1145
|
-
case "subgraph": {
|
|
1146
|
-
const parent = stack[stack.length - 1];
|
|
1147
|
-
if (groups.has(stmt.id)) {
|
|
1148
|
-
diagnostics.push({
|
|
1149
|
-
severity: "warning",
|
|
1150
|
-
message: `Duplicate subgraph id "${stmt.id}"`,
|
|
1151
|
-
line: index + 1,
|
|
1152
|
-
source: "pixerate"
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
groups.set(stmt.id, {
|
|
1156
|
-
id: stmt.id,
|
|
1157
|
-
label: stmt.title ?? stmt.id,
|
|
1158
|
-
parentId: parent,
|
|
1159
|
-
classes: [],
|
|
1160
|
-
styles: []
|
|
1161
|
-
});
|
|
1162
|
-
membership.set(stmt.id, /* @__PURE__ */ new Set());
|
|
1163
|
-
if (parent) membership.get(parent).add(stmt.id);
|
|
1164
|
-
stack.push(stmt.id);
|
|
1165
|
-
break;
|
|
1166
|
-
}
|
|
1167
|
-
case "end":
|
|
1168
|
-
if (!stack.length) {
|
|
1169
|
-
diagnostics.push({
|
|
1170
|
-
severity: "warning",
|
|
1171
|
-
message: "Stray `end` without a subgraph",
|
|
1172
|
-
line: index + 1,
|
|
1173
|
-
source: "pixerate"
|
|
1174
|
-
});
|
|
1175
|
-
}
|
|
1176
|
-
stack.pop();
|
|
1177
|
-
break;
|
|
1178
|
-
case "direction": {
|
|
1179
|
-
const current = stack[stack.length - 1];
|
|
1180
|
-
if (current) groups.get(current).direction = stmt.direction;
|
|
1181
|
-
break;
|
|
1182
|
-
}
|
|
1183
|
-
case "chain": {
|
|
1184
|
-
const { groups: ngroups, links } = stmt.chain;
|
|
1185
|
-
if (ngroups.length === 1 && ngroups[0].length === 1 && links.length === 0) {
|
|
1186
|
-
const ref = ngroups[0];
|
|
1187
|
-
const only = ref[0];
|
|
1188
|
-
if (only.form === "data" && only.data && !only.shape && only.label === void 0 && ("animate" in only.data || "animation" in only.data)) {
|
|
1189
|
-
dataOnly.push({ id: only.id, data: only.data });
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
for (const group of ngroups) for (const ref of group) touch(ref);
|
|
1194
|
-
links.forEach((link, i) => {
|
|
1195
|
-
const from = ngroups[i];
|
|
1196
|
-
const to = ngroups[i + 1];
|
|
1197
|
-
for (const s of from) {
|
|
1198
|
-
for (const t of to) {
|
|
1199
|
-
const id = link.id ?? autoEdgeId(s.id, t.id, ordinal);
|
|
1200
|
-
edges.push({
|
|
1201
|
-
id,
|
|
1202
|
-
source: s.id,
|
|
1203
|
-
target: t.id,
|
|
1204
|
-
label: link.label,
|
|
1205
|
-
pattern: link.pattern,
|
|
1206
|
-
arrowStart: link.arrowStart,
|
|
1207
|
-
arrowEnd: link.arrowEnd,
|
|
1208
|
-
length: link.length,
|
|
1209
|
-
classes: [],
|
|
1210
|
-
styles: [],
|
|
1211
|
-
userDefinedId: !!link.id
|
|
1212
|
-
});
|
|
1213
|
-
ordinal++;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
break;
|
|
1218
|
-
}
|
|
1219
|
-
case "classDef":
|
|
1220
|
-
for (const name of stmt.names) classDefs[name] = [...stmt.styles];
|
|
1221
|
-
break;
|
|
1222
|
-
case "class":
|
|
1223
|
-
classAssignments.push({ ids: stmt.ids, className: stmt.className });
|
|
1224
|
-
break;
|
|
1225
|
-
case "style":
|
|
1226
|
-
styleAssignments.push({ id: stmt.id, styles: stmt.styles });
|
|
1227
|
-
break;
|
|
1228
|
-
case "linkStyle":
|
|
1229
|
-
linkStyles.push({ targets: stmt.targets, styles: stmt.styles });
|
|
1230
|
-
break;
|
|
1231
|
-
case "click":
|
|
1232
|
-
clicks.push({ id: stmt.id, rest: stmt.rest });
|
|
1233
|
-
break;
|
|
1234
|
-
default:
|
|
1235
|
-
break;
|
|
1236
|
-
}
|
|
1237
|
-
});
|
|
1238
|
-
if (stack.length) {
|
|
1239
|
-
diagnostics.push({
|
|
1240
|
-
severity: "warning",
|
|
1241
|
-
message: `${stack.length} subgraph(s) missing \`end\``,
|
|
1242
|
-
source: "pixerate"
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
const parentOf2 = /* @__PURE__ */ new Map();
|
|
1246
|
-
for (const [groupId, members] of membership) {
|
|
1247
|
-
for (const member of members) parentOf2.set(member, groupId);
|
|
1248
|
-
}
|
|
1249
|
-
for (const node of nodes.values()) {
|
|
1250
|
-
const parent = parentOf2.get(node.id);
|
|
1251
|
-
if (parent && groups.has(parent)) node.parentId = parent;
|
|
1252
|
-
}
|
|
1253
|
-
for (const group of groups.values()) {
|
|
1254
|
-
const parent = parentOf2.get(group.id);
|
|
1255
|
-
if (parent && parent !== group.id && groups.has(parent)) group.parentId = parent;
|
|
1256
|
-
}
|
|
1257
|
-
for (const { ids, className } of classAssignments) {
|
|
1258
|
-
for (const id of ids) {
|
|
1259
|
-
const node = nodes.get(id);
|
|
1260
|
-
const group = groups.get(id);
|
|
1261
|
-
const target = node ?? group;
|
|
1262
|
-
if (target && !target.classes.includes(className)) target.classes.push(className);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
for (const { id, styles } of styleAssignments) {
|
|
1266
|
-
const target = nodes.get(id) ?? groups.get(id);
|
|
1267
|
-
if (target) target.styles.push(...styles);
|
|
1268
|
-
}
|
|
1269
|
-
for (const { id, rest } of clicks) {
|
|
1270
|
-
const node = nodes.get(id);
|
|
1271
|
-
if (!node) continue;
|
|
1272
|
-
const parsed = parseClick(rest);
|
|
1273
|
-
if (parsed.href) {
|
|
1274
|
-
node.link = parsed.href;
|
|
1275
|
-
node.linkTarget = parsed.target;
|
|
1276
|
-
}
|
|
1277
|
-
if (parsed.tooltip) node.tooltip = parsed.tooltip;
|
|
1278
|
-
if (parsed.callback) node.data = { ...node.data ?? {}, callback: parsed.callback };
|
|
1279
|
-
}
|
|
1280
|
-
for (const { targets, styles } of linkStyles) {
|
|
1281
|
-
if (targets === "default") {
|
|
1282
|
-
for (const edge of edges) edge.styles.push(...styles);
|
|
1283
|
-
} else {
|
|
1284
|
-
for (const idx of targets) {
|
|
1285
|
-
const edge = edges[idx];
|
|
1286
|
-
if (edge) edge.styles.push(...styles);
|
|
1287
|
-
else
|
|
1288
|
-
diagnostics.push({
|
|
1289
|
-
severity: "warning",
|
|
1290
|
-
message: `linkStyle index ${idx} has no edge`,
|
|
1291
|
-
source: "pixerate"
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
for (const { id, data } of dataOnly) {
|
|
1297
|
-
const edge = edges.find((e) => e.id === id);
|
|
1298
|
-
if (edge) {
|
|
1299
|
-
if (data.animate !== void 0) edge.animate = data.animate.trim() === "true";
|
|
1300
|
-
if (data.animation !== void 0) {
|
|
1301
|
-
const a = data.animation.trim();
|
|
1302
|
-
if (a === "fast" || a === "slow") edge.animation = a;
|
|
1303
|
-
}
|
|
1304
|
-
} else {
|
|
1305
|
-
const node = {
|
|
1306
|
-
id,
|
|
1307
|
-
label: id,
|
|
1308
|
-
shape: DEFAULT_SHAPE,
|
|
1309
|
-
classes: [],
|
|
1310
|
-
styles: [],
|
|
1311
|
-
data: stripQuotes(data)
|
|
1312
|
-
};
|
|
1313
|
-
nodes.set(id, node);
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
return {
|
|
1317
|
-
graph: {
|
|
1318
|
-
direction,
|
|
1319
|
-
nodes: [...nodes.values()],
|
|
1320
|
-
edges,
|
|
1321
|
-
groups: [...groups.values()],
|
|
1322
|
-
classDefs,
|
|
1323
|
-
title
|
|
1324
|
-
},
|
|
1325
|
-
diagnostics
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
function stripQuotes(data) {
|
|
1329
|
-
const out = {};
|
|
1330
|
-
for (const [k, v] of Object.entries(data)) {
|
|
1331
|
-
const t = v.trim();
|
|
1332
|
-
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))
|
|
1333
|
-
out[k] = t.slice(1, -1);
|
|
1334
|
-
else if (t === "true" || t === "false") out[k] = t === "true";
|
|
1335
|
-
else if (/^-?\d+(\.\d+)?$/.test(t)) out[k] = Number(t);
|
|
1336
|
-
else out[k] = t;
|
|
1337
|
-
}
|
|
1338
|
-
return out;
|
|
1339
|
-
}
|
|
1340
|
-
function parseClick(rest) {
|
|
1341
|
-
const out = {};
|
|
1342
|
-
const strings = [...rest.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
|
|
1343
|
-
const words = rest.replace(/"[^"]*"/g, "").trim().split(/\s+/).filter(Boolean);
|
|
1344
|
-
const targetWord = words.find((w) => /^_(blank|self|parent|top)$/.test(w));
|
|
1345
|
-
if (targetWord) out.target = targetWord;
|
|
1346
|
-
if (words[0] === "href") {
|
|
1347
|
-
out.href = strings[0];
|
|
1348
|
-
out.tooltip = strings[1];
|
|
1349
|
-
} else if (words[0] === "call" || words[0] === "callback") {
|
|
1350
|
-
out.callback = words[0] === "callback" ? words[1] : rest.replace(/^call\s+/, "").replace(/"[^"]*"/g, "").trim();
|
|
1351
|
-
out.tooltip = strings[0];
|
|
1352
|
-
} else if (strings.length && /^https?:|^\/|^#|^mailto:/.test(strings[0])) {
|
|
1353
|
-
out.href = strings[0];
|
|
1354
|
-
out.tooltip = strings[1];
|
|
1355
|
-
} else if (words.length) {
|
|
1356
|
-
out.callback = words[0];
|
|
1357
|
-
out.tooltip = strings[0];
|
|
1358
|
-
}
|
|
1359
|
-
return out;
|
|
1360
|
-
}
|
|
1361
|
-
function parseFlowchart(_source, ctx) {
|
|
1362
|
-
const statements = tokenizeBody(ctx.bodyLines);
|
|
1363
|
-
const direction = headerDirection(ctx.header);
|
|
1364
|
-
const title = typeof ctx.preamble.frontmatter?.title === "string" ? ctx.preamble.frontmatter.title : void 0;
|
|
1365
|
-
const { graph, diagnostics } = buildGraph(statements, direction, title);
|
|
1366
|
-
return {
|
|
1367
|
-
family: "flowchart",
|
|
1368
|
-
tier: "editable",
|
|
1369
|
-
source: ctx.source,
|
|
1370
|
-
preamble: ctx.preamble,
|
|
1371
|
-
header: ctx.header,
|
|
1372
|
-
trailing: ctx.trailing,
|
|
1373
|
-
eol: ctx.eol,
|
|
1374
|
-
graph,
|
|
1375
|
-
meta: ctx.meta,
|
|
1376
|
-
metaCarrier: ctx.metaCarrier,
|
|
1377
|
-
body: { statements },
|
|
1378
|
-
diagnostics,
|
|
1379
|
-
dirty: false
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// src/diagram/flowchart/serialize.ts
|
|
1384
|
-
function serializeStatements(statements) {
|
|
1385
|
-
const out = [];
|
|
1386
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
1387
|
-
const present = new Set(statements);
|
|
1388
|
-
for (const stmt of statements) {
|
|
1389
|
-
if (emitted.has(stmt)) continue;
|
|
1390
|
-
const compound = compoundInfo(stmt);
|
|
1391
|
-
if (compound) {
|
|
1392
|
-
const members = compound.members.filter((m) => present.has(m));
|
|
1393
|
-
const complete = members.length === compound.members.length;
|
|
1394
|
-
const anyDirty = members.some((m) => "dirty" in m && m.dirty);
|
|
1395
|
-
if (complete && !anyDirty) {
|
|
1396
|
-
out.push(compound.raw);
|
|
1397
|
-
for (const member of compound.members) emitted.add(member);
|
|
1398
|
-
continue;
|
|
1399
|
-
}
|
|
1400
|
-
const indent = "indent" in compound.members[0] ? compound.members[0].indent : "";
|
|
1401
|
-
for (const member of members) {
|
|
1402
|
-
const text = formatStatement(member);
|
|
1403
|
-
if (text.trim() === "") continue;
|
|
1404
|
-
out.push(text.startsWith(indent) ? text : indent + text.trimStart());
|
|
1405
|
-
}
|
|
1406
|
-
for (const member of compound.members) emitted.add(member);
|
|
1407
|
-
continue;
|
|
1408
|
-
}
|
|
1409
|
-
out.push(formatStatement(stmt));
|
|
1410
|
-
emitted.add(stmt);
|
|
1411
|
-
}
|
|
1412
|
-
return out;
|
|
1413
|
-
}
|
|
1414
|
-
function detectIndent(statements) {
|
|
1415
|
-
for (const stmt of statements) {
|
|
1416
|
-
if ("indent" in stmt && stmt.indent) return stmt.indent;
|
|
1417
|
-
}
|
|
1418
|
-
return " ";
|
|
1419
|
-
}
|
|
1420
|
-
function assembleDocument(doc, bodyLines, indent) {
|
|
1421
|
-
const meta = isMetaEmpty(doc.meta) ? null : doc.meta;
|
|
1422
|
-
const preambleLines = doc.metaCarrier === "frontmatter" ? writeMetaToPreamble(doc.preamble, meta) : [...doc.preamble.lines];
|
|
1423
|
-
const lines = [...preambleLines];
|
|
1424
|
-
if (doc.header) lines.push(doc.header);
|
|
1425
|
-
if (meta && doc.metaCarrier === "comment") lines.push(metaCommentLine(meta, indent));
|
|
1426
|
-
lines.push(...bodyLines);
|
|
1427
|
-
return lines.join(doc.eol) + doc.trailing;
|
|
1428
|
-
}
|
|
1429
|
-
function serializeFlowchart(doc) {
|
|
1430
|
-
const bodyLines = serializeStatements(doc.body.statements);
|
|
1431
|
-
return assembleDocument(doc, bodyLines, detectIndent(doc.body.statements));
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
// src/diagram/flowchart/edit.ts
|
|
1435
|
-
function applyFlowchartEdit(doc, op) {
|
|
1436
|
-
if (op.type === "removeEdges" && op.ids.length > 1) return removeEdgesOneByOne(doc, op.ids);
|
|
1437
|
-
const statements = cloneStatements(doc.body.statements);
|
|
1438
|
-
const work = {
|
|
1439
|
-
...doc,
|
|
1440
|
-
body: { statements },
|
|
1441
|
-
meta: { ...doc.meta, layout: { ...doc.meta.layout }, sizes: { ...doc.meta.sizes } }
|
|
1442
|
-
};
|
|
1443
|
-
const indent = detectIndent(statements);
|
|
1444
|
-
switch (op.type) {
|
|
1445
|
-
case "moveNodes":
|
|
1446
|
-
for (const { id, position } of op.moves) work.meta.layout[id] = position;
|
|
1447
|
-
return reparse(work, false);
|
|
1448
|
-
case "resizeNode":
|
|
1449
|
-
if (op.size) work.meta.sizes[op.id] = op.size;
|
|
1450
|
-
else delete work.meta.sizes[op.id];
|
|
1451
|
-
return reparse(work, false);
|
|
1452
|
-
case "setViewport":
|
|
1453
|
-
work.meta.viewport = op.viewport;
|
|
1454
|
-
return reparse(work, false);
|
|
1455
|
-
case "setMetaExt":
|
|
1456
|
-
work.meta.ext = { ...work.meta.ext ?? {}, [op.key]: op.value };
|
|
1457
|
-
if (op.value === void 0) delete work.meta.ext[op.key];
|
|
1458
|
-
return reparse(work, false);
|
|
1459
|
-
case "setDirection":
|
|
1460
|
-
work.header = setHeaderDirection(work.header, op.direction);
|
|
1461
|
-
return reparse(work, true);
|
|
1462
|
-
case "setTitle":
|
|
1463
|
-
work.preamble = {
|
|
1464
|
-
...work.preamble,
|
|
1465
|
-
lines: setFrontmatterTitle(work.preamble.lines, op.title)
|
|
1466
|
-
};
|
|
1467
|
-
return reparse(work, true);
|
|
1468
|
-
case "replaceSource":
|
|
1469
|
-
return parseFlowchartSource(op.source, work.metaCarrier);
|
|
1470
|
-
default:
|
|
1471
|
-
break;
|
|
1472
|
-
}
|
|
1473
|
-
switch (op.type) {
|
|
1474
|
-
case "addNode": {
|
|
1475
|
-
const ref = {
|
|
1476
|
-
id: op.node.id,
|
|
1477
|
-
label: op.node.label ?? op.node.id,
|
|
1478
|
-
shape: canonicalShape(op.node.shape ?? "rect"),
|
|
1479
|
-
form: "bracket",
|
|
1480
|
-
classes: [...op.node.classes ?? []]
|
|
1481
|
-
};
|
|
1482
|
-
const data = shapeDataFor(ref.shape, encodeData(op.node.data));
|
|
1483
|
-
if (Object.keys(data).length) {
|
|
1484
|
-
ref.form = "data";
|
|
1485
|
-
ref.data = data;
|
|
1486
|
-
}
|
|
1487
|
-
const stmt = {
|
|
1488
|
-
kind: "chain",
|
|
1489
|
-
raw: "",
|
|
1490
|
-
indent,
|
|
1491
|
-
chain: { groups: [[ref]], links: [] },
|
|
1492
|
-
dirty: true
|
|
1493
|
-
};
|
|
1494
|
-
insertIntoGroup(statements, stmt, op.node.parentId, indent);
|
|
1495
|
-
if (op.position) work.meta.layout[op.node.id] = op.position;
|
|
1496
|
-
if (op.node.styles?.length) {
|
|
1497
|
-
statements.push({
|
|
1498
|
-
kind: "style",
|
|
1499
|
-
raw: "",
|
|
1500
|
-
indent,
|
|
1501
|
-
id: op.node.id,
|
|
1502
|
-
styles: [...op.node.styles],
|
|
1503
|
-
dirty: true
|
|
1504
|
-
});
|
|
1505
|
-
}
|
|
1506
|
-
break;
|
|
1507
|
-
}
|
|
1508
|
-
case "removeNodes": {
|
|
1509
|
-
const ids = new Set(op.ids.filter((id) => !doc.graph.groups.some((g) => g.id === id)));
|
|
1510
|
-
for (const gid of op.ids.filter((id) => doc.graph.groups.some((g) => g.id === id))) {
|
|
1511
|
-
const range = groupRange(statements, gid);
|
|
1512
|
-
if (!range) continue;
|
|
1513
|
-
statements.splice(range.end, 1);
|
|
1514
|
-
statements.splice(range.start, 1);
|
|
1515
|
-
delete work.meta.layout[gid];
|
|
1516
|
-
delete work.meta.sizes[gid];
|
|
1517
|
-
}
|
|
1518
|
-
const removedEdgeOrdinals = edgeOrdinalsTouching(
|
|
1519
|
-
doc.graph.edges,
|
|
1520
|
-
(e) => ids.has(e.source) || ids.has(e.target)
|
|
1521
|
-
);
|
|
1522
|
-
removeNodesFromStatements(statements, ids);
|
|
1523
|
-
dropRedundantBareMentions(statements);
|
|
1524
|
-
filterInPlace(statements, (s) => {
|
|
1525
|
-
if (s.kind === "style" || s.kind === "click") return !ids.has(s.id);
|
|
1526
|
-
if (s.kind === "class") {
|
|
1527
|
-
s.ids = s.ids.filter((id) => !ids.has(id));
|
|
1528
|
-
if (!s.ids.length) return false;
|
|
1529
|
-
s.dirty = true;
|
|
1530
|
-
return true;
|
|
1531
|
-
}
|
|
1532
|
-
return true;
|
|
1533
|
-
});
|
|
1534
|
-
adjustLinkStyles(statements, removedEdgeOrdinals);
|
|
1535
|
-
for (const id of ids) {
|
|
1536
|
-
delete work.meta.layout[id];
|
|
1537
|
-
delete work.meta.sizes[id];
|
|
1538
|
-
}
|
|
1539
|
-
break;
|
|
1540
|
-
}
|
|
1541
|
-
case "renameNode": {
|
|
1542
|
-
const ref = declaringRef(statements, op.id, "label") ?? firstRef(statements, op.id);
|
|
1543
|
-
if (!ref) break;
|
|
1544
|
-
const node = doc.graph.nodes.find((n) => n.id === op.id);
|
|
1545
|
-
if (ref.ref.label === void 0) {
|
|
1546
|
-
ref.ref.shape = ref.ref.shape ?? node?.shape ?? "rect";
|
|
1547
|
-
ref.ref.form = ref.ref.form ?? "bracket";
|
|
1548
|
-
}
|
|
1549
|
-
ref.ref.label = op.label;
|
|
1550
|
-
ref.ref.labelQuoted = void 0;
|
|
1551
|
-
ref.stmt.dirty = true;
|
|
1552
|
-
break;
|
|
1553
|
-
}
|
|
1554
|
-
case "setNodeShape": {
|
|
1555
|
-
const shape = canonicalShape(op.shape);
|
|
1556
|
-
const ref = declaringRef(statements, op.id, "shape") ?? firstRef(statements, op.id);
|
|
1557
|
-
if (!ref) break;
|
|
1558
|
-
const node = doc.graph.nodes.find((n) => n.id === op.id);
|
|
1559
|
-
ref.ref.shape = shape;
|
|
1560
|
-
if (ref.ref.label === void 0) ref.ref.label = node?.label ?? op.id;
|
|
1561
|
-
ref.ref.form = ref.ref.form ?? "bracket";
|
|
1562
|
-
const data = shapeDataFor(shape, ref.ref.data ?? {});
|
|
1563
|
-
if (Object.keys(data).length) ref.ref.form = "data";
|
|
1564
|
-
ref.ref.data = Object.keys(data).length ? data : void 0;
|
|
1565
|
-
ref.stmt.dirty = true;
|
|
1566
|
-
break;
|
|
1567
|
-
}
|
|
1568
|
-
case "setNodeClasses": {
|
|
1569
|
-
for (const s of statements) {
|
|
1570
|
-
if (s.kind === "chain") {
|
|
1571
|
-
for (const g of s.chain.groups) {
|
|
1572
|
-
for (const r of g) {
|
|
1573
|
-
if (r.id === op.id && r.classes.length) {
|
|
1574
|
-
r.classes = [];
|
|
1575
|
-
s.dirty = true;
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
filterInPlace(statements, (s) => {
|
|
1582
|
-
if (s.kind !== "class") return true;
|
|
1583
|
-
if (!s.ids.includes(op.id)) return true;
|
|
1584
|
-
s.ids = s.ids.filter((id) => id !== op.id);
|
|
1585
|
-
s.dirty = true;
|
|
1586
|
-
return s.ids.length > 0;
|
|
1587
|
-
});
|
|
1588
|
-
const [first, ...rest] = op.classes;
|
|
1589
|
-
if (first) {
|
|
1590
|
-
const ref = declaringRef(statements, op.id, "label") ?? firstRef(statements, op.id);
|
|
1591
|
-
if (ref) {
|
|
1592
|
-
ref.ref.classes = [first];
|
|
1593
|
-
ref.stmt.dirty = true;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
for (const cls of rest)
|
|
1597
|
-
statements.push({
|
|
1598
|
-
kind: "class",
|
|
1599
|
-
raw: "",
|
|
1600
|
-
indent,
|
|
1601
|
-
ids: [op.id],
|
|
1602
|
-
className: cls,
|
|
1603
|
-
dirty: true
|
|
1604
|
-
});
|
|
1605
|
-
break;
|
|
1606
|
-
}
|
|
1607
|
-
case "setNodeStyles": {
|
|
1608
|
-
filterInPlace(statements, (s) => !(s.kind === "style" && s.id === op.id));
|
|
1609
|
-
if (op.styles.length)
|
|
1610
|
-
statements.push({
|
|
1611
|
-
kind: "style",
|
|
1612
|
-
raw: "",
|
|
1613
|
-
indent,
|
|
1614
|
-
id: op.id,
|
|
1615
|
-
styles: [...op.styles],
|
|
1616
|
-
dirty: true
|
|
1617
|
-
});
|
|
1618
|
-
break;
|
|
1619
|
-
}
|
|
1620
|
-
case "setNodeLink": {
|
|
1621
|
-
filterInPlace(statements, (s) => !(s.kind === "click" && s.id === op.id));
|
|
1622
|
-
if (op.link) {
|
|
1623
|
-
const parts = [`href "${op.link}"`];
|
|
1624
|
-
if (op.tooltip) parts.push(`"${op.tooltip}"`);
|
|
1625
|
-
if (op.target) parts.push(op.target);
|
|
1626
|
-
statements.push({
|
|
1627
|
-
kind: "click",
|
|
1628
|
-
raw: "",
|
|
1629
|
-
indent,
|
|
1630
|
-
id: op.id,
|
|
1631
|
-
rest: parts.join(" "),
|
|
1632
|
-
dirty: true
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
break;
|
|
1636
|
-
}
|
|
1637
|
-
case "setNodeData": {
|
|
1638
|
-
const ref = declaringRef(statements, op.id, "label") ?? firstRef(statements, op.id);
|
|
1639
|
-
if (!ref) break;
|
|
1640
|
-
const node = doc.graph.nodes.find((n) => n.id === op.id);
|
|
1641
|
-
ref.ref.form = "data";
|
|
1642
|
-
ref.ref.shape = ref.ref.shape ?? node?.shape ?? "rect";
|
|
1643
|
-
if (ref.ref.label === void 0) ref.ref.label = node?.label ?? op.id;
|
|
1644
|
-
const data = { ...ref.ref.data ?? {} };
|
|
1645
|
-
for (const [k, v] of Object.entries(op.data)) {
|
|
1646
|
-
if (v === void 0 || v === null) delete data[k];
|
|
1647
|
-
else data[k] = typeof v === "string" ? JSON.stringify(v) : String(v);
|
|
1648
|
-
}
|
|
1649
|
-
ref.ref.data = data;
|
|
1650
|
-
ref.stmt.dirty = true;
|
|
1651
|
-
break;
|
|
1652
|
-
}
|
|
1653
|
-
case "addEdge": {
|
|
1654
|
-
const link = {
|
|
1655
|
-
id: op.edge.userDefinedId ? op.edge.id : void 0,
|
|
1656
|
-
pattern: op.edge.pattern ?? "solid",
|
|
1657
|
-
arrowStart: op.edge.arrowStart ?? "none",
|
|
1658
|
-
arrowEnd: op.edge.arrowEnd ?? "arrow_point",
|
|
1659
|
-
length: op.edge.length ?? 1,
|
|
1660
|
-
label: op.edge.label || void 0,
|
|
1661
|
-
labelStyle: op.edge.label ? "pipe" : void 0
|
|
1662
|
-
};
|
|
1663
|
-
const stmt = {
|
|
1664
|
-
kind: "chain",
|
|
1665
|
-
raw: "",
|
|
1666
|
-
indent,
|
|
1667
|
-
chain: {
|
|
1668
|
-
groups: [[{ id: op.edge.source, classes: [] }], [{ id: op.edge.target, classes: [] }]],
|
|
1669
|
-
links: [link]
|
|
1670
|
-
},
|
|
1671
|
-
dirty: true
|
|
1672
|
-
};
|
|
1673
|
-
appendAtEnd(statements, stmt);
|
|
1674
|
-
if (op.edge.styles?.length) {
|
|
1675
|
-
statements.push({
|
|
1676
|
-
kind: "linkStyle",
|
|
1677
|
-
raw: "",
|
|
1678
|
-
indent,
|
|
1679
|
-
targets: [doc.graph.edges.length],
|
|
1680
|
-
styles: [...op.edge.styles],
|
|
1681
|
-
dirty: true
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
break;
|
|
1685
|
-
}
|
|
1686
|
-
case "removeEdges": {
|
|
1687
|
-
const ids = new Set(op.ids);
|
|
1688
|
-
const removedOrdinals = edgeOrdinalsTouching(doc.graph.edges, (e) => ids.has(e.id));
|
|
1689
|
-
for (const edge of doc.graph.edges) {
|
|
1690
|
-
if (!ids.has(edge.id)) continue;
|
|
1691
|
-
const located = locateEdge(statements, doc.graph.edges, edge.id);
|
|
1692
|
-
if (!located) continue;
|
|
1693
|
-
removeLinkFromChain(
|
|
1694
|
-
statements,
|
|
1695
|
-
located.stmt,
|
|
1696
|
-
located.linkIndex,
|
|
1697
|
-
located.source,
|
|
1698
|
-
located.target,
|
|
1699
|
-
indent
|
|
1700
|
-
);
|
|
1701
|
-
}
|
|
1702
|
-
adjustLinkStyles(statements, removedOrdinals);
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
case "setEdgeLabel":
|
|
1706
|
-
case "setEdgeStyle":
|
|
1707
|
-
case "reconnectEdge": {
|
|
1708
|
-
const located = locateEdge(statements, doc.graph.edges, op.id);
|
|
1709
|
-
if (!located) break;
|
|
1710
|
-
const stmt = isolateEdge(
|
|
1711
|
-
statements,
|
|
1712
|
-
located.stmt,
|
|
1713
|
-
located.linkIndex,
|
|
1714
|
-
located.source,
|
|
1715
|
-
located.target,
|
|
1716
|
-
indent
|
|
1717
|
-
);
|
|
1718
|
-
const link = stmt.chain.links[0];
|
|
1719
|
-
if (op.type === "setEdgeLabel") {
|
|
1720
|
-
link.label = op.label || void 0;
|
|
1721
|
-
if (link.label && !link.labelStyle) link.labelStyle = "pipe";
|
|
1722
|
-
} else if (op.type === "setEdgeStyle") {
|
|
1723
|
-
if (op.pattern) link.pattern = op.pattern;
|
|
1724
|
-
if (op.arrowStart) link.arrowStart = op.arrowStart;
|
|
1725
|
-
if (op.arrowEnd) link.arrowEnd = op.arrowEnd;
|
|
1726
|
-
if (op.length) link.length = op.length;
|
|
1727
|
-
if (op.styles) {
|
|
1728
|
-
const ordinal = doc.graph.edges.findIndex((e) => e.id === op.id);
|
|
1729
|
-
setLinkStyleFor(statements, ordinal, op.styles, indent);
|
|
1730
|
-
}
|
|
1731
|
-
} else {
|
|
1732
|
-
stmt.chain.groups[0] = [{ id: op.source, classes: [] }];
|
|
1733
|
-
stmt.chain.groups[1] = [{ id: op.target, classes: [] }];
|
|
1734
|
-
}
|
|
1735
|
-
stmt.dirty = true;
|
|
1736
|
-
break;
|
|
1737
|
-
}
|
|
1738
|
-
case "groupNodes": {
|
|
1739
|
-
const open = {
|
|
1740
|
-
kind: "subgraph",
|
|
1741
|
-
raw: "",
|
|
1742
|
-
indent,
|
|
1743
|
-
id: op.groupId,
|
|
1744
|
-
title: op.label,
|
|
1745
|
-
titleForm: "bracket",
|
|
1746
|
-
dirty: true
|
|
1747
|
-
};
|
|
1748
|
-
const members = op.nodeIds.map((id) => ({
|
|
1749
|
-
kind: "chain",
|
|
1750
|
-
raw: "",
|
|
1751
|
-
indent: indent + indent,
|
|
1752
|
-
chain: { groups: [[{ id, classes: [] }]], links: [] },
|
|
1753
|
-
dirty: true
|
|
1754
|
-
}));
|
|
1755
|
-
const block = [
|
|
1756
|
-
open,
|
|
1757
|
-
...members,
|
|
1758
|
-
{ kind: "end", raw: `${indent}end`, indent }
|
|
1759
|
-
];
|
|
1760
|
-
if (op.parentId) insertManyIntoGroup(statements, block, op.parentId, indent);
|
|
1761
|
-
else statements.push(...block);
|
|
1762
|
-
break;
|
|
1763
|
-
}
|
|
1764
|
-
case "ungroup": {
|
|
1765
|
-
const range = groupRange(statements, op.groupId);
|
|
1766
|
-
if (!range) break;
|
|
1767
|
-
const openIndent = statements[range.start].indent;
|
|
1768
|
-
const inner = openIndent + indent;
|
|
1769
|
-
for (let i = range.start + 1; i < range.end; i++) {
|
|
1770
|
-
const s = statements[i];
|
|
1771
|
-
if (s.kind === "raw" || s.kind === "end") {
|
|
1772
|
-
if (s.raw.startsWith(inner))
|
|
1773
|
-
s.raw = openIndent + s.raw.slice(inner.length);
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1776
|
-
if (s.indent.startsWith(inner)) {
|
|
1777
|
-
s.indent = openIndent + s.indent.slice(inner.length);
|
|
1778
|
-
s.dirty = true;
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
statements.splice(range.end, 1);
|
|
1782
|
-
statements.splice(range.start, 1);
|
|
1783
|
-
for (let i = range.start; i < range.end - 1; i++) {
|
|
1784
|
-
const s = statements[i];
|
|
1785
|
-
if (s && s.kind === "direction" && depthAt(statements, i) === depthAt(statements, range.start)) {
|
|
1786
|
-
statements.splice(i, 1);
|
|
1787
|
-
break;
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
delete work.meta.layout[op.groupId];
|
|
1791
|
-
delete work.meta.sizes[op.groupId];
|
|
1792
|
-
break;
|
|
1793
|
-
}
|
|
1794
|
-
case "renameGroup": {
|
|
1795
|
-
const range = groupRange(statements, op.id);
|
|
1796
|
-
if (!range) break;
|
|
1797
|
-
const open = statements[range.start];
|
|
1798
|
-
open.title = op.label;
|
|
1799
|
-
if (open.titleForm === "none") open.titleForm = "bracket";
|
|
1800
|
-
if (open.titleForm === "bare") {
|
|
1801
|
-
open.titleForm = "bracket";
|
|
1802
|
-
}
|
|
1803
|
-
open.dirty = true;
|
|
1804
|
-
break;
|
|
1805
|
-
}
|
|
1806
|
-
case "setGroupDirection": {
|
|
1807
|
-
const range = groupRange(statements, op.id);
|
|
1808
|
-
if (!range) break;
|
|
1809
|
-
let found = -1;
|
|
1810
|
-
for (let i = range.start + 1; i < range.end; i++) {
|
|
1811
|
-
const s = statements[i];
|
|
1812
|
-
if (s.kind === "direction" && depthAt(statements, i) === depthAt(statements, range.start) + 1) {
|
|
1813
|
-
found = i;
|
|
1814
|
-
break;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
if (op.direction) {
|
|
1818
|
-
if (found !== -1) {
|
|
1819
|
-
const s = statements[found];
|
|
1820
|
-
s.direction = op.direction;
|
|
1821
|
-
s.dirty = true;
|
|
1822
|
-
} else {
|
|
1823
|
-
const inner = statements[range.start].indent + indent;
|
|
1824
|
-
statements.splice(range.start + 1, 0, {
|
|
1825
|
-
kind: "direction",
|
|
1826
|
-
raw: "",
|
|
1827
|
-
indent: inner,
|
|
1828
|
-
direction: op.direction,
|
|
1829
|
-
dirty: true
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
} else if (found !== -1) {
|
|
1833
|
-
statements.splice(found, 1);
|
|
1834
|
-
}
|
|
1835
|
-
break;
|
|
1836
|
-
}
|
|
1837
|
-
case "setNodeParent": {
|
|
1838
|
-
for (const id of op.ids) {
|
|
1839
|
-
const bare = statements.map((s, i) => ({ s, i })).filter(
|
|
1840
|
-
({ s }) => s.kind === "chain" && s.chain.links.length === 0 && s.chain.groups[0].length === 1 && s.chain.groups[0][0].id === id
|
|
1841
|
-
);
|
|
1842
|
-
let decl;
|
|
1843
|
-
for (const { s, i } of bare.reverse()) {
|
|
1844
|
-
const ref = s.chain.groups[0][0];
|
|
1845
|
-
if (ref.label !== void 0 || ref.shape) decl = { ...ref };
|
|
1846
|
-
statements.splice(i, 1);
|
|
1847
|
-
}
|
|
1848
|
-
if (op.parentId) {
|
|
1849
|
-
const ref = decl ?? { id, classes: [] };
|
|
1850
|
-
const stmt = {
|
|
1851
|
-
kind: "chain",
|
|
1852
|
-
raw: "",
|
|
1853
|
-
indent,
|
|
1854
|
-
chain: { groups: [[ref]], links: [] },
|
|
1855
|
-
dirty: true
|
|
1856
|
-
};
|
|
1857
|
-
insertIntoGroup(statements, stmt, op.parentId, indent);
|
|
1858
|
-
} else if (decl) {
|
|
1859
|
-
appendAtEnd(statements, {
|
|
1860
|
-
kind: "chain",
|
|
1861
|
-
raw: "",
|
|
1862
|
-
indent,
|
|
1863
|
-
chain: { groups: [[decl]], links: [] },
|
|
1864
|
-
dirty: true
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
case "setClassDef": {
|
|
1871
|
-
let found = false;
|
|
1872
|
-
for (const s of statements) {
|
|
1873
|
-
if (s.kind !== "classDef") continue;
|
|
1874
|
-
if (s.names.length === 1 && s.names[0] === op.name) {
|
|
1875
|
-
found = true;
|
|
1876
|
-
if (op.styles) {
|
|
1877
|
-
s.styles = [...op.styles];
|
|
1878
|
-
s.dirty = true;
|
|
1879
|
-
}
|
|
1880
|
-
} else if (s.names.includes(op.name) && op.styles) {
|
|
1881
|
-
s.names = s.names.filter((n) => n !== op.name);
|
|
1882
|
-
s.dirty = true;
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
if (!op.styles)
|
|
1886
|
-
filterInPlace(
|
|
1887
|
-
statements,
|
|
1888
|
-
(s) => !(s.kind === "classDef" && s.names.length === 1 && s.names[0] === op.name)
|
|
1889
|
-
);
|
|
1890
|
-
else if (!found)
|
|
1891
|
-
statements.push({
|
|
1892
|
-
kind: "classDef",
|
|
1893
|
-
raw: "",
|
|
1894
|
-
indent,
|
|
1895
|
-
names: [op.name],
|
|
1896
|
-
styles: [...op.styles],
|
|
1897
|
-
dirty: true
|
|
1898
|
-
});
|
|
1899
|
-
break;
|
|
1900
|
-
}
|
|
1901
|
-
default:
|
|
1902
|
-
break;
|
|
1903
|
-
}
|
|
1904
|
-
return reparse(work, true);
|
|
1905
|
-
}
|
|
1906
|
-
function cloneStatements(statements) {
|
|
1907
|
-
const clones = statements.map(cloneStatement);
|
|
1908
|
-
const index = new Map(statements.map((s, i) => [s, i]));
|
|
1909
|
-
const done = /* @__PURE__ */ new Set();
|
|
1910
|
-
statements.forEach((original) => {
|
|
1911
|
-
const info = compoundInfo(original);
|
|
1912
|
-
if (!info || done.has(original)) return;
|
|
1913
|
-
const members = info.members.map((m) => clones[index.get(m)]);
|
|
1914
|
-
info.members.forEach((m, j) => {
|
|
1915
|
-
done.add(m);
|
|
1916
|
-
markCompound(members[j], members, j === 0 ? info.raw : "");
|
|
1917
|
-
});
|
|
1918
|
-
});
|
|
1919
|
-
return clones;
|
|
1920
|
-
}
|
|
1921
|
-
function cloneStatement(s) {
|
|
1922
|
-
if (s.kind === "chain") {
|
|
1923
|
-
return {
|
|
1924
|
-
...s,
|
|
1925
|
-
chain: {
|
|
1926
|
-
groups: s.chain.groups.map(
|
|
1927
|
-
(g) => g.map((r) => ({
|
|
1928
|
-
...r,
|
|
1929
|
-
classes: [...r.classes],
|
|
1930
|
-
data: r.data ? { ...r.data } : void 0
|
|
1931
|
-
}))
|
|
1932
|
-
),
|
|
1933
|
-
links: s.chain.links.map((l) => ({ ...l }))
|
|
1934
|
-
}
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
if (s.kind === "classDef") return { ...s, names: [...s.names], styles: [...s.styles] };
|
|
1938
|
-
if (s.kind === "class") return { ...s, ids: [...s.ids] };
|
|
1939
|
-
if (s.kind === "style") return { ...s, styles: [...s.styles] };
|
|
1940
|
-
if (s.kind === "linkStyle")
|
|
1941
|
-
return {
|
|
1942
|
-
...s,
|
|
1943
|
-
targets: s.targets === "default" ? "default" : [...s.targets],
|
|
1944
|
-
styles: [...s.styles]
|
|
1945
|
-
};
|
|
1946
|
-
return { ...s };
|
|
1947
|
-
}
|
|
1948
|
-
function reparse(work, structural) {
|
|
1949
|
-
const text = serializeFlowchart(work);
|
|
1950
|
-
const next = parseFlowchartSource(text, work.metaCarrier);
|
|
1951
|
-
next.dirty = doc_dirty(work, structural);
|
|
1952
|
-
return next;
|
|
1953
|
-
}
|
|
1954
|
-
function doc_dirty(work, structural) {
|
|
1955
|
-
return work.dirty || structural;
|
|
1956
|
-
}
|
|
1957
|
-
function parseFlowchartSource(source, carrier) {
|
|
1958
|
-
const split = splitSource(source);
|
|
1959
|
-
const fromBody = extractMetaFromBody(split.bodyLines);
|
|
1960
|
-
const fromFm = extractMetaFromFrontmatter(split.preamble);
|
|
1961
|
-
let meta = emptyMeta();
|
|
1962
|
-
if (fromFm) meta = mergeMeta(meta, fromFm);
|
|
1963
|
-
if (fromBody.meta) meta = mergeMeta(meta, fromBody.meta);
|
|
1964
|
-
return parseFlowchart(source, {
|
|
1965
|
-
source,
|
|
1966
|
-
eol: split.eol,
|
|
1967
|
-
preamble: split.preamble,
|
|
1968
|
-
header: split.header,
|
|
1969
|
-
bodyLines: fromBody.bodyLines,
|
|
1970
|
-
trailing: split.trailing,
|
|
1971
|
-
meta,
|
|
1972
|
-
metaCarrier: carrier ?? (fromFm && !fromBody.meta ? "frontmatter" : defaultCarrierFor("flowchart")),
|
|
1973
|
-
family: "flowchart"
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
function filterInPlace(arr, keep) {
|
|
1977
|
-
let j = 0;
|
|
1978
|
-
for (let i = 0; i < arr.length; i++) {
|
|
1979
|
-
if (keep(arr[i])) arr[j++] = arr[i];
|
|
1980
|
-
}
|
|
1981
|
-
arr.length = j;
|
|
1982
|
-
}
|
|
1983
|
-
function setHeaderDirection(header, direction) {
|
|
1984
|
-
const m = /^(\s*)(flowchart|graph)(-elk)?\b/i.exec(header);
|
|
1985
|
-
if (!m) return header;
|
|
1986
|
-
const keyword = `${m[2]}${m[3] ?? ""}`;
|
|
1987
|
-
const trailingSemi = /;\s*$/.test(header) ? ";" : "";
|
|
1988
|
-
return `${m[1]}${keyword} ${direction}${trailingSemi}`;
|
|
1989
|
-
}
|
|
1990
|
-
function setFrontmatterTitle(lines, title) {
|
|
1991
|
-
const out = [...lines];
|
|
1992
|
-
const hasFm = out[0] !== void 0 && /^---\s*$/.test(out[0]);
|
|
1993
|
-
if (!hasFm) {
|
|
1994
|
-
if (title === void 0) return out;
|
|
1995
|
-
return ["---", `title: ${title}`, "---", ...out];
|
|
1996
|
-
}
|
|
1997
|
-
const close = out.findIndex((l, i) => i > 0 && /^---\s*$/.test(l));
|
|
1998
|
-
const end = close === -1 ? out.length : close;
|
|
1999
|
-
const idx = out.findIndex((l, i) => i > 0 && i < end && /^title\s*:/.test(l));
|
|
2000
|
-
if (title === void 0) {
|
|
2001
|
-
if (idx !== -1) out.splice(idx, 1);
|
|
2002
|
-
const close2 = out.findIndex((l, i) => i > 0 && /^---\s*$/.test(l));
|
|
2003
|
-
if (close2 === 1) out.splice(0, 2);
|
|
2004
|
-
return out;
|
|
2005
|
-
}
|
|
2006
|
-
if (idx !== -1) out[idx] = `title: ${title}`;
|
|
2007
|
-
else out.splice(end, 0, `title: ${title}`);
|
|
2008
|
-
return out;
|
|
2009
|
-
}
|
|
2010
|
-
function depthAt(statements, index) {
|
|
2011
|
-
let depth = 0;
|
|
2012
|
-
for (let i = 0; i < index; i++) {
|
|
2013
|
-
const s = statements[i];
|
|
2014
|
-
if (s.kind === "subgraph") depth++;
|
|
2015
|
-
else if (s.kind === "end") depth = Math.max(0, depth - 1);
|
|
2016
|
-
}
|
|
2017
|
-
return depth;
|
|
2018
|
-
}
|
|
2019
|
-
function groupRange(statements, groupId) {
|
|
2020
|
-
const start = statements.findIndex((s) => s.kind === "subgraph" && s.id === groupId);
|
|
2021
|
-
if (start === -1) return null;
|
|
2022
|
-
let depth = 0;
|
|
2023
|
-
for (let i = start; i < statements.length; i++) {
|
|
2024
|
-
const s = statements[i];
|
|
2025
|
-
if (s.kind === "subgraph") depth++;
|
|
2026
|
-
else if (s.kind === "end") {
|
|
2027
|
-
depth--;
|
|
2028
|
-
if (depth === 0) return { start, end: i };
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
return { start, end: statements.length };
|
|
2032
|
-
}
|
|
2033
|
-
function appendAtEnd(statements, stmt) {
|
|
2034
|
-
statements.push(stmt);
|
|
2035
|
-
}
|
|
2036
|
-
function insertIntoGroup(statements, stmt, groupId, indent) {
|
|
2037
|
-
insertManyIntoGroup(statements, [stmt], groupId, indent);
|
|
2038
|
-
}
|
|
2039
|
-
function insertManyIntoGroup(statements, block, groupId, indent) {
|
|
2040
|
-
const range = groupId ? groupRange(statements, groupId) : null;
|
|
2041
|
-
if (!range) {
|
|
2042
|
-
statements.push(...block);
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
const open = statements[range.start];
|
|
2046
|
-
const inner = open.indent + indent;
|
|
2047
|
-
for (const s of block)
|
|
2048
|
-
if ("indent" in s)
|
|
2049
|
-
s.indent = inner + s.indent.slice(indent.length);
|
|
2050
|
-
statements.splice(range.end, 0, ...block);
|
|
2051
|
-
}
|
|
2052
|
-
function declaringRef(statements, id, prop) {
|
|
2053
|
-
for (const s of statements) {
|
|
2054
|
-
if (s.kind !== "chain") continue;
|
|
2055
|
-
for (const g of s.chain.groups) {
|
|
2056
|
-
for (const r of g) {
|
|
2057
|
-
if (r.id === id && r[prop] !== void 0) return { stmt: s, ref: r };
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
return null;
|
|
2062
|
-
}
|
|
2063
|
-
function firstRef(statements, id) {
|
|
2064
|
-
for (const s of statements) {
|
|
2065
|
-
if (s.kind !== "chain") continue;
|
|
2066
|
-
for (const g of s.chain.groups) for (const r of g) if (r.id === id) return { stmt: s, ref: r };
|
|
2067
|
-
}
|
|
2068
|
-
return null;
|
|
2069
|
-
}
|
|
2070
|
-
function edgeOrdinalsTouching(edges, predicate) {
|
|
2071
|
-
const out = [];
|
|
2072
|
-
edges.forEach((e, i) => {
|
|
2073
|
-
if (predicate(e)) out.push(i);
|
|
2074
|
-
});
|
|
2075
|
-
return out;
|
|
2076
|
-
}
|
|
2077
|
-
function adjustLinkStyles(statements, removedOrdinals) {
|
|
2078
|
-
if (!removedOrdinals.length) return;
|
|
2079
|
-
const removed = new Set(removedOrdinals);
|
|
2080
|
-
filterInPlace(statements, (s) => {
|
|
2081
|
-
if (s.kind !== "linkStyle" || s.targets === "default") return true;
|
|
2082
|
-
const next = s.targets.filter((t) => !removed.has(t)).map((t) => t - removedOrdinals.filter((r) => r < t).length);
|
|
2083
|
-
if (next.length !== s.targets.length || next.some((t, i) => t !== s.targets[i])) {
|
|
2084
|
-
s.targets = next;
|
|
2085
|
-
s.dirty = true;
|
|
2086
|
-
}
|
|
2087
|
-
return next.length > 0;
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
function setLinkStyleFor(statements, ordinal, styles, indent) {
|
|
2091
|
-
if (ordinal < 0) return;
|
|
2092
|
-
filterInPlace(statements, (s) => {
|
|
2093
|
-
if (s.kind !== "linkStyle" || s.targets === "default") return true;
|
|
2094
|
-
if (!s.targets.includes(ordinal)) return true;
|
|
2095
|
-
s.targets = s.targets.filter((t) => t !== ordinal);
|
|
2096
|
-
s.dirty = true;
|
|
2097
|
-
return s.targets.length > 0;
|
|
2098
|
-
});
|
|
2099
|
-
if (styles.length)
|
|
2100
|
-
statements.push({
|
|
2101
|
-
kind: "linkStyle",
|
|
2102
|
-
raw: "",
|
|
2103
|
-
indent,
|
|
2104
|
-
targets: [ordinal],
|
|
2105
|
-
styles: [...styles],
|
|
2106
|
-
dirty: true
|
|
2107
|
-
});
|
|
2108
|
-
}
|
|
2109
|
-
function locateEdge(statements, edges, edgeId) {
|
|
2110
|
-
const target = edges.find((e) => e.id === edgeId);
|
|
2111
|
-
if (!target) return null;
|
|
2112
|
-
const ordinal = edges.indexOf(target);
|
|
2113
|
-
let n = 0;
|
|
2114
|
-
for (const s of statements) {
|
|
2115
|
-
if (s.kind !== "chain") continue;
|
|
2116
|
-
const { groups, links } = s.chain;
|
|
2117
|
-
if (groups.length === 1 && groups[0].length === 1 && links.length === 0 && isEdgeDataStatement(groups[0][0]))
|
|
2118
|
-
continue;
|
|
2119
|
-
for (let i = 0; i < links.length; i++) {
|
|
2120
|
-
for (const from of groups[i]) {
|
|
2121
|
-
for (const to of groups[i + 1]) {
|
|
2122
|
-
if (n === ordinal) return { stmt: s, linkIndex: i, source: from.id, target: to.id };
|
|
2123
|
-
n++;
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
return null;
|
|
2129
|
-
}
|
|
2130
|
-
function isEdgeDataStatement(ref) {
|
|
2131
|
-
return ref.form === "data" && !!ref.data && !ref.shape && ref.label === void 0 && ("animate" in ref.data || "animation" in ref.data);
|
|
2132
|
-
}
|
|
2133
|
-
function isolateEdge(statements, stmt, linkIndex, source, target, indent) {
|
|
2134
|
-
const { groups, links } = stmt.chain;
|
|
2135
|
-
const simple = groups.length === 2 && groups[0].length === 1 && groups[1].length === 1;
|
|
2136
|
-
if (simple) return stmt;
|
|
2137
|
-
const expanded = expandChain(stmt.chain);
|
|
2138
|
-
const idx = statements.indexOf(stmt);
|
|
2139
|
-
const out = expanded.map((chain) => ({
|
|
2140
|
-
kind: "chain",
|
|
2141
|
-
raw: "",
|
|
2142
|
-
indent: stmt.indent || indent,
|
|
2143
|
-
chain,
|
|
2144
|
-
dirty: true
|
|
2145
|
-
}));
|
|
2146
|
-
statements.splice(idx, 1, ...out);
|
|
2147
|
-
const found = out.find(
|
|
2148
|
-
(s) => s.chain.links.length === 1 && s.chain.groups[0][0].id === source && s.chain.groups[1][0].id === target && sameLink(s.chain.links[0], links[linkIndex])
|
|
2149
|
-
);
|
|
2150
|
-
return found ?? out[0];
|
|
2151
|
-
}
|
|
2152
|
-
function sameLink(a, b) {
|
|
2153
|
-
return a.pattern === b.pattern && a.arrowStart === b.arrowStart && a.arrowEnd === b.arrowEnd && a.label === b.label && a.id === b.id && a.length === b.length;
|
|
2154
|
-
}
|
|
2155
|
-
function expandChain(chain) {
|
|
2156
|
-
const out = [];
|
|
2157
|
-
const declared = /* @__PURE__ */ new Set();
|
|
2158
|
-
const refFor = (r) => {
|
|
2159
|
-
if (declared.has(r.id)) return { id: r.id, classes: [] };
|
|
2160
|
-
declared.add(r.id);
|
|
2161
|
-
return { ...r, classes: [...r.classes] };
|
|
2162
|
-
};
|
|
2163
|
-
chain.links.forEach((link, i) => {
|
|
2164
|
-
for (const from of chain.groups[i]) {
|
|
2165
|
-
for (const to of chain.groups[i + 1]) {
|
|
2166
|
-
out.push({ groups: [[refFor(from)], [refFor(to)]], links: [{ ...link }] });
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
});
|
|
2170
|
-
if (!chain.links.length) {
|
|
2171
|
-
for (const ref of chain.groups[0]) out.push({ groups: [[refFor(ref)]], links: [] });
|
|
2172
|
-
}
|
|
2173
|
-
return out;
|
|
2174
|
-
}
|
|
2175
|
-
function removeLinkFromChain(statements, stmt, linkIndex, source, target, indent) {
|
|
2176
|
-
const isolated = isolateEdge(statements, stmt, linkIndex, source, target, indent);
|
|
2177
|
-
const idx = statements.indexOf(isolated);
|
|
2178
|
-
if (idx === -1) return;
|
|
2179
|
-
const [from, to] = [isolated.chain.groups[0][0], isolated.chain.groups[1][0]];
|
|
2180
|
-
const keep = [];
|
|
2181
|
-
for (const ref of [from, to]) {
|
|
2182
|
-
const declares = ref.label !== void 0 || ref.shape || ref.classes.length || ref.data && Object.keys(ref.data).length;
|
|
2183
|
-
const mentionedElsewhere = statements.some(
|
|
2184
|
-
(s) => s !== isolated && s.kind === "chain" && s.chain.groups.some((g) => g.some((r) => r.id === ref.id))
|
|
2185
|
-
);
|
|
2186
|
-
if (declares || !mentionedElsewhere) {
|
|
2187
|
-
keep.push({
|
|
2188
|
-
kind: "chain",
|
|
2189
|
-
raw: "",
|
|
2190
|
-
indent: isolated.indent || indent,
|
|
2191
|
-
chain: { groups: [[ref]], links: [] },
|
|
2192
|
-
dirty: true
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
statements.splice(idx, 1, ...keep);
|
|
2197
|
-
}
|
|
2198
|
-
function dropRedundantBareMentions(statements) {
|
|
2199
|
-
const mentions = /* @__PURE__ */ new Map();
|
|
2200
|
-
for (const s of statements) {
|
|
2201
|
-
if (s.kind !== "chain") continue;
|
|
2202
|
-
for (const g of s.chain.groups)
|
|
2203
|
-
for (const r of g) mentions.set(r.id, (mentions.get(r.id) ?? 0) + 1);
|
|
2204
|
-
}
|
|
2205
|
-
filterInPlace(statements, (s) => {
|
|
2206
|
-
if (s.kind !== "chain" || !s.dirty || s.chain.links.length || s.chain.groups[0].length !== 1)
|
|
2207
|
-
return true;
|
|
2208
|
-
const ref = s.chain.groups[0][0];
|
|
2209
|
-
const bare = ref.label === void 0 && !ref.shape && !ref.classes.length && !(ref.data && Object.keys(ref.data).length);
|
|
2210
|
-
if (!bare) return true;
|
|
2211
|
-
if ((mentions.get(ref.id) ?? 0) > 1) {
|
|
2212
|
-
mentions.set(ref.id, mentions.get(ref.id) - 1);
|
|
2213
|
-
return false;
|
|
2214
|
-
}
|
|
2215
|
-
return true;
|
|
2216
|
-
});
|
|
2217
|
-
}
|
|
2218
|
-
function removeNodesFromStatements(statements, ids) {
|
|
2219
|
-
for (let i = 0; i < statements.length; i++) {
|
|
2220
|
-
const s = statements[i];
|
|
2221
|
-
if (s.kind !== "chain") continue;
|
|
2222
|
-
const mentions = s.chain.groups.some((g) => g.some((r) => ids.has(r.id)));
|
|
2223
|
-
if (!mentions) continue;
|
|
2224
|
-
const pieces = splitChainWithout(s.chain, ids);
|
|
2225
|
-
const replacement = pieces.map((chain) => ({
|
|
2226
|
-
kind: "chain",
|
|
2227
|
-
raw: "",
|
|
2228
|
-
indent: s.indent,
|
|
2229
|
-
chain,
|
|
2230
|
-
dirty: true
|
|
2231
|
-
}));
|
|
2232
|
-
statements.splice(i, 1, ...replacement);
|
|
2233
|
-
i += replacement.length - 1;
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
function splitChainWithout(chain, ids) {
|
|
2237
|
-
const out = [];
|
|
2238
|
-
let cur = { groups: [], links: [] };
|
|
2239
|
-
for (let i = 0; i < chain.groups.length; i++) {
|
|
2240
|
-
const group = chain.groups[i].filter((r) => !ids.has(r.id));
|
|
2241
|
-
if (!group.length) {
|
|
2242
|
-
if (cur.groups.length) out.push(cur);
|
|
2243
|
-
cur = { groups: [], links: [] };
|
|
2244
|
-
continue;
|
|
2245
|
-
}
|
|
2246
|
-
if (cur.groups.length) cur.links.push({ ...chain.links[i - 1] });
|
|
2247
|
-
cur.groups.push(group);
|
|
2248
|
-
}
|
|
2249
|
-
if (cur.groups.length) out.push(cur);
|
|
2250
|
-
return out;
|
|
2251
|
-
}
|
|
2252
|
-
function removeEdgesOneByOne(doc, ids) {
|
|
2253
|
-
const wanted = new Set(ids);
|
|
2254
|
-
const targets = doc.graph.edges.map((edge, ordinal) => ({ edge, ordinal })).filter(({ edge }) => wanted.has(edge.id)).map(({ edge, ordinal }) => ({
|
|
2255
|
-
ordinal,
|
|
2256
|
-
source: edge.source,
|
|
2257
|
-
target: edge.target,
|
|
2258
|
-
pairIndex: doc.graph.edges.slice(0, ordinal).filter((e) => e.source === edge.source && e.target === edge.target).length
|
|
2259
|
-
})).sort((a, b) => b.ordinal - a.ordinal);
|
|
2260
|
-
let current = doc;
|
|
2261
|
-
for (const t of targets) {
|
|
2262
|
-
const match = current.graph.edges.filter((e) => e.source === t.source && e.target === t.target)[t.pairIndex];
|
|
2263
|
-
if (match) current = applyFlowchartEdit(current, { type: "removeEdges", ids: [match.id] });
|
|
2264
|
-
}
|
|
2265
|
-
return current;
|
|
2266
|
-
}
|
|
2267
|
-
function encodeData(data) {
|
|
2268
|
-
const out = {};
|
|
2269
|
-
for (const [k, v] of Object.entries(data ?? {})) {
|
|
2270
|
-
if (v === void 0 || v === null) continue;
|
|
2271
|
-
out[k] = typeof v === "string" ? JSON.stringify(v) : String(v);
|
|
2272
|
-
}
|
|
2273
|
-
return out;
|
|
2274
|
-
}
|
|
2275
|
-
var IMAGE_KEYS = ["img", "pos", "w", "h", "constraint"];
|
|
2276
|
-
function shapeDataFor(shape, data) {
|
|
2277
|
-
const out = { ...data };
|
|
2278
|
-
if (shape === "image") {
|
|
2279
|
-
if (!("img" in out)) out.img = '""';
|
|
2280
|
-
} else {
|
|
2281
|
-
for (const key of IMAGE_KEYS) delete out[key];
|
|
2282
|
-
}
|
|
2283
|
-
return out;
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
// src/diagram/flowchart/index.ts
|
|
2287
|
-
var flowchartAdapter = {
|
|
2288
|
-
family: "flowchart",
|
|
2289
|
-
tier: "editable",
|
|
2290
|
-
parse: parseFlowchart,
|
|
2291
|
-
serialize: serializeFlowchart,
|
|
2292
|
-
apply: applyFlowchartEdit
|
|
2293
|
-
};
|
|
2294
|
-
|
|
2295
|
-
// src/diagram/lines-grammar/engine.ts
|
|
2296
|
-
function buildLineGraph(grammar, statements, direction, title) {
|
|
2297
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
2298
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2299
|
-
const edges = [];
|
|
2300
|
-
const classDefs = {};
|
|
2301
|
-
const diagnostics = [];
|
|
2302
|
-
const stack = [];
|
|
2303
|
-
const classAssignments = [];
|
|
2304
|
-
const styleAssignments = [];
|
|
2305
|
-
let ordinal = 0;
|
|
2306
|
-
const touch = (decl, role) => {
|
|
2307
|
-
if (role !== "decl" && grammar.resolveEndpoint) {
|
|
2308
|
-
const resolved = grammar.resolveEndpoint(decl.id, stack, role);
|
|
2309
|
-
if (resolved) {
|
|
2310
|
-
if (!nodes.has(resolved.id)) {
|
|
2311
|
-
nodes.set(resolved.id, {
|
|
2312
|
-
id: resolved.id,
|
|
2313
|
-
label: resolved.label,
|
|
2314
|
-
shape: resolved.shape,
|
|
2315
|
-
parentId: stack[stack.length - 1],
|
|
2316
|
-
classes: [],
|
|
2317
|
-
styles: [],
|
|
2318
|
-
data: { synthetic: true }
|
|
2319
|
-
});
|
|
2320
|
-
}
|
|
2321
|
-
return resolved.id;
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
let node = nodes.get(decl.id);
|
|
2325
|
-
if (!node) {
|
|
2326
|
-
node = { id: decl.id, label: decl.id, shape: grammar.defaultShape, classes: [], styles: [] };
|
|
2327
|
-
nodes.set(decl.id, node);
|
|
2328
|
-
}
|
|
2329
|
-
const parent = stack[stack.length - 1];
|
|
2330
|
-
if (parent) node.parentId = parent;
|
|
2331
|
-
if (decl.label !== void 0) node.label = decl.label;
|
|
2332
|
-
if (decl.shape) node.shape = decl.shape;
|
|
2333
|
-
for (const cls of decl.classes) if (!node.classes.includes(cls)) node.classes.push(cls);
|
|
2334
|
-
if (decl.data) node.data = mergeData(node.data, decl.data);
|
|
2335
|
-
return node.id;
|
|
2336
|
-
};
|
|
2337
|
-
for (const stmt of statements) {
|
|
2338
|
-
switch (stmt.kind) {
|
|
2339
|
-
case "groupOpen": {
|
|
2340
|
-
const parent = stack[stack.length - 1];
|
|
2341
|
-
const existing = nodes.get(stmt.id);
|
|
2342
|
-
if (existing) nodes.delete(stmt.id);
|
|
2343
|
-
groups.set(stmt.id, {
|
|
2344
|
-
id: stmt.id,
|
|
2345
|
-
label: stmt.label ?? existing?.label ?? stmt.id,
|
|
2346
|
-
parentId: parent,
|
|
2347
|
-
classes: existing?.classes ?? [],
|
|
2348
|
-
styles: existing?.styles ?? [],
|
|
2349
|
-
data: existing?.data
|
|
2350
|
-
});
|
|
2351
|
-
stack.push(stmt.id);
|
|
2352
|
-
break;
|
|
2353
|
-
}
|
|
2354
|
-
case "groupClose":
|
|
2355
|
-
if (!stack.length)
|
|
2356
|
-
diagnostics.push({
|
|
2357
|
-
severity: "warning",
|
|
2358
|
-
message: "Unbalanced closing brace",
|
|
2359
|
-
source: "pixerate"
|
|
2360
|
-
});
|
|
2361
|
-
stack.pop();
|
|
2362
|
-
break;
|
|
2363
|
-
case "direction": {
|
|
2364
|
-
const current = stack[stack.length - 1];
|
|
2365
|
-
if (current) groups.get(current).direction = stmt.direction;
|
|
2366
|
-
break;
|
|
2367
|
-
}
|
|
2368
|
-
case "node":
|
|
2369
|
-
touch(stmt.decl, "decl");
|
|
2370
|
-
break;
|
|
2371
|
-
case "edge": {
|
|
2372
|
-
const s = touch(stmt.source, "source");
|
|
2373
|
-
const t = touch(stmt.target, "target");
|
|
2374
|
-
edges.push({
|
|
2375
|
-
id: `L_${s}_${t}_${ordinal}`,
|
|
2376
|
-
source: s,
|
|
2377
|
-
target: t,
|
|
2378
|
-
label: stmt.label,
|
|
2379
|
-
pattern: stmt.pattern,
|
|
2380
|
-
arrowStart: stmt.arrowStart,
|
|
2381
|
-
arrowEnd: stmt.arrowEnd,
|
|
2382
|
-
length: 1,
|
|
2383
|
-
classes: [],
|
|
2384
|
-
styles: [],
|
|
2385
|
-
userDefinedId: false,
|
|
2386
|
-
data: stmt.data
|
|
2387
|
-
});
|
|
2388
|
-
ordinal++;
|
|
2389
|
-
break;
|
|
2390
|
-
}
|
|
2391
|
-
case "classDef":
|
|
2392
|
-
for (const name of stmt.names) classDefs[name] = [...stmt.styles];
|
|
2393
|
-
break;
|
|
2394
|
-
case "class":
|
|
2395
|
-
classAssignments.push({ ids: stmt.ids, className: stmt.className });
|
|
2396
|
-
break;
|
|
2397
|
-
case "style":
|
|
2398
|
-
styleAssignments.push({ id: stmt.id, styles: stmt.styles });
|
|
2399
|
-
break;
|
|
2400
|
-
default:
|
|
2401
|
-
break;
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
if (stack.length)
|
|
2405
|
-
diagnostics.push({
|
|
2406
|
-
severity: "warning",
|
|
2407
|
-
message: `${stack.length} block(s) not closed`,
|
|
2408
|
-
source: "pixerate"
|
|
2409
|
-
});
|
|
2410
|
-
for (const edge of edges) {
|
|
2411
|
-
for (const end of ["source", "target"]) {
|
|
2412
|
-
const id = edge[end];
|
|
2413
|
-
if (groups.has(id) && nodes.has(id)) nodes.delete(id);
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
for (const { ids, className } of classAssignments) {
|
|
2417
|
-
for (const id of ids) {
|
|
2418
|
-
const target = nodes.get(id) ?? groups.get(id);
|
|
2419
|
-
if (target && !target.classes.includes(className)) target.classes.push(className);
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
for (const { id, styles } of styleAssignments) {
|
|
2423
|
-
const target = nodes.get(id) ?? groups.get(id);
|
|
2424
|
-
if (target) target.styles.push(...styles);
|
|
2425
|
-
}
|
|
2426
|
-
return {
|
|
2427
|
-
graph: {
|
|
2428
|
-
direction,
|
|
2429
|
-
nodes: [...nodes.values()],
|
|
2430
|
-
edges,
|
|
2431
|
-
groups: [...groups.values()],
|
|
2432
|
-
classDefs,
|
|
2433
|
-
title
|
|
2434
|
-
},
|
|
2435
|
-
diagnostics
|
|
2436
|
-
};
|
|
2437
|
-
}
|
|
2438
|
-
function mergeData(a, b) {
|
|
2439
|
-
const out = { ...a ?? {} };
|
|
2440
|
-
for (const [k, v] of Object.entries(b)) {
|
|
2441
|
-
if (Array.isArray(v) && Array.isArray(out[k])) out[k] = [...out[k], ...v];
|
|
2442
|
-
else out[k] = v;
|
|
2443
|
-
}
|
|
2444
|
-
return out;
|
|
2445
|
-
}
|
|
2446
|
-
function parseLineDocument(grammar, source, ctx) {
|
|
2447
|
-
const statements = grammar.joinLines(ctx.bodyLines).map((line) => grammar.parseLine(line));
|
|
2448
|
-
const direction = grammar.headerDirection(ctx.header, statements);
|
|
2449
|
-
const title = typeof ctx.preamble.frontmatter?.title === "string" ? ctx.preamble.frontmatter.title : void 0;
|
|
2450
|
-
const { graph, diagnostics } = buildLineGraph(grammar, statements, direction, title);
|
|
2451
|
-
return {
|
|
2452
|
-
family: grammar.family,
|
|
2453
|
-
tier: "editable",
|
|
2454
|
-
source,
|
|
2455
|
-
preamble: ctx.preamble,
|
|
2456
|
-
header: ctx.header,
|
|
2457
|
-
trailing: ctx.trailing,
|
|
2458
|
-
eol: ctx.eol,
|
|
2459
|
-
graph,
|
|
2460
|
-
meta: ctx.meta,
|
|
2461
|
-
metaCarrier: ctx.metaCarrier,
|
|
2462
|
-
body: { statements },
|
|
2463
|
-
diagnostics,
|
|
2464
|
-
dirty: false
|
|
2465
|
-
};
|
|
2466
|
-
}
|
|
2467
|
-
function serializeLineDocument(grammar, doc) {
|
|
2468
|
-
const lines = [];
|
|
2469
|
-
for (const stmt of doc.body.statements) {
|
|
2470
|
-
if (stmt.kind === "raw") {
|
|
2471
|
-
lines.push(stmt.raw);
|
|
2472
|
-
continue;
|
|
2473
|
-
}
|
|
2474
|
-
if (!("dirty" in stmt) || !stmt.dirty) {
|
|
2475
|
-
lines.push(stmt.raw);
|
|
2476
|
-
if (stmt.kind === "node" && stmt.body) lines.push(...stmt.body);
|
|
2477
|
-
continue;
|
|
2478
|
-
}
|
|
2479
|
-
lines.push(grammar.formatStatement(stmt));
|
|
2480
|
-
if (stmt.kind === "node" && stmt.body) lines.push(...stmt.body);
|
|
2481
|
-
}
|
|
2482
|
-
const indent = detectIndent2(doc.body.statements);
|
|
2483
|
-
return assembleDocument(doc, lines, indent);
|
|
2484
|
-
}
|
|
2485
|
-
function detectIndent2(statements) {
|
|
2486
|
-
for (const s of statements) if (s.indent) return s.indent;
|
|
2487
|
-
return " ";
|
|
2488
|
-
}
|
|
2489
|
-
function parseFull(grammar, source, carrier) {
|
|
2490
|
-
const split = splitSource(source);
|
|
2491
|
-
const fromBody = extractMetaFromBody(split.bodyLines);
|
|
2492
|
-
const fromFm = extractMetaFromFrontmatter(split.preamble);
|
|
2493
|
-
let meta = emptyMeta();
|
|
2494
|
-
if (fromFm) meta = mergeMeta(meta, fromFm);
|
|
2495
|
-
if (fromBody.meta) meta = mergeMeta(meta, fromBody.meta);
|
|
2496
|
-
return parseLineDocument(grammar, source, {
|
|
2497
|
-
source,
|
|
2498
|
-
eol: split.eol,
|
|
2499
|
-
preamble: split.preamble,
|
|
2500
|
-
header: split.header,
|
|
2501
|
-
bodyLines: fromBody.bodyLines,
|
|
2502
|
-
trailing: split.trailing,
|
|
2503
|
-
meta,
|
|
2504
|
-
metaCarrier: carrier ?? (fromFm && !fromBody.meta ? "frontmatter" : defaultCarrierFor(grammar.family)),
|
|
2505
|
-
family: grammar.family
|
|
2506
|
-
});
|
|
2507
|
-
}
|
|
2508
|
-
function applyLineEdit(grammar, doc, op) {
|
|
2509
|
-
const statements = doc.body.statements.map((s) => ({
|
|
2510
|
-
...s,
|
|
2511
|
-
..."decl" in s ? {
|
|
2512
|
-
decl: {
|
|
2513
|
-
...s.decl,
|
|
2514
|
-
classes: [...s.decl.classes],
|
|
2515
|
-
data: s.decl.data ? { ...s.decl.data } : void 0
|
|
2516
|
-
}
|
|
2517
|
-
} : {},
|
|
2518
|
-
..."source" in s ? {
|
|
2519
|
-
source: { ...s.source, classes: [...s.source.classes] },
|
|
2520
|
-
target: { ...s.target, classes: [...s.target.classes] }
|
|
2521
|
-
} : {}
|
|
2522
|
-
}));
|
|
2523
|
-
const work = {
|
|
2524
|
-
...doc,
|
|
2525
|
-
body: { statements },
|
|
2526
|
-
meta: { ...doc.meta, layout: { ...doc.meta.layout }, sizes: { ...doc.meta.sizes } }
|
|
2527
|
-
};
|
|
2528
|
-
const indent = detectIndent2(statements);
|
|
2529
|
-
const reparse2 = (structural) => {
|
|
2530
|
-
const text = serializeLineDocument(grammar, work);
|
|
2531
|
-
const next = parseFull(grammar, text, work.metaCarrier);
|
|
2532
|
-
next.dirty = doc.dirty || structural;
|
|
2533
|
-
return next;
|
|
2534
|
-
};
|
|
2535
|
-
switch (op.type) {
|
|
2536
|
-
case "moveNodes":
|
|
2537
|
-
for (const { id, position } of op.moves) work.meta.layout[id] = position;
|
|
2538
|
-
return reparse2(false);
|
|
2539
|
-
case "resizeNode":
|
|
2540
|
-
if (op.size) work.meta.sizes[op.id] = op.size;
|
|
2541
|
-
else delete work.meta.sizes[op.id];
|
|
2542
|
-
return reparse2(false);
|
|
2543
|
-
case "setViewport":
|
|
2544
|
-
work.meta.viewport = op.viewport;
|
|
2545
|
-
return reparse2(false);
|
|
2546
|
-
case "setMetaExt":
|
|
2547
|
-
work.meta.ext = { ...work.meta.ext ?? {}, [op.key]: op.value };
|
|
2548
|
-
if (op.value === void 0) delete work.meta.ext[op.key];
|
|
2549
|
-
return reparse2(false);
|
|
2550
|
-
case "replaceSource":
|
|
2551
|
-
return parseFull(grammar, op.source, work.metaCarrier);
|
|
2552
|
-
case "setTitle": {
|
|
2553
|
-
const lines = [...work.preamble.lines];
|
|
2554
|
-
work.preamble = { ...work.preamble, lines: setTitle(lines, op.title) };
|
|
2555
|
-
return reparse2(true);
|
|
2556
|
-
}
|
|
2557
|
-
case "setDirection": {
|
|
2558
|
-
if (grammar.directionAsStatement) {
|
|
2559
|
-
const idx = statements.findIndex(
|
|
2560
|
-
(s, i) => s.kind === "direction" && depthAt2(statements, i) === 0
|
|
2561
|
-
);
|
|
2562
|
-
if (idx !== -1) {
|
|
2563
|
-
const s = statements[idx];
|
|
2564
|
-
s.direction = op.direction;
|
|
2565
|
-
s.dirty = true;
|
|
2566
|
-
} else {
|
|
2567
|
-
statements.unshift({
|
|
2568
|
-
kind: "direction",
|
|
2569
|
-
raw: "",
|
|
2570
|
-
indent,
|
|
2571
|
-
direction: op.direction,
|
|
2572
|
-
dirty: true
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
} else {
|
|
2576
|
-
work.header = work.header.replace(/\s+(TB|TD|BT|LR|RL)\s*$/i, "") + ` ${op.direction}`;
|
|
2577
|
-
}
|
|
2578
|
-
return reparse2(true);
|
|
2579
|
-
}
|
|
2580
|
-
case "addNode": {
|
|
2581
|
-
const decl = {
|
|
2582
|
-
id: op.node.id,
|
|
2583
|
-
label: op.node.label,
|
|
2584
|
-
shape: op.node.shape,
|
|
2585
|
-
classes: [...op.node.classes ?? []],
|
|
2586
|
-
data: op.node.data
|
|
2587
|
-
};
|
|
2588
|
-
insertIntoGroup2(statements, [grammar.nodeStatement(decl, indent)], op.node.parentId, indent);
|
|
2589
|
-
if (op.position) work.meta.layout[op.node.id] = op.position;
|
|
2590
|
-
return reparse2(true);
|
|
2591
|
-
}
|
|
2592
|
-
case "removeNodes": {
|
|
2593
|
-
const ids = new Set(op.ids);
|
|
2594
|
-
filterInPlace2(statements, (s) => {
|
|
2595
|
-
if (s.kind === "node") return !ids.has(s.decl.id);
|
|
2596
|
-
if (s.kind === "edge") return !(ids.has(s.source.id) || ids.has(s.target.id));
|
|
2597
|
-
if (s.kind === "style") return !ids.has(s.id);
|
|
2598
|
-
if (s.kind === "class") {
|
|
2599
|
-
s.ids = s.ids.filter((id) => !ids.has(id));
|
|
2600
|
-
s.dirty = true;
|
|
2601
|
-
return s.ids.length > 0;
|
|
2602
|
-
}
|
|
2603
|
-
return true;
|
|
2604
|
-
});
|
|
2605
|
-
for (const id of ids) {
|
|
2606
|
-
const range = groupRange2(statements, id);
|
|
2607
|
-
if (range) {
|
|
2608
|
-
statements.splice(range.end, 1);
|
|
2609
|
-
statements.splice(range.start, 1);
|
|
2610
|
-
}
|
|
2611
|
-
delete work.meta.layout[id];
|
|
2612
|
-
delete work.meta.sizes[id];
|
|
2613
|
-
}
|
|
2614
|
-
return reparse2(true);
|
|
2615
|
-
}
|
|
2616
|
-
case "renameNode":
|
|
2617
|
-
case "setNodeShape":
|
|
2618
|
-
case "setNodeClasses":
|
|
2619
|
-
case "setNodeData": {
|
|
2620
|
-
const id = op.id;
|
|
2621
|
-
let decl = findDecl(statements, id);
|
|
2622
|
-
if (!decl) {
|
|
2623
|
-
const node = doc.graph.nodes.find((n) => n.id === id);
|
|
2624
|
-
const stmt = grammar.nodeStatement(
|
|
2625
|
-
{
|
|
2626
|
-
id,
|
|
2627
|
-
label: node?.label !== id ? node?.label : void 0,
|
|
2628
|
-
shape: node?.shape !== grammar.defaultShape ? node?.shape : void 0,
|
|
2629
|
-
classes: []
|
|
2630
|
-
},
|
|
2631
|
-
indent
|
|
2632
|
-
);
|
|
2633
|
-
const first = statements.findIndex(
|
|
2634
|
-
(s) => s.kind === "edge" && (s.source.id === id || s.target.id === id)
|
|
2635
|
-
);
|
|
2636
|
-
statements.splice(first === -1 ? statements.length : first, 0, stmt);
|
|
2637
|
-
decl = findDecl(statements, id);
|
|
2638
|
-
}
|
|
2639
|
-
const target = decl.stmt;
|
|
2640
|
-
if (op.type === "renameNode") decl.decl.label = op.label;
|
|
2641
|
-
if (op.type === "setNodeShape") decl.decl.shape = op.shape;
|
|
2642
|
-
if (op.type === "setNodeClasses") {
|
|
2643
|
-
decl.decl.classes = [...op.classes];
|
|
2644
|
-
filterInPlace2(statements, (s) => {
|
|
2645
|
-
if (s.kind !== "class" || !s.ids.includes(id)) return true;
|
|
2646
|
-
s.ids = s.ids.filter((x) => x !== id);
|
|
2647
|
-
s.dirty = true;
|
|
2648
|
-
return s.ids.length > 0;
|
|
2649
|
-
});
|
|
2650
|
-
}
|
|
2651
|
-
if (op.type === "setNodeData") {
|
|
2652
|
-
decl.decl.data = { ...decl.decl.data ?? {}, ...op.data };
|
|
2653
|
-
if (target.kind === "node" && ("members" in op.data || "attributes" in op.data)) {
|
|
2654
|
-
const fresh = grammar.nodeStatement(decl.decl, target.indent);
|
|
2655
|
-
target.body = fresh.kind === "node" ? fresh.body : void 0;
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
target.dirty = true;
|
|
2659
|
-
return reparse2(true);
|
|
2660
|
-
}
|
|
2661
|
-
case "setNodeStyles": {
|
|
2662
|
-
filterInPlace2(statements, (s) => !(s.kind === "style" && s.id === op.id));
|
|
2663
|
-
if (op.styles.length)
|
|
2664
|
-
statements.push({
|
|
2665
|
-
kind: "style",
|
|
2666
|
-
raw: "",
|
|
2667
|
-
indent,
|
|
2668
|
-
id: op.id,
|
|
2669
|
-
styles: [...op.styles],
|
|
2670
|
-
dirty: true
|
|
2671
|
-
});
|
|
2672
|
-
return reparse2(true);
|
|
2673
|
-
}
|
|
2674
|
-
case "addEdge": {
|
|
2675
|
-
const spec = {
|
|
2676
|
-
...grammar.defaultEdge,
|
|
2677
|
-
...op.edge.pattern ? { pattern: op.edge.pattern } : {},
|
|
2678
|
-
...op.edge.arrowStart ? { arrowStart: op.edge.arrowStart } : {},
|
|
2679
|
-
...op.edge.arrowEnd ? { arrowEnd: op.edge.arrowEnd } : {},
|
|
2680
|
-
label: op.edge.label,
|
|
2681
|
-
data: {
|
|
2682
|
-
...op.edge.data ?? {},
|
|
2683
|
-
synthetic: {
|
|
2684
|
-
source: isSyntheticEndpoint(doc, op.edge.source),
|
|
2685
|
-
target: isSyntheticEndpoint(doc, op.edge.target)
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
};
|
|
2689
|
-
const stmt = grammar.edgeStatement(op.edge.source, op.edge.target, spec, indent);
|
|
2690
|
-
const sp = parentOf(doc, op.edge.source);
|
|
2691
|
-
const tp = parentOf(doc, op.edge.target);
|
|
2692
|
-
insertIntoGroup2(statements, [stmt], sp && sp === tp ? sp : void 0, indent);
|
|
2693
|
-
return reparse2(true);
|
|
2694
|
-
}
|
|
2695
|
-
case "removeEdges": {
|
|
2696
|
-
const targets = new Set(op.ids.map((id) => edgeOrdinal(doc, id)).filter((n) => n >= 0));
|
|
2697
|
-
let ordinal = 0;
|
|
2698
|
-
filterInPlace2(statements, (s) => {
|
|
2699
|
-
if (s.kind !== "edge") return true;
|
|
2700
|
-
const keep = !targets.has(ordinal);
|
|
2701
|
-
ordinal++;
|
|
2702
|
-
return keep;
|
|
2703
|
-
});
|
|
2704
|
-
return reparse2(true);
|
|
2705
|
-
}
|
|
2706
|
-
case "setEdgeLabel":
|
|
2707
|
-
case "setEdgeStyle":
|
|
2708
|
-
case "reconnectEdge": {
|
|
2709
|
-
const stmt = edgeStatementFor(statements, doc, op.id);
|
|
2710
|
-
if (!stmt) return doc;
|
|
2711
|
-
if (op.type === "setEdgeLabel") stmt.label = op.label || void 0;
|
|
2712
|
-
if (op.type === "setEdgeStyle") {
|
|
2713
|
-
if (op.pattern) stmt.pattern = op.pattern;
|
|
2714
|
-
if (op.arrowStart) stmt.arrowStart = op.arrowStart;
|
|
2715
|
-
if (op.arrowEnd) stmt.arrowEnd = op.arrowEnd;
|
|
2716
|
-
}
|
|
2717
|
-
if (op.type === "reconnectEdge") {
|
|
2718
|
-
stmt.source = { id: op.source, classes: [] };
|
|
2719
|
-
stmt.target = { id: op.target, classes: [] };
|
|
2720
|
-
}
|
|
2721
|
-
stmt.dirty = true;
|
|
2722
|
-
return reparse2(true);
|
|
2723
|
-
}
|
|
2724
|
-
case "groupNodes": {
|
|
2725
|
-
if (grammar.supportsGroups === false) return doc;
|
|
2726
|
-
const { open, close } = grammar.groupStatements(op.groupId, op.label, indent);
|
|
2727
|
-
const moved = [];
|
|
2728
|
-
const set = new Set(op.nodeIds);
|
|
2729
|
-
filterInPlace2(statements, (s) => {
|
|
2730
|
-
if (s.kind === "node" && set.has(s.decl.id)) {
|
|
2731
|
-
moved.push(reindent(s, indent + indent));
|
|
2732
|
-
return false;
|
|
2733
|
-
}
|
|
2734
|
-
if (s.kind === "edge" && set.has(s.source.id) && set.has(s.target.id)) {
|
|
2735
|
-
moved.push(reindent(s, indent + indent));
|
|
2736
|
-
return false;
|
|
2737
|
-
}
|
|
2738
|
-
return true;
|
|
2739
|
-
});
|
|
2740
|
-
for (const id of op.nodeIds) {
|
|
2741
|
-
if (!moved.some((s) => s.kind === "node" && s.decl.id === id)) {
|
|
2742
|
-
moved.push(grammar.nodeStatement({ id, classes: [] }, indent + indent));
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
insertIntoGroup2(statements, [open, ...moved, close], op.parentId, indent);
|
|
2746
|
-
return reparse2(true);
|
|
2747
|
-
}
|
|
2748
|
-
case "ungroup": {
|
|
2749
|
-
const range = groupRange2(statements, op.groupId);
|
|
2750
|
-
if (!range) return doc;
|
|
2751
|
-
const inner = statements.slice(range.start + 1, range.end).map((s) => reindent(s, statements[range.start].indent));
|
|
2752
|
-
statements.splice(
|
|
2753
|
-
range.start,
|
|
2754
|
-
range.end - range.start + 1,
|
|
2755
|
-
...inner.filter((s) => s.kind !== "direction")
|
|
2756
|
-
);
|
|
2757
|
-
delete work.meta.layout[op.groupId];
|
|
2758
|
-
return reparse2(true);
|
|
2759
|
-
}
|
|
2760
|
-
case "renameGroup": {
|
|
2761
|
-
const range = groupRange2(statements, op.id);
|
|
2762
|
-
if (!range) return doc;
|
|
2763
|
-
const open = statements[range.start];
|
|
2764
|
-
open.label = op.label;
|
|
2765
|
-
open.dirty = true;
|
|
2766
|
-
return reparse2(true);
|
|
2767
|
-
}
|
|
2768
|
-
case "setGroupDirection": {
|
|
2769
|
-
const range = groupRange2(statements, op.id);
|
|
2770
|
-
if (!range) return doc;
|
|
2771
|
-
const idx = statements.findIndex(
|
|
2772
|
-
(s, i) => i > range.start && i < range.end && s.kind === "direction" && depthAt2(statements, i) === depthAt2(statements, range.start) + 1
|
|
2773
|
-
);
|
|
2774
|
-
if (op.direction) {
|
|
2775
|
-
if (idx !== -1) {
|
|
2776
|
-
const s = statements[idx];
|
|
2777
|
-
s.direction = op.direction;
|
|
2778
|
-
s.dirty = true;
|
|
2779
|
-
} else {
|
|
2780
|
-
const inner = statements[range.start].indent + indent;
|
|
2781
|
-
statements.splice(range.start + 1, 0, {
|
|
2782
|
-
kind: "direction",
|
|
2783
|
-
raw: "",
|
|
2784
|
-
indent: inner,
|
|
2785
|
-
direction: op.direction,
|
|
2786
|
-
dirty: true
|
|
2787
|
-
});
|
|
2788
|
-
}
|
|
2789
|
-
} else if (idx !== -1) statements.splice(idx, 1);
|
|
2790
|
-
return reparse2(true);
|
|
2791
|
-
}
|
|
2792
|
-
case "setNodeParent": {
|
|
2793
|
-
for (const id of op.ids) {
|
|
2794
|
-
const found = findDecl(statements, id);
|
|
2795
|
-
let stmt;
|
|
2796
|
-
if (found) {
|
|
2797
|
-
statements.splice(statements.indexOf(found.stmt), 1);
|
|
2798
|
-
stmt = found.stmt;
|
|
2799
|
-
} else {
|
|
2800
|
-
stmt = grammar.nodeStatement({ id, classes: [] }, indent);
|
|
2801
|
-
}
|
|
2802
|
-
insertIntoGroup2(statements, [stmt], op.parentId, indent);
|
|
2803
|
-
}
|
|
2804
|
-
return reparse2(true);
|
|
2805
|
-
}
|
|
2806
|
-
case "setClassDef": {
|
|
2807
|
-
let found = false;
|
|
2808
|
-
for (const s of statements) {
|
|
2809
|
-
if (s.kind !== "classDef") continue;
|
|
2810
|
-
if (s.names.length === 1 && s.names[0] === op.name) {
|
|
2811
|
-
found = true;
|
|
2812
|
-
if (op.styles) {
|
|
2813
|
-
s.styles = [...op.styles];
|
|
2814
|
-
s.dirty = true;
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
if (!op.styles)
|
|
2819
|
-
filterInPlace2(
|
|
2820
|
-
statements,
|
|
2821
|
-
(s) => !(s.kind === "classDef" && s.names.length === 1 && s.names[0] === op.name)
|
|
2822
|
-
);
|
|
2823
|
-
else if (!found)
|
|
2824
|
-
statements.push({
|
|
2825
|
-
kind: "classDef",
|
|
2826
|
-
raw: "",
|
|
2827
|
-
indent,
|
|
2828
|
-
names: [op.name],
|
|
2829
|
-
styles: [...op.styles],
|
|
2830
|
-
dirty: true
|
|
2831
|
-
});
|
|
2832
|
-
return reparse2(true);
|
|
2833
|
-
}
|
|
2834
|
-
default:
|
|
2835
|
-
return doc;
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
function isSyntheticEndpoint(doc, id) {
|
|
2839
|
-
const node = doc.graph.nodes.find((n) => n.id === id);
|
|
2840
|
-
if (node) return !!node.data?.synthetic;
|
|
2841
|
-
const m = /^(.+)_(start|end)$/.exec(id);
|
|
2842
|
-
if (!m) return false;
|
|
2843
|
-
return m[1] === "root" || doc.graph.groups.some((g) => g.id === m[1]);
|
|
2844
|
-
}
|
|
2845
|
-
function reindent(s, indent) {
|
|
2846
|
-
const next = { ...s, indent };
|
|
2847
|
-
if (s.kind === "raw") return s;
|
|
2848
|
-
next.dirty = true;
|
|
2849
|
-
if (s.kind === "node" && s.body) {
|
|
2850
|
-
const delta = indent.length - s.indent.length;
|
|
2851
|
-
next.body = s.body.map(
|
|
2852
|
-
(l) => delta >= 0 ? " ".repeat(delta) + l : l.slice(Math.min(-delta, l.length - l.trimStart().length))
|
|
2853
|
-
);
|
|
2854
|
-
}
|
|
2855
|
-
return next;
|
|
2856
|
-
}
|
|
2857
|
-
function findDecl(statements, id) {
|
|
2858
|
-
for (const s of statements) {
|
|
2859
|
-
if (s.kind === "node" && s.decl.id === id) return { stmt: s, decl: s.decl };
|
|
2860
|
-
}
|
|
2861
|
-
for (const s of statements) {
|
|
2862
|
-
if (s.kind === "edge") {
|
|
2863
|
-
if (s.source.id === id && (s.source.label !== void 0 || s.source.shape || s.source.classes.length))
|
|
2864
|
-
return { stmt: s, decl: s.source };
|
|
2865
|
-
if (s.target.id === id && (s.target.label !== void 0 || s.target.shape || s.target.classes.length))
|
|
2866
|
-
return { stmt: s, decl: s.target };
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
return null;
|
|
2870
|
-
}
|
|
2871
|
-
function parentOf(doc, id) {
|
|
2872
|
-
return doc.graph.nodes.find((n) => n.id === id)?.parentId ?? doc.graph.groups.find((g) => g.id === id)?.parentId;
|
|
2873
|
-
}
|
|
2874
|
-
function edgeOrdinal(doc, edgeId) {
|
|
2875
|
-
return doc.graph.edges.findIndex((e) => e.id === edgeId);
|
|
2876
|
-
}
|
|
2877
|
-
function edgeStatementFor(statements, doc, edgeId) {
|
|
2878
|
-
const ordinal = edgeOrdinal(doc, edgeId);
|
|
2879
|
-
if (ordinal < 0) return null;
|
|
2880
|
-
let n = 0;
|
|
2881
|
-
for (const s of statements) {
|
|
2882
|
-
if (s.kind !== "edge") continue;
|
|
2883
|
-
if (n === ordinal) return s;
|
|
2884
|
-
n++;
|
|
2885
|
-
}
|
|
2886
|
-
return null;
|
|
2887
|
-
}
|
|
2888
|
-
function depthAt2(statements, index) {
|
|
2889
|
-
let depth = 0;
|
|
2890
|
-
for (let i = 0; i < index; i++) {
|
|
2891
|
-
const s = statements[i];
|
|
2892
|
-
if (s.kind === "groupOpen") depth++;
|
|
2893
|
-
else if (s.kind === "groupClose") depth = Math.max(0, depth - 1);
|
|
2894
|
-
}
|
|
2895
|
-
return depth;
|
|
2896
|
-
}
|
|
2897
|
-
function groupRange2(statements, groupId) {
|
|
2898
|
-
const start = statements.findIndex((s) => s.kind === "groupOpen" && s.id === groupId);
|
|
2899
|
-
if (start === -1) return null;
|
|
2900
|
-
let depth = 0;
|
|
2901
|
-
for (let i = start; i < statements.length; i++) {
|
|
2902
|
-
const s = statements[i];
|
|
2903
|
-
if (s.kind === "groupOpen") depth++;
|
|
2904
|
-
else if (s.kind === "groupClose") {
|
|
2905
|
-
depth--;
|
|
2906
|
-
if (depth === 0) return { start, end: i };
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
return { start, end: statements.length };
|
|
2910
|
-
}
|
|
2911
|
-
function insertIntoGroup2(statements, block, groupId, indent) {
|
|
2912
|
-
const range = groupId ? groupRange2(statements, groupId) : null;
|
|
2913
|
-
if (!range) {
|
|
2914
|
-
statements.push(...block);
|
|
2915
|
-
return;
|
|
2916
|
-
}
|
|
2917
|
-
const open = statements[range.start];
|
|
2918
|
-
const inner = open.indent + indent;
|
|
2919
|
-
const adjusted = block.map(
|
|
2920
|
-
(s) => s.kind === "raw" ? s : reindent(s, inner + s.indent.slice(indent.length))
|
|
2921
|
-
);
|
|
2922
|
-
statements.splice(range.end, 0, ...adjusted);
|
|
2923
|
-
}
|
|
2924
|
-
function filterInPlace2(arr, keep) {
|
|
2925
|
-
let j = 0;
|
|
2926
|
-
for (let i = 0; i < arr.length; i++) if (keep(arr[i])) arr[j++] = arr[i];
|
|
2927
|
-
arr.length = j;
|
|
2928
|
-
}
|
|
2929
|
-
function setTitle(lines, title) {
|
|
2930
|
-
const out = [...lines];
|
|
2931
|
-
const hasFm = out[0] !== void 0 && /^---\s*$/.test(out[0]);
|
|
2932
|
-
if (!hasFm) return title === void 0 ? out : ["---", `title: ${title}`, "---", ...out];
|
|
2933
|
-
const close = out.findIndex((l, i) => i > 0 && /^---\s*$/.test(l));
|
|
2934
|
-
const end = close === -1 ? out.length : close;
|
|
2935
|
-
const idx = out.findIndex((l, i) => i > 0 && i < end && /^title\s*:/.test(l));
|
|
2936
|
-
if (title === void 0) {
|
|
2937
|
-
if (idx !== -1) out.splice(idx, 1);
|
|
2938
|
-
if (out.length >= 2 && /^---\s*$/.test(out[1])) out.splice(0, 2);
|
|
2939
|
-
return out;
|
|
2940
|
-
}
|
|
2941
|
-
if (idx !== -1) out[idx] = `title: ${title}`;
|
|
2942
|
-
else out.splice(end, 0, `title: ${title}`);
|
|
2943
|
-
return out;
|
|
2944
|
-
}
|
|
2945
|
-
function setFrontmatterTitle2(lines, title) {
|
|
2946
|
-
return setTitle(lines, title);
|
|
2947
|
-
}
|
|
2948
|
-
function createLineAdapter(grammar) {
|
|
2949
|
-
return {
|
|
2950
|
-
family: grammar.family,
|
|
2951
|
-
tier: "editable",
|
|
2952
|
-
parse: (source, ctx) => parseLineDocument(grammar, source, ctx),
|
|
2953
|
-
serialize: (doc) => serializeLineDocument(grammar, doc),
|
|
2954
|
-
apply: (doc, op) => applyLineEdit(grammar, doc, op)
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
|
-
function joinBraceBlocks(lines, extraBlock) {
|
|
2958
|
-
const out = [];
|
|
2959
|
-
let i = 0;
|
|
2960
|
-
while (i < lines.length) {
|
|
2961
|
-
const line = lines[i];
|
|
2962
|
-
if (extraBlock && extraBlock.open.test(line)) {
|
|
2963
|
-
let cur = line;
|
|
2964
|
-
let j = i + 1;
|
|
2965
|
-
while (j < lines.length && !extraBlock.close.test(lines[j])) {
|
|
2966
|
-
cur += "\n" + lines[j];
|
|
2967
|
-
j++;
|
|
2968
|
-
}
|
|
2969
|
-
if (j < lines.length) {
|
|
2970
|
-
cur += "\n" + lines[j];
|
|
2971
|
-
j++;
|
|
2972
|
-
}
|
|
2973
|
-
out.push(cur);
|
|
2974
|
-
i = j;
|
|
2975
|
-
continue;
|
|
2976
|
-
}
|
|
2977
|
-
const opens = (line.match(/\{/g) ?? []).length;
|
|
2978
|
-
const closes = (line.match(/\}/g) ?? []).length;
|
|
2979
|
-
if (opens > closes && /\{\s*$/.test(line)) {
|
|
2980
|
-
let depth = opens - closes;
|
|
2981
|
-
let j = i + 1;
|
|
2982
|
-
const body = [];
|
|
2983
|
-
let isSimpleBody = true;
|
|
2984
|
-
while (j < lines.length && depth > 0) {
|
|
2985
|
-
const l = lines[j];
|
|
2986
|
-
depth += (l.match(/\{/g) ?? []).length - (l.match(/\}/g) ?? []).length;
|
|
2987
|
-
if (depth > 0) body.push(l);
|
|
2988
|
-
if (/-->|<\|--|\*--|o--|\.\.>|--\|>|\|\|--|\}o--|\}\|--|^\s*(state|class|namespace|note|direction)\b/.test(
|
|
2989
|
-
l
|
|
2990
|
-
) && depth > 0)
|
|
2991
|
-
isSimpleBody = false;
|
|
2992
|
-
j++;
|
|
2993
|
-
}
|
|
2994
|
-
if (isSimpleBody && depth === 0) {
|
|
2995
|
-
out.push([line, ...body, lines[j - 1]].join("\n"));
|
|
2996
|
-
i = j;
|
|
2997
|
-
continue;
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
out.push(line);
|
|
3001
|
-
i++;
|
|
3002
|
-
}
|
|
3003
|
-
return out;
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
// src/diagram/passthrough.ts
|
|
3007
|
-
function createPassthroughAdapter(family, tier = "render-only") {
|
|
3008
|
-
const adapter = {
|
|
3009
|
-
family,
|
|
3010
|
-
tier,
|
|
3011
|
-
parse(source, ctx) {
|
|
3012
|
-
const title = typeof ctx.preamble.frontmatter?.title === "string" ? ctx.preamble.frontmatter.title : void 0;
|
|
3013
|
-
return {
|
|
3014
|
-
family,
|
|
3015
|
-
tier,
|
|
3016
|
-
source,
|
|
3017
|
-
preamble: ctx.preamble,
|
|
3018
|
-
header: ctx.header,
|
|
3019
|
-
trailing: ctx.trailing,
|
|
3020
|
-
eol: ctx.eol,
|
|
3021
|
-
graph: { direction: "TB", nodes: [], edges: [], groups: [], classDefs: {}, title },
|
|
3022
|
-
meta: ctx.meta,
|
|
3023
|
-
metaCarrier: ctx.metaCarrier,
|
|
3024
|
-
body: { lines: ctx.bodyLines },
|
|
3025
|
-
diagnostics: [],
|
|
3026
|
-
dirty: false
|
|
3027
|
-
};
|
|
3028
|
-
},
|
|
3029
|
-
serialize(doc) {
|
|
3030
|
-
return assembleDocument(doc, doc.body.lines, " ");
|
|
3031
|
-
},
|
|
3032
|
-
apply(doc, op) {
|
|
3033
|
-
const next = { ...doc, meta: { ...doc.meta } };
|
|
3034
|
-
switch (op.type) {
|
|
3035
|
-
case "setViewport":
|
|
3036
|
-
next.meta.viewport = op.viewport;
|
|
3037
|
-
break;
|
|
3038
|
-
case "setMetaExt":
|
|
3039
|
-
next.meta.ext = { ...next.meta.ext ?? {}, [op.key]: op.value };
|
|
3040
|
-
break;
|
|
3041
|
-
case "moveNodes":
|
|
3042
|
-
next.meta.layout = { ...next.meta.layout ?? {} };
|
|
3043
|
-
for (const { id, position } of op.moves) next.meta.layout[id] = position;
|
|
3044
|
-
break;
|
|
3045
|
-
case "setTitle":
|
|
3046
|
-
next.preamble = {
|
|
3047
|
-
...next.preamble,
|
|
3048
|
-
lines: setFrontmatterTitle2(next.preamble.lines, op.title)
|
|
3049
|
-
};
|
|
3050
|
-
next.dirty = true;
|
|
3051
|
-
break;
|
|
3052
|
-
default:
|
|
3053
|
-
return doc;
|
|
3054
|
-
}
|
|
3055
|
-
next.source = adapter.serialize(next);
|
|
3056
|
-
return next;
|
|
3057
|
-
}
|
|
3058
|
-
};
|
|
3059
|
-
return adapter;
|
|
3060
|
-
}
|
|
3061
|
-
|
|
3062
|
-
// src/diagram/lines-grammar/state.ts
|
|
3063
|
-
var ID = String.raw`[A-Za-z0-9_À-.-]+`;
|
|
3064
|
-
var TRANSITION = new RegExp(
|
|
3065
|
-
String.raw`^(\[\*\]|${ID}(?::::${ID})?)\s*-->\s*(\[\*\]|${ID}(?::::${ID})?)\s*(?::\s*(.*))?$`
|
|
3066
|
-
);
|
|
3067
|
-
var STATE_SHAPES = [
|
|
3068
|
-
{ name: "rect", title: "State" },
|
|
3069
|
-
{ name: "fork", title: "Fork" },
|
|
3070
|
-
{ name: "join", title: "Join" },
|
|
3071
|
-
{ name: "choice", title: "Choice" }
|
|
3072
|
-
];
|
|
3073
|
-
function splitClasses(token) {
|
|
3074
|
-
const [id, ...classes] = token.split(":::");
|
|
3075
|
-
return { id, classes };
|
|
3076
|
-
}
|
|
3077
|
-
var stateGrammar = {
|
|
3078
|
-
family: "state",
|
|
3079
|
-
defaultShape: "rect",
|
|
3080
|
-
directionAsStatement: true,
|
|
3081
|
-
defaultEdge: { arrowStart: "none", arrowEnd: "arrow_barb", pattern: "solid" },
|
|
3082
|
-
joinLines(lines) {
|
|
3083
|
-
return joinBraceBlocks(lines, {
|
|
3084
|
-
open: /^\s*note\s+(left|right)\s+of\s+\S+\s*$/i,
|
|
3085
|
-
close: /^\s*end\s+note\s*$/i
|
|
3086
|
-
}).flatMap((logical) => {
|
|
3087
|
-
if (/^\s*state\b.*\{\s*$/.test(logical.split("\n")[0]) && logical.includes("\n"))
|
|
3088
|
-
return logical.split("\n");
|
|
3089
|
-
return [logical];
|
|
3090
|
-
});
|
|
3091
|
-
},
|
|
3092
|
-
parseLine(line) {
|
|
3093
|
-
const indent = line.match(/^\s*/)[0];
|
|
3094
|
-
const text = line.trim();
|
|
3095
|
-
if (text === "" || text.startsWith("%%") || text.startsWith("note") || text === "--")
|
|
3096
|
-
return { kind: "raw", raw: line, indent };
|
|
3097
|
-
if (text === "}") return { kind: "groupClose", raw: line, indent };
|
|
3098
|
-
let m;
|
|
3099
|
-
if (m = /^direction\s+(TB|TD|BT|LR|RL)\s*$/i.exec(text))
|
|
3100
|
-
return { kind: "direction", raw: line, indent, direction: m[1].toUpperCase() };
|
|
3101
|
-
if (m = /^classDef\s+(\S+)\s+(.*)$/.exec(text))
|
|
3102
|
-
return {
|
|
3103
|
-
kind: "classDef",
|
|
3104
|
-
raw: line,
|
|
3105
|
-
indent,
|
|
3106
|
-
names: m[1].split(","),
|
|
3107
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3108
|
-
};
|
|
3109
|
-
if (m = /^class\s+(\S+)\s+(\S+)\s*$/.exec(text))
|
|
3110
|
-
return { kind: "class", raw: line, indent, ids: m[1].split(","), className: m[2] };
|
|
3111
|
-
if (m = /^style\s+(\S+)\s+(.*)$/.exec(text))
|
|
3112
|
-
return {
|
|
3113
|
-
kind: "style",
|
|
3114
|
-
raw: line,
|
|
3115
|
-
indent,
|
|
3116
|
-
id: m[1],
|
|
3117
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3118
|
-
};
|
|
3119
|
-
if (m = new RegExp(String.raw`^state\s+(?:"([^"]*)"\s+as\s+)?(${ID})\s*\{\s*$`).exec(text)) {
|
|
3120
|
-
return { kind: "groupOpen", raw: line, indent, id: m[2], label: m[1] };
|
|
3121
|
-
}
|
|
3122
|
-
if (m = new RegExp(
|
|
3123
|
-
String.raw`^state\s+(?:"([^"]*)"\s+as\s+)?(${ID})(?::::(${ID}))?(?:\s+<<(fork|join|choice|end)>>)?\s*$`
|
|
3124
|
-
).exec(text)) {
|
|
3125
|
-
const decl = { id: m[2], label: m[1], classes: m[3] ? [m[3]] : [] };
|
|
3126
|
-
if (m[4]) decl.shape = m[4] === "end" ? "stateEnd" : m[4];
|
|
3127
|
-
return { kind: "node", raw: line, indent, decl };
|
|
3128
|
-
}
|
|
3129
|
-
if (m = TRANSITION.exec(text)) {
|
|
3130
|
-
const s = splitClasses(m[1]);
|
|
3131
|
-
const t = splitClasses(m[2]);
|
|
3132
|
-
return {
|
|
3133
|
-
kind: "edge",
|
|
3134
|
-
raw: line,
|
|
3135
|
-
indent,
|
|
3136
|
-
source: { id: s.id, classes: s.classes },
|
|
3137
|
-
target: { id: t.id, classes: t.classes },
|
|
3138
|
-
label: m[3]?.trim() || void 0,
|
|
3139
|
-
arrowStart: "none",
|
|
3140
|
-
arrowEnd: "arrow_barb",
|
|
3141
|
-
pattern: "solid"
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
if (m = new RegExp(String.raw`^(${ID})\s*:\s*(.*)$`).exec(text)) {
|
|
3145
|
-
return {
|
|
3146
|
-
kind: "node",
|
|
3147
|
-
raw: line,
|
|
3148
|
-
indent,
|
|
3149
|
-
decl: { id: m[1], classes: [], data: { descriptions: [m[2].trim()] } }
|
|
3150
|
-
};
|
|
3151
|
-
}
|
|
3152
|
-
if (m = new RegExp(String.raw`^(${ID})(?::::(${ID}))?\s*$`).exec(text)) {
|
|
3153
|
-
return { kind: "node", raw: line, indent, decl: { id: m[1], classes: m[2] ? [m[2]] : [] } };
|
|
3154
|
-
}
|
|
3155
|
-
return { kind: "raw", raw: line, indent };
|
|
3156
|
-
},
|
|
3157
|
-
formatStatement(stmt) {
|
|
3158
|
-
switch (stmt.kind) {
|
|
3159
|
-
case "node": {
|
|
3160
|
-
const d = stmt.decl;
|
|
3161
|
-
const descriptions = d.data?.descriptions;
|
|
3162
|
-
if (descriptions?.length && d.label === void 0 && !d.shape) {
|
|
3163
|
-
return `${stmt.indent}${d.id} : ${descriptions[0]}`;
|
|
3164
|
-
}
|
|
3165
|
-
let out = `${stmt.indent}state `;
|
|
3166
|
-
if (d.label !== void 0 && d.label !== d.id) out += `"${d.label.replace(/"/g, "'")}" as `;
|
|
3167
|
-
out += d.id;
|
|
3168
|
-
for (const c of d.classes) out += `:::${c}`;
|
|
3169
|
-
if (d.shape && d.shape !== "rect")
|
|
3170
|
-
out += ` <<${d.shape === "stateEnd" ? "end" : d.shape}>>`;
|
|
3171
|
-
return out;
|
|
3172
|
-
}
|
|
3173
|
-
case "edge": {
|
|
3174
|
-
const fmt = (n) => n.id + n.classes.map((c) => `:::${c}`).join("");
|
|
3175
|
-
const src = fmt(stmt.source);
|
|
3176
|
-
const tgt = fmt(stmt.target);
|
|
3177
|
-
return `${stmt.indent}${src} --> ${tgt}${stmt.label ? ` : ${stmt.label}` : ""}`;
|
|
3178
|
-
}
|
|
3179
|
-
case "groupOpen":
|
|
3180
|
-
return `${stmt.indent}state ${stmt.label !== void 0 && stmt.label !== stmt.id ? `"${stmt.label}" as ` : ""}${stmt.id} {`;
|
|
3181
|
-
case "groupClose":
|
|
3182
|
-
return `${stmt.indent}}`;
|
|
3183
|
-
case "direction":
|
|
3184
|
-
return `${stmt.indent}direction ${stmt.direction}`;
|
|
3185
|
-
case "classDef":
|
|
3186
|
-
return `${stmt.indent}classDef ${stmt.names.join(",")} ${stmt.styles.join(",")}`;
|
|
3187
|
-
case "class":
|
|
3188
|
-
return `${stmt.indent}class ${stmt.ids.join(",")} ${stmt.className}`;
|
|
3189
|
-
case "style":
|
|
3190
|
-
return `${stmt.indent}style ${stmt.id} ${stmt.styles.join(",")}`;
|
|
3191
|
-
default:
|
|
3192
|
-
return stmt.raw;
|
|
3193
|
-
}
|
|
3194
|
-
},
|
|
3195
|
-
nodeStatement(decl, indent) {
|
|
3196
|
-
return {
|
|
3197
|
-
kind: "node",
|
|
3198
|
-
raw: "",
|
|
3199
|
-
indent,
|
|
3200
|
-
decl: { ...decl, classes: [...decl.classes] },
|
|
3201
|
-
dirty: true
|
|
3202
|
-
};
|
|
3203
|
-
},
|
|
3204
|
-
edgeStatement(source, target, spec, indent) {
|
|
3205
|
-
const synthetic = spec.data?.synthetic ?? {};
|
|
3206
|
-
const endpoint = (id, isSynthetic) => isSynthetic ? { id: "[*]", classes: [] } : { id, classes: [] };
|
|
3207
|
-
return {
|
|
3208
|
-
kind: "edge",
|
|
3209
|
-
raw: "",
|
|
3210
|
-
indent,
|
|
3211
|
-
source: endpoint(source, synthetic.source),
|
|
3212
|
-
target: endpoint(target, synthetic.target),
|
|
3213
|
-
label: spec.label,
|
|
3214
|
-
arrowStart: "none",
|
|
3215
|
-
arrowEnd: "arrow_barb",
|
|
3216
|
-
pattern: "solid",
|
|
3217
|
-
dirty: true
|
|
3218
|
-
};
|
|
3219
|
-
},
|
|
3220
|
-
groupStatements(id, label, indent) {
|
|
3221
|
-
return {
|
|
3222
|
-
open: { kind: "groupOpen", raw: "", indent, id, label, dirty: true },
|
|
3223
|
-
close: { kind: "groupClose", raw: `${indent}}`, indent }
|
|
3224
|
-
};
|
|
3225
|
-
},
|
|
3226
|
-
headerDirection(_header, statements) {
|
|
3227
|
-
const top = statements.find((s) => s.kind === "direction");
|
|
3228
|
-
return top && top.kind === "direction" ? top.direction : "TB";
|
|
3229
|
-
},
|
|
3230
|
-
resolveEndpoint(id, stack, role) {
|
|
3231
|
-
if (id !== "[*]") return null;
|
|
3232
|
-
const scope = stack[stack.length - 1] ?? "root";
|
|
3233
|
-
return role === "source" ? { id: `${scope}_start`, shape: "stateStart", label: "", synthetic: true } : { id: `${scope}_end`, shape: "stateEnd", label: "", synthetic: true };
|
|
3234
|
-
}
|
|
3235
|
-
};
|
|
3236
|
-
|
|
3237
|
-
// src/diagram/lines-grammar/class.ts
|
|
3238
|
-
var ID2 = String.raw`[A-Za-z0-9_À-.~-]+`;
|
|
3239
|
-
var CLASS_RELATIONS = [
|
|
3240
|
-
{
|
|
3241
|
-
token: "<|--",
|
|
3242
|
-
arrowStart: "extension",
|
|
3243
|
-
arrowEnd: "none",
|
|
3244
|
-
pattern: "solid",
|
|
3245
|
-
title: "Inheritance"
|
|
3246
|
-
},
|
|
3247
|
-
{
|
|
3248
|
-
token: "--|>",
|
|
3249
|
-
arrowStart: "none",
|
|
3250
|
-
arrowEnd: "extension",
|
|
3251
|
-
pattern: "solid",
|
|
3252
|
-
title: "Inheritance (reversed)"
|
|
3253
|
-
},
|
|
3254
|
-
{
|
|
3255
|
-
token: "<|..",
|
|
3256
|
-
arrowStart: "extension",
|
|
3257
|
-
arrowEnd: "none",
|
|
3258
|
-
pattern: "dotted",
|
|
3259
|
-
title: "Realization"
|
|
3260
|
-
},
|
|
3261
|
-
{
|
|
3262
|
-
token: "..|>",
|
|
3263
|
-
arrowStart: "none",
|
|
3264
|
-
arrowEnd: "extension",
|
|
3265
|
-
pattern: "dotted",
|
|
3266
|
-
title: "Realization (reversed)"
|
|
3267
|
-
},
|
|
3268
|
-
{
|
|
3269
|
-
token: "*--",
|
|
3270
|
-
arrowStart: "composition",
|
|
3271
|
-
arrowEnd: "none",
|
|
3272
|
-
pattern: "solid",
|
|
3273
|
-
title: "Composition"
|
|
3274
|
-
},
|
|
3275
|
-
{
|
|
3276
|
-
token: "--*",
|
|
3277
|
-
arrowStart: "none",
|
|
3278
|
-
arrowEnd: "composition",
|
|
3279
|
-
pattern: "solid",
|
|
3280
|
-
title: "Composition (reversed)"
|
|
3281
|
-
},
|
|
3282
|
-
{
|
|
3283
|
-
token: "o--",
|
|
3284
|
-
arrowStart: "aggregation",
|
|
3285
|
-
arrowEnd: "none",
|
|
3286
|
-
pattern: "solid",
|
|
3287
|
-
title: "Aggregation"
|
|
3288
|
-
},
|
|
3289
|
-
{
|
|
3290
|
-
token: "--o",
|
|
3291
|
-
arrowStart: "none",
|
|
3292
|
-
arrowEnd: "aggregation",
|
|
3293
|
-
pattern: "solid",
|
|
3294
|
-
title: "Aggregation (reversed)"
|
|
3295
|
-
},
|
|
3296
|
-
{
|
|
3297
|
-
token: "<--",
|
|
3298
|
-
arrowStart: "dependency",
|
|
3299
|
-
arrowEnd: "none",
|
|
3300
|
-
pattern: "solid",
|
|
3301
|
-
title: "Association (reversed)"
|
|
3302
|
-
},
|
|
3303
|
-
{
|
|
3304
|
-
token: "-->",
|
|
3305
|
-
arrowStart: "none",
|
|
3306
|
-
arrowEnd: "dependency",
|
|
3307
|
-
pattern: "solid",
|
|
3308
|
-
title: "Association"
|
|
3309
|
-
},
|
|
3310
|
-
{
|
|
3311
|
-
token: "<..",
|
|
3312
|
-
arrowStart: "dependency",
|
|
3313
|
-
arrowEnd: "none",
|
|
3314
|
-
pattern: "dotted",
|
|
3315
|
-
title: "Dependency (reversed)"
|
|
3316
|
-
},
|
|
3317
|
-
{
|
|
3318
|
-
token: "..>",
|
|
3319
|
-
arrowStart: "none",
|
|
3320
|
-
arrowEnd: "dependency",
|
|
3321
|
-
pattern: "dotted",
|
|
3322
|
-
title: "Dependency"
|
|
3323
|
-
},
|
|
3324
|
-
{ token: "()--", arrowStart: "lollipop", arrowEnd: "none", pattern: "solid", title: "Lollipop" },
|
|
3325
|
-
{
|
|
3326
|
-
token: "--()",
|
|
3327
|
-
arrowStart: "none",
|
|
3328
|
-
arrowEnd: "lollipop",
|
|
3329
|
-
pattern: "solid",
|
|
3330
|
-
title: "Lollipop (reversed)"
|
|
3331
|
-
},
|
|
3332
|
-
{ token: "--", arrowStart: "none", arrowEnd: "none", pattern: "solid", title: "Link (solid)" },
|
|
3333
|
-
{ token: "..", arrowStart: "none", arrowEnd: "none", pattern: "dotted", title: "Link (dashed)" }
|
|
3334
|
-
];
|
|
3335
|
-
var REL_TOKENS = CLASS_RELATIONS.map((r) => r.token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(
|
|
3336
|
-
"|"
|
|
3337
|
-
);
|
|
3338
|
-
var RELATION = new RegExp(
|
|
3339
|
-
String.raw`^(${ID2})(?:\["([^"]*)"\])?(?::::(${ID2}))?\s*(?:"([^"]*)"\s*)?(${REL_TOKENS})\s*(?:"([^"]*)"\s*)?(${ID2})(?:\["([^"]*)"\])?(?::::(${ID2}))?\s*(?::\s*(.*))?$`
|
|
3340
|
-
);
|
|
3341
|
-
function relationFor(arrowStart, arrowEnd, pattern) {
|
|
3342
|
-
return CLASS_RELATIONS.find(
|
|
3343
|
-
(r) => r.arrowStart === arrowStart && r.arrowEnd === arrowEnd && r.pattern === pattern
|
|
3344
|
-
) ?? CLASS_RELATIONS.find((r) => r.token === "-->");
|
|
3345
|
-
}
|
|
3346
|
-
function parseMembers(bodyLines) {
|
|
3347
|
-
const out = [];
|
|
3348
|
-
for (const line of bodyLines) {
|
|
3349
|
-
const t = line.trim();
|
|
3350
|
-
if (!t || t === "}" || t.startsWith("%%") || /^<<.*>>$/.test(t)) continue;
|
|
3351
|
-
const vis = /^[+\-#~]/.exec(t)?.[0];
|
|
3352
|
-
out.push({ raw: t, visibility: vis, kind: /\(.*\)/.test(t) ? "method" : "attribute" });
|
|
3353
|
-
}
|
|
3354
|
-
return out;
|
|
3355
|
-
}
|
|
3356
|
-
function annotationsOf(bodyLines) {
|
|
3357
|
-
return bodyLines.map((l) => l.trim()).filter((t) => /^<<.*>>$/.test(t)).map((t) => t.slice(2, -2));
|
|
3358
|
-
}
|
|
3359
|
-
var classGrammar = {
|
|
3360
|
-
family: "class",
|
|
3361
|
-
defaultShape: "classBox",
|
|
3362
|
-
directionAsStatement: true,
|
|
3363
|
-
defaultEdge: { arrowStart: "none", arrowEnd: "dependency", pattern: "solid" },
|
|
3364
|
-
joinLines(lines) {
|
|
3365
|
-
return joinBraceBlocks(lines, { open: /^\s*note\s+(for\s+\S+\s+)?"[^"]*$/i, close: /"\s*$/ });
|
|
3366
|
-
},
|
|
3367
|
-
parseLine(logical) {
|
|
3368
|
-
const [line, ...bodyRest] = logical.split("\n");
|
|
3369
|
-
const indent = line.match(/^\s*/)[0];
|
|
3370
|
-
const text = line.trim();
|
|
3371
|
-
if (text === "" || text.startsWith("%%") || /^(note|click|link|callback|<<)/.test(text))
|
|
3372
|
-
return { kind: "raw", raw: logical, indent };
|
|
3373
|
-
if (text === "}") return { kind: "groupClose", raw: line, indent };
|
|
3374
|
-
let m;
|
|
3375
|
-
if (m = /^direction\s+(TB|TD|BT|LR|RL)\s*$/i.exec(text))
|
|
3376
|
-
return { kind: "direction", raw: line, indent, direction: m[1].toUpperCase() };
|
|
3377
|
-
if (m = /^classDef\s+(\S+)\s+(.*)$/.exec(text))
|
|
3378
|
-
return {
|
|
3379
|
-
kind: "classDef",
|
|
3380
|
-
raw: line,
|
|
3381
|
-
indent,
|
|
3382
|
-
names: m[1].split(","),
|
|
3383
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3384
|
-
};
|
|
3385
|
-
if (m = /^cssClass\s+"([^"]+)"\s+(\S+)\s*$/.exec(text))
|
|
3386
|
-
return {
|
|
3387
|
-
kind: "class",
|
|
3388
|
-
raw: line,
|
|
3389
|
-
indent,
|
|
3390
|
-
ids: m[1].split(",").map((s) => s.trim()),
|
|
3391
|
-
className: m[2]
|
|
3392
|
-
};
|
|
3393
|
-
if (m = /^style\s+(\S+)\s+(.*)$/.exec(text))
|
|
3394
|
-
return {
|
|
3395
|
-
kind: "style",
|
|
3396
|
-
raw: line,
|
|
3397
|
-
indent,
|
|
3398
|
-
id: m[1],
|
|
3399
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3400
|
-
};
|
|
3401
|
-
if (m = new RegExp(String.raw`^namespace\s+(${ID2})\s*\{\s*$`).exec(text))
|
|
3402
|
-
return { kind: "groupOpen", raw: line, indent, id: m[1] };
|
|
3403
|
-
if (m = new RegExp(
|
|
3404
|
-
String.raw`^class\s+(${ID2})(?:\["([^"]*)"\])?(?::::(${ID2}))?\s*(\{)?\s*$`
|
|
3405
|
-
).exec(text)) {
|
|
3406
|
-
const decl = { id: m[1], label: m[2], classes: m[3] ? [m[3]] : [] };
|
|
3407
|
-
if (m[4] && bodyRest.length) {
|
|
3408
|
-
decl.data = { members: parseMembers(bodyRest), annotations: annotationsOf(bodyRest) };
|
|
3409
|
-
return { kind: "node", raw: line, indent, decl, body: bodyRest };
|
|
3410
|
-
}
|
|
3411
|
-
if (m[4] && !bodyRest.length) {
|
|
3412
|
-
return { kind: "raw", raw: line, indent };
|
|
3413
|
-
}
|
|
3414
|
-
return { kind: "node", raw: line, indent, decl };
|
|
3415
|
-
}
|
|
3416
|
-
if (m = RELATION.exec(text)) {
|
|
3417
|
-
const rel = CLASS_RELATIONS.find((r) => r.token === m[5]);
|
|
3418
|
-
return {
|
|
3419
|
-
kind: "edge",
|
|
3420
|
-
raw: line,
|
|
3421
|
-
indent,
|
|
3422
|
-
source: { id: m[1], label: m[2], classes: m[3] ? [m[3]] : [] },
|
|
3423
|
-
target: { id: m[7], label: m[8], classes: m[9] ? [m[9]] : [] },
|
|
3424
|
-
label: m[10]?.trim() || void 0,
|
|
3425
|
-
arrowStart: rel.arrowStart,
|
|
3426
|
-
arrowEnd: rel.arrowEnd,
|
|
3427
|
-
pattern: rel.pattern,
|
|
3428
|
-
data: { cardinalityStart: m[4], cardinalityEnd: m[6] }
|
|
3429
|
-
};
|
|
3430
|
-
}
|
|
3431
|
-
if (m = new RegExp(String.raw`^(${ID2})\s*:\s*(.+)$`).exec(text)) {
|
|
3432
|
-
return {
|
|
3433
|
-
kind: "node",
|
|
3434
|
-
raw: line,
|
|
3435
|
-
indent,
|
|
3436
|
-
decl: { id: m[1], classes: [], data: { members: parseMembers([m[2]]) } }
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
return { kind: "raw", raw: logical, indent };
|
|
3440
|
-
},
|
|
3441
|
-
formatStatement(stmt) {
|
|
3442
|
-
switch (stmt.kind) {
|
|
3443
|
-
case "node": {
|
|
3444
|
-
const d = stmt.decl;
|
|
3445
|
-
const members = d.data?.members;
|
|
3446
|
-
if (!stmt.body && members?.length === 1 && d.label === void 0 && !d.classes.length && stmt.raw.includes(" : ")) {
|
|
3447
|
-
return `${stmt.indent}${d.id} : ${members[0].raw}`;
|
|
3448
|
-
}
|
|
3449
|
-
let out = `${stmt.indent}class ${d.id}`;
|
|
3450
|
-
if (d.label !== void 0 && d.label !== d.id) out += `["${d.label.replace(/"/g, "'")}"]`;
|
|
3451
|
-
for (const c of d.classes) out += `:::${c}`;
|
|
3452
|
-
if (stmt.body) out += " {";
|
|
3453
|
-
return out;
|
|
3454
|
-
}
|
|
3455
|
-
case "edge": {
|
|
3456
|
-
const rel = relationFor(stmt.arrowStart, stmt.arrowEnd, stmt.pattern);
|
|
3457
|
-
const cs = stmt.data?.cardinalityStart;
|
|
3458
|
-
const ce = stmt.data?.cardinalityEnd;
|
|
3459
|
-
const fmt = (n) => n.id + (n.label !== void 0 ? `["${n.label}"]` : "") + n.classes.map((c) => `:::${c}`).join("");
|
|
3460
|
-
return `${stmt.indent}${fmt(stmt.source)} ${cs ? `"${cs}" ` : ""}${rel.token} ${ce ? `"${ce}" ` : ""}${fmt(stmt.target)}${stmt.label ? ` : ${stmt.label}` : ""}`;
|
|
3461
|
-
}
|
|
3462
|
-
case "groupOpen":
|
|
3463
|
-
return `${stmt.indent}namespace ${stmt.id} {`;
|
|
3464
|
-
case "groupClose":
|
|
3465
|
-
return `${stmt.indent}}`;
|
|
3466
|
-
case "direction":
|
|
3467
|
-
return `${stmt.indent}direction ${stmt.direction}`;
|
|
3468
|
-
case "classDef":
|
|
3469
|
-
return `${stmt.indent}classDef ${stmt.names.join(",")} ${stmt.styles.join(",")}`;
|
|
3470
|
-
case "class":
|
|
3471
|
-
return `${stmt.indent}cssClass "${stmt.ids.join(",")}" ${stmt.className}`;
|
|
3472
|
-
case "style":
|
|
3473
|
-
return `${stmt.indent}style ${stmt.id} ${stmt.styles.join(",")}`;
|
|
3474
|
-
default:
|
|
3475
|
-
return stmt.raw;
|
|
3476
|
-
}
|
|
3477
|
-
},
|
|
3478
|
-
nodeStatement(decl, indent) {
|
|
3479
|
-
const members = decl.data?.members;
|
|
3480
|
-
const body = members?.length ? [...members.map((m) => `${indent} ${m.raw}`), `${indent}}`] : void 0;
|
|
3481
|
-
return {
|
|
3482
|
-
kind: "node",
|
|
3483
|
-
raw: "",
|
|
3484
|
-
indent,
|
|
3485
|
-
decl: { ...decl, classes: [...decl.classes] },
|
|
3486
|
-
body,
|
|
3487
|
-
dirty: true
|
|
3488
|
-
};
|
|
3489
|
-
},
|
|
3490
|
-
edgeStatement(source, target, spec, indent) {
|
|
3491
|
-
return {
|
|
3492
|
-
kind: "edge",
|
|
3493
|
-
raw: "",
|
|
3494
|
-
indent,
|
|
3495
|
-
source: { id: source, classes: [] },
|
|
3496
|
-
target: { id: target, classes: [] },
|
|
3497
|
-
label: spec.label,
|
|
3498
|
-
arrowStart: spec.arrowStart,
|
|
3499
|
-
arrowEnd: spec.arrowEnd,
|
|
3500
|
-
pattern: spec.pattern,
|
|
3501
|
-
data: spec.data,
|
|
3502
|
-
dirty: true
|
|
3503
|
-
};
|
|
3504
|
-
},
|
|
3505
|
-
groupStatements(id, _label, indent) {
|
|
3506
|
-
return {
|
|
3507
|
-
open: { kind: "groupOpen", raw: "", indent, id, dirty: true },
|
|
3508
|
-
close: { kind: "groupClose", raw: `${indent}}`, indent }
|
|
3509
|
-
};
|
|
3510
|
-
},
|
|
3511
|
-
headerDirection(_header, statements) {
|
|
3512
|
-
const top = statements.find((s) => s.kind === "direction");
|
|
3513
|
-
return top && top.kind === "direction" ? top.direction : "TB";
|
|
3514
|
-
}
|
|
3515
|
-
};
|
|
3516
|
-
|
|
3517
|
-
// src/diagram/lines-grammar/er.ts
|
|
3518
|
-
var ID3 = String.raw`[A-Za-z0-9_À--]+|"[^"]+"`;
|
|
3519
|
-
var ER_CARDINALITIES = [
|
|
3520
|
-
{ token: "|o", head: "zero_or_one", side: "left", title: "Zero or one" },
|
|
3521
|
-
{ token: "||", head: "only_one", side: "left", title: "Exactly one" },
|
|
3522
|
-
{ token: "}o", head: "zero_or_more", side: "left", title: "Zero or more" },
|
|
3523
|
-
{ token: "}|", head: "one_or_more", side: "left", title: "One or more" },
|
|
3524
|
-
{ token: "o|", head: "zero_or_one", side: "right", title: "Zero or one" },
|
|
3525
|
-
{ token: "||", head: "only_one", side: "right", title: "Exactly one" },
|
|
3526
|
-
{ token: "o{", head: "zero_or_more", side: "right", title: "Zero or more" },
|
|
3527
|
-
{ token: "|{", head: "one_or_more", side: "right", title: "One or more" }
|
|
3528
|
-
];
|
|
3529
|
-
var LEFT = {
|
|
3530
|
-
"|o": "zero_or_one",
|
|
3531
|
-
"||": "only_one",
|
|
3532
|
-
"}o": "zero_or_more",
|
|
3533
|
-
"}|": "one_or_more"
|
|
3534
|
-
};
|
|
3535
|
-
var RIGHT = {
|
|
3536
|
-
"o|": "zero_or_one",
|
|
3537
|
-
"||": "only_one",
|
|
3538
|
-
"o{": "zero_or_more",
|
|
3539
|
-
"|{": "one_or_more"
|
|
3540
|
-
};
|
|
3541
|
-
var LEFT_TOKEN = {
|
|
3542
|
-
zero_or_one: "|o",
|
|
3543
|
-
only_one: "||",
|
|
3544
|
-
zero_or_more: "}o",
|
|
3545
|
-
one_or_more: "}|"
|
|
3546
|
-
};
|
|
3547
|
-
var RIGHT_TOKEN = {
|
|
3548
|
-
zero_or_one: "o|",
|
|
3549
|
-
only_one: "||",
|
|
3550
|
-
zero_or_more: "o{",
|
|
3551
|
-
one_or_more: "|{"
|
|
3552
|
-
};
|
|
3553
|
-
var RELATION2 = new RegExp(
|
|
3554
|
-
String.raw`^(${ID3})(?:\[([^\]]*)\])?(?::::([\w-]+))?\s+(\|o|\|\||\}o|\}\|)(--|\.\.|\.-|-\.)(o\||\|\||o\{|\|\{)\s+(${ID3})(?:\[([^\]]*)\])?(?::::([\w-]+))?\s*:\s*(.*)$`
|
|
3555
|
-
);
|
|
3556
|
-
function parseAttributes(bodyLines) {
|
|
3557
|
-
const out = [];
|
|
3558
|
-
for (const line of bodyLines) {
|
|
3559
|
-
const t = line.trim();
|
|
3560
|
-
if (!t || t === "}" || t.startsWith("%%")) continue;
|
|
3561
|
-
const m = /^(\S+)\s+(\S+)((?:\s+(?:PK|FK|UK)(?:\s*,\s*(?:PK|FK|UK))*)?)\s*(?:"([^"]*)")?\s*$/.exec(t);
|
|
3562
|
-
if (m) {
|
|
3563
|
-
out.push({
|
|
3564
|
-
raw: t,
|
|
3565
|
-
type: m[1],
|
|
3566
|
-
name: m[2],
|
|
3567
|
-
keys: (m[3] ?? "").split(/[\s,]+/).filter(Boolean),
|
|
3568
|
-
comment: m[4]
|
|
3569
|
-
});
|
|
3570
|
-
} else {
|
|
3571
|
-
out.push({ raw: t, type: "", name: t, keys: [] });
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
return out;
|
|
3575
|
-
}
|
|
3576
|
-
function unq(id) {
|
|
3577
|
-
return id.startsWith('"') && id.endsWith('"') ? id.slice(1, -1) : id;
|
|
3578
|
-
}
|
|
3579
|
-
function fmtId(id) {
|
|
3580
|
-
return /[\s]/.test(id) ? `"${id}"` : id;
|
|
3581
|
-
}
|
|
3582
|
-
var erGrammar = {
|
|
3583
|
-
family: "er",
|
|
3584
|
-
defaultShape: "erBox",
|
|
3585
|
-
directionAsStatement: true,
|
|
3586
|
-
supportsGroups: false,
|
|
3587
|
-
defaultEdge: { arrowStart: "only_one", arrowEnd: "zero_or_more", pattern: "solid", label: "has" },
|
|
3588
|
-
joinLines(lines) {
|
|
3589
|
-
return joinBraceBlocks(lines);
|
|
3590
|
-
},
|
|
3591
|
-
parseLine(logical) {
|
|
3592
|
-
const [line, ...bodyRest] = logical.split("\n");
|
|
3593
|
-
const indent = line.match(/^\s*/)[0];
|
|
3594
|
-
const text = line.trim();
|
|
3595
|
-
if (text === "" || text.startsWith("%%")) return { kind: "raw", raw: logical, indent };
|
|
3596
|
-
let m;
|
|
3597
|
-
if (m = /^direction\s+(TB|TD|BT|LR|RL)\s*$/i.exec(text))
|
|
3598
|
-
return { kind: "direction", raw: line, indent, direction: m[1].toUpperCase() };
|
|
3599
|
-
if (m = /^classDef\s+(\S+)\s+(.*)$/.exec(text))
|
|
3600
|
-
return {
|
|
3601
|
-
kind: "classDef",
|
|
3602
|
-
raw: line,
|
|
3603
|
-
indent,
|
|
3604
|
-
names: m[1].split(","),
|
|
3605
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3606
|
-
};
|
|
3607
|
-
if (m = /^class\s+(\S+)\s+(\S+)\s*$/.exec(text))
|
|
3608
|
-
return { kind: "class", raw: line, indent, ids: m[1].split(","), className: m[2] };
|
|
3609
|
-
if (m = /^style\s+(\S+)\s+(.*)$/.exec(text))
|
|
3610
|
-
return {
|
|
3611
|
-
kind: "style",
|
|
3612
|
-
raw: line,
|
|
3613
|
-
indent,
|
|
3614
|
-
id: m[1],
|
|
3615
|
-
styles: m[2].split(",").map((s) => s.trim()).filter(Boolean)
|
|
3616
|
-
};
|
|
3617
|
-
if (m = RELATION2.exec(text)) {
|
|
3618
|
-
const line_ = m[5];
|
|
3619
|
-
return {
|
|
3620
|
-
kind: "edge",
|
|
3621
|
-
raw: line,
|
|
3622
|
-
indent,
|
|
3623
|
-
source: { id: unq(m[1]), label: m[2], classes: m[3] ? [m[3]] : [] },
|
|
3624
|
-
target: { id: unq(m[7]), label: m[8], classes: m[9] ? [m[9]] : [] },
|
|
3625
|
-
label: unq(m[10].trim()),
|
|
3626
|
-
arrowStart: LEFT[m[4]],
|
|
3627
|
-
arrowEnd: RIGHT[m[6]],
|
|
3628
|
-
pattern: line_ === "--" ? "solid" : "dotted",
|
|
3629
|
-
data: { identifying: line_ === "--" }
|
|
3630
|
-
};
|
|
3631
|
-
}
|
|
3632
|
-
if (m = new RegExp(String.raw`^(${ID3})(?:\[([^\]]*)\])?(?::::([\w-]+))?\s*(\{)?\s*$`).exec(text)) {
|
|
3633
|
-
const decl = {
|
|
3634
|
-
id: unq(m[1]),
|
|
3635
|
-
label: m[2] !== void 0 ? unq(m[2]) : void 0,
|
|
3636
|
-
classes: m[3] ? [m[3]] : []
|
|
3637
|
-
};
|
|
3638
|
-
if (m[4]) {
|
|
3639
|
-
decl.data = { attributes: parseAttributes(bodyRest) };
|
|
3640
|
-
return { kind: "node", raw: line, indent, decl, body: bodyRest };
|
|
3641
|
-
}
|
|
3642
|
-
return { kind: "node", raw: line, indent, decl };
|
|
3643
|
-
}
|
|
3644
|
-
return { kind: "raw", raw: logical, indent };
|
|
3645
|
-
},
|
|
3646
|
-
formatStatement(stmt) {
|
|
3647
|
-
switch (stmt.kind) {
|
|
3648
|
-
case "node": {
|
|
3649
|
-
const d = stmt.decl;
|
|
3650
|
-
let out = `${stmt.indent}${fmtId(d.id)}`;
|
|
3651
|
-
if (d.label !== void 0 && d.label !== d.id) out += `["${d.label}"]`;
|
|
3652
|
-
for (const c of d.classes) out += `:::${c}`;
|
|
3653
|
-
if (stmt.body) out += " {";
|
|
3654
|
-
return out;
|
|
3655
|
-
}
|
|
3656
|
-
case "edge": {
|
|
3657
|
-
const fmt = (n) => fmtId(n.id) + (n.label !== void 0 ? `["${n.label}"]` : "") + n.classes.map((c) => `:::${c}`).join("");
|
|
3658
|
-
const l = LEFT_TOKEN[stmt.arrowStart] ?? "||";
|
|
3659
|
-
const r = RIGHT_TOKEN[stmt.arrowEnd] ?? "o{";
|
|
3660
|
-
const mid = stmt.pattern === "dotted" ? ".." : "--";
|
|
3661
|
-
const label = stmt.label ?? "";
|
|
3662
|
-
return `${stmt.indent}${fmt(stmt.source)} ${l}${mid}${r} ${fmt(stmt.target)} : ${/\s/.test(label) ? `"${label}"` : label || '""'}`;
|
|
3663
|
-
}
|
|
3664
|
-
case "direction":
|
|
3665
|
-
return `${stmt.indent}direction ${stmt.direction}`;
|
|
3666
|
-
case "classDef":
|
|
3667
|
-
return `${stmt.indent}classDef ${stmt.names.join(",")} ${stmt.styles.join(",")}`;
|
|
3668
|
-
case "class":
|
|
3669
|
-
return `${stmt.indent}class ${stmt.ids.join(",")} ${stmt.className}`;
|
|
3670
|
-
case "style":
|
|
3671
|
-
return `${stmt.indent}style ${stmt.id} ${stmt.styles.join(",")}`;
|
|
3672
|
-
default:
|
|
3673
|
-
return stmt.raw;
|
|
3674
|
-
}
|
|
3675
|
-
},
|
|
3676
|
-
nodeStatement(decl, indent) {
|
|
3677
|
-
const attrs = decl.data?.attributes;
|
|
3678
|
-
const body = attrs?.length ? [...attrs.map((a) => `${indent} ${a.raw}`), `${indent}}`] : void 0;
|
|
3679
|
-
return {
|
|
3680
|
-
kind: "node",
|
|
3681
|
-
raw: "",
|
|
3682
|
-
indent,
|
|
3683
|
-
decl: { ...decl, classes: [...decl.classes] },
|
|
3684
|
-
body,
|
|
3685
|
-
dirty: true
|
|
3686
|
-
};
|
|
3687
|
-
},
|
|
3688
|
-
edgeStatement(source, target, spec, indent) {
|
|
3689
|
-
return {
|
|
3690
|
-
kind: "edge",
|
|
3691
|
-
raw: "",
|
|
3692
|
-
indent,
|
|
3693
|
-
source: { id: source, classes: [] },
|
|
3694
|
-
target: { id: target, classes: [] },
|
|
3695
|
-
label: spec.label ?? "has",
|
|
3696
|
-
arrowStart: spec.arrowStart,
|
|
3697
|
-
arrowEnd: spec.arrowEnd,
|
|
3698
|
-
pattern: spec.pattern,
|
|
3699
|
-
data: spec.data,
|
|
3700
|
-
dirty: true
|
|
3701
|
-
};
|
|
3702
|
-
},
|
|
3703
|
-
groupStatements(id, _label, indent) {
|
|
3704
|
-
return {
|
|
3705
|
-
open: { kind: "raw", raw: `${indent}%% group ${id}`, indent },
|
|
3706
|
-
close: { kind: "raw", raw: `${indent}%% end group ${id}`, indent }
|
|
3707
|
-
};
|
|
3708
|
-
},
|
|
3709
|
-
headerDirection(_header, statements) {
|
|
3710
|
-
const top = statements.find((s) => s.kind === "direction");
|
|
3711
|
-
return top && top.kind === "direction" ? top.direction : "TB";
|
|
3712
|
-
}
|
|
3713
|
-
};
|
|
3714
|
-
var ER_PATTERNS = [
|
|
3715
|
-
{ pattern: "solid", title: "Identifying" },
|
|
3716
|
-
{ pattern: "dotted", title: "Non-identifying" }
|
|
3717
|
-
];
|
|
3718
|
-
|
|
3719
|
-
// src/diagram/registry.ts
|
|
3720
|
-
var adapters = /* @__PURE__ */ new Map();
|
|
3721
|
-
function registerDiagramAdapter(adapter) {
|
|
3722
|
-
adapters.set(adapter.family, adapter);
|
|
3723
|
-
}
|
|
3724
|
-
function getDiagramAdapter(family) {
|
|
3725
|
-
return adapters.get(family) ?? adapters.get("unknown");
|
|
3726
|
-
}
|
|
3727
|
-
function supportTierOf(family) {
|
|
3728
|
-
return getDiagramAdapter(family).tier;
|
|
3729
|
-
}
|
|
3730
|
-
registerDiagramAdapter(flowchartAdapter);
|
|
3731
|
-
registerDiagramAdapter(createLineAdapter(stateGrammar));
|
|
3732
|
-
registerDiagramAdapter(createLineAdapter(classGrammar));
|
|
3733
|
-
registerDiagramAdapter(createLineAdapter(erGrammar));
|
|
3734
|
-
for (const family of [
|
|
3735
|
-
"requirement",
|
|
3736
|
-
"mindmap",
|
|
3737
|
-
"sequence",
|
|
3738
|
-
"gantt",
|
|
3739
|
-
"pie",
|
|
3740
|
-
"journey",
|
|
3741
|
-
"timeline",
|
|
3742
|
-
"gitGraph",
|
|
3743
|
-
"c4",
|
|
3744
|
-
"quadrant",
|
|
3745
|
-
"sankey",
|
|
3746
|
-
"xychart",
|
|
3747
|
-
"block",
|
|
3748
|
-
"architecture",
|
|
3749
|
-
"packet",
|
|
3750
|
-
"kanban",
|
|
3751
|
-
"radar",
|
|
3752
|
-
"treemap",
|
|
3753
|
-
"info",
|
|
3754
|
-
"unknown"
|
|
3755
|
-
]) {
|
|
3756
|
-
registerDiagramAdapter(createPassthroughAdapter(family));
|
|
3757
|
-
}
|
|
3758
|
-
|
|
3759
|
-
// src/diagram/document.ts
|
|
3760
|
-
function detectFamily(source) {
|
|
3761
|
-
return detectFamilyFromHeader(splitSource(source).header);
|
|
3762
|
-
}
|
|
3763
|
-
function parseMermaid(source, options = {}) {
|
|
3764
|
-
const split = splitSource(source);
|
|
3765
|
-
const family = detectFamilyFromHeader(split.header);
|
|
3766
|
-
const fromBody = extractMetaFromBody(split.bodyLines);
|
|
3767
|
-
const fromFm = extractMetaFromFrontmatter(split.preamble);
|
|
3768
|
-
let meta = emptyMeta();
|
|
3769
|
-
if (fromFm) meta = mergeMeta(meta, fromFm);
|
|
3770
|
-
if (fromBody.meta) meta = mergeMeta(meta, fromBody.meta);
|
|
3771
|
-
const metaCarrier = options.metaCarrier ?? (fromFm && !fromBody.meta ? "frontmatter" : defaultCarrierFor(family));
|
|
3772
|
-
const adapter = getDiagramAdapter(family);
|
|
3773
|
-
const doc = adapter.parse(source, {
|
|
3774
|
-
source,
|
|
3775
|
-
eol: split.eol,
|
|
3776
|
-
preamble: split.preamble,
|
|
3777
|
-
header: split.header,
|
|
3778
|
-
bodyLines: fromBody.bodyLines,
|
|
3779
|
-
trailing: split.trailing,
|
|
3780
|
-
meta,
|
|
3781
|
-
metaCarrier,
|
|
3782
|
-
family
|
|
3783
|
-
});
|
|
3784
|
-
return doc;
|
|
3785
|
-
}
|
|
3786
|
-
function serializeMermaid(doc) {
|
|
3787
|
-
return getDiagramAdapter(doc.family).serialize(doc);
|
|
3788
|
-
}
|
|
3789
|
-
function applyEdit(doc, op) {
|
|
3790
|
-
if (op.type === "replaceSource") {
|
|
3791
|
-
const next2 = parseMermaid(op.source, { metaCarrier: doc.metaCarrier });
|
|
3792
|
-
next2.dirty = true;
|
|
3793
|
-
return next2;
|
|
3794
|
-
}
|
|
3795
|
-
const next = getDiagramAdapter(doc.family).apply(doc, op);
|
|
3796
|
-
if (next !== doc && next.source === doc.source) {
|
|
3797
|
-
next.source = serializeMermaid(next);
|
|
3798
|
-
}
|
|
3799
|
-
return next;
|
|
3800
|
-
}
|
|
3801
|
-
function applyEdits(doc, ops) {
|
|
3802
|
-
return ops.reduce((d, op) => applyEdit(d, op), doc);
|
|
3803
|
-
}
|
|
3804
|
-
|
|
3805
|
-
export {
|
|
3806
|
-
detectFamilyFromHeader,
|
|
3807
|
-
splitSource,
|
|
3808
|
-
splitStatements,
|
|
3809
|
-
META_COMMENT_PREFIX,
|
|
3810
|
-
META_FRONTMATTER_KEY,
|
|
3811
|
-
emptyMeta,
|
|
3812
|
-
mergeMeta,
|
|
3813
|
-
isMetaEmpty,
|
|
3814
|
-
serializeMeta,
|
|
3815
|
-
parseLink,
|
|
3816
|
-
quoteLabelIfNeeded,
|
|
3817
|
-
formatLink,
|
|
3818
|
-
FLOWCHART_SHAPES,
|
|
3819
|
-
resolveShape,
|
|
3820
|
-
canonicalShape,
|
|
3821
|
-
parseChain,
|
|
3822
|
-
parseStatement,
|
|
3823
|
-
formatNode,
|
|
3824
|
-
formatChain,
|
|
3825
|
-
parseFlowchartSource,
|
|
3826
|
-
flowchartAdapter,
|
|
3827
|
-
createLineAdapter,
|
|
3828
|
-
createPassthroughAdapter,
|
|
3829
|
-
STATE_SHAPES,
|
|
3830
|
-
stateGrammar,
|
|
3831
|
-
CLASS_RELATIONS,
|
|
3832
|
-
relationFor,
|
|
3833
|
-
parseMembers,
|
|
3834
|
-
classGrammar,
|
|
3835
|
-
ER_CARDINALITIES,
|
|
3836
|
-
parseAttributes,
|
|
3837
|
-
erGrammar,
|
|
3838
|
-
ER_PATTERNS,
|
|
3839
|
-
registerDiagramAdapter,
|
|
3840
|
-
getDiagramAdapter,
|
|
3841
|
-
supportTierOf,
|
|
3842
|
-
detectFamily,
|
|
3843
|
-
parseMermaid,
|
|
3844
|
-
serializeMermaid,
|
|
3845
|
-
applyEdit,
|
|
3846
|
-
applyEdits
|
|
3847
|
-
};
|
|
3848
|
-
//# sourceMappingURL=chunk-WWJNC7SX.js.map
|