@helping-ai-workflow/md2doc 3.17.2 → 3.17.3
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 +1640 -131
- 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 +34 -4
- 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 +25 -2
- package/lib/theme/tokens.js +4 -1
- package/package.json +1 -1
package/lib/editor/blockmap.js
CHANGED
|
@@ -374,13 +374,73 @@ function pushListItemBlocks(listToken, cursor, indent, blocks, nextId, inherited
|
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
// v3.17.3 N2 (R36): LINK REFERENCE DEFINITIONS LEAVE NO TOKEN. marked's lexer
|
|
378
|
+
// consumes '[r]: http://u' (its trailing blank lines included: the rule ends in
|
|
379
|
+
// `\n+`) into `tokens.links` and pushes nothing, so the cursor never advanced
|
|
380
|
+
// over those lines and every later block named a line above its own. Typing X
|
|
381
|
+
// at the end of 'P.' in '# D\n\nA.\n\n[r]: http://u\n\nP.\n' wrote
|
|
382
|
+
// '# D\n\nA.\n\nP.X\n\nP.\n': the definition was replaced and the old 'P.'
|
|
383
|
+
// left behind.
|
|
384
|
+
//
|
|
385
|
+
// So at every top-level boundary that sits at a line start, marked's own
|
|
386
|
+
// definition rule is asked whether a definition begins there, and each one it
|
|
387
|
+
// matches is stepped over (several in a row, a title on its own line). That is
|
|
388
|
+
// the lexer's decision, not a lookalike: at a boundary the lexer reaches `def`
|
|
389
|
+
// only after space, indented code, fences, heading, hr, blockquote, list and
|
|
390
|
+
// html have all declined, and none of those can start on a line whose first
|
|
391
|
+
// character after at most three spaces is '['. Where the rule matches, the
|
|
392
|
+
// lexer took a definition. A boundary inside a line (after a paragraph whose
|
|
393
|
+
// raw ends without its newline) is never tested: the source there starts with
|
|
394
|
+
// '\n', which no definition matches, and testing from the line start would
|
|
395
|
+
// re-read the paragraph's own last line. A definition inside a list item or a
|
|
396
|
+
// blockquote needs nothing here: those raws are faithful slices that already
|
|
397
|
+
// contain its lines.
|
|
398
|
+
const DEF_RULES = marked.Lexer.rules.block;
|
|
399
|
+
function definitionRule() {
|
|
400
|
+
const o = marked.defaults;
|
|
401
|
+
return (o.pedantic ? DEF_RULES.pedantic : o.gfm ? DEF_RULES.gfm : DEF_RULES.normal).def;
|
|
402
|
+
}
|
|
403
|
+
// The dropped definitions that start at line `line`: how many lines they take
|
|
404
|
+
// (their trailing blank lines included) and which of those lines hold text.
|
|
405
|
+
function droppedDefinitions(text, lineStarts, line, rule) {
|
|
406
|
+
let skipped = 0;
|
|
407
|
+
const textLines = [];
|
|
408
|
+
while (line + skipped <= lineStarts.length) {
|
|
409
|
+
const m = rule.exec(text.slice(lineStarts[line + skipped - 1]));
|
|
410
|
+
if (!m) break;
|
|
411
|
+
const parts = m[0].split('\n');
|
|
412
|
+
if (parts.length === 1) break; // a definition at EOF: no block follows it
|
|
413
|
+
parts.slice(0, -1).forEach((l, k) => { if (l.trim() !== '') textLines.push(line + skipped + k); });
|
|
414
|
+
skipped += parts.length - 1;
|
|
415
|
+
}
|
|
416
|
+
return { skipped, textLines };
|
|
417
|
+
}
|
|
418
|
+
|
|
377
419
|
function buildBlockMap(mdText) {
|
|
378
420
|
const tokens = marked.lexer(mdText);
|
|
379
421
|
const blocks = [];
|
|
380
422
|
let cursor = 1; // current line number of the token's first character
|
|
381
423
|
const nextId = { v: 0 };
|
|
424
|
+
// The lexer reads '\r\n' as '\n'; so does the definition rule. Same line count.
|
|
425
|
+
const text = mdText.replace(/\r\n/g, '\n');
|
|
426
|
+
const lineStarts = [0];
|
|
427
|
+
for (let i = text.indexOf('\n'); i >= 0; i = text.indexOf('\n', i + 1)) lineStarts.push(i + 1);
|
|
428
|
+
const defRule = definitionRule();
|
|
429
|
+
let atLineStart = true;
|
|
430
|
+
// v3.17.3 N2b (R54): the text lines of the definitions dropped since the
|
|
431
|
+
// last block, handed to the next block as `defLinesAbove` (distances above
|
|
432
|
+
// its startLine, nearest last). No block owns them, and the client's range
|
|
433
|
+
// helpers keep them when a gesture rewrites a range that spans them.
|
|
434
|
+
let pendingDefLines = [];
|
|
382
435
|
for (const t of tokens) {
|
|
436
|
+
if (atLineStart) {
|
|
437
|
+
const d = droppedDefinitions(text, lineStarts, cursor, defRule);
|
|
438
|
+
cursor += d.skipped;
|
|
439
|
+
pendingDefLines = pendingDefLines.concat(d.textLines);
|
|
440
|
+
}
|
|
441
|
+
atLineStart = t.raw.charAt(t.raw.length - 1) === '\n';
|
|
383
442
|
const info = topLevelLineInfo(t);
|
|
443
|
+
const firstNew = blocks.length;
|
|
384
444
|
if (t.type === 'list') {
|
|
385
445
|
pushListItemBlocks(t, cursor, 0, blocks, nextId);
|
|
386
446
|
} else if (t.type !== 'space') {
|
|
@@ -396,6 +456,11 @@ function buildBlockMap(mdText) {
|
|
|
396
456
|
// text-based count instead — see its own comment for why.
|
|
397
457
|
// If raw has no trailing newline (EOF), the next token doesn't exist,
|
|
398
458
|
// so the off-by-one is unobservable.
|
|
459
|
+
if (pendingDefLines.length && blocks.length > firstNew) {
|
|
460
|
+
const b = blocks[firstNew];
|
|
461
|
+
b.defLinesAbove = pendingDefLines.map((l) => b.startLine - l);
|
|
462
|
+
pendingDefLines = [];
|
|
463
|
+
}
|
|
399
464
|
cursor += info.rawNewlines;
|
|
400
465
|
}
|
|
401
466
|
return { blocks, lineCount: mdText.split('\n').length };
|