rapier-markdown-kit 1.2.43 → 1.2.44
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/dist/kit/render-markdown.mjs +229 -18
- package/dist/kit/render-sanitize.mjs +67 -2
- package/dist/kit/render.mjs +9 -5
- package/dist/layout/model.mjs +13 -0
- package/dist/spec/md-layout.mjs +1 -1
- package/package.json +1 -1
|
@@ -459,10 +459,215 @@ function initMarkdownIt() {
|
|
|
459
459
|
globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
|
|
460
460
|
const render = md.renderer.render;
|
|
461
461
|
md.renderer.render = function (tokens, options, env) {
|
|
462
|
-
return render.call(this, _rapierCloseOpenDetails(tokens), options, env);
|
|
462
|
+
return render.call(this, _rapierHideForeignParagraphs(_rapierCloseOpenDetails(tokens)), options, env);
|
|
463
463
|
};
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
function _rapierHtmlTagStack(html, stack = []) {
|
|
467
|
+
if (!html) return stack;
|
|
468
|
+
const swallow = _rapierHtmlTagStack.swallow || (_rapierHtmlTagStack.swallow = new Set('select template textarea title style xmp plaintext noscript iframe noembed noframes script svg math'.split(' ')));
|
|
469
|
+
const rawTags = _rapierHtmlTagStack.raw || (_rapierHtmlTagStack.raw = new Set('script style textarea title xmp iframe noembed noframes noscript plaintext'.split(' ')));
|
|
470
|
+
let i = 0;
|
|
471
|
+
while (i < html.length) {
|
|
472
|
+
const top = stack.at(-1);
|
|
473
|
+
if (top && top.raw) {
|
|
474
|
+
const closer = new RegExp('</' + top.name + '(?=[\\t\\n\\f\\r />])', 'ig');
|
|
475
|
+
closer.lastIndex = i;
|
|
476
|
+
const found = closer.exec(html);
|
|
477
|
+
if (!found) break;
|
|
478
|
+
i = found.index;
|
|
479
|
+
}
|
|
480
|
+
const open = html.indexOf('<', i);
|
|
481
|
+
if (open < 0) break;
|
|
482
|
+
if (html.startsWith('<!--', open)) {
|
|
483
|
+
const end = html.indexOf('-->', open + 4);
|
|
484
|
+
if (end < 0) break;
|
|
485
|
+
i = end + 3;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (html.startsWith('<!', open) || html.startsWith('<?', open)) {
|
|
489
|
+
const end = html.indexOf('>', open + 2);
|
|
490
|
+
i = end < 0 ? html.length : end + 1;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const match = /^<\/?([A-Za-z][A-Za-z0-9:-]*)/.exec(html.slice(open));
|
|
494
|
+
if (!match) { i = open + 1; continue; }
|
|
495
|
+
const name = match[1].toLowerCase();
|
|
496
|
+
let end = open + match[0].length, quote = '';
|
|
497
|
+
for (; end < html.length; end++) {
|
|
498
|
+
const character = html[end];
|
|
499
|
+
if (quote) { if (character === quote) quote = ''; }
|
|
500
|
+
else if (character === '"' || character === "'") quote = character;
|
|
501
|
+
else if (character === '>') break;
|
|
502
|
+
}
|
|
503
|
+
if (end >= html.length) break;
|
|
504
|
+
if (html[open + 1] === '/') {
|
|
505
|
+
let at = stack.length - 1;
|
|
506
|
+
while (at >= 0 && stack[at].name !== name && !swallow.has(stack[at].name)) at--;
|
|
507
|
+
if (at >= 0 && stack[at].name === name) stack.length = at;
|
|
508
|
+
} else stack.push({name, at: open, tagEnd: end + 1, raw: rawTags.has(name)});
|
|
509
|
+
i = end + 1;
|
|
510
|
+
}
|
|
511
|
+
return stack;
|
|
512
|
+
}
|
|
513
|
+
function _rapierForeignOpen(stack) {
|
|
514
|
+
for (let index = 0; index < stack.length; index++) {
|
|
515
|
+
const name = stack[index].name;
|
|
516
|
+
if (name === 'svg' || name === 'math') return true;
|
|
517
|
+
}
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
function _rapierSealBlockHtml(html) {
|
|
521
|
+
if (!html || html.indexOf('<') < 0) return html;
|
|
522
|
+
const stack = _rapierHtmlTagStack(html);
|
|
523
|
+
const swallow = _rapierHtmlTagStack.swallow;
|
|
524
|
+
const foreign = _rapierSealBlockHtml.foreign || (_rapierSealBlockHtml.foreign = new Set(['svg', 'math']));
|
|
525
|
+
const cut = stack.find(frame => swallow.has(frame.name) && (frame.raw || foreign.has(frame.name)));
|
|
526
|
+
if (cut) {
|
|
527
|
+
const wrappers = stack.filter(frame => frame.at < cut.at);
|
|
528
|
+
return html.slice(0, cut.at) + _rapierSealBlockTail(html.slice(cut.at), wrappers);
|
|
529
|
+
}
|
|
530
|
+
const stale = stack.filter(frame => swallow.has(frame.name));
|
|
531
|
+
if (!stale.length) return html;
|
|
532
|
+
let out = html;
|
|
533
|
+
for (const frame of stale.sort((left, right) => right.at - left.at))
|
|
534
|
+
out = out.slice(0, frame.at) + escapeRapierHtmlText(out.slice(frame.at, frame.tagEnd)) + out.slice(frame.tagEnd);
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function _rapierSealBlockTail(tail, wrappers) {
|
|
539
|
+
const open = wrappers.map(frame => frame.name);
|
|
540
|
+
let out = '', i = 0;
|
|
541
|
+
while (i < tail.length) {
|
|
542
|
+
const at = tail.indexOf('<', i);
|
|
543
|
+
if (at < 0) { out += escapeRapierHtmlText(tail.slice(i)); break; }
|
|
544
|
+
out += escapeRapierHtmlText(tail.slice(i, at));
|
|
545
|
+
const match = /^<\/([A-Za-z][A-Za-z0-9:-]*)\s*>/.exec(tail.slice(at));
|
|
546
|
+
if (match && open.at(-1) === match[1].toLowerCase()) {
|
|
547
|
+
out += match[0];
|
|
548
|
+
open.pop();
|
|
549
|
+
i = at + match[0].length;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
out += escapeRapierHtmlText(tail[at]);
|
|
553
|
+
i = at + 1;
|
|
554
|
+
}
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function _rapierRenderTokenHtml(tokens, index, env) {
|
|
559
|
+
const token = tokens[index], rule = md.renderer.rules[token.type];
|
|
560
|
+
return token.type === 'inline' ? md.renderer.renderInline(token.children, md.options, env)
|
|
561
|
+
: rule ? rule(tokens, index, md.options, env, md.renderer) : md.renderer.renderToken(tokens, index, md.options);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function _rapierTokenBlocks(tokens) {
|
|
565
|
+
const blocks = [];
|
|
566
|
+
let start = 0, depth = 0;
|
|
567
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
568
|
+
depth += tokens[index].nesting || 0;
|
|
569
|
+
if (depth <= 0) { blocks.push([start, index + 1]); start = index + 1; depth = 0; }
|
|
570
|
+
}
|
|
571
|
+
if (start < tokens.length) blocks.push([start, tokens.length]);
|
|
572
|
+
return blocks;
|
|
573
|
+
}
|
|
574
|
+
function _rapierScanHtmlBlocks(tokens, start, end, stack) {
|
|
575
|
+
for (let index = start; index < end; index++) {
|
|
576
|
+
if (tokens[index].type === 'html_block') _rapierHtmlTagStack(tokens[index].content || '', stack);
|
|
577
|
+
}
|
|
578
|
+
return stack;
|
|
579
|
+
}
|
|
580
|
+
function _rapierForeignSealRanges(tokens) {
|
|
581
|
+
const blocks = _rapierTokenBlocks(tokens);
|
|
582
|
+
const ranges = [];
|
|
583
|
+
const scanLater = ([start, end], frames) => {
|
|
584
|
+
if (tokens[start].type !== 'paragraph_open') return _rapierScanHtmlBlocks(tokens, start, end, frames);
|
|
585
|
+
for (const child of tokens[start + 1] && tokens[start + 1].children || [])
|
|
586
|
+
if (child.type === 'html_inline') _rapierHtmlTagStack(child.content || '', frames);
|
|
587
|
+
return frames;
|
|
588
|
+
};
|
|
589
|
+
for (let index = 0; index < blocks.length; index++) {
|
|
590
|
+
const stack = _rapierScanHtmlBlocks(tokens, blocks[index][0], blocks[index][1], []);
|
|
591
|
+
if (!_rapierForeignOpen(stack)) { ranges.push(blocks[index]); continue; }
|
|
592
|
+
const frames = stack.map(frame => ({name: frame.name, raw: frame.raw}));
|
|
593
|
+
let joined = -1;
|
|
594
|
+
for (let later = index + 1; later < blocks.length && later <= index + 512; later++) {
|
|
595
|
+
scanLater(blocks[later], frames);
|
|
596
|
+
if (!_rapierForeignOpen(frames)) { joined = later; break; }
|
|
597
|
+
}
|
|
598
|
+
if (joined < 0) { ranges.push(blocks[index]); continue; }
|
|
599
|
+
const range = [blocks[index][0], blocks[joined][1]];
|
|
600
|
+
for (let later = index + 1; later <= joined; later++) {
|
|
601
|
+
const [start, end] = blocks[later];
|
|
602
|
+
if (tokens[start].type === 'paragraph_open' && tokens[end - 1].type === 'paragraph_close' &&
|
|
603
|
+
(tokens[start + 1] && tokens[start + 1].children || []).some(child => child.type === 'html_inline'))
|
|
604
|
+
(range.hide || (range.hide = new Set())).add(start).add(end - 1);
|
|
605
|
+
}
|
|
606
|
+
ranges.push(range);
|
|
607
|
+
index = joined;
|
|
608
|
+
}
|
|
609
|
+
return ranges;
|
|
610
|
+
}
|
|
611
|
+
function _rapierHideForeignParagraphs(tokens) {
|
|
612
|
+
let out = tokens;
|
|
613
|
+
for (const range of _rapierForeignSealRanges(tokens)) {
|
|
614
|
+
if (!range.hide) continue;
|
|
615
|
+
if (out === tokens) out = tokens.slice();
|
|
616
|
+
for (const index of range.hide) out[index] = Object.assign(Object.create(Object.getPrototypeOf(tokens[index])), tokens[index], {hidden: true});
|
|
617
|
+
}
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
function _rapierMergeForeignBlocks(blocks, source, parser) {
|
|
621
|
+
const text = String(source || '');
|
|
622
|
+
const out = [];
|
|
623
|
+
const inline = new Map();
|
|
624
|
+
const scanLater = (row, frames) => {
|
|
625
|
+
if (!row) return frames;
|
|
626
|
+
if (row.type === 'html_block') return _rapierHtmlTagStack(String(row.raw || ''), frames);
|
|
627
|
+
if (row.type !== 'paragraph' || String(row.raw || '').indexOf('<') < 0) return frames;
|
|
628
|
+
let children = inline.get(row);
|
|
629
|
+
if (!children) {
|
|
630
|
+
if (!parser && !md) initMarkdownIt();
|
|
631
|
+
const parsed = (parser || md).parseInline(String(row.raw || ''), {});
|
|
632
|
+
children = parsed[0] && parsed[0].children || [];
|
|
633
|
+
inline.set(row, children);
|
|
634
|
+
}
|
|
635
|
+
for (const child of children) if (child.type === 'html_inline') _rapierHtmlTagStack(child.content || '', frames);
|
|
636
|
+
return frames;
|
|
637
|
+
};
|
|
638
|
+
let index = 0;
|
|
639
|
+
while (index < blocks.length) {
|
|
640
|
+
const block = blocks[index];
|
|
641
|
+
const stack = [];
|
|
642
|
+
if (block && block.type === 'html_block') _rapierHtmlTagStack(String(block.raw || ''), stack);
|
|
643
|
+
if (!_rapierForeignOpen(stack)) { out.push(block); index++; continue; }
|
|
644
|
+
const frames = stack.map(frame => ({name: frame.name, raw: frame.raw}));
|
|
645
|
+
let joined = -1;
|
|
646
|
+
for (let later = index + 1; later < blocks.length && later <= index + 512; later++) {
|
|
647
|
+
scanLater(blocks[later], frames);
|
|
648
|
+
if (!_rapierForeignOpen(frames)) { joined = later; break; }
|
|
649
|
+
}
|
|
650
|
+
if (joined < 0) { out.push(block); index++; continue; }
|
|
651
|
+
const start = Number(block._sourceStart), end = Number(blocks[joined]._sourceEnd);
|
|
652
|
+
const raw = Number.isFinite(start) && Number.isFinite(end) && start >= 0 && end >= start && end <= text.length
|
|
653
|
+
? text.slice(start, end)
|
|
654
|
+
: blocks.slice(index, joined + 1).map(row => String(row && row.raw || '')).join('\n\n');
|
|
655
|
+
out.push({...block, raw, _sourceEnd: Number.isFinite(end) ? end : block._sourceEnd, rendered: null});
|
|
656
|
+
index = joined + 1;
|
|
657
|
+
}
|
|
658
|
+
out._rapierFacts = Array.isArray(blocks && blocks._rapierFacts) ? blocks._rapierFacts.slice() : [];
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
function _rapierRenderSealedHtml(tokens, env) {
|
|
662
|
+
let html = '';
|
|
663
|
+
for (const range of _rapierForeignSealRanges(tokens)) {
|
|
664
|
+
let block = '';
|
|
665
|
+
for (let index = range[0]; index < range[1]; index++) block += range.hide && range.hide.has(index) ? (tokens[index].nesting < 0 ? '\n' : '') : _rapierRenderTokenHtml(tokens, index, env);
|
|
666
|
+
html += _rapierSealBlockHtml(block);
|
|
667
|
+
}
|
|
668
|
+
return html;
|
|
669
|
+
}
|
|
670
|
+
|
|
466
671
|
function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
467
672
|
const root = document.createElement('template').content.ownerDocument.createElement('div');
|
|
468
673
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
@@ -487,20 +692,21 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
487
692
|
if (wikiLinks) env.rapierWikiLinks = wikiLinks;
|
|
488
693
|
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
489
694
|
const nesting = md && options.maxNesting > 0 ? options.maxNesting : 0;
|
|
490
|
-
let cutAt = -1, rendered;
|
|
695
|
+
let cutAt = -1, rendered = '';
|
|
491
696
|
codeLeft = options.codeBudget >= 0 ? options.codeBudget : Infinity;
|
|
492
697
|
try {
|
|
493
|
-
if (
|
|
698
|
+
if (!md) rendered = '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
699
|
+
else {
|
|
494
700
|
const saved = md.options.maxNesting;
|
|
495
701
|
if (nesting) md.options.maxNesting = nesting;
|
|
496
702
|
try {
|
|
497
|
-
|
|
703
|
+
let tokens = md.parse(markdown, env);
|
|
498
704
|
if (nesting) cutAt = _rapierNestingCutAt(tokens, nesting);
|
|
499
|
-
|
|
705
|
+
tokens = _rapierCloseOpenDetails(tokens);
|
|
706
|
+
rendered = _rapierRenderSealedHtml(tokens, env);
|
|
707
|
+
if (md.renderer.rapierTransformHtml) rendered = md.renderer.rapierTransformHtml(rendered);
|
|
500
708
|
} finally { md.options.maxNesting = saved; }
|
|
501
|
-
}
|
|
502
|
-
? md.render(markdown, env)
|
|
503
|
-
: '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
709
|
+
}
|
|
504
710
|
} finally { codeLeft = Infinity; }
|
|
505
711
|
if (md && env.footnotes && Array.isArray(env.footnotes.list) && env.footnotes.list.length) {
|
|
506
712
|
const labels = env.footnotes.list.map((item, index) =>
|
|
@@ -565,17 +771,22 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
565
771
|
while (part.firstChild) root.appendChild(part.firstChild);
|
|
566
772
|
html = '';
|
|
567
773
|
};
|
|
774
|
+
const finishBlock = block => {
|
|
775
|
+
const sealed = _rapierSealBlockHtml(block);
|
|
776
|
+
html += sealed;
|
|
777
|
+
if (boundary(sealed) && html.length >= 16384) append();
|
|
778
|
+
};
|
|
568
779
|
try {
|
|
569
|
-
for (
|
|
570
|
-
|
|
571
|
-
let
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
began = performance.now();
|
|
780
|
+
for (const range of _rapierForeignSealRanges(tokens)) {
|
|
781
|
+
let block = '';
|
|
782
|
+
for (let index = range[0]; index < range[1]; index++) {
|
|
783
|
+
block += range.hide && range.hide.has(index) ? (tokens[index].nesting < 0 ? '\n' : '') : _rapierRenderTokenHtml(tokens, index, env);
|
|
784
|
+
if (performance.now() - began >= 8) {
|
|
785
|
+
await pause(work, .3 + .2 * (index + 1) / tokens.length);
|
|
786
|
+
began = performance.now();
|
|
787
|
+
}
|
|
578
788
|
}
|
|
789
|
+
finishBlock(block);
|
|
579
790
|
}
|
|
580
791
|
if (html) append();
|
|
581
792
|
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
@@ -1893,7 +2104,7 @@ function renderWithMathML(source, metadata = {}) {
|
|
|
1893
2104
|
return {html, equations};
|
|
1894
2105
|
}
|
|
1895
2106
|
|
|
1896
|
-
return {_rapierReadPartDecorations, renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierInertParse, _rapierUnwrapElement, _rapierBidiStrong, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierApplyImageGeometry, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
2107
|
+
return {_rapierReadPartDecorations, renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierInertParse, _rapierUnwrapElement, _rapierBidiStrong, _rapierSealBlockHtml, _rapierMergeForeignBlocks, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierApplyImageGeometry, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
1897
2108
|
}
|
|
1898
2109
|
export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};
|
|
1899
2110
|
|
|
@@ -69,7 +69,8 @@ function cleanDocumentStyle(tag, value, context, classes = '') {
|
|
|
69
69
|
const property = part.slice(0, colon).trim().toLowerCase(), body = part.slice(colon + 1).trim();
|
|
70
70
|
const diagramProperty = diagram && /^--(?:_[a-z0-9_-]+|pele-[a-z0-9-]+)$/.test(property);
|
|
71
71
|
if (!DOCUMENT_STYLE_PROPERTIES.has(property) && !/^--md-[a-z0-9-]+$/.test(property) && !diagramProperty && !(diagram && property === 'font')) return;
|
|
72
|
-
if (!body || /[
|
|
72
|
+
if (!body || /[\u0000-\u0008\u000b\u000e-\u001f{}@]|\/\*|\*\//.test(body) ||
|
|
73
|
+
/["'\\]/.test(body.replace(/"(?:[^"\\\n\r\f]|\\["\\])*"|'(?:[^'\\\n\r\f]|\\['\\])*'/g, ''))) return;
|
|
73
74
|
const withoutLocal = body.replace(/\burl\(\s*(['"]?)#[a-z0-9_.:-]+\1\s*\)/gi, '');
|
|
74
75
|
if (/(?:url|image|image-set|-webkit-image-set|element|paint|attr|expression)\s*\(/i.test(withoutLocal)) return;
|
|
75
76
|
kept.push(part.trim());
|
|
@@ -304,6 +305,31 @@ function _rapierInstallSanitizeHooks() {
|
|
|
304
305
|
if (level) node.setAttribute('data-rapier-word-level', String(level));
|
|
305
306
|
else if (node.nodeName === 'SPAN' && wordListIsMarker(style)) node.setAttribute('data-rapier-word-marker', '1');
|
|
306
307
|
});
|
|
308
|
+
DOMPurify.addHook('uponSanitizeElement', node => {
|
|
309
|
+
if (_rapierSanitizeRuntime.context !== 'paste' || !node || node.nodeName !== 'TABLE') return;
|
|
310
|
+
node.removeAttribute('data-rapier-paste-word-header');
|
|
311
|
+
try {
|
|
312
|
+
const own = (node.getAttribute('style') || '') + ' ' + (node.getAttribute('class') || '');
|
|
313
|
+
const word = /mso-yfti-|MsoTable/i.test(own) || !!node.querySelector('[style*="mso-"],[class*="Mso"]');
|
|
314
|
+
if (!word || node.tHead || node.querySelector('th')) return;
|
|
315
|
+
const row = Array.from(node.querySelectorAll('tr')).find(candidate => candidate.closest('table') === node);
|
|
316
|
+
if (!row) return;
|
|
317
|
+
const cells = Array.from(row.children).filter(cell => cell.nodeName === 'TD' || cell.nodeName === 'TH');
|
|
318
|
+
if (!cells.length) return;
|
|
319
|
+
let bold = false;
|
|
320
|
+
for (const cell of cells) {
|
|
321
|
+
const walker = node.ownerDocument.createTreeWalker(cell, NodeFilter.SHOW_TEXT);
|
|
322
|
+
for (let text = walker.nextNode(); text; text = walker.nextNode()) {
|
|
323
|
+
if (!/\S/.test(text.nodeValue || '')) continue;
|
|
324
|
+
const parent = text.parentElement;
|
|
325
|
+
const style = parent?.getAttribute?.('style') || '';
|
|
326
|
+
if (!(parent && (parent.closest('b,strong') || /font-weight\s*:\s*(?:bold|[6-9]00|1000)\b/i.test(style)))) return;
|
|
327
|
+
bold = true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (bold) node.setAttribute('data-rapier-paste-word-header', '');
|
|
331
|
+
} catch (_) {}
|
|
332
|
+
});
|
|
307
333
|
}
|
|
308
334
|
|
|
309
335
|
function _rapierInstallDocumentSanitizeHooks() {
|
|
@@ -414,9 +440,17 @@ function sanitize(value, context, fragment) {
|
|
|
414
440
|
if (purifyConfigured.get(DOMPurify) !== options) { DOMPurify.setConfig(options); purifyConfigured.set(DOMPurify, options); }
|
|
415
441
|
const previous = _rapierSanitizeRuntime.context;
|
|
416
442
|
_rapierSanitizeRuntime.context = selected;
|
|
443
|
+
ensureScratchParseHook();
|
|
444
|
+
const scratch = [];
|
|
445
|
+
const outerSink = scratchSink;
|
|
446
|
+
scratchSink = scratch;
|
|
447
|
+
let result;
|
|
417
448
|
try {
|
|
418
|
-
|
|
449
|
+
result = DOMPurify.sanitize(value == null ? '' : String(value));
|
|
450
|
+
return result;
|
|
419
451
|
} finally {
|
|
452
|
+
scratchSink = outerSink;
|
|
453
|
+
releaseScratchMedia(scratch, result);
|
|
420
454
|
if (labels) {
|
|
421
455
|
DOMPurify.removeHook('uponSanitizeElement', diagramLabelElement);
|
|
422
456
|
DOMPurify.removeHook('uponSanitizeAttribute', diagramLabelAttribute);
|
|
@@ -426,6 +460,37 @@ function sanitize(value, context, fragment) {
|
|
|
426
460
|
}
|
|
427
461
|
}
|
|
428
462
|
|
|
463
|
+
let scratchSink = null;
|
|
464
|
+
function ensureScratchParseHook() {
|
|
465
|
+
if (typeof DOMParser !== 'function' || DOMParser.prototype.parseFromString._rapierScratch) return;
|
|
466
|
+
const original = DOMParser.prototype.parseFromString;
|
|
467
|
+
function wrapped(source, type) {
|
|
468
|
+
const doc = original.call(this, source, type);
|
|
469
|
+
scratchSink?.push(doc);
|
|
470
|
+
return doc;
|
|
471
|
+
}
|
|
472
|
+
wrapped._rapierScratch = true;
|
|
473
|
+
DOMParser.prototype.parseFromString = wrapped;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function releaseScratchMedia(docs, result) {
|
|
477
|
+
const kept = result && typeof result === 'object' ? result.nodeType === 9 ? result : result.ownerDocument : null;
|
|
478
|
+
for (const doc of docs) {
|
|
479
|
+
if (!doc || doc === kept || !doc.querySelectorAll) continue;
|
|
480
|
+
for (const el of [...doc.querySelectorAll('video, audio')]) releaseMediaElement(el);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function releaseMediaElement(el) {
|
|
484
|
+
if (!el || (el.nodeName !== 'VIDEO' && el.nodeName !== 'AUDIO')) return;
|
|
485
|
+
try {
|
|
486
|
+
el.pause();
|
|
487
|
+
el.removeAttribute('src');
|
|
488
|
+
for (const source of el.querySelectorAll('source')) source.removeAttribute('src');
|
|
489
|
+
el.load();
|
|
490
|
+
} catch (_) {}
|
|
491
|
+
el.remove();
|
|
492
|
+
}
|
|
493
|
+
|
|
429
494
|
function escapeRapierHtmlText(value) {
|
|
430
495
|
return String(value == null ? '' : value)
|
|
431
496
|
.replace(/&/g, '&')
|
package/dist/kit/render.mjs
CHANGED
|
@@ -725,7 +725,7 @@ function* _rapierSharedPageLayoutSteps(root) {
|
|
|
725
725
|
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
726
726
|
yield;
|
|
727
727
|
const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
728
|
-
if (layout?.wrap !== 'around' && layout?.wrap !== 'box') continue;
|
|
728
|
+
if (layout?.wrap !== 'around' && layout?.wrap !== 'beside' && layout?.wrap !== 'box') continue;
|
|
729
729
|
const paragraph = _rapierSharePictureParagraph(image);
|
|
730
730
|
if (!paragraph) continue;
|
|
731
731
|
const owner = globalThis.RapierMarkdownLayout.wrapNeighbour(paragraph, _rapierShareWrapKind);
|
|
@@ -1458,6 +1458,8 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1458
1458
|
if (layout.wrap === 'box') {
|
|
1459
1459
|
value = boxProfile(image);
|
|
1460
1460
|
if (!value && rotateRad) value = geometry.rasterTiltProfile(image.naturalWidth, image.naturalHeight, rotateRad);
|
|
1461
|
+
} else if (layout.wrap === 'beside') {
|
|
1462
|
+
value = rotateRad ? geometry.rasterTiltProfile(image.naturalWidth, image.naturalHeight, rotateRad) : null;
|
|
1461
1463
|
} else {
|
|
1462
1464
|
const occupancy = typeof geometry.parseProfile === 'function' && geometry.parseProfile(image.getAttribute('data-rapier-occupancy'));
|
|
1463
1465
|
value = occupancy || geometry.alphaProfile(image);
|
|
@@ -1481,7 +1483,7 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1481
1483
|
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
1482
1484
|
if (exclude.has(image) || !image.complete || !image.naturalWidth || !image.naturalHeight) continue;
|
|
1483
1485
|
const layout = metadata.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
1484
|
-
if (!layout?.rotate || ['around', 'box', 'behind', 'front'].includes(layout.wrap)) continue;
|
|
1486
|
+
if (!layout?.rotate || ['around', 'beside', 'box', 'behind', 'front'].includes(layout.wrap)) continue;
|
|
1485
1487
|
const width = image.offsetWidth, height = image.offsetHeight;
|
|
1486
1488
|
if (!(width > 0 && height > 0)) continue;
|
|
1487
1489
|
const paragraph = image.closest('p');
|
|
@@ -1619,7 +1621,7 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1619
1621
|
const origin = box(root).top, obstacles = [], anchors = new Map(), wrapped = new Set(), placed = [], positionedImages = new Set();
|
|
1620
1622
|
for (const element of children) {
|
|
1621
1623
|
const layout = metadata.parseLayoutAttribute(element.getAttribute('data-md-layout'));
|
|
1622
|
-
const image = ['around', 'box', 'behind', 'front'].includes(layout?.wrap) && element.tagName === 'P'
|
|
1624
|
+
const image = ['around', 'beside', 'box', 'behind', 'front'].includes(layout?.wrap) && element.tagName === 'P'
|
|
1623
1625
|
? element.querySelector('img[data-rapier-image-layout]') : null;
|
|
1624
1626
|
if (!image?.complete || !image.naturalWidth || !image.naturalHeight || !imageOnly(element, image)) continue;
|
|
1625
1627
|
|
|
@@ -1665,7 +1667,9 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1665
1667
|
obstacle.y + obstacle.height > y - room && obstacle.y < y + rectangle.height + room)
|
|
1666
1668
|
y = obstacle.y + obstacle.height + room;
|
|
1667
1669
|
}
|
|
1668
|
-
|
|
1670
|
+
let slices = geometry.pictureSlices(profile(image, layout), left, y, rectangle.width, rectangle.height, scale);
|
|
1671
|
+
if (layout.wrap === 'beside') slices = geometry.besideSlices(slices, bounds.left + inset, bounds.left + inset + width, left, rectangle.width);
|
|
1672
|
+
obstacles.push(...geometry.linesSlices(slices, layout, lines, y, rectangle.height));
|
|
1669
1673
|
}
|
|
1670
1674
|
placed.push({source, image, rectangle, left, y, wrap: layout.wrap,
|
|
1671
1675
|
visualLeft: left + visualDeltaX, visualTop: y + visualDeltaY, visualWidth: fit.fit.width, visualHeight: fit.fit.height,
|
|
@@ -1746,7 +1750,7 @@ function _rapierArtifactLayoutScript(root, nonce = '', {pictures = true} = {}) {
|
|
|
1746
1750
|
const dependencies = globalThis.RapierArtifactLayoutDependencies;
|
|
1747
1751
|
const layoutOf = element => globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-md-layout'));
|
|
1748
1752
|
const pictureLayoutOf = image => globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
1749
|
-
const wrapping = pictures && ([...root.querySelectorAll('p[data-md-layout]')].some(element => ['around', 'box', 'behind', 'front'].includes(layoutOf(element)?.wrap)) ||
|
|
1753
|
+
const wrapping = pictures && ([...root.querySelectorAll('p[data-md-layout]')].some(element => ['around', 'beside', 'box', 'behind', 'front'].includes(layoutOf(element)?.wrap)) ||
|
|
1750
1754
|
[...root.querySelectorAll('img[data-rapier-image-layout]')].some(image => pictureLayoutOf(image)?.rotate));
|
|
1751
1755
|
const typography = _rapierArtifactTypographyScript(root);
|
|
1752
1756
|
if (!wrapping && !typography) return '';
|
package/dist/layout/model.mjs
CHANGED
|
@@ -309,6 +309,19 @@ export function pictureSlices(profile, x, y, width, height, scale = 1) {
|
|
|
309
309
|
return slices;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
export function besideSlices(slices, columnLeft, columnRight, pictureLeft, pictureWidth) {
|
|
313
|
+
if (!Array.isArray(slices) || !(columnRight > columnLeft) || !(pictureWidth > 0) || !finite(pictureLeft)) return slices;
|
|
314
|
+
const blockLeft = pictureLeft + pictureWidth / 2 <= (columnLeft + columnRight) / 2;
|
|
315
|
+
return slices.map(slice => {
|
|
316
|
+
if (!slice || !finite(slice.x) || !finite(slice.width)) return slice;
|
|
317
|
+
if (blockLeft) {
|
|
318
|
+
const right = slice.x + slice.width;
|
|
319
|
+
return {...slice, x: columnLeft, width: Math.max(0, right - columnLeft)};
|
|
320
|
+
}
|
|
321
|
+
return {...slice, width: Math.max(0, columnRight - slice.x)};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
312
325
|
export {slotSequence, columnsFilled};
|
|
313
326
|
export function slotsForBand(width, obstacles, top, height, minWidth = 40) {
|
|
314
327
|
if (!finite(width) || width <= 0 || !finite(top) || !finite(height) || height <= 0 ||
|
package/dist/spec/md-layout.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'marker', 'lock', 'hyphens', 'caps', 'hanging', 'before', 'after', 'line']);
|
|
4
4
|
export const SPACING_FIELDS = Object.freeze(['before', 'after', 'line']);
|
|
5
5
|
export const alignments = new Set(['left', 'center', 'right', 'justify']);
|
|
6
|
-
export const wraps = new Set(['around', 'box', 'behind', 'front']);
|
|
6
|
+
export const wraps = new Set(['around', 'beside', 'box', 'behind', 'front']);
|
|
7
7
|
const has = (value, key) => Object.hasOwn(value, key);
|
|
8
8
|
const percent = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
9
9
|
const lineCount = value => Number.isInteger(value) && value >= 1 && value <= 12;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.44",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|