rapier-markdown-kit 1.1.98 → 1.1.101
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/LICENSE +10 -2
- package/README.md +42 -8
- package/dist/kit/export-fonts.mjs +403 -0
- package/dist/kit/ledger/data.mjs +1 -1
- package/dist/kit/ledger/format.mjs +12 -5
- package/dist/kit/ledger/hash.mjs +16 -22
- package/dist/kit/ledger/history.mjs +59 -276
- package/dist/kit/ledger/inverse-source.mjs +316 -0
- package/dist/kit/ledger/journal-records.mjs +116 -12
- package/dist/kit/ledger/merge.mjs +7 -4
- package/dist/kit/ledger/transport.mjs +123 -19
- package/dist/kit/pdf-glyphs.mjs +159 -0
- package/dist/kit/pdf-vectors.mjs +204 -0
- package/dist/kit/print-capitals.mjs +85 -0
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-entry.mjs +1 -0
- package/dist/kit/render-ink.mjs +132 -0
- package/dist/kit/render-markdown.mjs +71 -17
- package/dist/kit/render-print.mjs +208 -60
- package/dist/kit/render-sanitize.mjs +23 -7
- package/dist/kit/render-styles.mjs +8 -12
- package/dist/kit/render.mjs +365 -133
- package/dist/layout/model.mjs +26 -20
- package/dist/layout/typography.mjs +364 -0
- package/dist/packages/rapier-diff/LICENSE +21 -0
- package/dist/packages/rapier-diff/index.mjs +610 -0
- package/dist/spec/document-fonts.mjs +244 -0
- package/dist/spec/document-settings.mjs +494 -0
- package/dist/spec/frontmatter.mjs +30 -0
- package/dist/spec/html-blocks.mjs +143 -0
- package/dist/spec/md-assets.mjs +77 -5
- package/dist/spec/md-layout.mjs +7 -3
- package/dist/spec/md-marks.mjs +114 -8
- package/package.json +1 -1
- package/style.css +110 -52
- package/dist/agent/diff.mjs +0 -52
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {parseLayoutAttribute} from '../spec/md-layout.mjs';
|
|
3
|
+
import {imageOnly, lineMetrics, linesHeight} from '../layout/model.mjs';
|
|
4
|
+
|
|
5
|
+
export function followingPrintProse(paragraph) {
|
|
6
|
+
let next = paragraph?.nextElementSibling;
|
|
7
|
+
while (next && (next.classList?.contains('rapier-will-carrier') || next.classList?.contains('rapier-image-asset-record')))
|
|
8
|
+
next = next.nextElementSibling;
|
|
9
|
+
return next;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The print compositor owns pagination. Only a normal-flow picture initial moves into
|
|
13
|
+
// its adjacent prose, so a native float can remain with the lines it introduces.
|
|
14
|
+
export function markPrintCapitals(root) {
|
|
15
|
+
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
16
|
+
const layout = parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
17
|
+
if (!layout?.lines || layout.wrap !== 'around' || layout.x != null || layout.y != null || layout.rotate ||
|
|
18
|
+
layout.align && layout.align !== 'left') continue;
|
|
19
|
+
const paragraph = image.closest('p'), owner = followingPrintProse(paragraph);
|
|
20
|
+
if (!paragraph || !imageOnly(paragraph, image) || paragraph.closest('table,pre,figure') ||
|
|
21
|
+
owner?.tagName !== 'P' || !owner.textContent.trim() || owner.hasAttribute('data-md-lines')) continue;
|
|
22
|
+
image.setAttribute('data-rapier-print-lines', String(layout.lines));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// This factory is also embedded in the offline page, with the same line metrics owner.
|
|
27
|
+
export function createPrintCapitals(lineMetrics, linesHeight, followingProse) {
|
|
28
|
+
const selector = 'img[data-rapier-print-lines]', states = new WeakMap();
|
|
29
|
+
const restoreStyle = (node, value) => value == null ? node.removeAttribute('style') : node.setAttribute('style', value);
|
|
30
|
+
function clear(root) {
|
|
31
|
+
for (const image of root.querySelectorAll(selector)) {
|
|
32
|
+
const state = states.get(image);
|
|
33
|
+
if (!state) continue;
|
|
34
|
+
const {paragraph, parent, next, occurrence, imageStyle, occurrenceStyle, paragraphStyle} = state;
|
|
35
|
+
parent.insertBefore(occurrence, next?.parentNode === parent ? next : null);
|
|
36
|
+
restoreStyle(image, imageStyle);
|
|
37
|
+
if (occurrence !== image) restoreStyle(occurrence, occurrenceStyle);
|
|
38
|
+
restoreStyle(paragraph, paragraphStyle);
|
|
39
|
+
states.delete(image);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function apply(root) {
|
|
43
|
+
for (const image of root.querySelectorAll(selector)) {
|
|
44
|
+
const lines = Number(image.getAttribute('data-rapier-print-lines'));
|
|
45
|
+
let state = states.get(image);
|
|
46
|
+
const paragraph = state?.paragraph || image.closest('p');
|
|
47
|
+
const owner = state?.owner || followingProse(paragraph);
|
|
48
|
+
if (!Number.isInteger(lines) || lines < 1 || lines > 12 || !image.isConnected ||
|
|
49
|
+
!owner || owner.tagName !== 'P' || !owner.textContent.trim()) continue;
|
|
50
|
+
const metrics = lineMetrics(owner), height = linesHeight(lines, metrics);
|
|
51
|
+
const naturalWidth = image.naturalWidth || Number(image.getAttribute('width'));
|
|
52
|
+
const naturalHeight = image.naturalHeight || Number(image.getAttribute('height'));
|
|
53
|
+
if (!(height > 0 && naturalWidth > 0 && naturalHeight > 0)) continue;
|
|
54
|
+
const style = owner.ownerDocument.defaultView.getComputedStyle(owner);
|
|
55
|
+
const availableWidth = owner.getBoundingClientRect().width -
|
|
56
|
+
['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'].reduce((sum, name) => sum + (parseFloat(style[name]) || 0), 0);
|
|
57
|
+
if (!(availableWidth > 0)) continue;
|
|
58
|
+
// As imageBox does, cap the width first and retain the picture's natural ratio.
|
|
59
|
+
const width = Math.min(availableWidth, height * naturalWidth / naturalHeight);
|
|
60
|
+
const occurrence = state?.occurrence || (image.parentElement.tagName === 'A' ? image.parentElement : image);
|
|
61
|
+
if (!state) {
|
|
62
|
+
state = {paragraph, owner, occurrence, parent: occurrence.parentNode, next: occurrence.nextSibling,
|
|
63
|
+
imageStyle: image.getAttribute('style'), occurrenceStyle: occurrence.getAttribute('style'),
|
|
64
|
+
paragraphStyle: paragraph.getAttribute('style')};
|
|
65
|
+
states.set(image, state);
|
|
66
|
+
owner.prepend(occurrence);
|
|
67
|
+
paragraph.style.setProperty('display', 'none', 'important');
|
|
68
|
+
}
|
|
69
|
+
const gap = (parseFloat(style.fontSize) || 16) * .4;
|
|
70
|
+
for (const [name, value] of Object.entries({display: 'block', position: 'static', float: 'left',
|
|
71
|
+
width: width + 'px', height: width * naturalHeight / naturalWidth + 'px', 'max-width': '100%',
|
|
72
|
+
'margin-top': metrics.capTop + 'px', 'margin-right': gap + 'px', 'margin-bottom': '0', 'margin-left': '0',
|
|
73
|
+
transform: 'none', 'object-fit': 'contain', 'break-inside': 'avoid'})) image.style.setProperty(name, value, 'important');
|
|
74
|
+
if (occurrence !== image) {
|
|
75
|
+
occurrence.style.setProperty('display', 'contents', 'important');
|
|
76
|
+
occurrence.style.setProperty('float', 'none', 'important');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {apply, clear};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const capitals = createPrintCapitals(lineMetrics, linesHeight, followingPrintProse);
|
|
84
|
+
export const applyPrintCapitals = capitals.apply;
|
|
85
|
+
export const clearPrintCapitals = capitals.clear;
|
|
@@ -7,8 +7,8 @@ import {merge, transportInterval, transportTouchedInterval} from './ledger/merge
|
|
|
7
7
|
export {merge, transportInterval, transportTouchedInterval};
|
|
8
8
|
import {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierRecordMetadata, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect} from './ledger/journal-records.mjs';
|
|
9
9
|
export {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierRecordMetadata, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect};
|
|
10
|
-
import {replayHistory, sourceBefore, historyProjection, historyPlaces, selectiveUndo, groupHistoryActs} from './ledger/history.mjs';
|
|
11
|
-
export {replayHistory, sourceBefore, historyProjection, historyPlaces, selectiveUndo, groupHistoryActs};
|
|
10
|
+
import {replayHistory, sourceBefore, historyProjection, historyProjections, historyPlaces, selectiveUndo, groupHistoryActs, pictureHistorySplices} from './ledger/history.mjs';
|
|
11
|
+
export {replayHistory, sourceBefore, historyProjection, historyProjections, historyPlaces, selectiveUndo, groupHistoryActs, pictureHistorySplices};
|
|
12
12
|
|
|
13
13
|
import {transposeSource} from './ledger/transport.mjs';
|
|
14
14
|
export {transposeSource};
|
|
@@ -5,3 +5,4 @@ export {createMarkdownRenderer} from './render-markdown.mjs';
|
|
|
5
5
|
export {createRenderStyles} from './render-styles.mjs';
|
|
6
6
|
export {createRenderSanitizer} from './render-sanitize.mjs';
|
|
7
7
|
export {createPrintRenderer} from './render-print.mjs';
|
|
8
|
+
export {exportFontCss, requireDocumentFonts, fontRequests, waitForFonts, waitForFontRequests} from './export-fonts.mjs';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Published typography is measured once; an annotation keeps its words and paths in one inert, scalable group.
|
|
3
|
+
import {check, pause} from './render-work.mjs';
|
|
4
|
+
import {apply as applyTypography} from '../layout/typography.mjs';
|
|
5
|
+
import {documentLanguage} from '../spec/document-settings.mjs';
|
|
6
|
+
import {waitForFonts} from './export-fonts.mjs';
|
|
7
|
+
|
|
8
|
+
const MARK = 'span.rapier-ink-mark[data-rapier-ink]';
|
|
9
|
+
const TYPOGRAPHY = 'p[data-md-hyphens="auto"],p[data-md-lines],p[data-md-hanging="on"]';
|
|
10
|
+
const SVG = 'http://www.w3.org/2000/svg';
|
|
11
|
+
const XHTML = 'http://www.w3.org/1999/xhtml';
|
|
12
|
+
const failure = message => Object.assign(new Error('The published page could not be prepared: ' + message), {code: 'EXPORT_INK_UNAVAILABLE'});
|
|
13
|
+
|
|
14
|
+
function groupsOf(root, spec) {
|
|
15
|
+
const nodes = [...root.childNodes], ranges = [], arrows = new Map();
|
|
16
|
+
const topIndex = element => {
|
|
17
|
+
while (element.parentElement && element.parentElement !== root) element = element.parentElement;
|
|
18
|
+
return nodes.indexOf(element);
|
|
19
|
+
};
|
|
20
|
+
for (const span of root.querySelectorAll(MARK)) {
|
|
21
|
+
const mark = spec.parseInkBody(span.getAttribute('data-rapier-ink'));
|
|
22
|
+
if (!mark) continue;
|
|
23
|
+
const index = topIndex(span);
|
|
24
|
+
if (index < 0) throw failure('an annotation has no containing block.');
|
|
25
|
+
ranges.push([index, index]);
|
|
26
|
+
if (mark.kind === 'arrow' || mark.kind === 'end') {
|
|
27
|
+
const pair = arrows.get(mark.id) || [];
|
|
28
|
+
pair.push(index); arrows.set(mark.id, pair);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const pair of arrows.values()) ranges.push([Math.min(...pair), Math.max(...pair)]);
|
|
32
|
+
ranges.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
33
|
+
const groups = [];
|
|
34
|
+
for (const range of ranges) {
|
|
35
|
+
const prior = groups.at(-1);
|
|
36
|
+
if (prior && range[0] <= prior[1]) prior[1] = Math.max(prior[1], range[1]);
|
|
37
|
+
else groups.push(range);
|
|
38
|
+
}
|
|
39
|
+
return {nodes, groups};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function freezePublishedInk(root, {document: doc, css, fontCss = '', settings, language, theme, work, spec, ink, draw}) {
|
|
43
|
+
const annotated = !!root.querySelector(MARK), refined = !!root.querySelector(TYPOGRAPHY);
|
|
44
|
+
if (!annotated && !refined) return;
|
|
45
|
+
if (!doc?.body || !doc.defaultView || annotated && (!spec?.parseInkBody || !ink?.deriveMark || !draw?.drawInk))
|
|
46
|
+
throw failure('the browser publication renderer is unavailable.');
|
|
47
|
+
check(work);
|
|
48
|
+
const frame = doc.createElement('iframe'), width = Math.max(1, doc.documentElement.clientWidth || doc.defaultView.innerWidth);
|
|
49
|
+
frame.setAttribute('sandbox', 'allow-same-origin');
|
|
50
|
+
frame.setAttribute('aria-hidden', 'true'); frame.setAttribute('tabindex', '-1');
|
|
51
|
+
frame.setAttribute('referrerpolicy', 'no-referrer');
|
|
52
|
+
frame.style.cssText = 'position:fixed;left:-100000px;top:0;width:' + width + 'px;height:1px;border:0;visibility:hidden;pointer-events:none';
|
|
53
|
+
const policy = "default-src 'none'; script-src 'none'; connect-src 'none'; img-src data:; font-src data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'";
|
|
54
|
+
let timer, abort;
|
|
55
|
+
const stopped = new Promise((_, reject) => {
|
|
56
|
+
timer = setTimeout(() => reject(failure('its embedded resources did not finish loading.')), 20000);
|
|
57
|
+
abort = () => { try { check(work); } catch (error) { reject(error); } };
|
|
58
|
+
work?.signal?.addEventListener('abort', abort, {once: true});
|
|
59
|
+
});
|
|
60
|
+
const wait = promise => Promise.race([promise, stopped]);
|
|
61
|
+
try {
|
|
62
|
+
const loaded = new Promise(resolve => frame.addEventListener('load', resolve, {once: true}));
|
|
63
|
+
frame.srcdoc = '<!doctype html><html lang="' + documentLanguage(settings, language) + '" data-rapier-theme="' + theme + '"><head><meta charset="utf-8">'
|
|
64
|
+
+ '<meta http-equiv="Content-Security-Policy" content="' + policy + '"><meta name="referrer" content="no-referrer">'
|
|
65
|
+
+ '<style>' + css + '</style></head><body class="' + (theme === 'light' ? 'light' : '')
|
|
66
|
+
+ '"><main class="rapier-page md-render" data-md-theme="' + theme + '"' + (settings?.space ? ' data-md-space' : '') + '>' + root.innerHTML + '</main></body></html>';
|
|
67
|
+
doc.body.appendChild(frame);
|
|
68
|
+
await wait(loaded);
|
|
69
|
+
const paper = frame.contentDocument, main = paper?.querySelector('main.rapier-page');
|
|
70
|
+
if (!main) throw failure('the browser could not open its isolated page.');
|
|
71
|
+
const captured = annotated ? groupsOf(main, spec) : {nodes: [], groups: []}, prepared = [];
|
|
72
|
+
for (const [first, last] of captured.groups) {
|
|
73
|
+
const group = paper.createElement('div');
|
|
74
|
+
group.setAttribute('data-rapier-published-ink', '');
|
|
75
|
+
group.style.cssText = 'display:flow-root;position:relative;width:100%;margin:0;padding:0;border:0;overflow:visible';
|
|
76
|
+
main.insertBefore(group, captured.nodes[first]);
|
|
77
|
+
for (let index = first; index <= last; index++) group.appendChild(captured.nodes[index]);
|
|
78
|
+
// A fixed group cannot grow after disclosure. Show each marked passage in full.
|
|
79
|
+
for (const span of group.querySelectorAll(MARK)) {
|
|
80
|
+
for (let parent = span.parentElement; parent && parent !== group; parent = parent.parentElement)
|
|
81
|
+
if (parent.localName === 'details') parent.open = true;
|
|
82
|
+
}
|
|
83
|
+
for (const summary of group.querySelectorAll('summary')) summary.setAttribute('inert', '');
|
|
84
|
+
prepared.push(group);
|
|
85
|
+
}
|
|
86
|
+
await wait(Promise.all([...main.querySelectorAll('img')].map(async image => {
|
|
87
|
+
image.loading = 'eager';
|
|
88
|
+
try { await image.decode(); }
|
|
89
|
+
catch (_) { throw failure('an embedded picture could not be decoded.'); }
|
|
90
|
+
})));
|
|
91
|
+
// The captured CSS supplies the faces; validate them before measuring typography or ink.
|
|
92
|
+
main.getBoundingClientRect();
|
|
93
|
+
await wait(waitForFonts(main, {settings, css, fontCss, install: false}));
|
|
94
|
+
if (paper.fonts?.ready) await wait(paper.fonts.ready);
|
|
95
|
+
await pause(work);
|
|
96
|
+
if (refined) applyTypography(main, {all: true});
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const group of prepared) {
|
|
99
|
+
check(work);
|
|
100
|
+
draw.drawInk(group, {spec, ink, place: 'start'});
|
|
101
|
+
const box = group.getBoundingClientRect();
|
|
102
|
+
if (!(box.width > 0 && box.height > 0)) throw failure('an annotation has no measurable words.');
|
|
103
|
+
const svg = paper.createElementNS(SVG, 'svg'), content = paper.createElementNS(SVG, 'foreignObject');
|
|
104
|
+
svg.setAttribute('viewBox', '0 0 ' + box.width + ' ' + box.height);
|
|
105
|
+
svg.setAttribute('width', String(box.width)); svg.setAttribute('height', String(box.height));
|
|
106
|
+
svg.setAttribute('role', 'group'); svg.setAttribute('data-rapier-published-ink', '');
|
|
107
|
+
svg.style.cssText = 'display:block;width:100%;max-width:' + box.width + 'px;height:auto;margin:0;padding:0;overflow:visible';
|
|
108
|
+
content.setAttribute('width', String(box.width)); content.setAttribute('height', String(box.height));
|
|
109
|
+
content.style.overflow = 'visible';
|
|
110
|
+
group.setAttribute('xmlns', XHTML);
|
|
111
|
+
group.style.width = box.width + 'px';
|
|
112
|
+
group.style.maxWidth = 'none';
|
|
113
|
+
content.appendChild(group.cloneNode(true)); svg.appendChild(content);
|
|
114
|
+
results.push(svg);
|
|
115
|
+
await pause(work);
|
|
116
|
+
}
|
|
117
|
+
// Keep the measured typography outside annotated groups too. The caller's detached
|
|
118
|
+
// projection changes only after every resource and group has succeeded.
|
|
119
|
+
for (const [index, group] of prepared.entries()) group.replaceWith(results[index]);
|
|
120
|
+
const projected = doc.createDocumentFragment();
|
|
121
|
+
for (const node of main.childNodes) {
|
|
122
|
+
projected.appendChild(doc.importNode(node, true));
|
|
123
|
+
await pause(work);
|
|
124
|
+
}
|
|
125
|
+
check(work);
|
|
126
|
+
root.replaceChildren(projected);
|
|
127
|
+
} finally {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
work?.signal?.removeEventListener('abort', abort);
|
|
130
|
+
frame.remove();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {htmlPlaceholder, mountHtmlBlocks, htmlImageRecord, htmlLayoutEdit} from '../spec/html-blocks.mjs';
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
2
3
|
import {finish, finishAsync, cloneTree, cleanTree, serializeTree, htmlBoundary, pause, check} from './render-work.mjs';
|
|
3
4
|
const RAPIER_TABLE_CAPTION_PREFIX_RE = /^(?:Table:|:) /;
|
|
@@ -56,6 +57,15 @@ function initMarkdownIt() {
|
|
|
56
57
|
return '<span data-md-color="' + hex + '" style="--md-color:' + hex + ';--md-color-dark:' + dark + '">';
|
|
57
58
|
};
|
|
58
59
|
md.renderer.rules.rapier_color_close = () => '</span>';
|
|
60
|
+
md.renderer.rules.rapier_font_open = (tokens, idx) => {
|
|
61
|
+
const token = tokens[idx], family = token.attrGet('data-md-font'), css = globalThis.RapierMarkdownSpec.fontFamilyCss(family);
|
|
62
|
+
if (!css) return '<span>';
|
|
63
|
+
const original = token.attrGet('data-md-font-open');
|
|
64
|
+
return '<span data-md-font="' + md.utils.escapeHtml(family) + '"'
|
|
65
|
+
+ (original ? ' data-md-font-open="' + md.utils.escapeHtml(original) + '"' : '')
|
|
66
|
+
+ ' style="font-family:' + md.utils.escapeHtml(css) + '">';
|
|
67
|
+
};
|
|
68
|
+
md.renderer.rules.rapier_font_close = () => '</span>';
|
|
59
69
|
// Ink: the span carries its opener's body (the grammar's inkOpenBody: a sanitizer drops an attribute holding the
|
|
60
70
|
// comment's close); the ink layer reads it back through the one grammar and draws the stroke over the words' own boxes.
|
|
61
71
|
// The span itself shows nothing.
|
|
@@ -120,6 +130,8 @@ function initMarkdownIt() {
|
|
|
120
130
|
if (!env.rapierPage && typeof globalThis.RapierEmbeddedImages?.imageHtml === 'function' &&
|
|
121
131
|
(_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
|
|
122
132
|
reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src);
|
|
133
|
+
const drawingAlt = typeof rawAlt === 'string' && alt === '' && asset?.codec === 'image/svg+xml' ? globalThis.RapierImageAssets.drawingImageAlt(src) : null;
|
|
134
|
+
const shownAlt = drawingAlt ?? alt;
|
|
123
135
|
let loadable = false;
|
|
124
136
|
try { loadable = !!src.trim() && /^(?:https?|file|blob|data):$/.test(new URL(src, globalThis.document?.baseURI || globalThis.location?.href || 'https://rapier.invalid/').protocol); } catch (_) {}
|
|
125
137
|
if (!loadable) {
|
|
@@ -139,14 +151,14 @@ function initMarkdownIt() {
|
|
|
139
151
|
const attrs = [
|
|
140
152
|
'data-rapier-markdown-image=""',
|
|
141
153
|
'src="' + md.utils.escapeHtml(src) + '"',
|
|
142
|
-
'alt="' + md.utils.escapeHtml(
|
|
154
|
+
'alt="' + md.utils.escapeHtml(shownAlt) + '"',
|
|
143
155
|
'loading="lazy"',
|
|
144
156
|
'decoding="async"',
|
|
145
157
|
];
|
|
146
158
|
if (token.meta?.mdImage?.source && token.meta.mdImage.source.length <= 4096)
|
|
147
159
|
attrs.push('data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '"');
|
|
148
160
|
if (title) attrs.push('title="' + md.utils.escapeHtml(title) + '"');
|
|
149
|
-
if (size && !token.meta?.mdLayout?.layout?.width) attrs.push('data-rapier-image-alt-source="' + md.utils.escapeHtml(rawAlt) + '"');
|
|
161
|
+
if (shownAlt !== alt || size && !token.meta?.mdLayout?.layout?.width) attrs.push('data-rapier-image-alt-source="' + md.utils.escapeHtml(rawAlt) + '"');
|
|
150
162
|
if (layout) attrs.push('data-rapier-image-layout="' + md.utils.escapeHtml(encodeURIComponent(layout)) + '"');
|
|
151
163
|
return '<img ' + attrs.join(' ') + _rapierImageSizeAttributes(size, layout) + '>';
|
|
152
164
|
};
|
|
@@ -186,7 +198,8 @@ function initMarkdownIt() {
|
|
|
186
198
|
return key.startsWith('data-') || key === 'for' || key === 'id' && _rapierChromeOwnsId(attributeText(value)) ? '' : whole;
|
|
187
199
|
}) : tag;
|
|
188
200
|
md.renderer.rules.html_inline = (tokens, idx) => cleanTag(tokens[idx].content);
|
|
189
|
-
md.renderer.rules.html_block = (tokens, idx) => _rapierRawHtmlFragment(tokens[idx].content,
|
|
201
|
+
md.renderer.rules.html_block = (tokens, idx, _options, env) => _rapierRawHtmlFragment(tokens[idx].content,
|
|
202
|
+
tag => env?.rapierDocumentSpace ? cleanTag(tag).replace(/^<([A-Za-z][A-Za-z0-9:-]*)(?=[\s/>])/, '<$1 data-md-html-block="' + idx + '"') : cleanTag(tag), md);
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
const _ulOpenDefault = md.renderer.rules.bullet_list_open
|
|
@@ -263,7 +276,8 @@ function initMarkdownIt() {
|
|
|
263
276
|
token.attrSet('data-rapier-fence', token.markup || '```');
|
|
264
277
|
token.attrSet('data-rapier-fence-info', token.info == null ? '' : String(token.info));
|
|
265
278
|
const lang = String(token.info || '').trim().split(/\s+/)[0].toLowerCase();
|
|
266
|
-
if (lang
|
|
279
|
+
if (lang === 'html') {const block = htmlPlaceholder(token, md.utils.escapeHtml); if (block) return block;}
|
|
280
|
+
if (lang !== 'mermaid') return _fenceDefault(tokens, idx, opts, env, self);
|
|
267
281
|
const src = String(token.content || '').replace(/\n$/, '');
|
|
268
282
|
const fence = md.utils.escapeHtml(token.markup || '```');
|
|
269
283
|
const info = token.info == null ? '' : String(token.info);
|
|
@@ -278,10 +292,11 @@ function initMarkdownIt() {
|
|
|
278
292
|
'<p class="diagram-reason">Mermaid block may be missing its closing <code>' + fence +
|
|
279
293
|
'</code> fence. Text below is still inside the block.</p>' + sourceHtml + '</figure>\n';
|
|
280
294
|
}
|
|
281
|
-
const
|
|
282
|
-
const
|
|
295
|
+
const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
|
|
296
|
+
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true ||
|
|
297
|
+
provider?.status === 'ready' && globalThis.RapierDiagrams?.parseDiagram(src).ok === true;
|
|
283
298
|
const ready = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
|
|
284
|
-
if (!ready && !_diagramPromptedOnce) {
|
|
299
|
+
if (!ready && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
285
300
|
_diagramPromptedOnce = true;
|
|
286
301
|
try { _rapierUiDiagram.request(); } catch (_) {}
|
|
287
302
|
}
|
|
@@ -316,13 +331,13 @@ function initMarkdownIt() {
|
|
|
316
331
|
|
|
317
332
|
{
|
|
318
333
|
let _mathPromptedOnce = false;
|
|
319
|
-
const _renderMath = (src, displayMode,
|
|
334
|
+
const _renderMath = (src, displayMode, env) => {
|
|
320
335
|
const marker = displayMode ? '$$' : '$';
|
|
321
336
|
const source = marker + src + marker, literal = md.utils.escapeHtml(source);
|
|
322
|
-
if (
|
|
323
|
-
const mathProvider = _rapierProviders.math;
|
|
337
|
+
if (env.rapierPage) return '<span data-rapier-math-source="">' + literal + '</span>';
|
|
338
|
+
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
324
339
|
if (!mathProvider || mathProvider.status !== 'ready' || typeof mathProvider.renderToString !== 'function') {
|
|
325
|
-
if (!_mathPromptedOnce) {
|
|
340
|
+
if (env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
326
341
|
_mathPromptedOnce = true;
|
|
327
342
|
try { _rapierUiMath.request(); } catch (_) {}
|
|
328
343
|
}
|
|
@@ -336,8 +351,8 @@ function initMarkdownIt() {
|
|
|
336
351
|
}
|
|
337
352
|
catch (_) { return '<span data-rapier-math-source="">' + literal + '</span>'; }
|
|
338
353
|
};
|
|
339
|
-
md.renderer.rules.math_inline = (tokens, idx, _opts, env) => _renderMath(tokens[idx].content, false, env
|
|
340
|
-
md.renderer.rules.math_block = (tokens, idx, _opts, env) => wrapDisplayMath(_renderMath(tokens[idx].content, true, env
|
|
354
|
+
md.renderer.rules.math_inline = (tokens, idx, _opts, env) => _renderMath(tokens[idx].content, false, env);
|
|
355
|
+
md.renderer.rules.math_block = (tokens, idx, _opts, env) => wrapDisplayMath(_renderMath(tokens[idx].content, true, env)) + '\n';
|
|
341
356
|
}
|
|
342
357
|
globalThis.RapierMarkdownLayout.installMarkdownLayout(md);
|
|
343
358
|
globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
|
|
@@ -361,7 +376,11 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
361
376
|
}
|
|
362
377
|
|
|
363
378
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
364
|
-
|
|
379
|
+
// Source-semantic output is independent of installed rendering providers. Complete
|
|
380
|
+
// artifacts keep the default provider path and materialize their visual content.
|
|
381
|
+
const env = {...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
382
|
+
env.rapierDocumentSpace = !!globalThis.RapierMarkdownSpec?.readDocumentSettings?.(canonical)?.space;
|
|
383
|
+
if (env.rapierDocumentSpace) root.setAttribute('data-md-space', '');
|
|
365
384
|
let rendered = md
|
|
366
385
|
? md.render(markdown, env)
|
|
367
386
|
: '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
@@ -396,8 +415,10 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
396
415
|
const fallbackEnv = options.page ? {rapierPage: true} : {};
|
|
397
416
|
const parsed = options.parsed || (runtime.parseAsync ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv});
|
|
398
417
|
const {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
|
|
418
|
+
env.rapierDocumentSpace = !!globalThis.RapierMarkdownSpec?.readDocumentSettings?.(canonical)?.space;
|
|
399
419
|
const root = document.createElement('div'), part = document.createElement('div'), boundary = htmlBoundary();
|
|
400
420
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
421
|
+
if (env.rapierDocumentSpace) root.setAttribute('data-md-space', '');
|
|
401
422
|
let html = '', began = performance.now();
|
|
402
423
|
const append = () => {
|
|
403
424
|
if (md.renderer.rapierTransformHtml) html = md.renderer.rapierTransformHtml(html);
|
|
@@ -1075,6 +1096,10 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1075
1096
|
math.replaceWith(code);
|
|
1076
1097
|
}
|
|
1077
1098
|
for (const math of root.querySelectorAll('math')) { yield;
|
|
1099
|
+
if (keepLayoutData) {
|
|
1100
|
+
for (const element of [math, ...math.querySelectorAll('*')]) nativeElements.add(element);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1078
1103
|
const code = document.createElement('code');
|
|
1079
1104
|
code.textContent = String(math.textContent || '').trim() || '[equation]';
|
|
1080
1105
|
math.replaceWith(code);
|
|
@@ -1093,7 +1118,7 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1093
1118
|
|
|
1094
1119
|
for (const item of root.querySelectorAll('.table-scroll-wrap,.math-display-wrap')) { yield; _rapierUnwrapElement(item); }
|
|
1095
1120
|
|
|
1096
|
-
for (const item of root.querySelectorAll('pre > code')) { yield; _rapierNormalizeCodeElement(item); }
|
|
1121
|
+
if (!keepLayoutData) for (const item of root.querySelectorAll('pre > code')) { yield; _rapierNormalizeCodeElement(item); }
|
|
1097
1122
|
|
|
1098
1123
|
for (const svg of root.querySelectorAll('svg')) { yield;
|
|
1099
1124
|
if (nativeElements.has(svg)) continue;
|
|
@@ -1106,6 +1131,7 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1106
1131
|
if (runtime.prefixAnchorsSteps) yield* runtime.prefixAnchorsSteps(root, opts.baseName);
|
|
1107
1132
|
else _rapierPrefixPortableAnchors(root, opts.baseName);
|
|
1108
1133
|
|
|
1134
|
+
const refinedProse = [];
|
|
1109
1135
|
for (const element of root.querySelectorAll('*')) { yield;
|
|
1110
1136
|
if (nativeElements.has(element)) continue;
|
|
1111
1137
|
const lang = element.tagName === 'CODE' ? _rapierLanguageClass(element) : '';
|
|
@@ -1113,14 +1139,24 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1113
1139
|
// separator and return-link spacing before it compares source-owned review notes.
|
|
1114
1140
|
const footnoteRole = keepLayoutData ? ['footnote-backref', 'footnotes-sep']
|
|
1115
1141
|
.filter(name => element.classList.contains(name)).join(' ') : '';
|
|
1142
|
+
const captionRole = keepLayoutData ? ['rapier-table-caption', 'rapier-figure-caption']
|
|
1143
|
+
.filter(name => element.classList.contains(name)).join(' ') : '';
|
|
1116
1144
|
const columnAlignment = /^(TH|TD)$/.test(element.tagName) && /^(left|center|right)$/.test(element.style.textAlign)
|
|
1117
1145
|
? element.style.textAlign : '';
|
|
1118
|
-
const
|
|
1146
|
+
const paragraphLayout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-md-layout'));
|
|
1147
|
+
const alignment = paragraphLayout?.align || columnAlignment;
|
|
1119
1148
|
const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
|
|
1120
1149
|
const measurements = keepLayoutData && /^(?:P|H[1-6]|LI|OL|UL)$/.test(element.tagName)
|
|
1121
1150
|
? ['text-indent', 'margin-inline-start'].map(property => [property, element.style.getPropertyValue(property)]) : [];
|
|
1151
|
+
if (keepLayoutData && element.closest('pre')) for (const property of ['color', 'font-weight', 'font-style'])
|
|
1152
|
+
measurements.push([property, element.style.getPropertyValue(property)]);
|
|
1153
|
+
if (keepLayoutData && element.tagName === 'BLOCKQUOTE')
|
|
1154
|
+
measurements.push(['border-inline-start-color', element.style.borderInlineStartColor]);
|
|
1155
|
+
const runTypography = keepLayoutData ? ['font-family', 'font-variant-caps'].map(property => [property, element.style.getPropertyValue(property)]) : [];
|
|
1122
1156
|
|
|
1123
1157
|
const colorHex = element.tagName === 'SPAN' ? element.getAttribute('data-md-color') : null;
|
|
1158
|
+
const fontFamily = element.tagName === 'SPAN' ? element.getAttribute('data-md-font') : null;
|
|
1159
|
+
const fontCss = fontFamily ? globalThis.RapierMarkdownSpec.fontFamilyCss(fontFamily) : '';
|
|
1124
1160
|
|
|
1125
1161
|
const highlightHex = element.tagName === 'MARK'
|
|
1126
1162
|
? (RAPIER_PORTABLE_HIGHLIGHT_HEX[String(element.getAttribute('data-rapier-highlight') || '').toLowerCase()] || RAPIER_PORTABLE_HIGHLIGHT_HEX.yellow)
|
|
@@ -1131,7 +1167,9 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1131
1167
|
const isPageBreak = element.hasAttribute('data-md-break');
|
|
1132
1168
|
for (const attribute of Array.from(element.attributes)) { yield;
|
|
1133
1169
|
const name = attribute.name.toLowerCase();
|
|
1134
|
-
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink'
|
|
1170
|
+
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink'
|
|
1171
|
+
|| name === 'data-md-first-line-end' || name === 'data-md-initial-font' || name === 'data-md-initial-size'
|
|
1172
|
+
|| name === 'data-md-picture-cap-height' || name === 'data-md-picture-cap-top')) continue;
|
|
1135
1173
|
if (name === 'class' || name === 'style' || name.startsWith('data-')
|
|
1136
1174
|
|| name === 'contenteditable' || name === 'spellcheck'
|
|
1137
1175
|
|| name === 'tabindex' || name === 'disabled' || name === 'aria-disabled') {
|
|
@@ -1139,15 +1177,23 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1139
1177
|
}
|
|
1140
1178
|
}
|
|
1141
1179
|
if (footnoteRole) element.className = footnoteRole;
|
|
1180
|
+
if (captionRole) element.className = captionRole;
|
|
1142
1181
|
if (lang) element.className = 'language-' + lang;
|
|
1143
1182
|
for (const [property, value] of measurements) if (value) element.style.setProperty(property, value);
|
|
1183
|
+
for (const [property, value] of runTypography) if (value) element.style.setProperty(property, value);
|
|
1144
1184
|
if (alignment) element.style.textAlign = alignment;
|
|
1185
|
+
if (!keepLayoutData && element.tagName === 'P' && !element.closest('table,pre')) {
|
|
1186
|
+
if (paragraphLayout?.hyphens === 'auto') { element.style.hyphens = 'auto'; element.style.textWrap = 'pretty'; }
|
|
1187
|
+
if (paragraphLayout?.hanging === 'on') element.style.setProperty('hanging-punctuation', 'first allow-end');
|
|
1188
|
+
if (paragraphLayout?.hyphens || paragraphLayout?.hanging || paragraphLayout?.caps || paragraphLayout?.lines) refinedProse.push(element);
|
|
1189
|
+
}
|
|
1145
1190
|
if (imageLayout && !keepLayoutData) {
|
|
1146
1191
|
const pictureStyle = globalThis.RapierMarkdownLayout.imageStyle(imageLayout);
|
|
1147
1192
|
if (imageLayout.width != null) element.removeAttribute('width');
|
|
1148
1193
|
if (pictureStyle) element.style.cssText = pictureStyle;
|
|
1149
1194
|
}
|
|
1150
1195
|
if (colorHex && /^#[0-9a-f]{6}$/i.test(colorHex)) element.style.color = colorHex.toLowerCase();
|
|
1196
|
+
if (fontCss) element.style.fontFamily = fontCss;
|
|
1151
1197
|
if (highlightHex) element.style.backgroundColor = highlightHex;
|
|
1152
1198
|
if (isTableCaption) { element.style.fontStyle = 'italic'; element.style.color = '#555555'; }
|
|
1153
1199
|
|
|
@@ -1157,6 +1203,12 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1157
1203
|
element.replaceWith(p);
|
|
1158
1204
|
}
|
|
1159
1205
|
}
|
|
1206
|
+
for (const paragraph of refinedProse) for (const literal of paragraph.querySelectorAll('code,kbd,samp,math')) { yield;
|
|
1207
|
+
literal.style.setProperty('hyphens', 'none');
|
|
1208
|
+
literal.style.setProperty('text-justify', 'none');
|
|
1209
|
+
literal.style.setProperty('font-variant-caps', 'normal');
|
|
1210
|
+
literal.style.setProperty('hanging-punctuation', 'none');
|
|
1211
|
+
}
|
|
1160
1212
|
|
|
1161
1213
|
return yield* cleanTree(root, sanitizeRapierHtml);
|
|
1162
1214
|
}
|
|
@@ -1441,3 +1493,5 @@ function _rapierBidiStrong(cp) {
|
|
|
1441
1493
|
return {_rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierUnwrapElement, _rapierBidiStrong, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
1442
1494
|
}
|
|
1443
1495
|
export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};
|
|
1496
|
+
|
|
1497
|
+
export {mountHtmlBlocks, htmlImageRecord, htmlLayoutEdit};
|