rapier-markdown-kit 1.2.8 → 1.2.9
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/agent/will.mjs +0 -6
- package/dist/images/gif.mjs +0 -3
- package/dist/images/playback.mjs +0 -10
- package/dist/kit/assets.mjs +0 -1
- package/dist/kit/conformance/measurer.mjs +0 -0
- package/dist/kit/conformance/run.mjs +1 -8
- package/dist/kit/export-fonts.mjs +0 -14
- package/dist/kit/ledger/authored-origin.mjs +0 -5
- package/dist/kit/ledger/authorship.mjs +0 -2
- package/dist/kit/ledger/carried.mjs +4 -7
- package/dist/kit/ledger/data.mjs +0 -8
- package/dist/kit/ledger/format.mjs +0 -25
- package/dist/kit/ledger/hash.mjs +11 -7
- package/dist/kit/ledger/history.mjs +10 -29
- package/dist/kit/ledger/inverse-source.mjs +0 -46
- package/dist/kit/ledger/journal-records.mjs +24 -56
- package/dist/kit/ledger/merge.mjs +0 -27
- package/dist/kit/ledger/text-merge.mjs +0 -20
- package/dist/kit/ledger/transport.mjs +0 -47
- package/dist/kit/ledger.mjs +0 -4
- package/dist/kit/markdown-it.mjs +0 -3
- package/dist/kit/pdf-glyphs.mjs +1 -10
- package/dist/kit/pdf-source.mjs +5 -5
- package/dist/kit/pdf-vectors.mjs +0 -3
- package/dist/kit/print-capitals.mjs +0 -4
- package/dist/kit/render-entry.mjs +0 -1
- package/dist/kit/render-ink.mjs +0 -5
- package/dist/kit/render-markdown.mjs +12 -117
- package/dist/kit/render-print.mjs +1 -47
- package/dist/kit/render-sanitize.mjs +7 -59
- package/dist/kit/render-styles.mjs +0 -7
- package/dist/kit/render-work.mjs +0 -12
- package/dist/kit/render.mjs +18 -173
- package/dist/layout/hyphenation.mjs +0 -23
- package/dist/layout/line-plan.mjs +0 -20
- package/dist/layout/model.mjs +0 -55
- package/dist/layout/paragraph-fit.mjs +50 -60
- package/dist/layout/preparation.mjs +23 -56
- package/dist/layout/prepared-paragraph.mjs +0 -25
- package/dist/layout/typography.mjs +103 -41
- package/dist/packages/rapier-diff/concerns.mjs +0 -16
- package/dist/packages/rapier-diff/diff.mjs +0 -7
- package/dist/packages/rapier-diff/index.mjs +0 -1
- package/dist/packages/rapier-diff/matching.mjs +0 -19
- package/dist/packages/rapier-diff/merge.mjs +0 -9
- package/dist/packages/rapier-diff/moves.mjs +0 -7
- package/dist/packages/rapier-diff/structure.mjs +0 -2
- package/dist/packages/rapier-diff/tokens.mjs +0 -6
- package/dist/packages/rapier-diff/walk.mjs +0 -12
- package/dist/spec/document-fonts.mjs +0 -9
- package/dist/spec/document-settings.mjs +4 -23
- package/dist/spec/frontmatter.mjs +0 -1
- package/dist/spec/html-blocks.mjs +0 -36
- package/dist/spec/md-assets.mjs +2 -61
- package/dist/spec/md-layout.mjs +0 -34
- package/dist/spec/md-marks.mjs +0 -51
- package/dist/spec/md-source.mjs +0 -16
- package/dist/spec/word-sidecar.mjs +0 -2
- package/package.json +1 -1
- package/style.css +32 -16
package/dist/kit/render-ink.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Published typography is measured once; an annotation keeps its words and paths in one inert, scalable group.
|
|
3
2
|
import {check, pause} from './render-work.mjs';
|
|
4
3
|
import {apply as applyTypography} from '../layout/typography.mjs';
|
|
5
4
|
import {documentLanguage} from '../spec/document-settings.mjs';
|
|
@@ -75,7 +74,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
|
|
|
75
74
|
group.style.cssText = 'display:flow-root;position:relative;width:100%;margin:0;padding:0;border:0;overflow:visible';
|
|
76
75
|
main.insertBefore(group, captured.nodes[first]);
|
|
77
76
|
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
77
|
for (const span of group.querySelectorAll(MARK)) {
|
|
80
78
|
for (let parent = span.parentElement; parent && parent !== group; parent = parent.parentElement)
|
|
81
79
|
if (parent.localName === 'details') parent.open = true;
|
|
@@ -88,7 +86,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
|
|
|
88
86
|
try { await image.decode(); }
|
|
89
87
|
catch (_) { throw failure('an embedded picture could not be decoded.'); }
|
|
90
88
|
})));
|
|
91
|
-
// The captured CSS supplies the faces; validate them before measuring typography or ink.
|
|
92
89
|
main.getBoundingClientRect();
|
|
93
90
|
await wait(waitForFonts(main, {settings, css, fontCss, install: false}));
|
|
94
91
|
if (paper.fonts?.ready) await wait(paper.fonts.ready);
|
|
@@ -114,8 +111,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
|
|
|
114
111
|
results.push(svg);
|
|
115
112
|
await pause(work);
|
|
116
113
|
}
|
|
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
114
|
for (const [index, group] of prepared.entries()) group.replaceWith(results[index]);
|
|
120
115
|
const projected = doc.createDocumentFragment();
|
|
121
116
|
for (const node of main.childNodes) {
|
|
@@ -6,17 +6,15 @@ const RAPIER_PORTABLE_HIGHLIGHT_HEX = Object.freeze({
|
|
|
6
6
|
green: '#35c57a', red: '#f05a62', blue: '#4f91f7', yellow: '#e6b91e', purple: '#9b6dea',
|
|
7
7
|
});
|
|
8
8
|
const _RAPIER_RTL_SCRIPT = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}\p{Script=Adlam}\p{Script=Phoenician}\p{Script=Imperial_Aramaic}\p{Script=Palmyrene}\p{Script=Nabataean}\p{Script=Hatran}\p{Script=Old_South_Arabian}\p{Script=Old_North_Arabian}\p{Script=Mende_Kikakui}\p{Script=Old_Hungarian}\p{Script=Old_Sogdian}\p{Script=Sogdian}\p{Script=Elymaic}\p{Script=Chorasmian}\p{Script=Yezidi}\p{Script=Old_Uyghur}\p{Script=Hanifi_Rohingya}]/u;
|
|
9
|
-
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
10
9
|
function createMarkdownRenderer(runtime) {
|
|
11
10
|
const {Node, _rapierPrefixPortableAnchors, RAPIER_HIGHLIGHT_COLOR_BY_MARKER, RAPIER_MARKDOWN_SPEC, RAPIER_RENDERED_HEADING_SELECTOR, _rapierApplyMarkdownSpec, _rapierChromeOwnsId, _rapierCodeHtml, _rapierDeriveDarkColor, _rapierDormantHeadings, _rapierEmbedAssetSource, _rapierHeadingSlugBase, _rapierNextHeadingSlug, _rapierHighlightAdmitted, _rapierInstallMarkdownMath, _rapierMarkdownPreview, _rapierPlainLayout, _rapierProviders, _rapierRemoteContent, _rapierRemoteImagePlaceholder, _rapierRemoteSubresourceOrigin, _rapierRenderedAnchorOriginalIds, _rapierSourceCharEscaped, _rapierSplitOpeningFrontmatter, _rapierUiDiagram, _rapierUiMath, document, escapeRapierHtmlText, globalThis, sanitizeRapierHtml, window} = runtime;
|
|
12
11
|
let md = runtime.md;
|
|
13
12
|
function initMarkdownIt() {
|
|
14
13
|
if (typeof window.markdownit !== 'function') throw new Error('markdownit');
|
|
15
|
-
if (typeof _rapierMarkdownPreview === 'function') globalThis.rapierRenderPreview = _rapierMarkdownPreview;
|
|
14
|
+
if (typeof _rapierMarkdownPreview === 'function') globalThis.rapierRenderPreview = _rapierMarkdownPreview;
|
|
16
15
|
|
|
17
16
|
md = window.markdownit({
|
|
18
17
|
...RAPIER_MARKDOWN_SPEC.options,
|
|
19
|
-
// The first paint is coloured: the CPU reading, synchronously, for every admitted fence.
|
|
20
18
|
highlight: (code, lang) => lang && _rapierHighlightAdmitted(code) ? _rapierCodeHtml(code, lang) : '',
|
|
21
19
|
});
|
|
22
20
|
|
|
@@ -50,8 +48,6 @@ function initMarkdownIt() {
|
|
|
50
48
|
return markOpenDefault(tokens, idx, opts, env, self);
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
// Every authored spelling lowers to the same admitted style facts. Original delimiters are
|
|
54
|
-
// transient edit-projection data; only the source owner can write them back.
|
|
55
51
|
const renderTypography = (tokens, index) => {
|
|
56
52
|
const typed = globalThis.RapierMarkdownSpec.typography, held = tokens[index].meta?.typography;
|
|
57
53
|
if (!held) return '<span>';
|
|
@@ -74,13 +70,8 @@ function initMarkdownIt() {
|
|
|
74
70
|
};
|
|
75
71
|
md.renderer.rules.rapier_typography_open = renderTypography;
|
|
76
72
|
md.renderer.rules.rapier_typography_close = () => '</span>';
|
|
77
|
-
// Ink: the span carries its opener's body (the grammar's inkOpenBody: a sanitizer drops an attribute holding the
|
|
78
|
-
// comment's close); the ink layer reads it back through the one grammar and draws the stroke over the words' own boxes.
|
|
79
|
-
// The span itself shows nothing.
|
|
80
73
|
md.renderer.rules.rapier_ink_open = (tokens, idx) => {
|
|
81
74
|
const body = String(tokens[idx].attrGet('data-rapier-ink') || '');
|
|
82
|
-
// The dark-page colour on the span, as the colour span's: an exported page draws its ink with no engine to ask (the
|
|
83
|
-
// rules run on this thread alone; the Worker plants the parser, not the renderer).
|
|
84
75
|
const spec = globalThis.RapierMarkdownSpec;
|
|
85
76
|
const mark = spec && typeof spec.parseInkBody === 'function' ? spec.parseInkBody(body) : null;
|
|
86
77
|
return '<span class="rapier-ink-mark" data-rapier-ink="' + md.utils.escapeHtml(body) + '"'
|
|
@@ -98,10 +89,6 @@ function initMarkdownIt() {
|
|
|
98
89
|
};
|
|
99
90
|
md.renderer.rules.softbreak = () =>
|
|
100
91
|
sourceToken('\n', '\u2060', 'rapier-source-token--softbreak');
|
|
101
|
-
// A backslash escape the edit surface's writer would not write itself (`https\://`, `www\.`, `a\@b`: an unlinked
|
|
102
|
-
// address, or a person's own escape) is carried through the edit projection as a source token, as a soft break and
|
|
103
|
-
// an emoji are; the parser joins escapes into plain text before rendering, so they are kept apart first. The
|
|
104
|
-
// characters the writer escapes on its own (\ * ` [ ] _) stay the writer's.
|
|
105
92
|
md.core.ruler.before('text_join', 'rapier_escape_token', state => {
|
|
106
93
|
for (const token of state.tokens) {
|
|
107
94
|
if (token.type !== 'inline' || !token.children) continue;
|
|
@@ -134,7 +121,6 @@ function initMarkdownIt() {
|
|
|
134
121
|
const alt = suffix && semanticAlt.endsWith(suffix) ? semanticAlt.slice(0, -suffix.length) : semanticAlt;
|
|
135
122
|
const layout = token.meta?.mdLayout?.imageOnly ? token.meta.mdLayout.marker : '';
|
|
136
123
|
const asset = globalThis.RapierImageAssets.dataImage(src), reference = token.meta?.mdImage?.reference;
|
|
137
|
-
// A connected host's own asset; a host without one (the server) supplies no port.
|
|
138
124
|
if (!env.rapierPage && typeof globalThis.RapierEmbeddedImages?.imageHtml === 'function' &&
|
|
139
125
|
(_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/gif' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
|
|
140
126
|
reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src, env.rapierImagePresentation !== false);
|
|
@@ -172,8 +158,6 @@ function initMarkdownIt() {
|
|
|
172
158
|
if (title) attrs.push('title="' + md.utils.escapeHtml(title) + '"');
|
|
173
159
|
if (shownAlt !== alt || size && !token.meta?.mdLayout?.layout?.width) attrs.push('data-rapier-image-alt-source="' + md.utils.escapeHtml(rawAlt) + '"');
|
|
174
160
|
if (layout) attrs.push('data-rapier-image-layout="' + md.utils.escapeHtml(encodeURIComponent(layout)) + '"');
|
|
175
|
-
// Portable semantic consumers keep their existing native image attributes. Only
|
|
176
|
-
// live editor/reader presentation needs provisional geometry and its load consumer.
|
|
177
161
|
if (env.rapierImagePresentation === false) return '<img ' + attrs.join(' ') + _rapierImageSizeAttributes(size, layout) + '>';
|
|
178
162
|
const geometry = !env.rapierPage && globalThis.RapierEmbeddedImages?.geometry
|
|
179
163
|
? globalThis.RapierEmbeddedImages.geometry(src) : globalThis.RapierImageAssets.imagePlaceholderDimensions(src);
|
|
@@ -189,17 +173,8 @@ function initMarkdownIt() {
|
|
|
189
173
|
md.renderer.rules.rapier_break = () =>
|
|
190
174
|
'<div class="rapier-page-break" data-md-break="page" contenteditable="false" role="separator" aria-label="Page break"></div>\n';
|
|
191
175
|
|
|
192
|
-
// Raw HTML carries no editor hooks. Keep its fragment boundaries until the complete Markdown
|
|
193
|
-
// markup reaches DOMPurify: sanitizing each HTML token closes containers before their body.
|
|
194
|
-
// The harness runs this installer alone for the parser's acceptance; without the page's sanitizer the tokens pass.
|
|
195
176
|
const inPage = typeof sanitizeRapierHtml === 'function' && typeof _rapierChromeOwnsId === 'function';
|
|
196
|
-
// HTML separates attributes and ends an unquoted value only at ASCII whitespace: JavaScript's \s also takes U+00A0,
|
|
197
|
-
// which would read a quoted value the browser never sees and keep the attributes hidden inside it.
|
|
198
177
|
const rawAttribute = /[\t\n\f\r ]+([A-Za-z_:][A-Za-z0-9_.:-]*)(?:[\t\n\f\r ]*=[\t\n\f\r ]*("[^"]*"|'[^']*'|[^"'=<>`\t\n\f\r ]+))?/g;
|
|
199
|
-
// The id is read as HTML reads it: a numeric character reference (`source-textarea` is `source-textarea`,
|
|
200
|
-
// `s` the same) and the named references that spell a character an id can hold are decoded before the
|
|
201
|
-
// chrome's ids are asked; every other name stays literal (no named reference spells a letter, a digit or a
|
|
202
|
-
// hyphen). A string walk, not a parser: the render pass is the one HTML parser this markup meets.
|
|
203
178
|
const RAW_ID_NAMED = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", lowbar: '_', UnderBar: '_', num: '#', colon: ':',
|
|
204
179
|
period: '.', sol: '/', excl: '!', quest: '?', commat: '@', plus: '+', ast: '*', percnt: '%', dollar: '$', semi: ';',
|
|
205
180
|
equals: '=', comma: ',', lpar: '(', rpar: ')', lsqb: '[', lbrack: '[', rsqb: ']', rbrack: ']', lcub: '{', lbrace: '{',
|
|
@@ -224,7 +199,6 @@ function initMarkdownIt() {
|
|
|
224
199
|
md.renderer.rules.html_inline = (tokens, idx) => cleanTag(tokens[idx].content);
|
|
225
200
|
md.renderer.rules.html_block = (tokens, idx) => _rapierRawHtmlFragment(tokens[idx].content, cleanTag, md);
|
|
226
201
|
}
|
|
227
|
-
// A highlight written in one of the picker's colours is that swatch, as `==🟢x==` is, and follows the theme.
|
|
228
202
|
const inlineHtml = md.renderer.rules.html_inline || ((tokens, idx) => tokens[idx].content);
|
|
229
203
|
md.renderer.rules.html_inline = (tokens, idx, ...rest) => {
|
|
230
204
|
const swatch = /^<mark style="background-color:(#[0-9a-f]{6});?">$/i.exec(tokens[idx].content);
|
|
@@ -306,6 +280,8 @@ function initMarkdownIt() {
|
|
|
306
280
|
token.attrSet('data-rapier-fence', token.markup || '```');
|
|
307
281
|
token.attrSet('data-rapier-fence-info', token.info == null ? '' : String(token.info));
|
|
308
282
|
const lang = String(token.info || '').trim().split(/\s+/)[0].toLowerCase();
|
|
283
|
+
if (lang === 'math' && token.meta?.rapierFenceClosed !== false && token.meta?.mathSource !== undefined)
|
|
284
|
+
return md.renderer.rules.math_block(tokens, idx, opts, env, self);
|
|
309
285
|
if (lang === 'html') {const block = htmlPlaceholder(token, md.utils.escapeHtml); if (block) return block;}
|
|
310
286
|
if (lang !== 'mermaid') return _fenceDefault(tokens, idx, opts, env, self);
|
|
311
287
|
const src = String(token.content || '').replace(/\n$/, '');
|
|
@@ -316,7 +292,6 @@ function initMarkdownIt() {
|
|
|
316
292
|
'" data-rapier-fence-info="' + md.utils.escapeHtml(info) + '">' + escapedSrc + '</code></pre>';
|
|
317
293
|
const encoded = encodeURIComponent(src);
|
|
318
294
|
if (token.meta?.rapierFenceClosed === false) {
|
|
319
|
-
// No render request: even a valid diagram body must not conceal an unfinished fence.
|
|
320
295
|
return '<figure class="diagram-block diagram-block--error" data-diagram-state="error"' +
|
|
321
296
|
' data-diagram-unclosed="" data-diagram-src="' + encoded + '">' +
|
|
322
297
|
'<p class="diagram-reason">Mermaid block may be missing its closing <code>' + fence +
|
|
@@ -363,19 +338,21 @@ function initMarkdownIt() {
|
|
|
363
338
|
const _renderMath = (src, displayMode, env, authored) => {
|
|
364
339
|
const marker = displayMode ? '$$' : '$';
|
|
365
340
|
const source = authored || marker + src + marker, literal = md.utils.escapeHtml(source);
|
|
366
|
-
|
|
341
|
+
let encoded;
|
|
342
|
+
try { encoded = encodeURIComponent(source); }
|
|
343
|
+
catch (error) { if (!(error instanceof URIError)) throw error; }
|
|
344
|
+
const sourceHtml = '<span data-rapier-math-source="' + (encoded || '') + '">' + literal + '</span>';
|
|
345
|
+
if (env.rapierPage) return sourceHtml;
|
|
367
346
|
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
368
347
|
if (!mathProvider || mathProvider.status !== 'ready' || typeof mathProvider.renderToString !== 'function') {
|
|
369
348
|
if (!mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
370
349
|
_mathPromptedOnce = true;
|
|
371
350
|
try { _rapierUiMath.request(); } catch (_) {}
|
|
372
351
|
}
|
|
373
|
-
return '<span class="math-placeholder" data-rapier-math-source="" title="Install the math plug-in in Settings to show this equation.">'
|
|
352
|
+
return '<span class="math-placeholder" data-rapier-math-source="' + (encoded || '') + '" title="Install the math plug-in in Settings to show this equation.">'
|
|
374
353
|
+ literal + '</span>';
|
|
375
354
|
}
|
|
376
355
|
try {
|
|
377
|
-
// The explicit output-only call keeps structured results beside the unchanged SVG
|
|
378
|
-
// islands. MathML never enters editable text projections or source serialization.
|
|
379
356
|
let rendered;
|
|
380
357
|
if (env.rapierMathEquations) {
|
|
381
358
|
const combined = mathProvider.renderWithMathML(src, {displayMode});
|
|
@@ -386,20 +363,13 @@ function initMarkdownIt() {
|
|
|
386
363
|
env.rapierMathEquations.push({source, tex: src, displayMode,
|
|
387
364
|
semantics: combined.semantics, errors: combined.errors});
|
|
388
365
|
} else rendered = mathProvider.renderToString(src, {displayMode});
|
|
389
|
-
|
|
390
|
-
try { encoded = encodeURIComponent(source); }
|
|
391
|
-
catch (error) {
|
|
392
|
-
// An ill-formed UTF-16 source cannot enter the URI-encoded edit marker.
|
|
393
|
-
// Keep the ordinary source-only HTML and the collected refusal diagnostic.
|
|
394
|
-
if (!(error instanceof URIError)) throw error;
|
|
395
|
-
return '<span data-rapier-math-source="">' + literal + '</span>';
|
|
396
|
-
}
|
|
366
|
+
if (encoded === undefined) return sourceHtml;
|
|
397
367
|
return '<span class="math-rendered" data-math-src="'
|
|
398
368
|
+ encoded + '">' + rendered + '</span>';
|
|
399
369
|
}
|
|
400
370
|
catch (error) {
|
|
401
371
|
if (env.rapierMathEquations) throw error;
|
|
402
|
-
return
|
|
372
|
+
return sourceHtml;
|
|
403
373
|
}
|
|
404
374
|
};
|
|
405
375
|
md.renderer.rules.math_inline = (tokens, idx, _opts, env) => _renderMath(tokens[idx].content, false, env, tokens[idx].meta?.mathSource);
|
|
@@ -427,10 +397,6 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
427
397
|
}
|
|
428
398
|
|
|
429
399
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
430
|
-
// Source-semantic output is independent of installed rendering providers. Complete
|
|
431
|
-
// artifacts keep the default provider path and materialize their visual content.
|
|
432
|
-
// A portable projection keeps natural sizing. Live readers opt into the same transient
|
|
433
|
-
// AUTO policy as the editor; neither a saved page nor its source inherits that choice.
|
|
434
400
|
const env = {rapierImagePresentation: options.imagePresentation === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
435
401
|
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
436
402
|
let rendered = md
|
|
@@ -493,9 +459,6 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
493
459
|
}
|
|
494
460
|
}
|
|
495
461
|
if (html) append();
|
|
496
|
-
// Token/environment objects are no longer inputs once their complete HTML
|
|
497
|
-
// is admitted. Drop our references before the document-wide DOM passes;
|
|
498
|
-
// never mutate an optional parsed result retained by another caller.
|
|
499
462
|
parsed = tokens = env = labels = null;
|
|
500
463
|
await finishAsync(_rapierSemanticDecorations(root), work);
|
|
501
464
|
await pause(work, .6);
|
|
@@ -690,9 +653,6 @@ function _rapierImageAltText(value, env = {}) {
|
|
|
690
653
|
return md.renderer.renderInlineAsText(tokens[0]?.children || [], md.options, env);
|
|
691
654
|
}
|
|
692
655
|
|
|
693
|
-
// Intrinsic facts are a presentation input, never an authored width or a resource admission.
|
|
694
|
-
// Contain both edges within the column, sharing a 1.6-column sum-of-edges budget.
|
|
695
|
-
// Reciprocal portraits/landscapes have equal weight; the native-width cap never enlarges art.
|
|
696
656
|
function _rapierImageGeometryFacts(geometry, automatic) {
|
|
697
657
|
const w = Number(geometry?.width), h = Number(geometry?.height);
|
|
698
658
|
const known = Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && Number.isFinite(w / h) && w / h > 0;
|
|
@@ -707,8 +667,6 @@ function _rapierImageGeometryFacts(geometry, automatic) {
|
|
|
707
667
|
return {width, height, styles};
|
|
708
668
|
}
|
|
709
669
|
|
|
710
|
-
// Native loads supply their current element's dimensions synchronously. Embedded images
|
|
711
|
-
// supply an admitted row only after their existing document/resource ticket checks.
|
|
712
670
|
function _rapierApplyImageGeometry(image, geometry) {
|
|
713
671
|
if (image?.tagName !== 'IMG' || image.hasAttribute('data-rapier-html')) return false;
|
|
714
672
|
const mode = image.getAttribute('data-rapier-image-geometry');
|
|
@@ -726,8 +684,6 @@ function _rapierApplyImageGeometry(image, geometry) {
|
|
|
726
684
|
].filter(([name, value]) => image.getAttribute(name) !== value);
|
|
727
685
|
const styles = facts.styles.filter(([name, value]) => (name === 'aspect-ratio'
|
|
728
686
|
? image.style.getPropertyValue(name).replace(/\s/g, '') : image.style.getPropertyValue(name)) !== value);
|
|
729
|
-
// Ordinary mutations revisit mounted images. Settled facts need no source-URL
|
|
730
|
-
// read or parse (which can be the entire inline image), nor any DOM writes.
|
|
731
687
|
if (!attributes.length && !styles.length) return false;
|
|
732
688
|
if (native) {
|
|
733
689
|
const src = image.getAttribute('src'), current = image.currentSrc;
|
|
@@ -742,13 +698,9 @@ function _rapierApplyImageGeometry(image, geometry) {
|
|
|
742
698
|
|
|
743
699
|
function _rapierImageSizeAttributes(size, marker, geometry, automatic = true) {
|
|
744
700
|
const layout = globalThis.RapierMarkdownLayout.parseLayout(marker || '');
|
|
745
|
-
// Plain (rung 0, docs/standard-adoption.md): every picture renders at its natural width, the
|
|
746
|
-
// same as a reader with no layout facts at all -- Obsidian's `![alt|N]` alt-text width (the
|
|
747
|
-
// `size` fallback below) is a different, established convention and is unaffected.
|
|
748
701
|
let style = layout && !_rapierPlainLayout() ? globalThis.RapierMarkdownLayout.imageStyle(layout) : '';
|
|
749
702
|
let attributes = '';
|
|
750
703
|
if (layout?.width != null && style) attributes = ' data-md-image-width="' + layout.width + '"';
|
|
751
|
-
// A fade rides beside any width, the alt-text's own included.
|
|
752
704
|
else if (size) {
|
|
753
705
|
attributes = ' width="' + size.width + '" data-rapier-image-size="' + size.width + '"';
|
|
754
706
|
style = '--md-image-width:' + size.width + 'px' + (style ? ';' + style : '');
|
|
@@ -923,9 +875,6 @@ function* _rapierDisambiguateRenderedAnchorsSteps(root) {
|
|
|
923
875
|
}
|
|
924
876
|
|
|
925
877
|
function _rapierDecodeHash(hash) {
|
|
926
|
-
// A document's own address may carry a heading after it (`#d/<id>/<heading>`): the heading is the
|
|
927
|
-
// anchor, and an address with no heading names nothing to jump to. A view's (`#v/draw`,
|
|
928
|
-
// `#v/notes`) names no heading either (docs/agents.md "The address of a document").
|
|
929
878
|
let raw = String(hash || '').replace(/^#/, '');
|
|
930
879
|
const own = /^[dnv]\/[^/]*(?:\/(.*))?$/.exec(raw);
|
|
931
880
|
if (own) raw = own[1] || '';
|
|
@@ -958,8 +907,6 @@ function* _rapierDecorateJumpListsSteps(root) {
|
|
|
958
907
|
}
|
|
959
908
|
}
|
|
960
909
|
|
|
961
|
-
// Do not retain a live collection on the root that Copy will consume. Some
|
|
962
|
-
// DOM hosts eagerly refresh it on every later removal of a complete unit.
|
|
963
910
|
const first = root.firstElementChild, only = first && !first.nextElementSibling ? first : null;
|
|
964
911
|
if (only && only.tagName === 'P' && /^jump\s+to:?$/i.test(String(only.textContent || '').trim())) {
|
|
965
912
|
only.classList.add('rapier-jump-label-candidate');
|
|
@@ -969,7 +916,6 @@ function* _rapierDecorateJumpListsSteps(root) {
|
|
|
969
916
|
function _rapierMarkFigureCaptions(root) { return finish(_rapierMarkFigureCaptionsSteps(root)); }
|
|
970
917
|
|
|
971
918
|
function* _rapierMarkFigureCaptionsSteps(root) {
|
|
972
|
-
// Same path as a table caption: an ordinary paragraph after the picture, still the source line.
|
|
973
919
|
const figureCaption = /^Figure: /;
|
|
974
920
|
for (const paragraph of root.querySelectorAll('p')) { yield;
|
|
975
921
|
if (!figureCaption.test(paragraph.textContent || '')) continue;
|
|
@@ -1006,11 +952,6 @@ function* _rapierMarkListContinuationsSteps(blocks) {
|
|
|
1006
952
|
function _rapierMarkTableCaptions(root) { return finish(_rapierMarkTableCaptionsSteps(root)); }
|
|
1007
953
|
|
|
1008
954
|
function* _rapierMarkTableCaptionsSteps(root) {
|
|
1009
|
-
// The source convention is untouched: the caption stays an ordinary trailing paragraph
|
|
1010
|
-
// (docs/markdown-standard.md). Only this rendered projection associates it with its table —
|
|
1011
|
-
// via a generated id plus aria-describedby, not a real <caption>, which would have to move the
|
|
1012
|
-
// paragraph inside <table> and break the one-DOM-node-per-source-block correspondence other
|
|
1013
|
-
// passes (heading slugs, portable export) rely on for this same root.
|
|
1014
955
|
const used = new Set(Array.from(root.querySelectorAll('[id]'), element => element.id));
|
|
1015
956
|
let ordinal = 0;
|
|
1016
957
|
for (const table of root.querySelectorAll('table')) { yield;
|
|
@@ -1038,7 +979,6 @@ function _rapierNormalizeCodeElement(code) {
|
|
|
1038
979
|
|
|
1039
980
|
function _rapierLanguageClass(element) {
|
|
1040
981
|
if (!element || !element.classList) return '';
|
|
1041
|
-
// CommonMark's first info word is an identifier, including Unicode and punctuation.
|
|
1042
982
|
const found = Array.from(element.classList).find(name => /^language-.+/i.test(name));
|
|
1043
983
|
return found ? found.slice(9) : '';
|
|
1044
984
|
}
|
|
@@ -1174,28 +1114,15 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1174
1114
|
|
|
1175
1115
|
function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
1176
1116
|
const opts = options || {};
|
|
1177
|
-
// This projection is HTML-shaped (width baked into a CSS `style`, `data-*` stripped) for the
|
|
1178
|
-
// consumers that read HTML -- publishing/fragment export, TXT. DOCX is not an HTML consumer: its
|
|
1179
|
-
// writer (interchange/docx.mjs) reads the layout comment itself, so stripping it first and
|
|
1180
|
-
// handing back CSS only throws the fact away before the writer ever sees it. `keepLayoutData`
|
|
1181
|
-
// leaves the image and text layout records, ink meaning and paragraph alignment exactly as
|
|
1182
|
-
// the semantic root carries them. Ordinary paragraph/list CSS measurements also reach their
|
|
1183
|
-
// native reader; no derived private CSS is needed.
|
|
1184
1117
|
const keepLayoutData = !!opts.keepLayoutData;
|
|
1185
|
-
// Copy owns its freshly rendered, detached root. Other callers retain their
|
|
1186
|
-
// semantic tree and receive the usual independent portable projection.
|
|
1187
1118
|
const root = opts.inPlace ? semanticRoot : yield* cloneTree(semanticRoot), nativeElements = new Set();
|
|
1188
|
-
// A source soft break is a reading space, never the editor's invisible token.
|
|
1189
1119
|
yield* _rapierResolveSoftBreakTokensSteps(root);
|
|
1190
|
-
// Word consumes these source-bearing islands as pictures with TeX alternatives. Keep
|
|
1191
|
-
// their SVG through this projection; the normal export sanitizer still owns admission.
|
|
1192
1120
|
if (opts.keepMath) for (const math of root.querySelectorAll('.math-rendered[data-math-src],.math-placeholder,[data-rapier-math-source]')) {
|
|
1193
1121
|
for (const element of [math, ...math.querySelectorAll('*')]) nativeElements.add(element);
|
|
1194
1122
|
}
|
|
1195
1123
|
if (opts.keepDiagrams) for (const figure of root.querySelectorAll('.diagram-block[data-diagram-native]')) {
|
|
1196
1124
|
const svg = figure.querySelector('.diagram-cache > svg.rapier-native-flowchart');
|
|
1197
1125
|
if (figure.getAttribute('data-diagram-state') === 'ready' && svg) {
|
|
1198
|
-
// A class in authored HTML grants no exemption: only the native materialization above does.
|
|
1199
1126
|
for (const element of [svg, ...svg.querySelectorAll('*')]) nativeElements.add(element);
|
|
1200
1127
|
figure.replaceWith(svg);
|
|
1201
1128
|
}
|
|
@@ -1279,8 +1206,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1279
1206
|
for (const element of root.querySelectorAll('*')) { yield;
|
|
1280
1207
|
if (nativeElements.has(element)) continue;
|
|
1281
1208
|
const lang = element.tagName === 'CODE' ? _rapierLanguageClass(element) : '';
|
|
1282
|
-
// The native note reader needs these transient roles to omit the renderer's
|
|
1283
|
-
// separator and return-link spacing before it compares source-owned review notes.
|
|
1284
1209
|
const footnoteRole = keepLayoutData ? ['footnote-backref', 'footnotes-sep']
|
|
1285
1210
|
.filter(name => element.classList.contains(name)).join(' ') : '';
|
|
1286
1211
|
const captionRole = keepLayoutData ? ['rapier-table-caption', 'rapier-figure-caption']
|
|
@@ -1292,7 +1217,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1292
1217
|
const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
|
|
1293
1218
|
const measurements = keepLayoutData && /^(?:P|H[1-6]|LI|OL|UL)$/.test(element.tagName)
|
|
1294
1219
|
? ['text-indent', 'margin-inline-start'].map(property => [property, element.style.getPropertyValue(property)]) : [];
|
|
1295
|
-
// The native table reader admits automatic hyphenation from ordinary paragraph CSS.
|
|
1296
1220
|
if (keepLayoutData && element.tagName === 'P' && element.closest('table') && element.style.getPropertyValue('hyphens').trim() === 'auto')
|
|
1297
1221
|
measurements.push(['hyphens', 'auto']);
|
|
1298
1222
|
if (keepLayoutData && element.closest('pre')) for (const property of ['color', 'font-weight', 'font-style'])
|
|
@@ -1312,8 +1236,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1312
1236
|
const isPageBreak = element.hasAttribute('data-md-break');
|
|
1313
1237
|
for (const attribute of Array.from(element.attributes)) { yield;
|
|
1314
1238
|
const name = attribute.name.toLowerCase();
|
|
1315
|
-
// Copy reads these inert words from the final admitted nodes, then removes
|
|
1316
|
-
// them before emitting HTML. They grant no rendering or resource authority.
|
|
1317
1239
|
if (opts.heldPictures && /^(?:SPAN|IMG)$/.test(element.tagName) &&
|
|
1318
1240
|
(name === 'data-rapier-remote-src' || name === 'data-rapier-remote-alt')) continue;
|
|
1319
1241
|
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink'
|
|
@@ -1344,8 +1266,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1344
1266
|
}
|
|
1345
1267
|
if (highlightHex) element.style.backgroundColor = highlightHex;
|
|
1346
1268
|
if (isTableCaption) { element.style.fontStyle = 'italic'; element.style.color = '#555555'; }
|
|
1347
|
-
// Already validated facts serialize directly. An inert host's partial CSSOM must
|
|
1348
|
-
// not silently discard a quoted family or round an authored decimal.
|
|
1349
1269
|
const css = typed.style(typography);
|
|
1350
1270
|
if (css) element.setAttribute('style', (element.getAttribute('style') ? element.getAttribute('style') + ';' : '') + css);
|
|
1351
1271
|
|
|
@@ -1370,9 +1290,6 @@ function _rapierPortableHtml(root) {
|
|
|
1370
1290
|
}
|
|
1371
1291
|
async function _rapierPortableHtmlAsync(root, work) { return finishAsync(serializeTree(root, sanitizeRapierHtml, true), work); }
|
|
1372
1292
|
|
|
1373
|
-
// One captured document owns this pipeline and its detached nodes. Global semantic
|
|
1374
|
-
// facts are resolved before projection. Final admission consumes the old tree a
|
|
1375
|
-
// complete fragment at a time; HTML and words then share that admitted tree.
|
|
1376
1293
|
async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
1377
1294
|
const work = options.work;
|
|
1378
1295
|
let semantic;
|
|
@@ -1389,8 +1306,6 @@ async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
|
1389
1306
|
const plain = await finishAsync((function* () {
|
|
1390
1307
|
for (const html of htmlParts(semantic, 16384, true)) {
|
|
1391
1308
|
if (html === null) { yield; continue; }
|
|
1392
|
-
// Reparse the complete transformed unit at the existing final export
|
|
1393
|
-
// boundary. Both formats consume these admitted nodes before release.
|
|
1394
1309
|
admitHtml(part, html, sanitizeRapierHtml, runtime.sanitizeRapierFragment);
|
|
1395
1310
|
yield;
|
|
1396
1311
|
if (words) yield* words.write(part);
|
|
@@ -1408,9 +1323,6 @@ async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
|
1408
1323
|
check(work);
|
|
1409
1324
|
if (!options.formatted) return {plain};
|
|
1410
1325
|
return {plain, html: parts.join('').trim(), withoutPictures: pictures ? async () => {
|
|
1411
|
-
// Only this writer's finished HTML reaches this closure. On refusal,
|
|
1412
|
-
// reconstruct one complete fragment, change pictures to text and admit
|
|
1413
|
-
// that changed output. No full fallback DOM survives the operation.
|
|
1414
1326
|
return finishAsync((function* () {
|
|
1415
1327
|
const output = [], part = document.createElement('div');
|
|
1416
1328
|
try {
|
|
@@ -1441,15 +1353,12 @@ function _rapierNormalizeInlineText(value) {
|
|
|
1441
1353
|
.trim();
|
|
1442
1354
|
}
|
|
1443
1355
|
|
|
1444
|
-
// What a text file says where a picture stands: its words, and its address unless the picture is carried in the document itself.
|
|
1445
1356
|
function _rapierPictureWords(alt, src) {
|
|
1446
1357
|
if (/^data:image\//i.test(src)) return alt ? '[Image: ' + alt + ']' : '[Image]';
|
|
1447
1358
|
if (src) return alt ? '[Image: ' + alt + ' — ' + src + ']' : '[Image: ' + src + ']';
|
|
1448
1359
|
return alt ? '[Image: ' + alt + ']' : '[Image]';
|
|
1449
1360
|
}
|
|
1450
1361
|
|
|
1451
|
-
// Held Markdown and HTML pictures keep their addresses as inert data. Text export reads them before
|
|
1452
|
-
// the portable projection removes editor attributes, without restoring a fetching image source.
|
|
1453
1362
|
function _rapierHeldPicturesAsWords(root) { return finish(_rapierHeldPicturesAsWordsSteps(root)); }
|
|
1454
1363
|
async function _rapierHeldPicturesAsWordsAsync(root, work) { return finishAsync(_rapierHeldPicturesAsWordsSteps(root), work); }
|
|
1455
1364
|
|
|
@@ -1473,7 +1382,6 @@ function* _rapierPortableInlineTextSteps(node, options) {
|
|
|
1473
1382
|
return _rapierPictureWords(String(node.getAttribute('data-rapier-remote-alt') || '').trim(),
|
|
1474
1383
|
String(node.getAttribute('data-rapier-remote-src') || '').trim());
|
|
1475
1384
|
if (tag === 'BR') return '\n';
|
|
1476
|
-
// A task's box is what the person sees of it.
|
|
1477
1385
|
if (tag === 'INPUT') return /^checkbox$/i.test(node.type || '') ? (node.checked || node.hasAttribute('checked') ? '☑ ' : '☐ ') : '';
|
|
1478
1386
|
if (tag === 'IMG') {
|
|
1479
1387
|
if (opts.visible) return '';
|
|
@@ -1510,7 +1418,6 @@ function* _rapierPortableListTextSteps(list, depth, options) {
|
|
|
1510
1418
|
else content.push(yield* _rapierPortableInlineTextSteps(child, opts));
|
|
1511
1419
|
}
|
|
1512
1420
|
const normalized = _rapierNormalizeInlineText(content.join('')), indent = ' '.repeat(level);
|
|
1513
|
-
// A task shows its box where a bullet would be.
|
|
1514
1421
|
const marker = /^[☐☑☒]/.test(normalized) ? '' : ordered ? (counter++) + '. ' : '• ';
|
|
1515
1422
|
const parts = normalized.split('\n');
|
|
1516
1423
|
lines.push(indent + marker + (parts.shift() || ''));
|
|
@@ -1523,12 +1430,11 @@ function* _rapierPortableListTextSteps(list, depth, options) {
|
|
|
1523
1430
|
return lines.join('\n');
|
|
1524
1431
|
}
|
|
1525
1432
|
|
|
1526
|
-
// A table, or a part of one a selection clipped out (a head, a body, a row): its rows.
|
|
1527
1433
|
function _rapierPortableTableText(table, options) { return finish(_rapierPortableTableTextSteps(table, options)); }
|
|
1528
1434
|
|
|
1529
1435
|
function* _rapierPortableTableTextSteps(table, options) {
|
|
1530
1436
|
const opts = options || {}, rows = [];
|
|
1531
|
-
const walker = document.createTreeWalker(table, 1
|
|
1437
|
+
const walker = document.createTreeWalker(table, 1 );
|
|
1532
1438
|
let row = table.tagName === 'TR' ? table : walker.nextNode();
|
|
1533
1439
|
while (row) {
|
|
1534
1440
|
yield;
|
|
@@ -1587,8 +1493,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1587
1493
|
for (let child = parent.firstChild; child; child = child.nextSibling) {
|
|
1588
1494
|
yield;
|
|
1589
1495
|
if (tablePart(child)) {
|
|
1590
|
-
// A clipped table can cross an output fragment. Retain its words,
|
|
1591
|
-
// not detached rows, until the next non-table node closes the block.
|
|
1592
1496
|
if (!current.table) { flush(current); current.table = []; }
|
|
1593
1497
|
current.table.push(yield* _rapierPortableTableTextSteps(child, opts));
|
|
1594
1498
|
continue;
|
|
@@ -1599,7 +1503,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1599
1503
|
flush(current);
|
|
1600
1504
|
yield* collectBlock(child);
|
|
1601
1505
|
} else if (opts.visible && child.nodeType === Node.ELEMENT_NODE && child.tagName === 'CODE' && String(child.textContent || '').includes('\n')) {
|
|
1602
|
-
// Lines clipped from a code block retain every authored space.
|
|
1603
1506
|
flush(current);
|
|
1604
1507
|
pushBlock(String(child.textContent || ''), true);
|
|
1605
1508
|
} else current.inline.push(yield* _rapierPortableInlineTextSteps(child, opts));
|
|
@@ -1612,7 +1515,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1612
1515
|
const tag = node.tagName;
|
|
1613
1516
|
if (/^H[1-6]$/.test(tag) || tag === 'P') {
|
|
1614
1517
|
const text = _rapierNormalizeInlineText(yield* _rapierPortableInlineTextSteps(node, opts));
|
|
1615
|
-
// An empty paragraph (the line Enter makes) is a line of its own, as it is in Word's plain text: one newline more between its neighbours.
|
|
1616
1518
|
if (tag === 'P' && !text && node.childNodes.length && !node.hasAttribute('data-md-break')) blocks.push({ text: '', blank: true });
|
|
1617
1519
|
else pushBlock(text, false);
|
|
1618
1520
|
return;
|
|
@@ -1667,7 +1569,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1667
1569
|
const pending = state();
|
|
1668
1570
|
return {write: root => collectChildren(root, pending, false), finish: function* () {
|
|
1669
1571
|
flush(pending);
|
|
1670
|
-
// Each block stands a blank line from the next, and each empty paragraph between them adds one newline more.
|
|
1671
1572
|
let joined = '', blanks = 0;
|
|
1672
1573
|
for (const block of blocks) {
|
|
1673
1574
|
yield;
|
|
@@ -1680,8 +1581,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1680
1581
|
}};
|
|
1681
1582
|
}
|
|
1682
1583
|
|
|
1683
|
-
// The text file Export writes: a Markdown document's portable words and one final newline, any other document's own text.
|
|
1684
|
-
// The editor's Export and the door's txt both end here.
|
|
1685
1584
|
function _rapierPlainTextFile(context) {
|
|
1686
1585
|
return context.metadata.docKind === 'markdown'
|
|
1687
1586
|
? _rapierPortablePlainText(_rapierProjectPortableRoot(_rapierHeldPicturesAsWords(context.semanticRoot), { baseName: context.baseName }), { finalNewline: true })
|
|
@@ -1717,8 +1616,6 @@ function* _rapierResolveSoftBreakTokensSteps(root) {
|
|
|
1717
1616
|
return root;
|
|
1718
1617
|
}
|
|
1719
1618
|
|
|
1720
|
-
// Markup parsed in a document of its own, which runs no script and loads nothing (Copy's clipboard tree): one element per
|
|
1721
|
-
// markup, or no boxes where the joined parse did not keep them apart.
|
|
1722
1619
|
function _rapierInertParse(markups) {
|
|
1723
1620
|
const doc = new DOMParser().parseFromString(markups.map((html, at) => '<div data-rapier-batch="' + at + '">' + html + '</div>').join(''), 'text/html');
|
|
1724
1621
|
const boxes = [...doc.body.childNodes];
|
|
@@ -1739,8 +1636,6 @@ function _rapierBidiStrong(cp) {
|
|
|
1739
1636
|
return null;
|
|
1740
1637
|
}
|
|
1741
1638
|
|
|
1742
|
-
// A consuming operation, not an editable DOM mode. Both outputs share one admitted
|
|
1743
|
-
// parse per equation; callers decide whether and how to expose the MathML to people.
|
|
1744
1639
|
function renderWithMathML(source, metadata = {}) {
|
|
1745
1640
|
const provider = _rapierProviders.math;
|
|
1746
1641
|
if (!provider || provider.status !== 'ready' || typeof provider.renderToString !== 'function' ||
|