@helping-ai-workflow/md2doc 3.16.0 → 3.17.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/lib/editor/client.js +2459 -106
- package/lib/editor/code-edit.js +184 -0
- package/lib/editor/icons.js +3 -0
- package/lib/editor/inline-md.js +105 -1
- package/lib/editor/server.js +30 -2
- package/lib/highlight.js +46 -5
- package/lib/md2doc.js +257 -39
- package/lib/theme/runtime.js +33 -2
- package/package.json +2 -2
package/lib/md2doc.js
CHANGED
|
@@ -1993,6 +1993,48 @@ ${edUlMarkerCss}${edOlCounterCss}
|
|
|
1993
1993
|
color: var(--md-strong); cursor: pointer; box-shadow: 0 1px 3px var(--md-ed-shadow);
|
|
1994
1994
|
}
|
|
1995
1995
|
.ed-wave-edit-btn:hover { background: var(--md-ed-surface); border-color: var(--md-border); }
|
|
1996
|
+
.ed-diagram-edit-btn {
|
|
1997
|
+
position: fixed; z-index: 12; padding: 3px 10px; font-size: 12px;
|
|
1998
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
1999
|
+
border: 1px solid var(--md-border); border-radius: 4px; background: var(--md-ed-surface);
|
|
2000
|
+
color: var(--md-strong); cursor: pointer; box-shadow: 0 1px 3px var(--md-ed-shadow);
|
|
2001
|
+
}
|
|
2002
|
+
.ed-diagram-edit-btn[hidden] { display: none; }
|
|
2003
|
+
.ed-diagram-edit-btn:hover { background: var(--md-ed-surface); border-color: var(--md-border); }
|
|
2004
|
+
.ed-diagram-edit-btn svg { flex: 0 0 auto; width: 14px; height: 14px; margin: 0; }
|
|
2005
|
+
/* v3.17.0 T5: the diagram panel (openDiagramPanel() in the client runtime).
|
|
2006
|
+
It replaces the block's content in place, inside the block, like the raw
|
|
2007
|
+
box: a 2px accent frame, the header (language, the muted hint, the done
|
|
2008
|
+
button), the source (monospace on the field colour, 50vh at most, then it
|
|
2009
|
+
scrolls), the preview (60vh at most, scrolls both ways) and one message
|
|
2010
|
+
line. A preview kept after an error, or after an answer with nothing to
|
|
2011
|
+
draw, is dimmed (.is-stale). No backticks in this comment: it lives
|
|
2012
|
+
inside a JS template literal. */
|
|
2013
|
+
.ed-diagram-panel {
|
|
2014
|
+
border: 2px solid var(--md-accent); border-radius: 6px; padding: 8px;
|
|
2015
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
2016
|
+
}
|
|
2017
|
+
.ed-diagram-head { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; font-size: 13px; }
|
|
2018
|
+
.ed-diagram-lang { font-weight: 600; }
|
|
2019
|
+
.ed-diagram-hint { flex: 1 1 auto; min-width: 0; color: var(--md-muted); font-size: 12px; }
|
|
2020
|
+
.ed-diagram-done {
|
|
2021
|
+
flex: 0 0 auto; font: inherit; font-size: 13px; padding: 2px 12px; cursor: pointer;
|
|
2022
|
+
border: 1px solid var(--md-border); border-radius: 6px;
|
|
2023
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
2024
|
+
}
|
|
2025
|
+
.ed-diagram-done:hover { background: var(--md-ed-hover); }
|
|
2026
|
+
.ed-diagram-src {
|
|
2027
|
+
display: block; width: 100%; box-sizing: border-box; min-height: 3em; max-height: 50vh; overflow-y: auto;
|
|
2028
|
+
padding: 6px; resize: vertical; tab-size: 2;
|
|
2029
|
+
font-family: "Cascadia Mono", Consolas, ui-monospace, monospace; font-size: 13px;
|
|
2030
|
+
border: 1px solid var(--md-border); border-radius: 6px;
|
|
2031
|
+
background: var(--md-ed-field); color: var(--md-strong);
|
|
2032
|
+
}
|
|
2033
|
+
.ed-diagram-preview { margin-top: 8px; max-height: 60vh; overflow: auto; outline: none; }
|
|
2034
|
+
.ed-diagram-preview svg { display: block; max-width: none; }
|
|
2035
|
+
.ed-diagram-preview.is-stale { opacity: 0.35; }
|
|
2036
|
+
.ed-diagram-error { margin-top: 6px; font-size: 12px; color: var(--md-ed-err); }
|
|
2037
|
+
.ed-diagram-error[data-kind="empty"] { color: var(--md-muted); }
|
|
1996
2038
|
.ed-wave-overlay {
|
|
1997
2039
|
position: fixed; inset: 0; z-index: 998; background: rgba(0,0,0,.45);
|
|
1998
2040
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -2905,6 +2947,34 @@ const html = `<!DOCTYPE html>
|
|
|
2905
2947
|
max-width: 100%;
|
|
2906
2948
|
margin: 0 auto;
|
|
2907
2949
|
}
|
|
2950
|
+
/* v3.16.1: a capture tool hides every sticky element after its first slice,
|
|
2951
|
+
so a sticky first column came out blank from the second screen down. The
|
|
2952
|
+
runtime also widens each box that scrolls sideways (fitWideBoxes); the
|
|
2953
|
+
rules below are its last resort, for a box still wider than the screen. */
|
|
2954
|
+
body[data-capture-mode] .content table tbody td:first-child,
|
|
2955
|
+
body[data-capture-mode] .content table thead th:first-child {
|
|
2956
|
+
position: static;
|
|
2957
|
+
}
|
|
2958
|
+
body[data-capture-mode] .content table.capture-wrap th:not(.cell-narrow),
|
|
2959
|
+
body[data-capture-mode] .content table.capture-wrap td:not(.cell-narrow),
|
|
2960
|
+
body[data-capture-mode] .content table.capture-wrap td:not(.cell-narrow) code {
|
|
2961
|
+
white-space: normal;
|
|
2962
|
+
overflow-wrap: break-word;
|
|
2963
|
+
min-width: 0;
|
|
2964
|
+
}
|
|
2965
|
+
body[data-capture-mode] .content table.capture-wrap th,
|
|
2966
|
+
body[data-capture-mode] .content table.capture-wrap td {
|
|
2967
|
+
padding: 0.4em 0.6em;
|
|
2968
|
+
}
|
|
2969
|
+
body[data-capture-mode] .content pre.capture-wrap,
|
|
2970
|
+
body[data-capture-mode] .content pre.capture-wrap code {
|
|
2971
|
+
white-space: pre-wrap;
|
|
2972
|
+
overflow-wrap: anywhere;
|
|
2973
|
+
}
|
|
2974
|
+
body[data-capture-mode] .content .diagram-scroll.capture-wrap > svg {
|
|
2975
|
+
max-width: 100% !important;
|
|
2976
|
+
height: auto;
|
|
2977
|
+
}
|
|
2908
2978
|
.capture-toast {
|
|
2909
2979
|
position: fixed;
|
|
2910
2980
|
top: 16px;
|
|
@@ -3492,6 +3562,17 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3492
3562
|
outline: 2px solid var(--md-accent); outline-offset: 2px;
|
|
3493
3563
|
caret-color: var(--md-accent);
|
|
3494
3564
|
}
|
|
3565
|
+
/* v3.17.0 T2: a fenced code block edited in place. The pre itself is the
|
|
3566
|
+
editing host (editCodeInPlace() in the client runtime) and takes the
|
|
3567
|
+
same ring as an armed surface, drawn outside its own padding. Code never
|
|
3568
|
+
wraps, also where an engine's contenteditable default would wrap it, and
|
|
3569
|
+
an empty body keeps one line of height to click and type into. */
|
|
3570
|
+
pre[data-ed-code-edit] {
|
|
3571
|
+
white-space: pre; overflow-wrap: normal; word-wrap: normal; line-break: auto;
|
|
3572
|
+
cursor: text; caret-color: var(--md-accent);
|
|
3573
|
+
}
|
|
3574
|
+
pre[data-ed-code-edit]:focus { outline: 2px solid var(--md-accent); outline-offset: 2px; }
|
|
3575
|
+
pre[data-ed-code-edit] > code { display: block; min-height: 1lh; }
|
|
3495
3576
|
/* v3.14.0 T4: the empty-paragraph hint. syncEmptyMark() in the client sets
|
|
3496
3577
|
data-ed-empty on a paragraph surface with no text; a real paragraph shows
|
|
3497
3578
|
the hint while focused, the transient (an empty document's, or one Enter
|
|
@@ -3659,6 +3740,53 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3659
3740
|
flex: 0 0 auto; padding: 6px 10px; font-size: 11px; color: var(--md-muted);
|
|
3660
3741
|
border-top: 1px solid var(--md-ed-ring);
|
|
3661
3742
|
}
|
|
3743
|
+
/* v3.17.0 T3: the language chip and the copy chip of a fenced code block.
|
|
3744
|
+
Both live in one singleton on document.body (not inside .content: the
|
|
3745
|
+
client reads .content back as its own render), fixed at the top-right of
|
|
3746
|
+
the block the pointer is over or that is being edited. 24px tall, the
|
|
3747
|
+
editor surface with a 1px ring and a 6px radius; the copy chip shows the
|
|
3748
|
+
same check and Copied feedback as the reader button. The language menu
|
|
3749
|
+
is the slash menu look: 220px wide, 320px tall at most, its list scrolls
|
|
3750
|
+
on its own. No backticks in this comment: it lives inside a JS template
|
|
3751
|
+
literal. */
|
|
3752
|
+
.ed-code-chips {
|
|
3753
|
+
position: fixed; z-index: 12; display: flex; align-items: center; gap: 4px;
|
|
3754
|
+
}
|
|
3755
|
+
.ed-code-chips[hidden] { display: none; }
|
|
3756
|
+
.ed-code-chip {
|
|
3757
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 4px;
|
|
3758
|
+
height: 24px; min-width: 24px; padding: 0 6px; margin: 0; box-sizing: border-box;
|
|
3759
|
+
border: 0; border-radius: 6px; background: var(--md-ed-surface); color: var(--md-muted);
|
|
3760
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring);
|
|
3761
|
+
font: inherit; font-size: 12px; line-height: 1; cursor: pointer;
|
|
3762
|
+
}
|
|
3763
|
+
.ed-code-chip:hover, .ed-code-chip[aria-expanded="true"] { background: var(--md-ed-hover); color: var(--md-strong); }
|
|
3764
|
+
.ed-code-chip svg { flex: 0 0 auto; width: 14px; height: 14px; margin: 0; }
|
|
3765
|
+
.ed-code-copy.is-copied { color: var(--md-accent); }
|
|
3766
|
+
.ed-code-langmenu {
|
|
3767
|
+
position: fixed; z-index: 100; width: 220px; max-height: 320px;
|
|
3768
|
+
display: flex; flex-direction: column; box-sizing: border-box; overflow: hidden;
|
|
3769
|
+
border-radius: 8px; background: var(--md-ed-surface); color: var(--md-strong);
|
|
3770
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3771
|
+
font-size: 13px; line-height: 1.3;
|
|
3772
|
+
}
|
|
3773
|
+
.ed-code-langfilter {
|
|
3774
|
+
flex: 0 0 auto; margin: 4px 4px 0; padding: 0 8px; height: 28px; box-sizing: border-box;
|
|
3775
|
+
border: 0; border-radius: 6px; background: var(--md-ed-hover); color: inherit;
|
|
3776
|
+
font: inherit; outline: none;
|
|
3777
|
+
}
|
|
3778
|
+
.ed-code-langfilter::placeholder { color: var(--md-muted); }
|
|
3779
|
+
.ed-code-langlist { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px; }
|
|
3780
|
+
.ed-code-langitem {
|
|
3781
|
+
display: flex; align-items: center; gap: 8px; width: 100%; height: 32px;
|
|
3782
|
+
padding: 0 8px; box-sizing: border-box; margin: 0;
|
|
3783
|
+
border: 0; border-radius: 6px; background: transparent; color: inherit;
|
|
3784
|
+
font: inherit; text-align: left; cursor: pointer;
|
|
3785
|
+
}
|
|
3786
|
+
.ed-code-langitem:hover, .ed-code-langitem[data-ed-lang-cursor] { background: var(--md-ed-hover); }
|
|
3787
|
+
.ed-code-langitem span { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3788
|
+
.ed-code-langitem svg { flex: 0 0 auto; width: 14px; height: 14px; margin: 0; }
|
|
3789
|
+
.ed-code-langempty { padding: 8px; color: var(--md-muted); }
|
|
3662
3790
|
/* §10-gap fix: block-level INSERT — the + button sits NEXT TO the ⠿
|
|
3663
3791
|
handle in the left gutter, same node/visibility-toggle shape as
|
|
3664
3792
|
.ed-handle above (real per-block node, opacity-only reveal on hover).
|
|
@@ -3963,6 +4091,32 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3963
4091
|
background: transparent; color: inherit; font: inherit; font-size: 13px; cursor: pointer;
|
|
3964
4092
|
}
|
|
3965
4093
|
.ed-linkcard-btn:hover { background: var(--md-ed-hover); }
|
|
4094
|
+
/* v3.17.0 T7: the image toolbar, 8px inside the top-left of the image the
|
|
4095
|
+
pointer is over (a fixed singleton on body, like the link card; the same
|
|
4096
|
+
surface, ring and shadow roles). 刪除 is the error colour. In its alt-text
|
|
4097
|
+
mode the buttons give way to one input. */
|
|
4098
|
+
.ed-imgtb {
|
|
4099
|
+
position: fixed; z-index: 15;
|
|
4100
|
+
display: flex; align-items: center; gap: 2px; box-sizing: border-box;
|
|
4101
|
+
max-width: calc(100vw - 8px); padding: 3px; border-radius: 8px;
|
|
4102
|
+
background: var(--md-ed-surface); color: var(--md-strong); font-size: 13px;
|
|
4103
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
4104
|
+
}
|
|
4105
|
+
.ed-imgtb[hidden] { display: none; }
|
|
4106
|
+
.ed-imgtb-btns { display: flex; align-items: center; gap: 2px; }
|
|
4107
|
+
.ed-imgtb-btns[hidden], .ed-imgtb-input[hidden] { display: none; }
|
|
4108
|
+
.ed-imgtb-btn {
|
|
4109
|
+
flex: none; height: 26px; padding: 0 8px; border: 0; border-radius: 6px;
|
|
4110
|
+
background: transparent; color: inherit; font: inherit; font-size: 13px; cursor: pointer;
|
|
4111
|
+
}
|
|
4112
|
+
.ed-imgtb-btn:hover { background: var(--md-ed-hover); }
|
|
4113
|
+
.ed-imgtb-delete { color: var(--md-ed-err); }
|
|
4114
|
+
.ed-imgtb-input {
|
|
4115
|
+
flex: 1 1 auto; width: 260px; max-width: calc(100vw - 24px); min-width: 0; height: 28px;
|
|
4116
|
+
box-sizing: border-box; padding: 0 8px; border: 1px solid var(--md-ed-ring); border-radius: 6px;
|
|
4117
|
+
background: var(--md-ed-field); color: inherit; font: inherit; font-size: 13px;
|
|
4118
|
+
}
|
|
4119
|
+
.ed-imgtb-input:focus { outline: none; border-color: var(--md-muted); }
|
|
3966
4120
|
/* v3.1.0 §4: the always-visible editing toolbar. Its numeric language is
|
|
3967
4121
|
.lightbox-bar's (8px/12px padding, 6px gap, 13px type) over .ed-seltb's
|
|
3968
4122
|
dark pill, so the two floating surfaces read as one family.
|
|
@@ -5103,11 +5257,49 @@ ${mermaidInitTag}`)
|
|
|
5103
5257
|
// and the reading position holds. Esc leaves; a reload never comes back in it.
|
|
5104
5258
|
const captureBtn = document.getElementById('capture-mode-toggle');
|
|
5105
5259
|
let captureToastTimer = 0;
|
|
5260
|
+
// A capture tool only sees the part of a sideways-scrolling box that is in
|
|
5261
|
+
// view, so each one (a wide table, a long code line, a wide diagram) is
|
|
5262
|
+
// widened to its full width, centred on the screen, at most the screen less
|
|
5263
|
+
// a 16px gutter each side; the prose keeps its reading width. One still too
|
|
5264
|
+
// wide wraps (.capture-wrap), and a table still too wide after that gets
|
|
5265
|
+
// smaller type. Each box's own style attribute is kept and put back on exit.
|
|
5266
|
+
let captureFitted = [];
|
|
5267
|
+
function fitWideBoxes() {
|
|
5268
|
+
const vw = document.documentElement.clientWidth;
|
|
5269
|
+
const boxes = document.querySelectorAll('main.content table, main.content pre, main.content .diagram-scroll');
|
|
5270
|
+
for (const el of boxes) {
|
|
5271
|
+
if (el.scrollWidth <= el.clientWidth + 1) continue;
|
|
5272
|
+
captureFitted.push({ el: el, style: el.getAttribute('style') });
|
|
5273
|
+
const w = Math.min(el.scrollWidth, vw - 32);
|
|
5274
|
+
el.style.boxSizing = 'border-box';
|
|
5275
|
+
el.style.width = w + 'px';
|
|
5276
|
+
el.style.maxWidth = 'none';
|
|
5277
|
+
const margin = parseFloat(getComputedStyle(el).marginLeft) || 0;
|
|
5278
|
+
el.style.marginLeft = (margin + (vw - w) / 2 - el.getBoundingClientRect().left) + 'px';
|
|
5279
|
+
if (el.scrollWidth > el.clientWidth + 1) el.classList.add('capture-wrap');
|
|
5280
|
+
// Many columns on a narrow screen can stay too wide even wrapped: shrink
|
|
5281
|
+
// the table's type (the wrap rule's cell padding is in em) until it fits.
|
|
5282
|
+
let size = parseFloat(getComputedStyle(el).fontSize);
|
|
5283
|
+
for (let i = 0; i < 8 && el.tagName === 'TABLE' && el.scrollWidth > el.clientWidth + 1; i++) {
|
|
5284
|
+
size = size * el.clientWidth / el.scrollWidth * 0.98;
|
|
5285
|
+
el.style.fontSize = size + 'px';
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
function unfitWideBoxes() {
|
|
5290
|
+
for (const f of captureFitted) {
|
|
5291
|
+
f.el.classList.remove('capture-wrap');
|
|
5292
|
+
if (f.style === null) f.el.removeAttribute('style');
|
|
5293
|
+
else f.el.setAttribute('style', f.style);
|
|
5294
|
+
}
|
|
5295
|
+
captureFitted = [];
|
|
5296
|
+
}
|
|
5106
5297
|
function setCaptureMode(on) {
|
|
5107
5298
|
clearTimeout(captureToastTimer);
|
|
5108
5299
|
const oldToast = document.querySelector('.capture-toast');
|
|
5109
5300
|
if (oldToast) oldToast.remove();
|
|
5110
5301
|
if (!on) {
|
|
5302
|
+
unfitWideBoxes();
|
|
5111
5303
|
document.body.removeAttribute('data-capture-mode');
|
|
5112
5304
|
document.body.style.removeProperty('--md2doc-capture-w');
|
|
5113
5305
|
return;
|
|
@@ -5116,6 +5308,8 @@ ${mermaidInitTag}`)
|
|
|
5116
5308
|
const w = document.querySelector('main.content').getBoundingClientRect().width;
|
|
5117
5309
|
document.body.style.setProperty('--md2doc-capture-w', w + 'px');
|
|
5118
5310
|
document.body.setAttribute('data-capture-mode', '');
|
|
5311
|
+
unfitWideBoxes();
|
|
5312
|
+
fitWideBoxes();
|
|
5119
5313
|
const toast = document.createElement('div');
|
|
5120
5314
|
toast.className = 'capture-toast';
|
|
5121
5315
|
toast.setAttribute('role', 'status');
|
|
@@ -6584,7 +6778,7 @@ ${usesDrawioMultiPage ? ` // v3.4.0 batch2 Task 6: wired through a NAMED, reusa
|
|
|
6584
6778
|
// server's on-demand GET /api/lib/<name> serves exactly what this file inlines
|
|
6585
6779
|
// instead of resolving them a second time.
|
|
6586
6780
|
const diagramEnginePaths = { mermaid: localMermaidJs, wavedrom: localWaveDromJs, wavedromSkin: localWaveDromSkin };
|
|
6587
|
-
module.exports = { renderMarkdown, buildKatexStyleTag, diagramEnginePaths };
|
|
6781
|
+
module.exports = { renderMarkdown, buildKatexStyleTag, diagramEnginePaths, renderDotSvg };
|
|
6588
6782
|
|
|
6589
6783
|
// Index of the first "{" in dot source that opens the graph body: not inside a
|
|
6590
6784
|
// double-quoted string, a // or /* */ comment, a line starting with # (dot
|
|
@@ -6622,59 +6816,83 @@ function firstDotBrace(src) {
|
|
|
6622
6816
|
return -1;
|
|
6623
6817
|
}
|
|
6624
6818
|
|
|
6819
|
+
// One dot path for the bake and the editor preview. The loaded Graphviz
|
|
6820
|
+
// instance is cached at module scope (the library does not memoise load()).
|
|
6821
|
+
let graphvizLoading = null;
|
|
6822
|
+
function loadGraphviz() {
|
|
6823
|
+
if (!graphvizLoading) {
|
|
6824
|
+
const { Graphviz } = require('@hpcc-js/wasm-graphviz');
|
|
6825
|
+
graphvizLoading = Graphviz.load();
|
|
6826
|
+
// A failed load must not poison the cache: the next call tries again.
|
|
6827
|
+
graphvizLoading.catch(() => { graphvizLoading = null; });
|
|
6828
|
+
}
|
|
6829
|
+
return graphvizLoading;
|
|
6830
|
+
}
|
|
6831
|
+
async function renderDotSvg(dotSrc) {
|
|
6832
|
+
const gv = await loadGraphviz();
|
|
6833
|
+
// Graphviz measures label widths with the font it lays out in, and with
|
|
6834
|
+
// no fontname that is Times, whose metrics are narrower than the sans
|
|
6835
|
+
// the SVG is drawn in -- so labels overflowed their boxes. Declare
|
|
6836
|
+
// Helvetica BEFORE layout, right after the first brace; a fontname the
|
|
6837
|
+
// author writes later in the source still wins. Exactly "Helvetica":
|
|
6838
|
+
// a "Helvetica,sans-Serif" family list falls back to Times widths.
|
|
6839
|
+
const at = firstDotBrace(dotSrc);
|
|
6840
|
+
const laidOut = at < 0 ? dotSrc : dotSrc.slice(0, at + 1) +
|
|
6841
|
+
' graph [fontname="Helvetica"]; node [fontname="Helvetica"]; edge [fontname="Helvetica"]; ' + dotSrc.slice(at + 1);
|
|
6842
|
+
let laid;
|
|
6843
|
+
try {
|
|
6844
|
+
laid = gv.dot(laidOut);
|
|
6845
|
+
} catch (injectErr) {
|
|
6846
|
+
// Injection must never turn a valid graph into an error: lay the
|
|
6847
|
+
// original source out as the author wrote it.
|
|
6848
|
+
if (laidOut === dotSrc) throw injectErr;
|
|
6849
|
+
laid = gv.dot(dotSrc);
|
|
6850
|
+
}
|
|
6851
|
+
return laid
|
|
6852
|
+
.replace(/<\?xml[^>]*\?>/g, '')
|
|
6853
|
+
.replace(/<!DOCTYPE[\s\S]*?>/g, '')
|
|
6854
|
+
// drop graphviz's "Generated by graphviz" banner comment so the div and <svg> are adjacent
|
|
6855
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
6856
|
+
// v3.8.0: graphviz falls back to Times when no fontname is set. The
|
|
6857
|
+
// injection above makes Helvetica the layout font, so this rewrite
|
|
6858
|
+
// now only touches an author's explicit Times-Roman, or a figure
|
|
6859
|
+
// whose injection fell back to the original source; other explicit
|
|
6860
|
+
// fontnames are untouched.
|
|
6861
|
+
.replace(/font-family="Times,serif"/g, 'font-family="Helvetica,sans-Serif"')
|
|
6862
|
+
.trim();
|
|
6863
|
+
}
|
|
6864
|
+
|
|
6625
6865
|
// Render every deferred dot/graphviz placeholder to inline SVG using the
|
|
6626
6866
|
// in-process WASM engine. Loads the WASM module only when at least one dot
|
|
6627
6867
|
// block exists, so text-only docs pay nothing.
|
|
6628
6868
|
async function bakeGraphviz(htmlStr) {
|
|
6629
6869
|
if (!GRAPHVIZ_PLACEHOLDER.probe.test(htmlStr)) return htmlStr;
|
|
6630
|
-
let Graphviz;
|
|
6631
6870
|
try {
|
|
6632
|
-
|
|
6871
|
+
require('@hpcc-js/wasm-graphviz');
|
|
6633
6872
|
} catch (e) {
|
|
6634
6873
|
console.error('[ERROR] @hpcc-js/wasm-graphviz not installed — run `npm install`:', e.message);
|
|
6635
6874
|
return htmlStr;
|
|
6636
6875
|
}
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6876
|
+
// A WASM load failure throws, as it always did; only a per-block render
|
|
6877
|
+
// failure degrades that block to pre/code.
|
|
6878
|
+
await loadGraphviz();
|
|
6879
|
+
const jobs = [];
|
|
6880
|
+
htmlStr.replace(GRAPHVIZ_PLACEHOLDER.re, (m, b64) => {
|
|
6881
|
+
jobs.push(Buffer.from(b64, 'base64').toString('utf8'));
|
|
6882
|
+
return m;
|
|
6883
|
+
});
|
|
6884
|
+
const out = [];
|
|
6885
|
+
for (const dotSrc of jobs) {
|
|
6640
6886
|
try {
|
|
6641
|
-
|
|
6642
|
-
// no fontname that is Times, whose metrics are narrower than the sans
|
|
6643
|
-
// the SVG is drawn in -- so labels overflowed their boxes. Declare
|
|
6644
|
-
// Helvetica BEFORE layout, right after the first brace; a fontname the
|
|
6645
|
-
// author writes later in the source still wins. Exactly "Helvetica":
|
|
6646
|
-
// a "Helvetica,sans-Serif" family list falls back to Times widths.
|
|
6647
|
-
const at = firstDotBrace(dotSrc);
|
|
6648
|
-
const laidOut = at < 0 ? dotSrc : dotSrc.slice(0, at + 1) +
|
|
6649
|
-
' graph [fontname="Helvetica"]; node [fontname="Helvetica"]; edge [fontname="Helvetica"]; ' + dotSrc.slice(at + 1);
|
|
6650
|
-
let laid;
|
|
6651
|
-
try {
|
|
6652
|
-
laid = gv.dot(laidOut);
|
|
6653
|
-
} catch (injectErr) {
|
|
6654
|
-
// Injection must never turn a valid graph into an error: lay the
|
|
6655
|
-
// original source out as the author wrote it.
|
|
6656
|
-
if (laidOut === dotSrc) throw injectErr;
|
|
6657
|
-
laid = gv.dot(dotSrc);
|
|
6658
|
-
}
|
|
6659
|
-
const svg = laid
|
|
6660
|
-
.replace(/<\?xml[^>]*\?>/g, '')
|
|
6661
|
-
.replace(/<!DOCTYPE[\s\S]*?>/g, '')
|
|
6662
|
-
// drop graphviz's "Generated by graphviz" banner comment so the div and <svg> are adjacent
|
|
6663
|
-
.replace(/<!--[\s\S]*?-->/g, '')
|
|
6664
|
-
// v3.8.0: graphviz falls back to Times when no fontname is set. The
|
|
6665
|
-
// injection above makes Helvetica the layout font, so this rewrite
|
|
6666
|
-
// now only touches an author's explicit Times-Roman, or a figure
|
|
6667
|
-
// whose injection fell back to the original source; other explicit
|
|
6668
|
-
// fontnames are untouched.
|
|
6669
|
-
.replace(/font-family="Times,serif"/g, 'font-family="Helvetica,sans-Serif"')
|
|
6670
|
-
.trim();
|
|
6671
|
-
return `<div class="graphviz">${svg}</div>`;
|
|
6887
|
+
out.push(`<div class="graphviz">${await renderDotSvg(dotSrc)}</div>`);
|
|
6672
6888
|
} catch (e) {
|
|
6673
6889
|
console.error('[WARN] graphviz render failed:', e.message);
|
|
6674
6890
|
const escaped = dotSrc.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
6675
|
-
|
|
6891
|
+
out.push(`<pre><code class="language-dot">${escaped}</code></pre>\n`);
|
|
6676
6892
|
}
|
|
6677
|
-
}
|
|
6893
|
+
}
|
|
6894
|
+
let i = 0;
|
|
6895
|
+
return htmlStr.replace(GRAPHVIZ_PLACEHOLDER.re, () => out[i++]);
|
|
6678
6896
|
}
|
|
6679
6897
|
|
|
6680
6898
|
function launchBrowser(puppeteer) {
|
package/lib/theme/runtime.js
CHANGED
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
if (at !== -1) listeners.splice(at, 1);
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
// Over a copy: a listener may unsubscribe itself (or another) while it
|
|
26
|
+
// runs, and a splice under the loop would skip the next one.
|
|
27
|
+
function notify(dark) { var ls = listeners.slice(); for (var i = 0; i < ls.length; i++) ls[i](dark); }
|
|
26
28
|
function isDark() { return root.getAttribute('data-md2doc-theme') === 'dark'; }
|
|
27
29
|
function save(dark) {
|
|
28
30
|
try {
|
|
@@ -228,6 +230,35 @@
|
|
|
228
230
|
return true;
|
|
229
231
|
};
|
|
230
232
|
|
|
233
|
+
// v3.17.0 (D13): one off-page render for the editor's diagram panel preview.
|
|
234
|
+
// It waits its turn on the same chain, because mermaid has ONE global config
|
|
235
|
+
// and a render beside a light / dark draw could be drawn with the other's
|
|
236
|
+
// theme. Its turn initialises the theme on screen at that moment, renders
|
|
237
|
+
// under a fresh id, and puts the light config back after (the invariant
|
|
238
|
+
// every draw here keeps). Resolves the svg text; rejects with mermaid's
|
|
239
|
+
// error, its stray #d<id> element removed. Counted in inFlight like the
|
|
240
|
+
// draws, so a toggle to a cached theme waits behind it too.
|
|
241
|
+
var previewSeq = 0;
|
|
242
|
+
function renderMermaid(src) {
|
|
243
|
+
if (typeof window.mermaid === 'undefined') return Promise.reject(new Error('mermaid is not loaded'));
|
|
244
|
+
inFlight++;
|
|
245
|
+
var run = chain.then(function () {
|
|
246
|
+
var id = 'md2doc-mermaid-preview-' + (++previewSeq);
|
|
247
|
+
window.mermaid.initialize(mermaidConfig(isDark() ? 'dark' : 'light'));
|
|
248
|
+
return Promise.resolve().then(function () { return window.mermaid.render(id, String(src)); }).then(function (res) {
|
|
249
|
+
window.mermaid.initialize(mermaidConfig('light'));
|
|
250
|
+
return res.svg;
|
|
251
|
+
}, function (e) {
|
|
252
|
+
var stray = document.getElementById('d' + id);
|
|
253
|
+
if (stray && stray.parentNode) stray.parentNode.removeChild(stray);
|
|
254
|
+
window.mermaid.initialize(mermaidConfig('light'));
|
|
255
|
+
throw e;
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
chain = run.catch(function () { /* the caller shows the error */ }).then(function () { inFlight--; });
|
|
259
|
+
return run;
|
|
260
|
+
}
|
|
261
|
+
|
|
231
262
|
// ── graphviz / WaveDrom / baked SVG: recolour by computed colour ──
|
|
232
263
|
// Rules: spec "Diagram recolour rules (D10)". Reading COMPUTED fill/stroke
|
|
233
264
|
// covers both graphviz presentation attributes and WaveDrom's own <style>.
|
|
@@ -617,6 +648,6 @@
|
|
|
617
648
|
// Public surface for pages that draw their own SVG outside .content (the wave editor canvas).
|
|
618
649
|
// `__listenerCount` is a test hook (test/editor-click.test.js): the wave
|
|
619
650
|
// editor subscribes once per open and must unsubscribe on close.
|
|
620
|
-
window.md2docTheme = { isDark: isDark, onTheme: onTheme, recolourSvg: recolourSvg,
|
|
651
|
+
window.md2docTheme = { isDark: isDark, onTheme: onTheme, recolourSvg: recolourSvg, renderMermaid: renderMermaid,
|
|
621
652
|
__listenerCount: function () { return listeners.length; } };
|
|
622
653
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helping-ai-workflow/md2doc",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, Graphviz, and draw.io support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -46,7 +46,7 @@
|
|
|
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/block-catalog.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",
|
|
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/block-catalog.test.js && node test/code-edit.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/theme-runtime.test.js && node test/release-check.test.js && node test/wave-overflow.test.js",
|
|
50
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": {
|