@helping-ai-workflow/md2doc 3.13.1 → 3.14.1
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/README.md +146 -346
- package/lib/editor/client.js +2141 -220
- package/lib/editor/inline-md.js +21 -0
- package/lib/editor/toolbar-model.js +11 -1
- package/lib/md2doc.js +31 -10
- package/lib/theme/tokens.js +1 -1
- package/package.json +1 -1
package/lib/editor/inline-md.js
CHANGED
|
@@ -267,6 +267,10 @@
|
|
|
267
267
|
// STRONG, DEL/S, U, the citation label) only ever needs `.md`, because
|
|
268
268
|
// its own outer delimiter is a fixed, known tag regardless of what's
|
|
269
269
|
// nested inside.
|
|
270
|
+
function markIsSearchHit(node) {
|
|
271
|
+
if (typeof node.getAttribute !== 'function') return false;
|
|
272
|
+
return (' ' + (node.getAttribute('class') || '') + ' ').indexOf(' search-hit ') !== -1;
|
|
273
|
+
}
|
|
270
274
|
function walkChildren(nodes, unsupported) {
|
|
271
275
|
let out = '';
|
|
272
276
|
let firstSegment = true;
|
|
@@ -292,6 +296,15 @@
|
|
|
292
296
|
firstSegment = false;
|
|
293
297
|
continue;
|
|
294
298
|
}
|
|
299
|
+
if (name === 'MARK' && markIsSearchHit(node)) {
|
|
300
|
+
// v3.14.0 T9: the reader's search paints its hits as this element and
|
|
301
|
+
// it is transient, so it serializes as what it wraps.
|
|
302
|
+
const inner = walkChildren(node.childNodes, unsupported);
|
|
303
|
+
out += inner.md;
|
|
304
|
+
if (inner.md.length) lastMarkTag = inner.lastMarkTag;
|
|
305
|
+
firstSegment = false;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
295
308
|
if (name === 'SPAN') {
|
|
296
309
|
if (spanHasAttributes(node)) {
|
|
297
310
|
unsupported.push(name);
|
|
@@ -356,6 +369,14 @@
|
|
|
356
369
|
firstSegment = false;
|
|
357
370
|
continue;
|
|
358
371
|
}
|
|
372
|
+
if (name === 'BR' && typeof node.getAttribute === 'function' &&
|
|
373
|
+
node.getAttribute('data-ed-tail') === '1') {
|
|
374
|
+
// v3.14.0 T8: the editor's tail <br> (insertBrAtCaret() in client.js)
|
|
375
|
+
// only gives a line break at the end of a surface a line to stand on.
|
|
376
|
+
// It is not content: nothing written, and like an empty text node it
|
|
377
|
+
// leaves lastMarkTag / firstSegment as they were.
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
359
380
|
if (name === 'BR') {
|
|
360
381
|
// Spec §3.12. Two different things arrive here as the same element:
|
|
361
382
|
// * a <br> the EDIT-MODE renderer marked (lib/md2doc.js's
|
|
@@ -101,7 +101,7 @@ const MARK_IDS = Object.freeze(Object.keys(MARK_TAG_BY_ID));
|
|
|
101
101
|
// 'inert' pressing the button would do nothing at all
|
|
102
102
|
|
|
103
103
|
// ctx: {blockType, indent, headingDepth, inList, listOrdered, hasSelection,
|
|
104
|
-
// marks, dirty}
|
|
104
|
+
// marks, dirty, transient}
|
|
105
105
|
// -> { [id]: {active: bool, mixed: bool, disabled: bool, label?: string} }
|
|
106
106
|
function deriveState(ctx) {
|
|
107
107
|
const c = ctx || {};
|
|
@@ -151,6 +151,16 @@ function deriveState(ctx) {
|
|
|
151
151
|
// primary case (spec review ruling: gating it on inList made it dead for
|
|
152
152
|
// that case).
|
|
153
153
|
|
|
154
|
+
// v3.14.0 T4 (D15): an EMPTY transient paragraph has no text to convert, so
|
|
155
|
+
// the conversions that wrap existing text (quote, numbered list, check) and
|
|
156
|
+
// the list-depth buttons stay off until it has some. ctx.transient is
|
|
157
|
+
// 'empty' | 'text' | null; the client derives it from the DOM.
|
|
158
|
+
if (c.transient === 'empty') {
|
|
159
|
+
for (const id of ['quote', 'ordered-list', 'check', 'indent', 'outdent']) {
|
|
160
|
+
state[id].disabled = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
// No block at all (rerenderAll's zeroed state): everything disabled except
|
|
155
165
|
// the handful of buttons that don't depend on having a focused block.
|
|
156
166
|
if (!hasBlock) {
|
package/lib/md2doc.js
CHANGED
|
@@ -1395,8 +1395,12 @@ ${itemsHtml}
|
|
|
1395
1395
|
resetLiRunState(biRef); // §3.8 rule (c): a non-li block ends every run
|
|
1396
1396
|
const b = blocks[biRef.v++];
|
|
1397
1397
|
const inner = marked.parser([t]);
|
|
1398
|
+
// v3.14.0 T10: the reader wraps an svg-only block in .inline-svg for the
|
|
1399
|
+
// dark white plate; edit mode keeps its DOM, so it marks the wrapper.
|
|
1400
|
+
const svgPlate = ((t.type === 'html' && t.block && wrapsSvg(t.text)) ||
|
|
1401
|
+
(t.type === 'paragraph' && wrapsSvgParagraph(t.raw))) ? ' data-svg-plate="1"' : '';
|
|
1398
1402
|
parts.push(
|
|
1399
|
-
`<div class="ed-block" data-block-id="${b.id}" data-block-type="${b.type}">` +
|
|
1403
|
+
`<div class="ed-block" data-block-id="${b.id}" data-block-type="${b.type}"${svgPlate}>` +
|
|
1400
1404
|
inner + '</div>'
|
|
1401
1405
|
);
|
|
1402
1406
|
}
|
|
@@ -3446,6 +3450,16 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3446
3450
|
outline: 2px solid var(--md-accent); outline-offset: 2px;
|
|
3447
3451
|
caret-color: var(--md-accent);
|
|
3448
3452
|
}
|
|
3453
|
+
/* v3.14.0 T4: the empty-paragraph hint. syncEmptyMark() in the client sets
|
|
3454
|
+
data-ed-empty on a paragraph surface with no text; a real paragraph shows
|
|
3455
|
+
the hint while focused, the transient (an empty document's, or one Enter
|
|
3456
|
+
left behind) shows it even unfocused so a blank line is never a blank
|
|
3457
|
+
page. An empty transient also gets a line of height of its own. */
|
|
3458
|
+
.ed-block[data-block-type="paragraph"] > .ed-wys-armed[data-ed-empty]:focus::before,
|
|
3459
|
+
.ed-block[data-ed-transient] > .ed-wys-armed[data-ed-empty]::before {
|
|
3460
|
+
content: attr(data-placeholder); color: var(--md-muted-2); pointer-events: none;
|
|
3461
|
+
}
|
|
3462
|
+
.ed-block[data-ed-transient] > .ed-wys-armed { min-height: 1.6em; }
|
|
3449
3463
|
/* Task 5: table cells armed PERMANENTLY (one edit surface per CELL, not
|
|
3450
3464
|
per table root — see armEditables()'s 'table' branch in the client
|
|
3451
3465
|
runtime) — same "no persistent outline, blue on :focus" language as
|
|
@@ -4584,23 +4598,31 @@ ${mermaidInitTag}`)
|
|
|
4584
4598
|
)).join('');
|
|
4585
4599
|
}
|
|
4586
4600
|
|
|
4601
|
+
// On the edit page every block sits in a .ed-block wrapper, so the heading's
|
|
4602
|
+
// siblings are wrappers: the section ends at the next wrapper that is, or
|
|
4603
|
+
// directly holds, a heading of the same or a shallower depth. In the reader
|
|
4604
|
+
// the unit is the heading itself and the walk is the one it always was.
|
|
4587
4605
|
function sectionBoundary(sectionId) {
|
|
4588
4606
|
const start = document.getElementById(sectionId);
|
|
4589
4607
|
if (!start) return null;
|
|
4590
4608
|
const startDepth = Number(start.getAttribute('data-reader-depth') || '1');
|
|
4609
|
+
const unit = start.closest('.ed-block') || start;
|
|
4591
4610
|
let end = null;
|
|
4592
|
-
let node =
|
|
4611
|
+
let node = unit.nextElementSibling;
|
|
4593
4612
|
while (node) {
|
|
4594
|
-
|
|
4595
|
-
|
|
4613
|
+
let heading = null;
|
|
4614
|
+
if (node.matches && node.matches('[data-reader-heading]')) heading = node;
|
|
4615
|
+
else if (unit !== start && node.querySelector) heading = node.querySelector(':scope > [data-reader-heading]');
|
|
4616
|
+
if (heading) {
|
|
4617
|
+
const depth = Number(heading.getAttribute('data-reader-depth') || '1');
|
|
4596
4618
|
if (depth <= startDepth) {
|
|
4597
|
-
end =
|
|
4619
|
+
end = heading;
|
|
4598
4620
|
break;
|
|
4599
4621
|
}
|
|
4600
4622
|
}
|
|
4601
4623
|
node = node.nextElementSibling;
|
|
4602
4624
|
}
|
|
4603
|
-
return { start, end };
|
|
4625
|
+
return { start, end, container: unit.parentNode };
|
|
4604
4626
|
}
|
|
4605
4627
|
|
|
4606
4628
|
// A whitespace-only text node is the real space between words when it sits in
|
|
@@ -4634,13 +4656,12 @@ ${mermaidInitTag}`)
|
|
|
4634
4656
|
function highlightFirstOccurrence(sectionId, query) {
|
|
4635
4657
|
const bounds = sectionBoundary(sectionId);
|
|
4636
4658
|
if (!bounds) return null;
|
|
4637
|
-
const { start, end } = bounds;
|
|
4638
|
-
const container = start.parentNode;
|
|
4659
|
+
const { start, end, container } = bounds;
|
|
4639
4660
|
if (!container) return null;
|
|
4640
4661
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
|
|
4641
4662
|
acceptNode(node) {
|
|
4642
4663
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
4643
|
-
if (node.matches(SKIP_SELECTOR) || node.matches('.reader-sidebar, .heading-anchor')) return NodeFilter.FILTER_REJECT;
|
|
4664
|
+
if (node.matches(SKIP_SELECTOR) || node.matches('.reader-sidebar, .heading-anchor, button')) return NodeFilter.FILTER_REJECT;
|
|
4644
4665
|
return node.matches('br, .katex') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
4645
4666
|
}
|
|
4646
4667
|
if (!node.nodeValue) return NodeFilter.FILTER_REJECT;
|
|
@@ -4650,7 +4671,7 @@ ${mermaidInitTag}`)
|
|
|
4650
4671
|
});
|
|
4651
4672
|
// Nothing before the heading is in range, so the walk starts at it.
|
|
4652
4673
|
walker.currentNode = start;
|
|
4653
|
-
const BLOCK = 'p, li, td, th, h1, h2, h3, h4, h5, h6, pre, blockquote, dt, dd, figcaption, .katex-display';
|
|
4674
|
+
const BLOCK = 'p, li, .ed-li-text, td, th, h1, h2, h3, h4, h5, h6, pre, blockquote, dt, dd, figcaption, .katex-display';
|
|
4654
4675
|
const nodes = [];
|
|
4655
4676
|
let flat = '';
|
|
4656
4677
|
const at = [];
|
package/lib/theme/tokens.js
CHANGED
|
@@ -165,7 +165,7 @@ function themeCss() {
|
|
|
165
165
|
' ' + D + ' .content .drawio { background: #ffffff; border-radius: 6px; box-shadow: 0 0 0 10px #ffffff; }',
|
|
166
166
|
// R7 甲: a Markdown-native inline svg (wrapped in .inline-svg by the renderer) gets the
|
|
167
167
|
// same white plate; its ink stays the light text colour because the plate is always white.
|
|
168
|
-
' ' + D + ' .content .inline-svg > svg { background: #ffffff; color: #1f2328; border-radius: 4px; box-shadow: 0 0 0 10px #ffffff; }',
|
|
168
|
+
' ' + D + ' .content .inline-svg > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] > svg, ' + D + ' .content .ed-block[data-svg-plate="1"] > p > svg { background: #ffffff; color: #1f2328; border-radius: 4px; box-shadow: 0 0 0 10px #ffffff; }',
|
|
169
169
|
' ' + D + ' .lightbox-canvas > svg[data-md2doc-recoloured] { background: var(--md-bg); }',
|
|
170
170
|
' }',
|
|
171
171
|
' .md2doc-theme-toggle { position: fixed; right: 20px; bottom: 20px; z-index: 60; width: 40px; height: 40px;',
|