@helping-ai-workflow/md2doc 3.12.1 → 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 +3588 -2232
- package/lib/md2doc.js +342 -460
- package/lib/theme/runtime.js +18 -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) {
|
|
@@ -268,7 +277,8 @@
|
|
|
268
277
|
return /^rect$/i.test(el.tagName) && next !== null && /^text$/i.test(next.tagName) &&
|
|
269
278
|
el.closest('[id^="wavearcs"]') !== null;
|
|
270
279
|
}
|
|
271
|
-
function recolourSvg(svg, dark) {
|
|
280
|
+
function recolourSvg(svg, dark, opts) {
|
|
281
|
+
var backing = (opts && opts.backing) || 'var(--md-bg)';
|
|
272
282
|
var els = svg.querySelectorAll('polygon, ellipse, path, polyline, rect, line, circle, text, tspan, foreignObject *');
|
|
273
283
|
var live = svg.hasAttribute('data-md2doc-live');
|
|
274
284
|
for (var i = 0; i < els.length; i++) {
|
|
@@ -314,7 +324,7 @@
|
|
|
314
324
|
} else {
|
|
315
325
|
if (fill && fill[3] > 0) {
|
|
316
326
|
var L = lum(fill);
|
|
317
|
-
if (L > 0.85) ov += isWaveLabelBacking(el) ? 'fill:
|
|
327
|
+
if (L > 0.85) ov += isWaveLabelBacking(el) ? 'fill:' + backing + ' !important;' : 'fill:transparent !important;';
|
|
318
328
|
else if (L > 0.3) ov += 'fill:' + tone(fill, 0.26, 0.55) + ' !important;';
|
|
319
329
|
else if (el.closest('g.edge, marker')) ov += 'fill:' + lineColour(fill) + ' !important;';
|
|
320
330
|
}
|
|
@@ -352,4 +362,9 @@
|
|
|
352
362
|
}).observe(content, { childList: true, subtree: true });
|
|
353
363
|
}
|
|
354
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; } };
|
|
355
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)));
|