@helping-ai-workflow/md2doc 3.3.0 → 3.4.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/README.md +33 -8
- package/THIRD-PARTY-NOTICES.md +44 -0
- package/lib/drawio.js +178 -0
- package/lib/editor/blockmap.js +79 -6
- package/lib/editor/client.js +3212 -140
- package/lib/editor/inline-md.js +122 -12
- package/lib/editor/lineops.js +190 -3
- package/lib/editor/server.js +323 -3
- package/lib/editor/toolbar-model.js +51 -14
- package/lib/editor/wave-codec.js +2631 -0
- package/lib/editor/wave-geometry.js +320 -0
- package/lib/editor/wave-store.js +950 -0
- package/lib/editor/wave-ui.js +2124 -0
- package/lib/md2doc.js +677 -14
- package/package.json +14 -6
- package/vendor/drawio/LICENSE +201 -0
- package/vendor/drawio/NOTICE +109 -0
- package/vendor/drawio/README.md +25 -0
- package/vendor/drawio/viewer-static.min.js +8596 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wave-geometry — where every cycle of a WaveJSON document sits, and which
|
|
5
|
+
* cycle a point lands in. Pure arithmetic: no DOM, no measuring, no mutation.
|
|
6
|
+
* Every size arrives as a parameter, so the layer above can hand in whatever
|
|
7
|
+
* the rendered SVG actually measures and this file stays testable in
|
|
8
|
+
* milliseconds.
|
|
9
|
+
*
|
|
10
|
+
* It lives on its own for one reason: the hit test's boundary rule — a cell's
|
|
11
|
+
* LEFT edge belongs to it, its RIGHT edge belongs to the next cell — is the
|
|
12
|
+
* single easiest thing in a wave GUI to get wrong by one pixel, and the single
|
|
13
|
+
* easiest thing to pin exactly if it is arithmetic instead of a click handler.
|
|
14
|
+
* Buried in the SVG editor it would need a browser to check; here it needs
|
|
15
|
+
* `node test/wave-geometry.test.js`.
|
|
16
|
+
*
|
|
17
|
+
* Two facts about the engine this file is shaped around, both measured against
|
|
18
|
+
* the pinned `wavedrom@3.5.0` in `node_modules` rather than assumed:
|
|
19
|
+
*
|
|
20
|
+
* 1. A GROUP TITLE DOES NOT OCCUPY A ROW. `{signal:[{a},['grp',{b},{c}],{d}]}`
|
|
21
|
+
* and the same four lanes with no group render to the identical height
|
|
22
|
+
* (120) with lanes at the identical y (5/35/65/95). What a group costs is
|
|
23
|
+
* horizontal: the name column widens (the `lanes_0` translate goes 40.5 →
|
|
24
|
+
* 60.5 for one named group, 80.5 nested). So a row is exactly
|
|
25
|
+
* `index * laneHeight`, and no row model richer than that is needed.
|
|
26
|
+
*
|
|
27
|
+
* 2. A REPEATER IS A CYCLE. `0...`, `0.0.` and `0000` all render 4 cycles
|
|
28
|
+
* wide (220px), `0|0` 3 (180px), `0||0` 4. So cycle counts come from the
|
|
29
|
+
* codec's `levelsOf`, which models what the engine draws, rather than from
|
|
30
|
+
* counting value characters.
|
|
31
|
+
*
|
|
32
|
+
* The lane index this file speaks is the codec's: the flattened, depth-first
|
|
33
|
+
* display order over real lanes, skipping the strings a group uses for its
|
|
34
|
+
* title. It is not re-derived here — `wave-codec.lanePath` is asked for each
|
|
35
|
+
* one in turn, so the two files cannot drift into two index spaces that agree
|
|
36
|
+
* today. Two disagreeing lane orders inside one module was the most expensive
|
|
37
|
+
* defect in this batch, and it was silent; a caller that hit-tests here and
|
|
38
|
+
* edits through the codec must be pointing at the same lane.
|
|
39
|
+
*
|
|
40
|
+
* Coordinates are layout-local: (0, 0) is the top-left of the whole diagram,
|
|
41
|
+
* name column included. Translating a pointer event into that space belongs to
|
|
42
|
+
* the caller, which is the only layer that knows about scroll and scale.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const codec = require('./wave-codec.js');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sizes to use when the caller has nothing better, read out of the engine's own
|
|
49
|
+
* `lane` module after a render rather than inferred: `lane.yo` (row pitch) is
|
|
50
|
+
* 30, `lane.xs` (brick width) is 20 and a cycle is two bricks, so 40, and
|
|
51
|
+
* `lane.xg` (name column) is 40 for a short-named, ungrouped diagram. The
|
|
52
|
+
* measured `lanes_0` translate of 40.5 is `lane.xg + 0.5` — the engine's own
|
|
53
|
+
* half-pixel stroke alignment, added in `insert-svg-template.js` — not a name
|
|
54
|
+
* column of 40.5, so 40 is the exact number and not a rounded one.
|
|
55
|
+
*
|
|
56
|
+
* These are a fallback. A caller that has the rendered SVG in front of it
|
|
57
|
+
* should measure and pass its own; under browser zoom those measurements are
|
|
58
|
+
* fractional, which the hit test below is built to survive.
|
|
59
|
+
*/
|
|
60
|
+
const DEFAULTS = { laneHeight: 30, cycleWidth: 40, nameColWidth: 40 };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* How many lanes are laid out before the rest are dropped.
|
|
64
|
+
*
|
|
65
|
+
* This is a ceiling on the RESULT, not a cure for a cost. The enumeration below
|
|
66
|
+
* asks the codec once for the whole list, so laying a document out is one walk
|
|
67
|
+
* of it: measured through `layoutOf`, 1024 lanes 4ms, 20000 lanes 6ms, 50000
|
|
68
|
+
* lanes 8ms, and a 508900-byte fence of 20000 lanes goes from `parseSource`
|
|
69
|
+
* (98ms) to a layout in 2ms. The earlier shape of this file asked position by
|
|
70
|
+
* position, which walked the whole document once per lane: enumerating the same
|
|
71
|
+
* 1024 lanes cost 100ms in a 1024-lane document, 2089ms in a 20000-lane one and
|
|
72
|
+
* 3630ms in a 50000-lane one. No ceiling on the result could bound that, because
|
|
73
|
+
* the cost followed the document and not the ceiling.
|
|
74
|
+
*
|
|
75
|
+
* What the ceiling is still for: a diagram with more rows than this is not a
|
|
76
|
+
* diagram, and the layer above would be drawing them. Past it the layout is
|
|
77
|
+
* truncated and says so on the way out. Truncating rather than raising is
|
|
78
|
+
* deliberate: the source is the user's markdown, and a fence the codec's parser
|
|
79
|
+
* accepts must never take the editor down. A broken document lays out as best
|
|
80
|
+
* it can; only a broken SIZE raises, because that is caller code.
|
|
81
|
+
*/
|
|
82
|
+
const MAX_LANES = 1024;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A size the caller passed. Absent means "use the default"; present but not a
|
|
86
|
+
* usable number is a programming error and says so. Quietly substituting a
|
|
87
|
+
* default there would not fail — it would lay the diagram out at the wrong
|
|
88
|
+
* scale and paint cycles into the wrong place, which is the whole class of
|
|
89
|
+
* defect this file exists to make impossible.
|
|
90
|
+
*/
|
|
91
|
+
function size(value, fallback, name, allowZero) {
|
|
92
|
+
if (value === undefined) return fallback;
|
|
93
|
+
if (typeof value !== 'number' || !isFinite(value) || value < 0 ||
|
|
94
|
+
(value === 0 && !allowZero)) {
|
|
95
|
+
throw new TypeError('wave-geometry: ' + name + ' must be a finite ' +
|
|
96
|
+
(allowZero ? 'non-negative' : 'positive') + ' number, got ' + String(value));
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The options bag itself. `undefined` and `null` mean "no options"; anything
|
|
103
|
+
* else that is not a plain options object — a number, a string, an array, a
|
|
104
|
+
* callable — is the same programming error as a bad size, and by the same
|
|
105
|
+
* argument: `layoutOf(doc, 42)` laying the diagram out at default sizes is a
|
|
106
|
+
* plausible-looking layout at the wrong scale with no complaint anywhere.
|
|
107
|
+
*/
|
|
108
|
+
function options(opts) {
|
|
109
|
+
if (opts === undefined || opts === null) return {};
|
|
110
|
+
const proto = typeof opts === 'object' ? Object.getPrototypeOf(opts) : false;
|
|
111
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
112
|
+
throw new TypeError('wave-geometry: opts must be a plain object, got ' +
|
|
113
|
+
(typeof opts === 'object' ? Object.prototype.toString.call(opts) : typeof opts));
|
|
114
|
+
}
|
|
115
|
+
return opts;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The value at `path` in `doc`, or `undefined` if the path does not land. */
|
|
119
|
+
function valueAt(doc, path) {
|
|
120
|
+
let node = doc;
|
|
121
|
+
for (const step of path) {
|
|
122
|
+
if (node === null || typeof node !== 'object') return undefined;
|
|
123
|
+
node = node[step];
|
|
124
|
+
}
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Every lane of the document, in display order, each with the codec's own path
|
|
130
|
+
* to it — from the codec's single walk, not from a second one here. There is
|
|
131
|
+
* only one lane order in this batch, and asking for it rather than deriving it
|
|
132
|
+
* is what keeps that true.
|
|
133
|
+
*/
|
|
134
|
+
function enumerateLanes(doc) {
|
|
135
|
+
const paths = codec.lanePaths(doc);
|
|
136
|
+
const out = [];
|
|
137
|
+
const n = paths.length < MAX_LANES ? paths.length : MAX_LANES;
|
|
138
|
+
for (let i = 0; i < n; i++) {
|
|
139
|
+
out.push({ path: paths[i], lane: valueAt(doc, paths[i]) });
|
|
140
|
+
}
|
|
141
|
+
return { lanes: out, truncated: paths.length > MAX_LANES };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** How many cycles a lane is: what the engine draws, which is what the codec models. */
|
|
145
|
+
function cyclesOf(lane) {
|
|
146
|
+
if (lane === null || typeof lane !== 'object') return 0;
|
|
147
|
+
if (typeof lane.wave !== 'string') return 0;
|
|
148
|
+
return codec.levelsOf(lane.wave).length;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Where everything is.
|
|
153
|
+
*
|
|
154
|
+
* `lanes[i]` carries the row (`y`, `height`), the codec's `path` to that lane
|
|
155
|
+
* and the lane object itself, so a caller that hit-tests here can hand the
|
|
156
|
+
* index straight to a codec document op without looking the lane up again. It
|
|
157
|
+
* also carries `cycles` — that lane's own length, which can be shorter than the
|
|
158
|
+
* diagram's — and `depth`, the group nesting read off the path rather than
|
|
159
|
+
* walked for a second time.
|
|
160
|
+
*
|
|
161
|
+
* `cycles` is the longest lane, floored at one whenever there is a lane at all.
|
|
162
|
+
* The engine does the same: a lane whose wave is empty still renders 100px wide
|
|
163
|
+
* with `lane.xmax` 2, exactly as `wave: '0'` does — one cycle's worth of space
|
|
164
|
+
* reserved — while a document with no lanes renders 40 wide and reserves none.
|
|
165
|
+
* Without that floor a fresh diagram has no coordinate anywhere at which to
|
|
166
|
+
* place its first cycle, and the layer above would have to invent one, which
|
|
167
|
+
* would put two different beliefs about the width of an empty diagram in two
|
|
168
|
+
* different files.
|
|
169
|
+
*
|
|
170
|
+
* `width` and `height` span the hit-testable extent: the name column plus every
|
|
171
|
+
* cycle, and every row. They are not the engine's canvas — the engine adds
|
|
172
|
+
* about half a cycle of right margin (four cycles render 220 wide where this
|
|
173
|
+
* reports 200 at the default sizes). That margin is not a cell and must not be
|
|
174
|
+
* hit-testable, so a caller sizing an overlay from these should add its own.
|
|
175
|
+
*
|
|
176
|
+
* `truncated` is true when the document had more lanes than `MAX_LANES` and the
|
|
177
|
+
* rest were dropped.
|
|
178
|
+
*
|
|
179
|
+
* The domain this file claims is sizes a rendered SVG can actually produce:
|
|
180
|
+
* positive, finite, and within a few orders of magnitude of each other. Outside
|
|
181
|
+
* it the arithmetic stops being able to represent the answer rather than
|
|
182
|
+
* computing it wrongly, and the round trip goes with it — measured, a
|
|
183
|
+
* `cycleWidth` of 1e-13 against a `nameColWidth` of 1e6 is absorbed entirely, so
|
|
184
|
+
* `width` collapses onto `nameColWidth` and every point misses; sizes at 1e308
|
|
185
|
+
* overflow `width` to Infinity. Both are outside anything `getBoundingClientRect`
|
|
186
|
+
* can report, and neither is defended against here.
|
|
187
|
+
*/
|
|
188
|
+
function layoutOf(doc, opts) {
|
|
189
|
+
const o = options(opts);
|
|
190
|
+
const laneHeight = size(o.laneHeight, DEFAULTS.laneHeight, 'laneHeight', false);
|
|
191
|
+
const cycleWidth = size(o.cycleWidth, DEFAULTS.cycleWidth, 'cycleWidth', false);
|
|
192
|
+
const nameColWidth = size(o.nameColWidth, DEFAULTS.nameColWidth, 'nameColWidth', true);
|
|
193
|
+
|
|
194
|
+
const found = enumerateLanes(doc);
|
|
195
|
+
const lanes = [];
|
|
196
|
+
let cycles = 0;
|
|
197
|
+
for (let i = 0; i < found.lanes.length; i++) {
|
|
198
|
+
const laneCycles = cyclesOf(found.lanes[i].lane);
|
|
199
|
+
if (laneCycles > cycles) cycles = laneCycles;
|
|
200
|
+
lanes.push({
|
|
201
|
+
index: i,
|
|
202
|
+
y: i * laneHeight,
|
|
203
|
+
height: laneHeight,
|
|
204
|
+
cycles: laneCycles,
|
|
205
|
+
depth: found.lanes[i].path.length - 2,
|
|
206
|
+
path: found.lanes[i].path,
|
|
207
|
+
lane: found.lanes[i].lane,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (lanes.length > 0 && cycles === 0) cycles = 1;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
laneHeight: laneHeight,
|
|
214
|
+
cycleWidth: cycleWidth,
|
|
215
|
+
nameColWidth: nameColWidth,
|
|
216
|
+
lanes: lanes,
|
|
217
|
+
cycles: cycles,
|
|
218
|
+
truncated: found.truncated,
|
|
219
|
+
width: nameColWidth + cycles * cycleWidth,
|
|
220
|
+
height: lanes.length * laneHeight,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** A real, finite coordinate — `'70'` and `NaN` are not one. */
|
|
225
|
+
function isCoord(n) {
|
|
226
|
+
return typeof n === 'number' && isFinite(n);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isIndex(n) {
|
|
230
|
+
return typeof n === 'number' && isFinite(n) && Math.floor(n) === n;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Which index `pos` falls in, given cells that start at `origin + i * step`.
|
|
235
|
+
*
|
|
236
|
+
* The rule is a half-open interval `[start, start + step)`, so every edge
|
|
237
|
+
* belongs to the cell on its far side and there is no rounding and no epsilon
|
|
238
|
+
* anywhere. Dividing gets the answer in real arithmetic; it does not get it in
|
|
239
|
+
* floating point, because the subtraction and the division each round, and when
|
|
240
|
+
* they round down a cell's own left edge lands in the cell before it. Measured
|
|
241
|
+
* on a fractional fuzz of 96000 cells: 15715 of them had that wrong left edge,
|
|
242
|
+
* and 1352 of 2000 size combinations had at least one. Integer sizes never show
|
|
243
|
+
* it, which is why the fixtures that only used integers could not see it.
|
|
244
|
+
*
|
|
245
|
+
* So the division only proposes, and the answer is then snapped against the
|
|
246
|
+
* very expression `cellRect` lays cells out with. Whatever the arithmetic does
|
|
247
|
+
* to `origin + i * step`, it does the same thing on both sides, and the two
|
|
248
|
+
* functions invert each other by construction rather than by algebra. The walk
|
|
249
|
+
* is bounded by `count`, and starts within one of the answer, so it is a step
|
|
250
|
+
* or two in practice.
|
|
251
|
+
*/
|
|
252
|
+
function indexAt(pos, origin, step, count) {
|
|
253
|
+
let i = Math.floor((pos - origin) / step);
|
|
254
|
+
if (i < 0) i = 0;
|
|
255
|
+
if (i > count - 1) i = count - 1;
|
|
256
|
+
while (i < count - 1 && origin + (i + 1) * step <= pos) i++;
|
|
257
|
+
while (i > 0 && origin + i * step > pos) i--;
|
|
258
|
+
return i + 0; // `Math.floor(-0.2)` is -0, and -0 fails deepStrictEqual against 0
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Which cell a point is in, or `null` for the name column, outside the diagram,
|
|
263
|
+
* or a coordinate that is not a number.
|
|
264
|
+
*
|
|
265
|
+
* The two ends of the diagram follow from the same half-open rule rather than
|
|
266
|
+
* from special cases: the name column's right edge is the first cell's left
|
|
267
|
+
* edge and hits cycle 0, while the diagram's right edge has no next cell to
|
|
268
|
+
* belong to and is outside.
|
|
269
|
+
*
|
|
270
|
+
* A cycle past the end of that particular lane still hits: the lane is shorter
|
|
271
|
+
* than the diagram, painting there is how a lane gets extended, and the caller
|
|
272
|
+
* can see it went past from `layout.lanes[laneIndex].cycles`.
|
|
273
|
+
*/
|
|
274
|
+
function cellAt(layout, x, y) {
|
|
275
|
+
if (layout === null || typeof layout !== 'object' || !Array.isArray(layout.lanes)) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (!isCoord(x) || !isCoord(y)) return null;
|
|
279
|
+
if (x < layout.nameColWidth || x >= layout.width) return null;
|
|
280
|
+
if (y < 0 || y >= layout.height) return null;
|
|
281
|
+
if (layout.lanes.length === 0 || layout.cycles === 0) return null;
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
laneIndex: indexAt(y, 0, layout.laneHeight, layout.lanes.length),
|
|
285
|
+
cycle: indexAt(x, layout.nameColWidth, layout.cycleWidth, layout.cycles),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The box one cell occupies, or `null` if that cell is not in the diagram.
|
|
291
|
+
*
|
|
292
|
+
* The inverse of `cellAt` in the only sense that matters: the centre of what
|
|
293
|
+
* this returns hit-tests back to the cell that was asked for, and so does its
|
|
294
|
+
* top-left corner. A cycle past the diagram's width has no box, so extending a
|
|
295
|
+
* lane is an edit the caller makes and then lays out again, not a rectangle it
|
|
296
|
+
* can ask for in advance.
|
|
297
|
+
*/
|
|
298
|
+
function cellRect(layout, laneIndex, cycle) {
|
|
299
|
+
if (layout === null || typeof layout !== 'object' || !Array.isArray(layout.lanes)) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
if (!isIndex(laneIndex) || laneIndex < 0 || laneIndex >= layout.lanes.length) return null;
|
|
303
|
+
if (!isIndex(cycle) || cycle < 0 || cycle >= layout.cycles) return null;
|
|
304
|
+
|
|
305
|
+
const row = layout.lanes[laneIndex];
|
|
306
|
+
return {
|
|
307
|
+
x: layout.nameColWidth + cycle * layout.cycleWidth,
|
|
308
|
+
y: row.y,
|
|
309
|
+
width: layout.cycleWidth,
|
|
310
|
+
height: row.height,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
layoutOf: layoutOf,
|
|
316
|
+
cellAt: cellAt,
|
|
317
|
+
cellRect: cellRect,
|
|
318
|
+
DEFAULTS: DEFAULTS,
|
|
319
|
+
MAX_LANES: MAX_LANES,
|
|
320
|
+
};
|