rapier-markdown-kit 1.2.7 → 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/README.md +32 -30
- package/dist/agent/will.mjs +1 -6
- package/dist/images/gif.mjs +0 -3
- package/dist/images/playback.mjs +1 -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/index.mjs +1 -1
- package/dist/kit/ledger/authored-origin.mjs +0 -5
- package/dist/kit/ledger/authorship.mjs +0 -2
- package/dist/kit/ledger/carried.d.mts +20 -0
- 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.d.mts +88 -0
- package/dist/kit/ledger.mjs +14 -0
- package/dist/kit/markdown-it.d.mts +4 -0
- package/dist/kit/markdown-it.mjs +36 -0
- package/dist/kit/pdf-glyphs.mjs +39 -45
- package/dist/kit/pdf-source.d.mts +5 -0
- package/dist/kit/pdf-source.mjs +125 -0
- 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 +15 -119
- package/dist/kit/render-print.mjs +4 -49
- package/dist/kit/render-sanitize.mjs +22 -58
- 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/kit/will.d.mts +42 -0
- 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 +6 -25
- package/dist/spec/frontmatter.mjs +0 -1
- package/dist/spec/html-blocks.mjs +3 -37
- 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 +21 -4
- package/style.css +38 -21
|
@@ -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);
|
|
@@ -410,7 +380,7 @@ function initMarkdownIt() {
|
|
|
410
380
|
}
|
|
411
381
|
|
|
412
382
|
function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
413
|
-
const root = document.createElement('div');
|
|
383
|
+
const root = document.createElement('template').content.ownerDocument.createElement('div');
|
|
414
384
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
415
385
|
|
|
416
386
|
if (metadata.docKind !== 'markdown') {
|
|
@@ -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
|
|
@@ -469,7 +435,8 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
469
435
|
let {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
|
|
470
436
|
env = {...env, rapierImagePresentation: options.imagePresentation === true};
|
|
471
437
|
await pause(work, .3);
|
|
472
|
-
const
|
|
438
|
+
const inert = document.createElement('template').content.ownerDocument;
|
|
439
|
+
const root = inert.createElement('div'), part = inert.createElement('div'), boundary = htmlBoundary();
|
|
473
440
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
474
441
|
let html = '', began = performance.now(), complete = false;
|
|
475
442
|
const append = () => {
|
|
@@ -492,9 +459,6 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
492
459
|
}
|
|
493
460
|
}
|
|
494
461
|
if (html) append();
|
|
495
|
-
// Token/environment objects are no longer inputs once their complete HTML
|
|
496
|
-
// is admitted. Drop our references before the document-wide DOM passes;
|
|
497
|
-
// never mutate an optional parsed result retained by another caller.
|
|
498
462
|
parsed = tokens = env = labels = null;
|
|
499
463
|
await finishAsync(_rapierSemanticDecorations(root), work);
|
|
500
464
|
await pause(work, .6);
|
|
@@ -689,9 +653,6 @@ function _rapierImageAltText(value, env = {}) {
|
|
|
689
653
|
return md.renderer.renderInlineAsText(tokens[0]?.children || [], md.options, env);
|
|
690
654
|
}
|
|
691
655
|
|
|
692
|
-
// Intrinsic facts are a presentation input, never an authored width or a resource admission.
|
|
693
|
-
// Contain both edges within the column, sharing a 1.6-column sum-of-edges budget.
|
|
694
|
-
// Reciprocal portraits/landscapes have equal weight; the native-width cap never enlarges art.
|
|
695
656
|
function _rapierImageGeometryFacts(geometry, automatic) {
|
|
696
657
|
const w = Number(geometry?.width), h = Number(geometry?.height);
|
|
697
658
|
const known = Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && Number.isFinite(w / h) && w / h > 0;
|
|
@@ -706,8 +667,6 @@ function _rapierImageGeometryFacts(geometry, automatic) {
|
|
|
706
667
|
return {width, height, styles};
|
|
707
668
|
}
|
|
708
669
|
|
|
709
|
-
// Native loads supply their current element's dimensions synchronously. Embedded images
|
|
710
|
-
// supply an admitted row only after their existing document/resource ticket checks.
|
|
711
670
|
function _rapierApplyImageGeometry(image, geometry) {
|
|
712
671
|
if (image?.tagName !== 'IMG' || image.hasAttribute('data-rapier-html')) return false;
|
|
713
672
|
const mode = image.getAttribute('data-rapier-image-geometry');
|
|
@@ -725,8 +684,6 @@ function _rapierApplyImageGeometry(image, geometry) {
|
|
|
725
684
|
].filter(([name, value]) => image.getAttribute(name) !== value);
|
|
726
685
|
const styles = facts.styles.filter(([name, value]) => (name === 'aspect-ratio'
|
|
727
686
|
? image.style.getPropertyValue(name).replace(/\s/g, '') : image.style.getPropertyValue(name)) !== value);
|
|
728
|
-
// Ordinary mutations revisit mounted images. Settled facts need no source-URL
|
|
729
|
-
// read or parse (which can be the entire inline image), nor any DOM writes.
|
|
730
687
|
if (!attributes.length && !styles.length) return false;
|
|
731
688
|
if (native) {
|
|
732
689
|
const src = image.getAttribute('src'), current = image.currentSrc;
|
|
@@ -741,13 +698,9 @@ function _rapierApplyImageGeometry(image, geometry) {
|
|
|
741
698
|
|
|
742
699
|
function _rapierImageSizeAttributes(size, marker, geometry, automatic = true) {
|
|
743
700
|
const layout = globalThis.RapierMarkdownLayout.parseLayout(marker || '');
|
|
744
|
-
// Plain (rung 0, docs/standard-adoption.md): every picture renders at its natural width, the
|
|
745
|
-
// same as a reader with no layout facts at all -- Obsidian's `![alt|N]` alt-text width (the
|
|
746
|
-
// `size` fallback below) is a different, established convention and is unaffected.
|
|
747
701
|
let style = layout && !_rapierPlainLayout() ? globalThis.RapierMarkdownLayout.imageStyle(layout) : '';
|
|
748
702
|
let attributes = '';
|
|
749
703
|
if (layout?.width != null && style) attributes = ' data-md-image-width="' + layout.width + '"';
|
|
750
|
-
// A fade rides beside any width, the alt-text's own included.
|
|
751
704
|
else if (size) {
|
|
752
705
|
attributes = ' width="' + size.width + '" data-rapier-image-size="' + size.width + '"';
|
|
753
706
|
style = '--md-image-width:' + size.width + 'px' + (style ? ';' + style : '');
|
|
@@ -922,9 +875,6 @@ function* _rapierDisambiguateRenderedAnchorsSteps(root) {
|
|
|
922
875
|
}
|
|
923
876
|
|
|
924
877
|
function _rapierDecodeHash(hash) {
|
|
925
|
-
// A document's own address may carry a heading after it (`#d/<id>/<heading>`): the heading is the
|
|
926
|
-
// anchor, and an address with no heading names nothing to jump to. A view's (`#v/draw`,
|
|
927
|
-
// `#v/notes`) names no heading either (docs/agents.md "The address of a document").
|
|
928
878
|
let raw = String(hash || '').replace(/^#/, '');
|
|
929
879
|
const own = /^[dnv]\/[^/]*(?:\/(.*))?$/.exec(raw);
|
|
930
880
|
if (own) raw = own[1] || '';
|
|
@@ -957,8 +907,6 @@ function* _rapierDecorateJumpListsSteps(root) {
|
|
|
957
907
|
}
|
|
958
908
|
}
|
|
959
909
|
|
|
960
|
-
// Do not retain a live collection on the root that Copy will consume. Some
|
|
961
|
-
// DOM hosts eagerly refresh it on every later removal of a complete unit.
|
|
962
910
|
const first = root.firstElementChild, only = first && !first.nextElementSibling ? first : null;
|
|
963
911
|
if (only && only.tagName === 'P' && /^jump\s+to:?$/i.test(String(only.textContent || '').trim())) {
|
|
964
912
|
only.classList.add('rapier-jump-label-candidate');
|
|
@@ -968,7 +916,6 @@ function* _rapierDecorateJumpListsSteps(root) {
|
|
|
968
916
|
function _rapierMarkFigureCaptions(root) { return finish(_rapierMarkFigureCaptionsSteps(root)); }
|
|
969
917
|
|
|
970
918
|
function* _rapierMarkFigureCaptionsSteps(root) {
|
|
971
|
-
// Same path as a table caption: an ordinary paragraph after the picture, still the source line.
|
|
972
919
|
const figureCaption = /^Figure: /;
|
|
973
920
|
for (const paragraph of root.querySelectorAll('p')) { yield;
|
|
974
921
|
if (!figureCaption.test(paragraph.textContent || '')) continue;
|
|
@@ -1005,11 +952,6 @@ function* _rapierMarkListContinuationsSteps(blocks) {
|
|
|
1005
952
|
function _rapierMarkTableCaptions(root) { return finish(_rapierMarkTableCaptionsSteps(root)); }
|
|
1006
953
|
|
|
1007
954
|
function* _rapierMarkTableCaptionsSteps(root) {
|
|
1008
|
-
// The source convention is untouched: the caption stays an ordinary trailing paragraph
|
|
1009
|
-
// (docs/markdown-standard.md). Only this rendered projection associates it with its table —
|
|
1010
|
-
// via a generated id plus aria-describedby, not a real <caption>, which would have to move the
|
|
1011
|
-
// paragraph inside <table> and break the one-DOM-node-per-source-block correspondence other
|
|
1012
|
-
// passes (heading slugs, portable export) rely on for this same root.
|
|
1013
955
|
const used = new Set(Array.from(root.querySelectorAll('[id]'), element => element.id));
|
|
1014
956
|
let ordinal = 0;
|
|
1015
957
|
for (const table of root.querySelectorAll('table')) { yield;
|
|
@@ -1037,7 +979,6 @@ function _rapierNormalizeCodeElement(code) {
|
|
|
1037
979
|
|
|
1038
980
|
function _rapierLanguageClass(element) {
|
|
1039
981
|
if (!element || !element.classList) return '';
|
|
1040
|
-
// CommonMark's first info word is an identifier, including Unicode and punctuation.
|
|
1041
982
|
const found = Array.from(element.classList).find(name => /^language-.+/i.test(name));
|
|
1042
983
|
return found ? found.slice(9) : '';
|
|
1043
984
|
}
|
|
@@ -1173,28 +1114,15 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1173
1114
|
|
|
1174
1115
|
function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
1175
1116
|
const opts = options || {};
|
|
1176
|
-
// This projection is HTML-shaped (width baked into a CSS `style`, `data-*` stripped) for the
|
|
1177
|
-
// consumers that read HTML -- publishing/fragment export, TXT. DOCX is not an HTML consumer: its
|
|
1178
|
-
// writer (interchange/docx.mjs) reads the layout comment itself, so stripping it first and
|
|
1179
|
-
// handing back CSS only throws the fact away before the writer ever sees it. `keepLayoutData`
|
|
1180
|
-
// leaves the image and text layout records, ink meaning and paragraph alignment exactly as
|
|
1181
|
-
// the semantic root carries them. Ordinary paragraph/list CSS measurements also reach their
|
|
1182
|
-
// native reader; no derived private CSS is needed.
|
|
1183
1117
|
const keepLayoutData = !!opts.keepLayoutData;
|
|
1184
|
-
// Copy owns its freshly rendered, detached root. Other callers retain their
|
|
1185
|
-
// semantic tree and receive the usual independent portable projection.
|
|
1186
1118
|
const root = opts.inPlace ? semanticRoot : yield* cloneTree(semanticRoot), nativeElements = new Set();
|
|
1187
|
-
// A source soft break is a reading space, never the editor's invisible token.
|
|
1188
1119
|
yield* _rapierResolveSoftBreakTokensSteps(root);
|
|
1189
|
-
// Word consumes these source-bearing islands as pictures with TeX alternatives. Keep
|
|
1190
|
-
// their SVG through this projection; the normal export sanitizer still owns admission.
|
|
1191
1120
|
if (opts.keepMath) for (const math of root.querySelectorAll('.math-rendered[data-math-src],.math-placeholder,[data-rapier-math-source]')) {
|
|
1192
1121
|
for (const element of [math, ...math.querySelectorAll('*')]) nativeElements.add(element);
|
|
1193
1122
|
}
|
|
1194
1123
|
if (opts.keepDiagrams) for (const figure of root.querySelectorAll('.diagram-block[data-diagram-native]')) {
|
|
1195
1124
|
const svg = figure.querySelector('.diagram-cache > svg.rapier-native-flowchart');
|
|
1196
1125
|
if (figure.getAttribute('data-diagram-state') === 'ready' && svg) {
|
|
1197
|
-
// A class in authored HTML grants no exemption: only the native materialization above does.
|
|
1198
1126
|
for (const element of [svg, ...svg.querySelectorAll('*')]) nativeElements.add(element);
|
|
1199
1127
|
figure.replaceWith(svg);
|
|
1200
1128
|
}
|
|
@@ -1278,8 +1206,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1278
1206
|
for (const element of root.querySelectorAll('*')) { yield;
|
|
1279
1207
|
if (nativeElements.has(element)) continue;
|
|
1280
1208
|
const lang = element.tagName === 'CODE' ? _rapierLanguageClass(element) : '';
|
|
1281
|
-
// The native note reader needs these transient roles to omit the renderer's
|
|
1282
|
-
// separator and return-link spacing before it compares source-owned review notes.
|
|
1283
1209
|
const footnoteRole = keepLayoutData ? ['footnote-backref', 'footnotes-sep']
|
|
1284
1210
|
.filter(name => element.classList.contains(name)).join(' ') : '';
|
|
1285
1211
|
const captionRole = keepLayoutData ? ['rapier-table-caption', 'rapier-figure-caption']
|
|
@@ -1291,7 +1217,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1291
1217
|
const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
|
|
1292
1218
|
const measurements = keepLayoutData && /^(?:P|H[1-6]|LI|OL|UL)$/.test(element.tagName)
|
|
1293
1219
|
? ['text-indent', 'margin-inline-start'].map(property => [property, element.style.getPropertyValue(property)]) : [];
|
|
1294
|
-
// The native table reader admits automatic hyphenation from ordinary paragraph CSS.
|
|
1295
1220
|
if (keepLayoutData && element.tagName === 'P' && element.closest('table') && element.style.getPropertyValue('hyphens').trim() === 'auto')
|
|
1296
1221
|
measurements.push(['hyphens', 'auto']);
|
|
1297
1222
|
if (keepLayoutData && element.closest('pre')) for (const property of ['color', 'font-weight', 'font-style'])
|
|
@@ -1311,8 +1236,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1311
1236
|
const isPageBreak = element.hasAttribute('data-md-break');
|
|
1312
1237
|
for (const attribute of Array.from(element.attributes)) { yield;
|
|
1313
1238
|
const name = attribute.name.toLowerCase();
|
|
1314
|
-
// Copy reads these inert words from the final admitted nodes, then removes
|
|
1315
|
-
// them before emitting HTML. They grant no rendering or resource authority.
|
|
1316
1239
|
if (opts.heldPictures && /^(?:SPAN|IMG)$/.test(element.tagName) &&
|
|
1317
1240
|
(name === 'data-rapier-remote-src' || name === 'data-rapier-remote-alt')) continue;
|
|
1318
1241
|
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink'
|
|
@@ -1343,8 +1266,6 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1343
1266
|
}
|
|
1344
1267
|
if (highlightHex) element.style.backgroundColor = highlightHex;
|
|
1345
1268
|
if (isTableCaption) { element.style.fontStyle = 'italic'; element.style.color = '#555555'; }
|
|
1346
|
-
// Already validated facts serialize directly. An inert host's partial CSSOM must
|
|
1347
|
-
// not silently discard a quoted family or round an authored decimal.
|
|
1348
1269
|
const css = typed.style(typography);
|
|
1349
1270
|
if (css) element.setAttribute('style', (element.getAttribute('style') ? element.getAttribute('style') + ';' : '') + css);
|
|
1350
1271
|
|
|
@@ -1369,9 +1290,6 @@ function _rapierPortableHtml(root) {
|
|
|
1369
1290
|
}
|
|
1370
1291
|
async function _rapierPortableHtmlAsync(root, work) { return finishAsync(serializeTree(root, sanitizeRapierHtml, true), work); }
|
|
1371
1292
|
|
|
1372
|
-
// One captured document owns this pipeline and its detached nodes. Global semantic
|
|
1373
|
-
// facts are resolved before projection. Final admission consumes the old tree a
|
|
1374
|
-
// complete fragment at a time; HTML and words then share that admitted tree.
|
|
1375
1293
|
async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
1376
1294
|
const work = options.work;
|
|
1377
1295
|
let semantic;
|
|
@@ -1388,8 +1306,6 @@ async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
|
1388
1306
|
const plain = await finishAsync((function* () {
|
|
1389
1307
|
for (const html of htmlParts(semantic, 16384, true)) {
|
|
1390
1308
|
if (html === null) { yield; continue; }
|
|
1391
|
-
// Reparse the complete transformed unit at the existing final export
|
|
1392
|
-
// boundary. Both formats consume these admitted nodes before release.
|
|
1393
1309
|
admitHtml(part, html, sanitizeRapierHtml, runtime.sanitizeRapierFragment);
|
|
1394
1310
|
yield;
|
|
1395
1311
|
if (words) yield* words.write(part);
|
|
@@ -1407,9 +1323,6 @@ async function _rapierClipboardPayloadAsync(canonical, metadata, options = {}) {
|
|
|
1407
1323
|
check(work);
|
|
1408
1324
|
if (!options.formatted) return {plain};
|
|
1409
1325
|
return {plain, html: parts.join('').trim(), withoutPictures: pictures ? async () => {
|
|
1410
|
-
// Only this writer's finished HTML reaches this closure. On refusal,
|
|
1411
|
-
// reconstruct one complete fragment, change pictures to text and admit
|
|
1412
|
-
// that changed output. No full fallback DOM survives the operation.
|
|
1413
1326
|
return finishAsync((function* () {
|
|
1414
1327
|
const output = [], part = document.createElement('div');
|
|
1415
1328
|
try {
|
|
@@ -1440,15 +1353,12 @@ function _rapierNormalizeInlineText(value) {
|
|
|
1440
1353
|
.trim();
|
|
1441
1354
|
}
|
|
1442
1355
|
|
|
1443
|
-
// What a text file says where a picture stands: its words, and its address unless the picture is carried in the document itself.
|
|
1444
1356
|
function _rapierPictureWords(alt, src) {
|
|
1445
1357
|
if (/^data:image\//i.test(src)) return alt ? '[Image: ' + alt + ']' : '[Image]';
|
|
1446
1358
|
if (src) return alt ? '[Image: ' + alt + ' — ' + src + ']' : '[Image: ' + src + ']';
|
|
1447
1359
|
return alt ? '[Image: ' + alt + ']' : '[Image]';
|
|
1448
1360
|
}
|
|
1449
1361
|
|
|
1450
|
-
// Held Markdown and HTML pictures keep their addresses as inert data. Text export reads them before
|
|
1451
|
-
// the portable projection removes editor attributes, without restoring a fetching image source.
|
|
1452
1362
|
function _rapierHeldPicturesAsWords(root) { return finish(_rapierHeldPicturesAsWordsSteps(root)); }
|
|
1453
1363
|
async function _rapierHeldPicturesAsWordsAsync(root, work) { return finishAsync(_rapierHeldPicturesAsWordsSteps(root), work); }
|
|
1454
1364
|
|
|
@@ -1472,7 +1382,6 @@ function* _rapierPortableInlineTextSteps(node, options) {
|
|
|
1472
1382
|
return _rapierPictureWords(String(node.getAttribute('data-rapier-remote-alt') || '').trim(),
|
|
1473
1383
|
String(node.getAttribute('data-rapier-remote-src') || '').trim());
|
|
1474
1384
|
if (tag === 'BR') return '\n';
|
|
1475
|
-
// A task's box is what the person sees of it.
|
|
1476
1385
|
if (tag === 'INPUT') return /^checkbox$/i.test(node.type || '') ? (node.checked || node.hasAttribute('checked') ? '☑ ' : '☐ ') : '';
|
|
1477
1386
|
if (tag === 'IMG') {
|
|
1478
1387
|
if (opts.visible) return '';
|
|
@@ -1509,7 +1418,6 @@ function* _rapierPortableListTextSteps(list, depth, options) {
|
|
|
1509
1418
|
else content.push(yield* _rapierPortableInlineTextSteps(child, opts));
|
|
1510
1419
|
}
|
|
1511
1420
|
const normalized = _rapierNormalizeInlineText(content.join('')), indent = ' '.repeat(level);
|
|
1512
|
-
// A task shows its box where a bullet would be.
|
|
1513
1421
|
const marker = /^[☐☑☒]/.test(normalized) ? '' : ordered ? (counter++) + '. ' : '• ';
|
|
1514
1422
|
const parts = normalized.split('\n');
|
|
1515
1423
|
lines.push(indent + marker + (parts.shift() || ''));
|
|
@@ -1522,12 +1430,11 @@ function* _rapierPortableListTextSteps(list, depth, options) {
|
|
|
1522
1430
|
return lines.join('\n');
|
|
1523
1431
|
}
|
|
1524
1432
|
|
|
1525
|
-
// A table, or a part of one a selection clipped out (a head, a body, a row): its rows.
|
|
1526
1433
|
function _rapierPortableTableText(table, options) { return finish(_rapierPortableTableTextSteps(table, options)); }
|
|
1527
1434
|
|
|
1528
1435
|
function* _rapierPortableTableTextSteps(table, options) {
|
|
1529
1436
|
const opts = options || {}, rows = [];
|
|
1530
|
-
const walker = document.createTreeWalker(table, 1
|
|
1437
|
+
const walker = document.createTreeWalker(table, 1 );
|
|
1531
1438
|
let row = table.tagName === 'TR' ? table : walker.nextNode();
|
|
1532
1439
|
while (row) {
|
|
1533
1440
|
yield;
|
|
@@ -1586,8 +1493,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1586
1493
|
for (let child = parent.firstChild; child; child = child.nextSibling) {
|
|
1587
1494
|
yield;
|
|
1588
1495
|
if (tablePart(child)) {
|
|
1589
|
-
// A clipped table can cross an output fragment. Retain its words,
|
|
1590
|
-
// not detached rows, until the next non-table node closes the block.
|
|
1591
1496
|
if (!current.table) { flush(current); current.table = []; }
|
|
1592
1497
|
current.table.push(yield* _rapierPortableTableTextSteps(child, opts));
|
|
1593
1498
|
continue;
|
|
@@ -1598,7 +1503,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1598
1503
|
flush(current);
|
|
1599
1504
|
yield* collectBlock(child);
|
|
1600
1505
|
} else if (opts.visible && child.nodeType === Node.ELEMENT_NODE && child.tagName === 'CODE' && String(child.textContent || '').includes('\n')) {
|
|
1601
|
-
// Lines clipped from a code block retain every authored space.
|
|
1602
1506
|
flush(current);
|
|
1603
1507
|
pushBlock(String(child.textContent || ''), true);
|
|
1604
1508
|
} else current.inline.push(yield* _rapierPortableInlineTextSteps(child, opts));
|
|
@@ -1611,7 +1515,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1611
1515
|
const tag = node.tagName;
|
|
1612
1516
|
if (/^H[1-6]$/.test(tag) || tag === 'P') {
|
|
1613
1517
|
const text = _rapierNormalizeInlineText(yield* _rapierPortableInlineTextSteps(node, opts));
|
|
1614
|
-
// 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.
|
|
1615
1518
|
if (tag === 'P' && !text && node.childNodes.length && !node.hasAttribute('data-md-break')) blocks.push({ text: '', blank: true });
|
|
1616
1519
|
else pushBlock(text, false);
|
|
1617
1520
|
return;
|
|
@@ -1666,7 +1569,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1666
1569
|
const pending = state();
|
|
1667
1570
|
return {write: root => collectChildren(root, pending, false), finish: function* () {
|
|
1668
1571
|
flush(pending);
|
|
1669
|
-
// Each block stands a blank line from the next, and each empty paragraph between them adds one newline more.
|
|
1670
1572
|
let joined = '', blanks = 0;
|
|
1671
1573
|
for (const block of blocks) {
|
|
1672
1574
|
yield;
|
|
@@ -1679,8 +1581,6 @@ function _rapierPortableTextWriter(options) {
|
|
|
1679
1581
|
}};
|
|
1680
1582
|
}
|
|
1681
1583
|
|
|
1682
|
-
// The text file Export writes: a Markdown document's portable words and one final newline, any other document's own text.
|
|
1683
|
-
// The editor's Export and the door's txt both end here.
|
|
1684
1584
|
function _rapierPlainTextFile(context) {
|
|
1685
1585
|
return context.metadata.docKind === 'markdown'
|
|
1686
1586
|
? _rapierPortablePlainText(_rapierProjectPortableRoot(_rapierHeldPicturesAsWords(context.semanticRoot), { baseName: context.baseName }), { finalNewline: true })
|
|
@@ -1716,8 +1616,6 @@ function* _rapierResolveSoftBreakTokensSteps(root) {
|
|
|
1716
1616
|
return root;
|
|
1717
1617
|
}
|
|
1718
1618
|
|
|
1719
|
-
// Markup parsed in a document of its own, which runs no script and loads nothing (Copy's clipboard tree): one element per
|
|
1720
|
-
// markup, or no boxes where the joined parse did not keep them apart.
|
|
1721
1619
|
function _rapierInertParse(markups) {
|
|
1722
1620
|
const doc = new DOMParser().parseFromString(markups.map((html, at) => '<div data-rapier-batch="' + at + '">' + html + '</div>').join(''), 'text/html');
|
|
1723
1621
|
const boxes = [...doc.body.childNodes];
|
|
@@ -1738,8 +1636,6 @@ function _rapierBidiStrong(cp) {
|
|
|
1738
1636
|
return null;
|
|
1739
1637
|
}
|
|
1740
1638
|
|
|
1741
|
-
// A consuming operation, not an editable DOM mode. Both outputs share one admitted
|
|
1742
|
-
// parse per equation; callers decide whether and how to expose the MathML to people.
|
|
1743
1639
|
function renderWithMathML(source, metadata = {}) {
|
|
1744
1640
|
const provider = _rapierProviders.math;
|
|
1745
1641
|
if (!provider || provider.status !== 'ready' || typeof provider.renderToString !== 'function' ||
|