@helping-ai-workflow/md2doc 3.17.2 → 3.17.4
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/blockmap.js +65 -0
- package/lib/editor/client.js +1861 -154
- package/lib/editor/code-edit.js +6 -5
- package/lib/editor/convert-md.js +9 -2
- package/lib/editor/dot-preview.js +65 -0
- package/lib/editor/dot-worker.js +14 -0
- package/lib/editor/inline-md.js +40 -5
- package/lib/editor/list-md.js +143 -47
- package/lib/editor/server.js +52 -3
- package/lib/editor/wave-ui.js +9 -0
- package/lib/md2doc.js +46 -6
- package/lib/theme/tokens.js +5 -1
- package/package.json +1 -1
package/lib/md2doc.js
CHANGED
|
@@ -379,6 +379,9 @@ function renderEditModeList(listToken, blocks, biRef, out) {
|
|
|
379
379
|
const check = b.task
|
|
380
380
|
? `<span class="ed-li-check" data-checked="${b.checked ? 1 : 0}" role="checkbox" aria-checked="${!!b.checked}"></span>`
|
|
381
381
|
: '';
|
|
382
|
+
// v3.17.3 C10: an svg in the item's own text gets the same white plate
|
|
383
|
+
// in dark as a top-level svg block (the attribute is read by the dark CSS).
|
|
384
|
+
const svgPlate = ownTokens.some((tk) => /<svg[\s>]/i.test(String(tk.raw || ''))) ? ' data-svg-plate="1"' : '';
|
|
382
385
|
const runStart = liRunStartsHere(biRef, b) ? ' data-run-start="1"' : '';
|
|
383
386
|
const listStartAttr = listStart ? ' data-list-start="1"' : '';
|
|
384
387
|
listStart = false;
|
|
@@ -389,7 +392,7 @@ function renderEditModeList(listToken, blocks, biRef, out) {
|
|
|
389
392
|
parts.push(
|
|
390
393
|
`<div class="ed-block" data-block-id="${b.id}" data-block-type="li"` +
|
|
391
394
|
` data-list-type="${b.listType}" data-task="${b.task ? 1 : 0}" data-indent="${b.indent}"` +
|
|
392
|
-
runStart + listStartAttr + looseAttr + ` style="--ed-indent:${b.indent}">` +
|
|
395
|
+
runStart + listStartAttr + looseAttr + svgPlate + ` style="--ed-indent:${b.indent}">` +
|
|
393
396
|
`<span class="ed-li-marker" aria-hidden="true"></span>` + check +
|
|
394
397
|
`<div class="ed-li-text">${inner}</div>` +
|
|
395
398
|
`</div>`
|
|
@@ -1079,6 +1082,13 @@ ${itemsHtml}
|
|
|
1079
1082
|
openSvg = 0;
|
|
1080
1083
|
return isSvgOnly(src);
|
|
1081
1084
|
}
|
|
1085
|
+
// v3.17.4 E5: an svg that is real inline html among a block's own inline
|
|
1086
|
+
// tokens (a code span naming it is not one). The reader marks the block
|
|
1087
|
+
// with data-svg-plate so dark mode plates it, whatever text sits beside it.
|
|
1088
|
+
function hasInlineSvg(toks) {
|
|
1089
|
+
return Array.isArray(toks) && toks.some((tk) => tk && ((tk.type === 'html' && /<svg[\s>]/i.test(String(tk.raw || ''))) ||
|
|
1090
|
+
(tk.type === 'text' && hasInlineSvg(tk.tokens))));
|
|
1091
|
+
}
|
|
1082
1092
|
|
|
1083
1093
|
const baseHtml = renderer.html.bind(renderer);
|
|
1084
1094
|
renderer.html = function(token) {
|
|
@@ -1238,6 +1248,9 @@ ${itemsHtml}
|
|
|
1238
1248
|
};
|
|
1239
1249
|
const baseParagraph = renderer.paragraph.bind(renderer);
|
|
1240
1250
|
renderer.paragraph = function(token) {
|
|
1251
|
+
// A loose item's text paragraph is made up by marked from the already
|
|
1252
|
+
// rendered body, so its svg is real html there (a code span is escaped).
|
|
1253
|
+
const madeUp = skipMadeUpParagraph;
|
|
1241
1254
|
if (skipMadeUpParagraph) {
|
|
1242
1255
|
skipMadeUpParagraph = false;
|
|
1243
1256
|
} else {
|
|
@@ -1247,6 +1260,9 @@ ${itemsHtml}
|
|
|
1247
1260
|
if (!opts.editMode && wrapsSvgParagraph(token.raw)) {
|
|
1248
1261
|
return baseParagraph(token).replace(/^<p>/, '<p class="inline-svg">');
|
|
1249
1262
|
}
|
|
1263
|
+
if (!opts.editMode && (hasInlineSvg(token.tokens) || (madeUp && /<svg[\s>]/i.test(String(token.text || ''))))) {
|
|
1264
|
+
return baseParagraph(token).replace(/^<p>/, '<p data-svg-plate="1">');
|
|
1265
|
+
}
|
|
1250
1266
|
return baseParagraph(token);
|
|
1251
1267
|
};
|
|
1252
1268
|
|
|
@@ -1261,7 +1277,11 @@ ${itemsHtml}
|
|
|
1261
1277
|
} finally {
|
|
1262
1278
|
looseItemText = outerLoose;
|
|
1263
1279
|
}
|
|
1264
|
-
|
|
1280
|
+
// v3.17.4 E5: a tight item's own text sits straight in the li (no p). Marked
|
|
1281
|
+
// last, because the task wrapping below anchors on a bare li tag.
|
|
1282
|
+
const plateTight = (h) => (!opts.editMode && !token.loose && hasInlineSvg(token.tokens))
|
|
1283
|
+
? h.replace(/^<li( class="[^"]*")?/, '$& data-svg-plate="1"') : h;
|
|
1284
|
+
if (!token.task) return plateTight(html);
|
|
1265
1285
|
// v3.10.0: a task item drops its bullet and keeps its own text in a span,
|
|
1266
1286
|
// so a checked item's strike-through stops at its own words — CSS
|
|
1267
1287
|
// text-decoration cannot be undone on a nested child list. Shapes: tight
|
|
@@ -1270,8 +1290,8 @@ ${itemsHtml}
|
|
|
1270
1290
|
// first: a block child's opening tag, the loose item's </p>, or </li>.
|
|
1271
1291
|
const cls = token.checked ? 'task-item is-checked' : 'task-item';
|
|
1272
1292
|
const m = html.match(/^<li>([\s\S]*?<input[^>]*>)([\s\S]*?)(<(?:ul|ol|blockquote|pre|table|div|p|h[1-6])[\s>][\s\S]*|<\/p>[\s\S]*|<\/li>\s*)$/);
|
|
1273
|
-
if (!m) return html.replace(/^<li>/, '<li class="' + cls + '">');
|
|
1274
|
-
return '<li class="' + cls + '">' + m[1] + ' <span class="task-text">' + m[2].trim() + '</span>' + m[3];
|
|
1293
|
+
if (!m) return plateTight(html.replace(/^<li>/, '<li class="' + cls + '">'));
|
|
1294
|
+
return plateTight('<li class="' + cls + '">' + m[1] + ' <span class="task-text">' + m[2].trim() + '</span>' + m[3]);
|
|
1275
1295
|
};
|
|
1276
1296
|
|
|
1277
1297
|
const baseBlockquote = renderer.blockquote.bind(renderer);
|
|
@@ -1402,7 +1422,7 @@ ${itemsHtml}
|
|
|
1402
1422
|
// v3.14.0 T10: the reader wraps an svg-only block in .inline-svg for the
|
|
1403
1423
|
// dark white plate; edit mode keeps its DOM, so it marks the wrapper.
|
|
1404
1424
|
const svgPlate = ((t.type === 'html' && t.block && wrapsSvg(t.text)) ||
|
|
1405
|
-
(t.type === 'paragraph' && wrapsSvgParagraph(t.raw))) ? ' data-svg-plate="1"' : '';
|
|
1425
|
+
(t.type === 'paragraph' && (wrapsSvgParagraph(t.raw) || hasInlineSvg(t.tokens)))) ? ' data-svg-plate="1"' : '';
|
|
1406
1426
|
parts.push(
|
|
1407
1427
|
`<div class="ed-block" data-block-id="${b.id}" data-block-type="${b.type}"${svgPlate}>` +
|
|
1408
1428
|
inner + '</div>'
|
|
@@ -2767,6 +2787,7 @@ const html = `<!DOCTYPE html>
|
|
|
2767
2787
|
border-radius: 3px;
|
|
2768
2788
|
}
|
|
2769
2789
|
mark.search-hit-block { display: block; }
|
|
2790
|
+
svg .svg-hit { stroke: var(--md-svg-hit); stroke-width: 6px; stroke-linejoin: round; paint-order: stroke; }
|
|
2770
2791
|
.toc {
|
|
2771
2792
|
display: flex;
|
|
2772
2793
|
flex-direction: column;
|
|
@@ -4799,6 +4820,15 @@ ${mermaidInitTag}`)
|
|
|
4799
4820
|
}
|
|
4800
4821
|
|
|
4801
4822
|
const SKIP_SELECTOR = 'svg, .mermaid, .graphviz, script, style';
|
|
4823
|
+
// v3.17.3 C11: a Markdown-native inline svg is read (its text is in the
|
|
4824
|
+
// search index); a diagram's own svg (mermaid, graphviz, draw.io, WaveDrom,
|
|
4825
|
+
// a formula) is not. Inside a native svg every text node is walked, the
|
|
4826
|
+
// ones in its title and desc elements too: the index holds those as well
|
|
4827
|
+
// (the paragraph's text tokens between the html tags), so a query that
|
|
4828
|
+
// finds the section can find them here.
|
|
4829
|
+
function isNativeSvg(el) {
|
|
4830
|
+
return el.matches('svg') && !el.closest('.mermaid, .graphviz, .drawio, .katex, .code-block, [class*="wave"], [id^="WaveDrom"]');
|
|
4831
|
+
}
|
|
4802
4832
|
|
|
4803
4833
|
function buildSnippet(section, query) {
|
|
4804
4834
|
const haystack = section.searchText || section.title || '';
|
|
@@ -4845,6 +4875,7 @@ ${mermaidInitTag}`)
|
|
|
4845
4875
|
? readerState.activeHighlights
|
|
4846
4876
|
: (readerState.activeHighlight ? [readerState.activeHighlight] : []);
|
|
4847
4877
|
marks.forEach((mark) => {
|
|
4878
|
+
if (mark.namespaceURI === 'http://www.w3.org/2000/svg') { mark.classList.remove('svg-hit'); if (!mark.getAttribute('class')) mark.removeAttribute('class'); return; }
|
|
4848
4879
|
const parent = mark.parentNode;
|
|
4849
4880
|
if (!parent) return;
|
|
4850
4881
|
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
@@ -4975,7 +5006,8 @@ ${mermaidInitTag}`)
|
|
|
4975
5006
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
|
|
4976
5007
|
acceptNode(node) {
|
|
4977
5008
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
4978
|
-
if (node.matches(SKIP_SELECTOR)
|
|
5009
|
+
if (node.matches(SKIP_SELECTOR) && !isNativeSvg(node)) return NodeFilter.FILTER_REJECT;
|
|
5010
|
+
if (node.matches('.reader-sidebar, .heading-anchor, button')) return NodeFilter.FILTER_REJECT;
|
|
4979
5011
|
return node.matches('br, .katex') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
4980
5012
|
}
|
|
4981
5013
|
if (!node.nodeValue) return NodeFilter.FILTER_REJECT;
|
|
@@ -5065,6 +5097,14 @@ ${mermaidInitTag}`)
|
|
|
5065
5097
|
const marks = [];
|
|
5066
5098
|
Array.from(spans.keys()).sort((a, b) => b - a).forEach((ni) => {
|
|
5067
5099
|
const node = nodes[ni];
|
|
5100
|
+
// v3.17.3 C11: svg cannot hold a mark element; the text node's parent
|
|
5101
|
+
// (text or tspan) is flagged instead and painted by .svg-hit.
|
|
5102
|
+
if (node.nodeType === Node.TEXT_NODE && node.parentElement && node.parentElement.closest('svg')) {
|
|
5103
|
+
// Two text nodes can share one parent (a tspan or a comment splits it).
|
|
5104
|
+
node.parentElement.classList.add('svg-hit');
|
|
5105
|
+
if (marks.indexOf(node.parentElement) === -1) marks.unshift(node.parentElement);
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
5068
5108
|
const mark = document.createElement('mark');
|
|
5069
5109
|
mark.className = 'search-hit is-selected';
|
|
5070
5110
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
package/lib/theme/tokens.js
CHANGED
|
@@ -34,6 +34,9 @@ const THEME_TOKENS = [
|
|
|
34
34
|
{ name: 'active-bg', light: ['#dbe6f3'], dark: '#24364f' },
|
|
35
35
|
{ name: 'hit-bg', light: ['#fff5c2'], dark: '#4d4417' },
|
|
36
36
|
{ name: 'mark-bg', light: ['#fde68a'], dark: '#6e5e14' },
|
|
37
|
+
// A search hit inside an inline svg is painted on the svg plate, which is white in both themes,
|
|
38
|
+
// so its halo is the same yellow in both (the dark mark-bg would read worse than no hit at all).
|
|
39
|
+
{ name: 'svg-hit', light: ['#fde68a'], dark: '#fde68a', direct: true },
|
|
37
40
|
{ name: 'code', light: ['#b93a0c'], dark: '#ff9f70' },
|
|
38
41
|
{ name: 'accent-soft', light: ['#93c5fd'], dark: '#6ea8f5' },
|
|
39
42
|
{ name: 'error', light: ['#b91c1c'], dark: '#ff8a8a' },
|
|
@@ -165,7 +168,8 @@ function themeCss() {
|
|
|
165
168
|
' ' + D + ' .content .drawio { background: #ffffff; border-radius: 6px; box-shadow: 0 0 0 10px #ffffff; }',
|
|
166
169
|
// R7 甲: a Markdown-native inline svg (wrapped in .inline-svg by the renderer) gets the
|
|
167
170
|
// same white plate; its ink stays the light text colour because the plate is always white.
|
|
168
|
-
|
|
171
|
+
// A task item's svg sits one level down, inside span.task-text (tight li or loose p).
|
|
172
|
+
' ' + D + ' .content .inline-svg > svg, ' + D + ' .content p[data-svg-plate="1"] > svg, ' + D + ' .content li[data-svg-plate="1"] > svg, ' + D + ' .content li[data-svg-plate="1"] > p > svg, ' + D + ' .content li[data-svg-plate="1"] > .task-text > svg, ' + D + ' .content p[data-svg-plate="1"] > .task-text > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] > p > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] .ed-li-text > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] .ed-li-text > p > svg { background: #ffffff; color: #1f2328; border-radius: 4px; box-shadow: 0 0 0 10px #ffffff; }',
|
|
169
173
|
' ' + D + ' .lightbox-canvas > svg[data-md2doc-recoloured] { background: var(--md-bg); }',
|
|
170
174
|
' }',
|
|
171
175
|
' .md2doc-theme-toggle { position: fixed; right: 20px; bottom: 20px; z-index: 60; width: 40px; height: 40px;',
|