@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.
@@ -12,7 +12,16 @@
12
12
  btn.hidden = false;
13
13
  var data = JSON.parse(dataEl.textContent);
14
14
  var listeners = [];
15
- function onTheme(fn) { listeners.push(fn); }
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
- function recolourSvg(svg, dark) {
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.12.0",
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/wave-rail-clearance.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",
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)));