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.
Files changed (60) hide show
  1. package/dist/agent/will.mjs +0 -6
  2. package/dist/images/gif.mjs +0 -3
  3. package/dist/images/playback.mjs +0 -10
  4. package/dist/kit/assets.mjs +0 -1
  5. package/dist/kit/conformance/measurer.mjs +0 -0
  6. package/dist/kit/conformance/run.mjs +1 -8
  7. package/dist/kit/export-fonts.mjs +0 -14
  8. package/dist/kit/ledger/authored-origin.mjs +0 -5
  9. package/dist/kit/ledger/authorship.mjs +0 -2
  10. package/dist/kit/ledger/carried.mjs +4 -7
  11. package/dist/kit/ledger/data.mjs +0 -8
  12. package/dist/kit/ledger/format.mjs +0 -25
  13. package/dist/kit/ledger/hash.mjs +11 -7
  14. package/dist/kit/ledger/history.mjs +10 -29
  15. package/dist/kit/ledger/inverse-source.mjs +0 -46
  16. package/dist/kit/ledger/journal-records.mjs +24 -56
  17. package/dist/kit/ledger/merge.mjs +0 -27
  18. package/dist/kit/ledger/text-merge.mjs +0 -20
  19. package/dist/kit/ledger/transport.mjs +0 -47
  20. package/dist/kit/ledger.mjs +0 -4
  21. package/dist/kit/markdown-it.mjs +0 -3
  22. package/dist/kit/pdf-glyphs.mjs +1 -10
  23. package/dist/kit/pdf-source.mjs +5 -5
  24. package/dist/kit/pdf-vectors.mjs +0 -3
  25. package/dist/kit/print-capitals.mjs +0 -4
  26. package/dist/kit/render-entry.mjs +0 -1
  27. package/dist/kit/render-ink.mjs +0 -5
  28. package/dist/kit/render-markdown.mjs +12 -117
  29. package/dist/kit/render-print.mjs +1 -47
  30. package/dist/kit/render-sanitize.mjs +7 -59
  31. package/dist/kit/render-styles.mjs +0 -7
  32. package/dist/kit/render-work.mjs +0 -12
  33. package/dist/kit/render.mjs +18 -173
  34. package/dist/layout/hyphenation.mjs +0 -23
  35. package/dist/layout/line-plan.mjs +0 -20
  36. package/dist/layout/model.mjs +0 -55
  37. package/dist/layout/paragraph-fit.mjs +50 -60
  38. package/dist/layout/preparation.mjs +23 -56
  39. package/dist/layout/prepared-paragraph.mjs +0 -25
  40. package/dist/layout/typography.mjs +103 -41
  41. package/dist/packages/rapier-diff/concerns.mjs +0 -16
  42. package/dist/packages/rapier-diff/diff.mjs +0 -7
  43. package/dist/packages/rapier-diff/index.mjs +0 -1
  44. package/dist/packages/rapier-diff/matching.mjs +0 -19
  45. package/dist/packages/rapier-diff/merge.mjs +0 -9
  46. package/dist/packages/rapier-diff/moves.mjs +0 -7
  47. package/dist/packages/rapier-diff/structure.mjs +0 -2
  48. package/dist/packages/rapier-diff/tokens.mjs +0 -6
  49. package/dist/packages/rapier-diff/walk.mjs +0 -12
  50. package/dist/spec/document-fonts.mjs +0 -9
  51. package/dist/spec/document-settings.mjs +4 -23
  52. package/dist/spec/frontmatter.mjs +0 -1
  53. package/dist/spec/html-blocks.mjs +0 -36
  54. package/dist/spec/md-assets.mjs +2 -61
  55. package/dist/spec/md-layout.mjs +0 -34
  56. package/dist/spec/md-marks.mjs +0 -51
  57. package/dist/spec/md-source.mjs +0 -16
  58. package/dist/spec/word-sidecar.mjs +0 -2
  59. package/package.json +1 -1
  60. package/style.css +32 -16
@@ -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; // Notes' cards read through here; the Node witnesses that run this function alone have no such neighbour
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 (`&#115;ource-textarea` is `source-textarea`,
200
- // `&#x73;` 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
- if (env.rapierPage) return '<span data-rapier-math-source="">' + literal + '</span>';
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
- let encoded;
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 '<span data-rapier-math-source="">' + literal + '</span>';
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 /* SHOW_ELEMENT */);
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' ||