@helping-ai-workflow/md2doc 3.12.1 → 3.13.1
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/bin/md2doc.js +6 -0
- package/lib/editor/client.js +1015 -194
- package/lib/editor/inline-md.js +45 -1
- package/lib/editor/list-md.js +8 -1
- package/lib/editor/server.js +5 -0
- package/lib/editor/wave-canvas.js +301 -0
- package/lib/editor/wave-codec.js +417 -11
- package/lib/editor/wave-draw.js +209 -1202
- package/lib/editor/wave-geometry.js +145 -10
- package/lib/editor/wave-panels.js +907 -918
- package/lib/editor/wave-store.js +486 -91
- package/lib/editor/wave-ui.js +3588 -2232
- package/lib/md2doc.js +765 -534
- package/lib/theme/runtime.js +296 -29
- package/lib/theme/tokens.js +3 -0
- package/lib/wave-overflow.js +107 -0
- package/package.json +3 -3
- package/scripts/release-check.js +60 -0
package/lib/editor/inline-md.js
CHANGED
|
@@ -47,6 +47,50 @@
|
|
|
47
47
|
return out;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// v3.13.1 T2 (F-1): text a person typed at the start of a paragraph or
|
|
51
|
+
// list-item line that a renderer would read as a block marker. Measured
|
|
52
|
+
// before this: typing `## `, `- `, `> ` or `1. ` at a paragraph start saved
|
|
53
|
+
// the line unescaped, and the next render silently turned the paragraph
|
|
54
|
+
// into a heading, list or quote.
|
|
55
|
+
//
|
|
56
|
+
// Input is escapeText() output, possibly several lines (a hard break's
|
|
57
|
+
// backslash-newline, or a lazy continuation). Each line, after up to three
|
|
58
|
+
// spaces of indent, gets the FIRST matching rule below; nothing else is
|
|
59
|
+
// touched. The list is the decision:
|
|
60
|
+
// ATX heading `#`..`######` then space/tab/end -> `\#…`
|
|
61
|
+
// bullet `-` or `+` then space/tab/end -> `\-…` / `\+…`
|
|
62
|
+
// blockquote `>` (no space needed) -> `\>…`
|
|
63
|
+
// ordered 1-9 digits, `.` or `)`, space/end -> `1\.` / `1\)`
|
|
64
|
+
// setext/rule a run of `=` or of `-`, alone -> `\===` / `\---`
|
|
65
|
+
// spaced rule 3+ `-` with spaces between, alone -> `\-- -`
|
|
66
|
+
// table row `|` -> `\|…`
|
|
67
|
+
// `*`, `_`, backtick, `~`, `<` and `[` never get here unescaped
|
|
68
|
+
// (escapeText()). A line whose first character is `\` matches nothing,
|
|
69
|
+
// which is what keeps already-escaped text byte-stable. `-5 dBm` and
|
|
70
|
+
// `1.5 V` match nothing either: the marker must be followed by a space, a
|
|
71
|
+
// tab or the end of the line. Indented code (4+ spaces) is out of scope.
|
|
72
|
+
const BLOCK_START_RULES = [
|
|
73
|
+
{ re: /^#{1,6}(?=[ \t]|$)/, fix: (rest) => '\\' + rest },
|
|
74
|
+
{ re: /^[-+](?=[ \t]|$)/, fix: (rest) => '\\' + rest },
|
|
75
|
+
{ re: /^>/, fix: (rest) => '\\' + rest },
|
|
76
|
+
{ re: /^(\d{1,9})([.)])(?=[ \t]|$)/, fix: (rest) => rest.replace(/^(\d{1,9})([.)])/, '$1\\$2') },
|
|
77
|
+
{ re: /^(=+|-+)[ \t]*$/, fix: (rest) => '\\' + rest },
|
|
78
|
+
// A thematic break may have spaces between its dashes (`-- -`,
|
|
79
|
+
// `---- -`); the run rule above only sees an unbroken one.
|
|
80
|
+
{ re: /^-(?:[ \t]*-){2,}[ \t]*$/, fix: (rest) => '\\' + rest },
|
|
81
|
+
{ re: /^\|/, fix: (rest) => '\\' + rest },
|
|
82
|
+
];
|
|
83
|
+
function escapeBlockStarts(md) {
|
|
84
|
+
return String(md).split('\n').map((line) => {
|
|
85
|
+
const ind = /^ {0,3}/.exec(line)[0];
|
|
86
|
+
const rest = line.slice(ind.length);
|
|
87
|
+
for (let i = 0; i < BLOCK_START_RULES.length; i++) {
|
|
88
|
+
if (BLOCK_START_RULES[i].re.test(rest)) return ind + BLOCK_START_RULES[i].fix(rest);
|
|
89
|
+
}
|
|
90
|
+
return line;
|
|
91
|
+
}).join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
50
94
|
// Real contenteditable output wraps plain runs in attribute-less <span>s
|
|
51
95
|
// (Chrome/Firefox formatting artifacts). Those are transparent. A <span>
|
|
52
96
|
// carrying any of these attributes is a real style/class/behavior carrier
|
|
@@ -358,5 +402,5 @@
|
|
|
358
402
|
return serializeInline(rootEl).unsupported.length === 0;
|
|
359
403
|
}
|
|
360
404
|
|
|
361
|
-
return { serializeInline, canWysiwyg, escapeText };
|
|
405
|
+
return { serializeInline, canWysiwyg, escapeText, escapeBlockStarts };
|
|
362
406
|
});
|
package/lib/editor/list-md.js
CHANGED
|
@@ -224,7 +224,9 @@
|
|
|
224
224
|
|
|
225
225
|
// strip leading whitespace left behind by a dropped leading element
|
|
226
226
|
// (e.g. a checkbox <input>) — see header note.
|
|
227
|
-
|
|
227
|
+
// v3.13.1 T2 (F-1): text that begins with a block marker is escaped,
|
|
228
|
+
// or the item's own text would re-lex as a nested heading/list/quote.
|
|
229
|
+
const itemMd = inlineMd.escapeBlockStarts(rawItemMd.replace(/^[ \t]+/, ''));
|
|
228
230
|
|
|
229
231
|
// Two-part marker: bullet (ordered ordinal or plain '- ') and an
|
|
230
232
|
// optional checkbox prefix. The two parts are independent so that
|
|
@@ -617,6 +619,11 @@
|
|
|
617
619
|
// zero-width character it never asked for, and the item would stop
|
|
618
620
|
// reading as empty to every `itemMd === ''` test in this function.
|
|
619
621
|
if (SETEXT_ESCAPE_RE.test(itemMd)) itemMd = '';
|
|
622
|
+
// v3.13.1 T2 (F-1): an item whose text begins with `## `, `- `, `> `,
|
|
623
|
+
// `1. ` (…) is written escaped, or the next render reads it as a
|
|
624
|
+
// nested block. Only re-serialized items get here; a bystander
|
|
625
|
+
// replayed from its source lines never does.
|
|
626
|
+
itemMd = inlineMd.escapeBlockStarts(itemMd);
|
|
620
627
|
}
|
|
621
628
|
|
|
622
629
|
// Anything in the block that is neither chrome nor the text surface is
|
package/lib/editor/server.js
CHANGED
|
@@ -88,8 +88,11 @@ const PASTE_MD_SRC = fs.readFileSync(path.join(__dirname, 'paste-md.js'), 'utf8'
|
|
|
88
88
|
//
|
|
89
89
|
// wave-ui.js is UMD like its neighbours and needs no shim; in the page it
|
|
90
90
|
// defines `window.md2docWaveUi` and takes the other five as parameters.
|
|
91
|
+
const WAVE_OVERFLOW_SRC = fs.readFileSync(path.join(__dirname, '..', 'wave-overflow.js'), 'utf8');
|
|
91
92
|
const WAVE_CODEC_SRC = fs.readFileSync(path.join(__dirname, 'wave-codec.js'), 'utf8');
|
|
92
93
|
const WAVE_GEOMETRY_SRC = fs.readFileSync(path.join(__dirname, 'wave-geometry.js'), 'utf8');
|
|
94
|
+
// wave-redesign P1 Task 4: the engine-drawn canvas (createCanvas), same shim.
|
|
95
|
+
const WAVE_CANVAS_SRC = fs.readFileSync(path.join(__dirname, 'wave-canvas.js'), 'utf8');
|
|
93
96
|
const WAVE_STORE_SRC = fs.readFileSync(path.join(__dirname, 'wave-store.js'), 'utf8');
|
|
94
97
|
const WAVE_DRAW_SRC = fs.readFileSync(path.join(__dirname, 'wave-draw.js'), 'utf8');
|
|
95
98
|
const WAVE_PANELS_SRC = fs.readFileSync(path.join(__dirname, 'wave-panels.js'), 'utf8');
|
|
@@ -742,7 +745,9 @@ async function createEditorServer({
|
|
|
742
745
|
`<script>${TURNDOWN_SRC}</script>\n` +
|
|
743
746
|
`<script>${PASTE_MD_SRC}</script>\n` +
|
|
744
747
|
waveModuleTag('wave-codec.js', WAVE_CODEC_SRC) +
|
|
748
|
+
waveModuleTag('wave-overflow.js', WAVE_OVERFLOW_SRC) +
|
|
745
749
|
waveModuleTag('wave-geometry.js', WAVE_GEOMETRY_SRC) +
|
|
750
|
+
waveModuleTag('wave-canvas.js', WAVE_CANVAS_SRC) +
|
|
746
751
|
waveModuleTag('wave-store.js', WAVE_STORE_SRC) +
|
|
747
752
|
waveModuleTag('wave-draw.js', WAVE_DRAW_SRC) +
|
|
748
753
|
waveModuleTag('wave-panels.js', WAVE_PANELS_SRC) +
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The wave editor's canvas, drawn by the WaveDrom engine itself
|
|
5
|
+
* (wave-redesign spec §3).
|
|
6
|
+
*
|
|
7
|
+
* The editor used to paint its own picture of the waveform, and four of the
|
|
8
|
+
* seven defects in spec §5 were that picture disagreeing with the reader's.
|
|
9
|
+
* Here the engine draws the canvas — the same call the old preview panel
|
|
10
|
+
* made — so what the editor shows IS the output. This module owns that call
|
|
11
|
+
* and the coordinate bridge on top of it; the interaction layer (selection,
|
|
12
|
+
* cursor, dots, handles) is drawn by the layers above, in client coordinates
|
|
13
|
+
* this hands out.
|
|
14
|
+
*
|
|
15
|
+
* Coordinates, three spaces:
|
|
16
|
+
* - engine units: the svg's viewBox (a cycle is 40, a lane 30). `layout`
|
|
17
|
+
* and `measured` speak these.
|
|
18
|
+
* - the svg is enlarged by `scale` (width/height multiplied, viewBox kept),
|
|
19
|
+
* so one engine unit is `scale` CSS px.
|
|
20
|
+
* - client: what pointer events carry. Converted by subtracting the svg's
|
|
21
|
+
* client rect and dividing by `scale`.
|
|
22
|
+
*
|
|
23
|
+
* UMD: in the edit page lib/editor/server.js wraps this as the
|
|
24
|
+
* `window.__md2docWave['wave-canvas.js']` module; every dependency (the
|
|
25
|
+
* document, the engine, the theme API, geometry, codec) is handed in.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** How long a label can be beyond its segment: lib/wave-overflow.js `PAD`. */
|
|
29
|
+
const LABEL_PAD = 6;
|
|
30
|
+
/** The engine's cycle width in its own units (2 bricks of `lane.xs` 20). */
|
|
31
|
+
const CYCLE_UNITS = 40;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A plain, unfrozen copy of a document: the store's documents are frozen and
|
|
35
|
+
* the engine writes into the source it is given. Same bound as wave-panels.js
|
|
36
|
+
* `plainCopy`, which this mirrors.
|
|
37
|
+
*/
|
|
38
|
+
function plainCopy(value, depth) {
|
|
39
|
+
if (depth > 64 || value === null || typeof value !== 'object') return value;
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (let i = 0; i < value.length; i++) out.push(plainCopy(value[i], depth + 1));
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const key of Object.keys(value)) out[key] = plainCopy(value[key], depth + 1);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createCanvas(opts) {
|
|
51
|
+
const d = opts.d;
|
|
52
|
+
const engine = opts.engine;
|
|
53
|
+
const theme = opts.theme || null;
|
|
54
|
+
const geometry = opts.geometry;
|
|
55
|
+
const codec = opts.codec;
|
|
56
|
+
const host = opts.host;
|
|
57
|
+
// Not 0: the document's own diagrams render at 0, and the ids the engine
|
|
58
|
+
// derives from the index would collide (wave-ui.js PREVIEW_INDEX explains
|
|
59
|
+
// the measurement). The prefix avoids `WaveDrom_Display_`, which the
|
|
60
|
+
// reader's own scripts (lightbox, recolour observer) treat as theirs.
|
|
61
|
+
const index = opts.index === undefined ? 9000 : opts.index;
|
|
62
|
+
const prefix = opts.prefix === undefined ? 'md2doc-wave-preview-' : opts.prefix;
|
|
63
|
+
const scale = opts.scale === undefined ? 1.5 : opts.scale;
|
|
64
|
+
|
|
65
|
+
const target = d.createElement('div');
|
|
66
|
+
target.id = prefix + index;
|
|
67
|
+
host.appendChild(target);
|
|
68
|
+
|
|
69
|
+
let svg = null;
|
|
70
|
+
let layout = null;
|
|
71
|
+
let measured = null;
|
|
72
|
+
let lastDoc = null;
|
|
73
|
+
let destroyed = false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The canvas background the label backings take in dark (spec §5-5): the
|
|
77
|
+
* first ancestor of the host, the host included, whose computed background
|
|
78
|
+
* is not transparent.
|
|
79
|
+
*/
|
|
80
|
+
function backing() {
|
|
81
|
+
for (let el = host; el && el.nodeType === 1; el = el.parentElement) {
|
|
82
|
+
const bg = getComputedStyle(el).backgroundColor;
|
|
83
|
+
if (bg && bg !== 'transparent' && !/^rgba\(.*,\s*0\)$/.test(bg)) return bg;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function recolour(dark) {
|
|
89
|
+
if (theme === null || svg === null) return;
|
|
90
|
+
theme.recolourSvg(svg, dark, { backing: backing() });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether a WaveDrom skin is on the page OUTSIDE this canvas. The engine
|
|
95
|
+
* empties the target before drawing, so a skin this canvas carried itself
|
|
96
|
+
* would be gone by the time the new svg looked for it. `socket` is asked
|
|
97
|
+
* for inside a WaveDrom svg's defs, not anywhere: a document's inline HTML
|
|
98
|
+
* may carry its own `id="socket"` (the same test the WaveDrom preview
|
|
99
|
+
* panel the redesign retired used to make).
|
|
100
|
+
*/
|
|
101
|
+
function skinElsewhere() {
|
|
102
|
+
const found = d.querySelectorAll('svg.WaveDrom defs #socket');
|
|
103
|
+
for (let i = 0; i < found.length; i++) if (!target.contains(found[i])) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function render(doc) {
|
|
108
|
+
if (engine === null || engine === undefined || typeof engine.RenderWaveForm !== 'function') {
|
|
109
|
+
return { ok: false, error: '這一頁沒有載入 WaveDrom' };
|
|
110
|
+
}
|
|
111
|
+
if (!target.isConnected) return { ok: false, error: '畫布還沒放進頁面' };
|
|
112
|
+
try {
|
|
113
|
+
engine.RenderWaveForm(index, plainCopy(doc, 0), prefix, skinElsewhere());
|
|
114
|
+
} catch (e) {
|
|
115
|
+
svg = null; layout = null; measured = null;
|
|
116
|
+
return { ok: false, error: 'WaveDrom 畫不出這份文件:' + String(e && e.message) };
|
|
117
|
+
}
|
|
118
|
+
svg = target.querySelector('svg');
|
|
119
|
+
if (svg === null) {
|
|
120
|
+
layout = null; measured = null;
|
|
121
|
+
return { ok: false, error: 'WaveDrom 沒有畫出圖' };
|
|
122
|
+
}
|
|
123
|
+
const w = Number(svg.getAttribute('width'));
|
|
124
|
+
const h = Number(svg.getAttribute('height'));
|
|
125
|
+
svg.setAttribute('width', String(w * scale));
|
|
126
|
+
svg.setAttribute('height', String(h * scale));
|
|
127
|
+
if (theme !== null && theme.isDark()) recolour(true);
|
|
128
|
+
lastDoc = doc;
|
|
129
|
+
measured = geometry.measureEngineSvg(svg, doc);
|
|
130
|
+
layout = geometry.layoutOf(doc, {
|
|
131
|
+
laneHeight: measured.laneHeight,
|
|
132
|
+
cycleWidth: CYCLE_UNITS,
|
|
133
|
+
nameColWidth: measured.xg + 0.5,
|
|
134
|
+
originY: measured.laneTops.length > 0 ? measured.laneTops[0] : measured.originY,
|
|
135
|
+
});
|
|
136
|
+
return { ok: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function origin() {
|
|
140
|
+
const r = svg.getBoundingClientRect();
|
|
141
|
+
return { left: r.left, top: r.top };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function toEngine(clientX, clientY) {
|
|
145
|
+
const o = origin();
|
|
146
|
+
return { x: (clientX - o.left) / scale, y: (clientY - o.top) / scale };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function cellAt(clientX, clientY) {
|
|
150
|
+
if (layout === null) return null;
|
|
151
|
+
const p = toEngine(clientX, clientY);
|
|
152
|
+
return geometry.cellAt(layout, p.x, p.y);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function boundaryAt(clientX, clientY) {
|
|
156
|
+
if (layout === null) return null;
|
|
157
|
+
const p = toEngine(clientX, clientY);
|
|
158
|
+
return geometry.boundaryAt(layout, p.x, p.y);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cellRectClient(laneIndex, cycle) {
|
|
162
|
+
if (layout === null) return null;
|
|
163
|
+
const r = geometry.cellRect(layout, laneIndex, cycle);
|
|
164
|
+
if (r === null) return null;
|
|
165
|
+
const o = origin();
|
|
166
|
+
return { left: o.left + r.x * scale, top: o.top + r.y * scale,
|
|
167
|
+
width: r.width * scale, height: r.height * scale };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function anchorClient(laneIndex, cell) {
|
|
171
|
+
if (layout === null) return null;
|
|
172
|
+
const a = geometry.anchorOfCell(layout, laneIndex, cell);
|
|
173
|
+
if (a === null) return null;
|
|
174
|
+
const o = origin();
|
|
175
|
+
return { x: o.left + a.x * scale, y: o.top + a.y * scale };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function labelBoxes() {
|
|
179
|
+
if (measured === null) return [];
|
|
180
|
+
const o = origin();
|
|
181
|
+
return measured.labels.map(function (l) {
|
|
182
|
+
return {
|
|
183
|
+
laneIndex: l.laneIndex, slot: l.slot, text: l.text,
|
|
184
|
+
left: o.left + (l.x - l.width / 2) * scale, top: o.top + l.y * scale,
|
|
185
|
+
width: l.width * scale, height: l.height * scale,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Labels wider than their segment, by the width the engine really drew —
|
|
192
|
+
* the same rule as lib/wave-overflow.js `overflowOf` (label + LABEL_PAD
|
|
193
|
+
* against the segment's `cells * 40 * period`, times hscale), which can
|
|
194
|
+
* only estimate the width.
|
|
195
|
+
*/
|
|
196
|
+
function overflow() {
|
|
197
|
+
if (measured === null || layout === null) return [];
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const l of measured.labels) {
|
|
200
|
+
if (l.slot === null || l.cells === null) continue;
|
|
201
|
+
const row = layout.lanes[l.laneIndex];
|
|
202
|
+
const lane = row && row.lane && typeof row.lane === 'object' ? row.lane : {};
|
|
203
|
+
const p = Number(lane.period);
|
|
204
|
+
const unit = l.cells * CYCLE_UNITS * (p > 0 ? p : 1);
|
|
205
|
+
const need = l.width + LABEL_PAD;
|
|
206
|
+
if (need <= unit * layout.hscale) continue;
|
|
207
|
+
out.push({ laneIndex: l.laneIndex, slot: l.slot, label: l.text, needScale: Math.ceil(need / unit) });
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The engine's own drawing of every `edge` entry it drew (Task 9): `[{index,
|
|
214
|
+
* path, label}]`, `index` the entry's position in `doc.edge`, `path` the
|
|
215
|
+
* engine's `gmark_<from>_<to>` path, `label` the `g` holding its label box
|
|
216
|
+
* or null. The engine writes them into `wavearcs_<index>` in entry order,
|
|
217
|
+
* each path followed by its label when the entry has one, and skips an
|
|
218
|
+
* entry when either letter names no anchor (render-arcs.js `archer`), so
|
|
219
|
+
* the walk below repeats exactly that test: the same split of the entry
|
|
220
|
+
* (`trim().split(/\s+/)`, the label taken from the UNtrimmed string) and
|
|
221
|
+
* the same anchor set (every non-`.` character of every lane's `node`).
|
|
222
|
+
* The lowercase letter labels the engine adds after the last edge are
|
|
223
|
+
* never reached. If a drawn element is not the one expected (an id that
|
|
224
|
+
* does not name the entry's letters), the walk stops there rather than
|
|
225
|
+
* pair an entry with another entry's drawing.
|
|
226
|
+
*/
|
|
227
|
+
function arcs() {
|
|
228
|
+
if (svg === null || lastDoc === null || !Array.isArray(lastDoc.edge)) return [];
|
|
229
|
+
const group = svg.querySelector('g[id^="wavearcs_"]');
|
|
230
|
+
if (group === null) return [];
|
|
231
|
+
const events = new Set();
|
|
232
|
+
for (const p of codec.lanePaths(lastDoc)) {
|
|
233
|
+
const lane = geometry.valueAt(lastDoc, p);
|
|
234
|
+
const node = lane !== null && typeof lane === 'object' && typeof lane.node === 'string' ? lane.node : '';
|
|
235
|
+
for (const ch of node) if (ch !== '.') events.add(ch);
|
|
236
|
+
}
|
|
237
|
+
const kids = [];
|
|
238
|
+
for (let n = group.firstChild; n !== null; n = n.nextSibling) if (n.nodeType === 1) kids.push(n);
|
|
239
|
+
const out = [];
|
|
240
|
+
let k = 0;
|
|
241
|
+
for (let i = 0; i < lastDoc.edge.length; i++) {
|
|
242
|
+
const entry = lastDoc.edge[i];
|
|
243
|
+
if (typeof entry !== 'string') return out;
|
|
244
|
+
const head = entry.trim().split(/\s+/)[0];
|
|
245
|
+
const from = head.substr(0, 1);
|
|
246
|
+
const to = head.substr(-1, 1);
|
|
247
|
+
if (!events.has(from) || !events.has(to)) continue;
|
|
248
|
+
const path = kids[k];
|
|
249
|
+
if (path === undefined || path.tagName.toLowerCase() !== 'path' ||
|
|
250
|
+
path.getAttribute('id') !== 'gmark_' + from + '_' + to) return out;
|
|
251
|
+
k++;
|
|
252
|
+
let label = null;
|
|
253
|
+
if (entry.substring(head.length).substring(1) !== '') {
|
|
254
|
+
label = kids[k];
|
|
255
|
+
if (label === undefined || label.tagName.toLowerCase() !== 'g') return out;
|
|
256
|
+
k++;
|
|
257
|
+
}
|
|
258
|
+
out.push({ index: i, path: path, label: label });
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// `onTheme` returns the unsubscribe (lib/theme/runtime.js); `destroy()`
|
|
264
|
+
// calls it, so a closed canvas is not kept alive by the theme's listener
|
|
265
|
+
// list — that listener reaches the host, and through it the whole editor
|
|
266
|
+
// overlay and its store, for the life of the page (final review M3).
|
|
267
|
+
let unsubscribe = null;
|
|
268
|
+
if (theme !== null && typeof theme.onTheme === 'function') {
|
|
269
|
+
const off = theme.onTheme(function (dark) { if (!destroyed) recolour(dark); });
|
|
270
|
+
if (typeof off === 'function') unsubscribe = off;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Stops following the theme and takes the canvas out of the host. */
|
|
274
|
+
function destroy() {
|
|
275
|
+
destroyed = true;
|
|
276
|
+
if (unsubscribe !== null) { unsubscribe(); unsubscribe = null; }
|
|
277
|
+
if (target.parentNode) target.parentNode.removeChild(target);
|
|
278
|
+
svg = null; layout = null; measured = null; lastDoc = null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
render: render,
|
|
283
|
+
get svg() { return svg; },
|
|
284
|
+
get layout() { return layout; },
|
|
285
|
+
get measured() { return measured; },
|
|
286
|
+
get doc() { return lastDoc; },
|
|
287
|
+
scale: scale,
|
|
288
|
+
cellAt: cellAt,
|
|
289
|
+
boundaryAt: boundaryAt,
|
|
290
|
+
cellRectClient: cellRectClient,
|
|
291
|
+
anchorClient: anchorClient,
|
|
292
|
+
labelBoxes: labelBoxes,
|
|
293
|
+
overflow: overflow,
|
|
294
|
+
arcs: arcs,
|
|
295
|
+
destroy: destroy,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const api = { createCanvas: createCanvas, plainCopy: plainCopy };
|
|
300
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
301
|
+
else if (typeof window !== 'undefined') window.md2docWaveCanvas = api;
|