@helping-ai-workflow/md2doc 3.12.0 → 3.13.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/bin/md2doc.js +6 -0
- package/lib/editor/client.js +356 -180
- 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 +3594 -2232
- package/lib/md2doc.js +342 -460
- package/lib/theme/runtime.js +27 -3
- package/lib/wave-overflow.js +107 -0
- package/package.json +3 -3
- package/scripts/release-check.js +60 -0
package/lib/theme/runtime.js
CHANGED
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
btn.hidden = false;
|
|
13
13
|
var data = JSON.parse(dataEl.textContent);
|
|
14
14
|
var listeners = [];
|
|
15
|
-
|
|
15
|
+
// Returns the unsubscribe: a page that draws and discards its own svgs (the
|
|
16
|
+
// wave editor's canvas, once per open) must not leave a listener behind
|
|
17
|
+
// holding what it drew.
|
|
18
|
+
function onTheme(fn) {
|
|
19
|
+
listeners.push(fn);
|
|
20
|
+
return function () {
|
|
21
|
+
var at = listeners.indexOf(fn);
|
|
22
|
+
if (at !== -1) listeners.splice(at, 1);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
16
25
|
function notify(dark) { for (var i = 0; i < listeners.length; i++) listeners[i](dark); }
|
|
17
26
|
function isDark() { return root.getAttribute('data-md2doc-theme') === 'dark'; }
|
|
18
27
|
function save(dark) {
|
|
@@ -259,7 +268,17 @@
|
|
|
259
268
|
return 'hsl(' + Math.round(x[0]) + ', ' + Math.round(Math.min(x[1], smax) * 100) + '%, ' + Math.round(l * 100) + '%)';
|
|
260
269
|
}
|
|
261
270
|
function lineColour(c) { return hsl(c)[1] > 0.25 ? tone(c, 0.68, 0.8) : INK; }
|
|
262
|
-
|
|
271
|
+
// WaveDrom draws every edge label (and point note) as g > rect + text inside wavearcs_N, the
|
|
272
|
+
// white rect masking the edge and waveform behind the words. Made transparent like other
|
|
273
|
+
// near-white fills, the line runs straight through the text in dark; it takes the page
|
|
274
|
+
// colour instead. Only this shape: graphviz/mermaid white fills still go transparent.
|
|
275
|
+
function isWaveLabelBacking(el) {
|
|
276
|
+
var next = el.nextElementSibling;
|
|
277
|
+
return /^rect$/i.test(el.tagName) && next !== null && /^text$/i.test(next.tagName) &&
|
|
278
|
+
el.closest('[id^="wavearcs"]') !== null;
|
|
279
|
+
}
|
|
280
|
+
function recolourSvg(svg, dark, opts) {
|
|
281
|
+
var backing = (opts && opts.backing) || 'var(--md-bg)';
|
|
263
282
|
var els = svg.querySelectorAll('polygon, ellipse, path, polyline, rect, line, circle, text, tspan, foreignObject *');
|
|
264
283
|
var live = svg.hasAttribute('data-md2doc-live');
|
|
265
284
|
for (var i = 0; i < els.length; i++) {
|
|
@@ -305,7 +324,7 @@
|
|
|
305
324
|
} else {
|
|
306
325
|
if (fill && fill[3] > 0) {
|
|
307
326
|
var L = lum(fill);
|
|
308
|
-
if (L > 0.85) ov += 'fill:transparent !important;';
|
|
327
|
+
if (L > 0.85) ov += isWaveLabelBacking(el) ? 'fill:' + backing + ' !important;' : 'fill:transparent !important;';
|
|
309
328
|
else if (L > 0.3) ov += 'fill:' + tone(fill, 0.26, 0.55) + ' !important;';
|
|
310
329
|
else if (el.closest('g.edge, marker')) ov += 'fill:' + lineColour(fill) + ' !important;';
|
|
311
330
|
}
|
|
@@ -343,4 +362,9 @@
|
|
|
343
362
|
}).observe(content, { childList: true, subtree: true });
|
|
344
363
|
}
|
|
345
364
|
if (isDark()) recolourAll(true);
|
|
365
|
+
// Public surface for pages that draw their own SVG outside .content (the wave editor canvas).
|
|
366
|
+
// `__listenerCount` is a test hook (test/editor-click.test.js): the wave
|
|
367
|
+
// editor subscribes once per open and must unsubscribe on close.
|
|
368
|
+
window.md2docTheme = { isDark: isDark, onTheme: onTheme, recolourSvg: recolourSvg,
|
|
369
|
+
__listenerCount: function () { return listeners.length; } };
|
|
346
370
|
})();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wave-overflow — estimate how wide a WaveDrom data label is and find the ones
|
|
5
|
+
* wider than the segment they sit in.
|
|
6
|
+
*
|
|
7
|
+
* UMD: node `module.exports`. The edit page loads it through lib/editor/server.js
|
|
8
|
+
* `waveModuleTag`, whose CommonJS shim supplies `module`, so in the browser it
|
|
9
|
+
* lands at `window.__md2docWave['wave-overflow.js']` (wave-ui.js receives it as
|
|
10
|
+
* `opts.waveOverflow`). Segments come from wave-codec (`expandWave`,
|
|
11
|
+
* `dataSlotOf`), never from a second parser of the wave string.
|
|
12
|
+
*
|
|
13
|
+
* Widths are the standard Helvetica AFM advances (1/1000 em) for ASCII 32-126;
|
|
14
|
+
* anything else counts one em. The reader draws data labels in Helvetica at
|
|
15
|
+
* 14.6667px, which measured 107px for twelve F and 149px for
|
|
16
|
+
* "D2 (client FCS last 4B)"; this table reproduces both within 2px.
|
|
17
|
+
*/
|
|
18
|
+
(function (root, factory) {
|
|
19
|
+
if (typeof module === 'object' && module.exports) {
|
|
20
|
+
module.exports = factory(require('./editor/wave-codec.js'));
|
|
21
|
+
} else {
|
|
22
|
+
// Only a bare <script> with no shim reaches this branch; nothing ships it
|
|
23
|
+
// that way today.
|
|
24
|
+
root.md2docWaveOverflow = factory(root.__md2docWave['wave-codec.js']);
|
|
25
|
+
}
|
|
26
|
+
}(typeof self !== 'undefined' ? self : this, function (codec) {
|
|
27
|
+
// Index 0 is code 32 (space).
|
|
28
|
+
const AFM = [
|
|
29
|
+
278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278,
|
|
30
|
+
556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556,
|
|
31
|
+
1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778,
|
|
32
|
+
667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556,
|
|
33
|
+
333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556,
|
|
34
|
+
556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584,
|
|
35
|
+
];
|
|
36
|
+
const DEFAULT_FONT_PX = 14.6667;
|
|
37
|
+
const CYCLE_UNITS = 40;
|
|
38
|
+
const PAD = 6; // breathing room kept on top of the text
|
|
39
|
+
|
|
40
|
+
function estimateTextWidth(text, fontPx) {
|
|
41
|
+
const px = typeof fontPx === 'number' ? fontPx : DEFAULT_FONT_PX;
|
|
42
|
+
let em = 0;
|
|
43
|
+
for (const ch of String(text)) {
|
|
44
|
+
const c = ch.codePointAt(0);
|
|
45
|
+
em += (c >= 32 && c <= 126 ? AFM[c - 32] : 1000) / 1000;
|
|
46
|
+
}
|
|
47
|
+
return em * px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// WaveDrom's own rule (parse-config.js): rounded, <=0 -> 1, capped at 100.
|
|
51
|
+
function hscaleOf(doc) {
|
|
52
|
+
const h = doc && doc.config && doc.config.hscale;
|
|
53
|
+
if (!h) return 1;
|
|
54
|
+
const n = Math.round(h > 0 ? Math.round(h) : 1);
|
|
55
|
+
return n > 0 ? Math.min(n, 100) : 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function lanesOf(list, out) {
|
|
59
|
+
for (const item of list) {
|
|
60
|
+
if (Array.isArray(item)) lanesOf(item, out);
|
|
61
|
+
else if (item && typeof item === 'object') out.push(item);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function dataList(lane) {
|
|
67
|
+
if (Array.isArray(lane.data)) return lane.data.map(String);
|
|
68
|
+
if (typeof lane.data === 'string') return lane.data.split(/\s+/).filter(Boolean);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function overflowOf(doc) {
|
|
73
|
+
const out = [];
|
|
74
|
+
if (!doc || !Array.isArray(doc.signal)) return out;
|
|
75
|
+
const hscale = hscaleOf(doc);
|
|
76
|
+
lanesOf(doc.signal, []).forEach(function (lane, laneIndex) {
|
|
77
|
+
if (typeof lane.wave !== 'string') return;
|
|
78
|
+
const cells = codec.expandWave(lane.wave);
|
|
79
|
+
const data = dataList(lane);
|
|
80
|
+
const p = Number(lane.period);
|
|
81
|
+
const period = p > 0 ? p : 1;
|
|
82
|
+
for (let i = 0; i < cells.length; i++) {
|
|
83
|
+
const slot = codec.dataSlotOf(lane, i);
|
|
84
|
+
if (slot === null || slot >= data.length) continue;
|
|
85
|
+
let n = 1;
|
|
86
|
+
while (i + n < cells.length && cells[i + n].held) n++;
|
|
87
|
+
const label = data[slot];
|
|
88
|
+
const need = estimateTextWidth(label) + PAD;
|
|
89
|
+
const unit = n * CYCLE_UNITS * period;
|
|
90
|
+
if (need <= unit * hscale) continue;
|
|
91
|
+
out.push({
|
|
92
|
+
laneIndex: laneIndex, name: typeof lane.name === 'string' ? lane.name : '',
|
|
93
|
+
slot: slot, label: label, cells: n, needScale: Math.ceil(need / unit),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function suggestScale(list, current) {
|
|
101
|
+
let k = 0;
|
|
102
|
+
for (const o of list || []) if (o.needScale > k) k = o.needScale;
|
|
103
|
+
return k || (typeof current === 'number' ? current : 1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { estimateTextWidth: estimateTextWidth, overflowOf: overflowOf, suggestScale: suggestScale };
|
|
107
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helping-ai-workflow/md2doc",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, Graphviz, and draw.io support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"preinstall": "node scripts/preinstall.js",
|
|
49
|
-
"test": "node test/md2doc.test.js && node test/highlight.test.js && node test/blockmap.test.js && node test/drawio.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/selection.test.js && node test/cell-range.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/table-anchor-recovery.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js && node test/table-width.test.js && node test/toolbar-model.test.js && node test/asset.test.js && node test/paste-md.test.js && node test/patchmap.test.js && node test/wave-codec.test.js && node test/wave-draw.test.js && node test/wave-geometry.test.js && node test/wave-store.test.js && node test/
|
|
50
|
-
"test:browser": "node test/reader-click.test.js && node test/editor-click.test.js"
|
|
49
|
+
"test": "node test/md2doc.test.js && node test/highlight.test.js && node test/blockmap.test.js && node test/drawio.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/selection.test.js && node test/cell-range.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/table-anchor-recovery.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js && node test/table-width.test.js && node test/toolbar-model.test.js && node test/asset.test.js && node test/paste-md.test.js && node test/patchmap.test.js && node test/wave-codec.test.js && node test/wave-draw.test.js && node test/wave-geometry.test.js && node test/wave-store.test.js && node test/editor-journey.test.js && node test/detach-census.test.js && node test/editor-arrow-nav.test.js && node test/editor-cell-range.test.js && node test/reader-design.test.js && node test/theme.test.js && node test/release-check.test.js && node test/wave-overflow.test.js",
|
|
50
|
+
"test:browser": "node test/reader-click.test.js && node test/editor-click.test.js && node test/wave-canvas.test.js"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Release gate for .github/workflows/publish.yml.
|
|
5
|
+
//
|
|
6
|
+
// The test suites run once, on the pull request. Branch protection on `main`
|
|
7
|
+
// requires those checks to be green and the branch to be up to date before a
|
|
8
|
+
// merge, so every commit on main is a tree that already passed them; running
|
|
9
|
+
// `npm test` again on the tag only repeated a 40-minute job (v3.12.1: four
|
|
10
|
+
// `npm test` runs for one release). What the tag job still has to prove is that
|
|
11
|
+
// the release metadata agrees:
|
|
12
|
+
// - the tag is vX.Y.Z and equals package.json's version;
|
|
13
|
+
// - the top `## vX.Y.Z — YYYY-MM-DD` heading of CHANGELOG.md is that version;
|
|
14
|
+
// - (CLI, --on-main) the tagged commit is reachable from origin/main.
|
|
15
|
+
//
|
|
16
|
+
// Usage: node scripts/release-check.js <tag> [--on-main]
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { execFileSync } = require('child_process');
|
|
21
|
+
|
|
22
|
+
const HEADING = /^## v(\d+\.\d+\.\d+) — \d{4}-\d{2}-\d{2}\s*$/m;
|
|
23
|
+
|
|
24
|
+
function checkRelease({ tag, pkgVersion, changelog }) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
if (!/^v\d+\.\d+\.\d+$/.test(tag)) {
|
|
27
|
+
errors.push(`tag "${tag}" is not of the form vX.Y.Z`);
|
|
28
|
+
} else if (tag !== 'v' + pkgVersion) {
|
|
29
|
+
errors.push(`tag ${tag} does not match package.json version ${pkgVersion}`);
|
|
30
|
+
}
|
|
31
|
+
const m = HEADING.exec(changelog);
|
|
32
|
+
if (m === null) {
|
|
33
|
+
errors.push('CHANGELOG.md has no "## vX.Y.Z — YYYY-MM-DD" heading');
|
|
34
|
+
} else if ('v' + m[1] !== tag) {
|
|
35
|
+
errors.push(`CHANGELOG.md top section is v${m[1]}, not ${tag}`);
|
|
36
|
+
}
|
|
37
|
+
return errors;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function main(argv) {
|
|
41
|
+
const tag = argv[0];
|
|
42
|
+
if (!tag) { console.error('usage: node scripts/release-check.js <tag> [--on-main]'); return 2; }
|
|
43
|
+
const root = path.join(__dirname, '..');
|
|
44
|
+
const pkgVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version;
|
|
45
|
+
const changelog = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8');
|
|
46
|
+
const errors = checkRelease({ tag, pkgVersion, changelog });
|
|
47
|
+
if (argv.includes('--on-main')) {
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('git', ['merge-base', '--is-ancestor', 'HEAD', 'origin/main'], { cwd: root, stdio: 'ignore' });
|
|
50
|
+
} catch (e) {
|
|
51
|
+
errors.push(`the tagged commit is not on origin/main (tag only merge commits on main)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const e of errors) console.error('release-check: ' + e);
|
|
55
|
+
if (errors.length === 0) console.log(`release-check: ${tag} ok (package.json ${pkgVersion}, CHANGELOG top section ${tag})`);
|
|
56
|
+
return errors.length === 0 ? 0 : 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { checkRelease };
|
|
60
|
+
if (require.main === module) process.exit(main(process.argv.slice(2)));
|