rapier-markdown-kit 1.1.78 → 1.1.80

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.
@@ -1,8 +1,12 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  const RAPIER_TABLE_CAPTION_PREFIX_RE = /^(?:Table:|:) /;
3
+ const RAPIER_PORTABLE_HIGHLIGHT_HEX = Object.freeze({
4
+ green: '#35c57a', red: '#f05a62', blue: '#4f91f7', yellow: '#e6b91e', purple: '#9b6dea',
5
+ });
6
+ 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;
3
7
  // The document's renderer. DOM, codecs and host state are explicit inputs.
4
8
  function createMarkdownRenderer(runtime) {
5
- const {RAPIER_HIGHLIGHT_COLOR_BY_MARKER, RAPIER_MARKDOWN_SPEC, RAPIER_RENDERED_HEADING_SELECTOR, _rapierApplyMarkdownSpec, _rapierBidiStrong, _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;
9
+ 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;
6
10
  let md = runtime.md;
7
11
  function initMarkdownIt() {
8
12
  if (typeof window.markdownit !== 'function') throw new Error('markdownit');
@@ -112,9 +116,10 @@ function initMarkdownIt() {
112
116
  const layout = token.meta?.mdLayout?.imageOnly ? token.meta.mdLayout.marker : '';
113
117
  const asset = globalThis.RapierImageAssets.dataImage(src), reference = token.meta?.mdImage?.reference;
114
118
  // A connected host's own asset; a host without one (the server) supplies no port.
115
- if (_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/svg+xml')) return globalThis.RapierEmbeddedImages.imageHtml(
119
+ if (!env.rapierPage && typeof globalThis.RapierEmbeddedImages?.imageHtml === 'function' &&
120
+ (_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
116
121
  reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src);
117
- if (!_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
122
+ if (!env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
118
123
  const html = _rapierRemoteImagePlaceholder(src, alt, title, size, layout, rawAlt);
119
124
  return html.replace('<span ', '<span data-rapier-markdown-image="" ' +
120
125
  (token.meta?.mdImage?.source ? 'data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '" ' : ''));
@@ -246,7 +251,7 @@ function initMarkdownIt() {
246
251
  token.attrSet('data-rapier-fence', token.markup || '```');
247
252
  token.attrSet('data-rapier-fence-info', token.info == null ? '' : String(token.info));
248
253
  const lang = String(token.info || '').trim().split(/\s+/)[0].toLowerCase();
249
- if (lang !== 'mermaid') return _fenceDefault(tokens, idx, opts, env, self);
254
+ if (lang !== 'mermaid' || env.rapierPage) return _fenceDefault(tokens, idx, opts, env, self);
250
255
  const src = String(token.content || '').replace(/\n$/, '');
251
256
  const fence = md.utils.escapeHtml(token.markup || '```');
252
257
  const info = token.info == null ? '' : String(token.info);
@@ -299,9 +304,10 @@ function initMarkdownIt() {
299
304
 
300
305
  {
301
306
  let _mathPromptedOnce = false;
302
- const _renderMath = (src, displayMode) => {
307
+ const _renderMath = (src, displayMode, page) => {
303
308
  const marker = displayMode ? '$$' : '$';
304
309
  const source = marker + src + marker, literal = md.utils.escapeHtml(source);
310
+ if (page) return '<span data-rapier-math-source="">' + literal + '</span>';
305
311
  const mathProvider = _rapierProviders.math;
306
312
  if (!mathProvider || mathProvider.status !== 'ready' || typeof mathProvider.renderToString !== 'function') {
307
313
  if (!_mathPromptedOnce) {
@@ -318,14 +324,14 @@ function initMarkdownIt() {
318
324
  }
319
325
  catch (_) { return '<span data-rapier-math-source="">' + literal + '</span>'; }
320
326
  };
321
- md.renderer.rules.math_inline = (tokens, idx) => _renderMath(tokens[idx].content, false);
322
- md.renderer.rules.math_block = (tokens, idx) => wrapDisplayMath(_renderMath(tokens[idx].content, true)) + '\n';
327
+ md.renderer.rules.math_inline = (tokens, idx, _opts, env) => _renderMath(tokens[idx].content, false, env.rapierPage);
328
+ md.renderer.rules.math_block = (tokens, idx, _opts, env) => wrapDisplayMath(_renderMath(tokens[idx].content, true, env.rapierPage)) + '\n';
323
329
  }
324
330
  globalThis.RapierMarkdownLayout.installMarkdownLayout(md);
325
331
  globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
326
332
  }
327
333
 
328
- function _rapierRenderSemanticRoot(canonical, metadata) {
334
+ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
329
335
  const root = document.createElement('div');
330
336
  root.setAttribute('data-rapier-semantic-root', 'true');
331
337
 
@@ -343,7 +349,7 @@ function _rapierRenderSemanticRoot(canonical, metadata) {
343
349
  }
344
350
 
345
351
  const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
346
- const env = {};
352
+ const env = options.page ? {rapierPage: true} : {};
347
353
  let rendered = md
348
354
  ? md.render(markdown, env)
349
355
  : '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
@@ -352,7 +358,7 @@ function _rapierRenderSemanticRoot(canonical, metadata) {
352
358
  String(item && item.label || ('inline-' + (index + 1))));
353
359
  rendered = _relabelFootnoteHtml(rendered, labels);
354
360
  }
355
- root.innerHTML = sanitizeRapierHtml(rendered, 'export');
361
+ root.innerHTML = sanitizeRapierHtml(rendered, options.page ? 'source' : 'export');
356
362
  _rapierApplyBlockDirection(root);
357
363
 
358
364
  _rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false);
@@ -818,6 +824,477 @@ function wrapDisplayMath(html) {
818
824
  return '<span class="math-display-wrap">' + html + '</span>';
819
825
  }
820
826
 
821
- return {render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
827
+ function _rapierCalloutInfo(blockquote) {
828
+ const first = blockquote && blockquote.querySelector(':scope > p:first-child');
829
+ if (!first) return null;
830
+ const existing = first.querySelector('.callout__label');
831
+ if (existing) {
832
+ const type = String(existing.textContent || '').trim().toUpperCase();
833
+ return /^(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER|INFO)$/.test(type) ? { first, type, existing } : null;
834
+ }
835
+ const match = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER|INFO)\]/i.exec(first.textContent || '');
836
+ return match ? { first, type: match[1].toUpperCase(), existing: null } : null;
837
+ }
838
+
839
+ function _rapierRemoveCalloutMarker(first) {
840
+ if (!first) return;
841
+ first.innerHTML = first.innerHTML.replace(
842
+ /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER|INFO)\]\s*(?:<br\s*\/?>)?\s*/i,
843
+ ''
844
+ );
845
+ }
846
+
847
+ function _rapierMathSource(node) {
848
+ if (!node) return '';
849
+ const carrier = node.matches && node.matches('[data-math-src]')
850
+ ? node
851
+ : node.querySelector && node.querySelector('[data-math-src]');
852
+ if (carrier) {
853
+ const encoded = carrier.getAttribute('data-math-src') || '';
854
+ try { return decodeURIComponent(encoded); } catch (_) { return encoded; }
855
+ }
856
+ const placeholder = node.matches && node.matches('.math-placeholder')
857
+ ? node
858
+ : node.querySelector && node.querySelector('.math-placeholder');
859
+ return String((placeholder || node).textContent || '').trim();
860
+ }
861
+
862
+ function _rapierFlattenPortableDetails(root) {
863
+ const blockTags = new Set([
864
+ 'ADDRESS','ARTICLE','ASIDE','BLOCKQUOTE','DIV','DL','FIELDSET','FIGURE',
865
+ 'FOOTER','FORM','H1','H2','H3','H4','H5','H6','HEADER','HR','MAIN',
866
+ 'NAV','OL','P','PRE','SECTION','TABLE','UL',
867
+ ]);
868
+ Array.from(root.querySelectorAll('details')).reverse().forEach(details => {
869
+ const summary = Array.from(details.children).find(child => child.tagName === 'SUMMARY');
870
+ const section = document.createElement('section');
871
+ if (details.id) section.id = details.id;
872
+ if (summary && (summary.textContent.trim() || summary.children.length)) {
873
+ const heading = summary.querySelector('h1,h2,h3,h4,h5,h6');
874
+ const label = document.createElement(heading ? 'div' : 'p');
875
+ if (summary.id) label.id = summary.id;
876
+ const content = heading ? label : document.createElement('strong');
877
+ while (summary.firstChild) content.appendChild(summary.firstChild);
878
+ if (!heading) label.appendChild(content);
879
+ section.appendChild(label);
880
+ }
881
+
882
+ let paragraph = null;
883
+ Array.from(details.childNodes).forEach(child => {
884
+ if (child === summary) return;
885
+ const isText = child.nodeType === Node.TEXT_NODE;
886
+ if (isText && !String(child.nodeValue || '').trim()) return;
887
+ const isBlock = child.nodeType === Node.ELEMENT_NODE && blockTags.has(child.tagName);
888
+ if (!isBlock) {
889
+ if (!paragraph) {
890
+ paragraph = document.createElement('p');
891
+ section.appendChild(paragraph);
892
+ }
893
+ paragraph.appendChild(child);
894
+ return;
895
+ }
896
+ paragraph = null;
897
+ section.appendChild(child);
898
+ });
899
+ details.replaceWith(section);
900
+ });
901
+ }
902
+
903
+ function _rapierProjectPortableRoot(semanticRoot, options) {
904
+ const opts = options || {};
905
+ // This projection is HTML-shaped (width baked into a CSS `style`, `data-*` stripped) for the
906
+ // consumers that read HTML -- publishing/fragment export, TXT. DOCX is not an HTML consumer: its
907
+ // writer (interchange/docx.mjs) reads the layout comment itself, so stripping it first and
908
+ // handing back CSS only throws the fact away before the writer ever sees it. `keepLayoutData`
909
+ // leaves `data-rapier-image-layout` (width, align AND rotation) and the paragraph's
910
+ // `data-md-align` exactly as the semantic root already carries them, instead of the CSS-only
911
+ // projection, so the one existing layout representation reaches the writer.
912
+ const keepLayoutData = !!opts.keepLayoutData;
913
+ const root = semanticRoot.cloneNode(true), nativeElements = new Set();
914
+ // A source soft break is a reading space, never the editor's invisible token.
915
+ _rapierResolveSoftBreakTokens(root);
916
+ if (opts.keepDiagrams) for (const figure of root.querySelectorAll('.diagram-block[data-diagram-native]')) {
917
+ const svg = figure.querySelector('.diagram-cache > svg.rapier-native-flowchart');
918
+ if (figure.getAttribute('data-diagram-state') === 'ready' && svg) {
919
+ // A class in authored HTML grants no exemption: only the native materialization above does.
920
+ for (const element of [svg, ...svg.querySelectorAll('*')]) nativeElements.add(element);
921
+ figure.replaceWith(svg);
922
+ }
923
+ }
924
+
925
+ root.querySelectorAll('blockquote').forEach(blockquote => {
926
+ const info = _rapierCalloutInfo(blockquote);
927
+ if (!info) return;
928
+ if (info.existing) info.existing.remove();
929
+ else _rapierRemoveCalloutMarker(info.first);
930
+ const label = document.createElement('strong');
931
+ label.textContent = info.type.charAt(0) + info.type.slice(1).toLowerCase() + ': ';
932
+ info.first.insertBefore(label, info.first.firstChild);
933
+ });
934
+
935
+ root.querySelectorAll('input[type="checkbox"]').forEach(input => {
936
+ const nextStartsWithSpace = input.nextSibling
937
+ && input.nextSibling.nodeType === Node.TEXT_NODE
938
+ && /^\s/.test(input.nextSibling.nodeValue || '');
939
+ input.replaceWith(document.createTextNode((input.checked ? '☒' : '☐') + (nextStartsWithSpace ? '' : ' ')));
940
+ });
941
+
942
+ root.querySelectorAll('.math-display-wrap').forEach(wrapper => {
943
+ const source = _rapierMathSource(wrapper);
944
+ const p = document.createElement('p');
945
+ const code = document.createElement('code');
946
+ code.textContent = source || '[equation]';
947
+ p.appendChild(code);
948
+ wrapper.replaceWith(p);
949
+ });
950
+ root.querySelectorAll('.math-rendered,.math-placeholder').forEach(math => {
951
+ const code = document.createElement('code');
952
+ code.textContent = _rapierMathSource(math) || '[equation]';
953
+ math.replaceWith(code);
954
+ });
955
+ root.querySelectorAll('math').forEach(math => {
956
+ const code = document.createElement('code');
957
+ code.textContent = String(math.textContent || '').trim() || '[equation]';
958
+ math.replaceWith(code);
959
+ });
960
+
961
+ _rapierFlattenPortableDetails(root);
962
+
963
+ root.querySelectorAll('section.footnotes').forEach(section => {
964
+ section.querySelectorAll('.footnotes-sep').forEach(node => node.remove());
965
+ if (!section.querySelector(':scope > h1,:scope > h2,:scope > h3')) {
966
+ const heading = document.createElement('h2');
967
+ heading.textContent = 'Footnotes';
968
+ section.insertBefore(heading, section.firstChild);
969
+ }
970
+ });
971
+
972
+ root.querySelectorAll('.table-scroll-wrap,.math-display-wrap').forEach(_rapierUnwrapElement);
973
+
974
+ root.querySelectorAll('pre > code').forEach(_rapierNormalizeCodeElement);
975
+
976
+ root.querySelectorAll('svg').forEach(svg => {
977
+ if (nativeElements.has(svg)) return;
978
+ const title = svg.getAttribute('aria-label')
979
+ || (svg.querySelector('title') && svg.querySelector('title').textContent)
980
+ || '';
981
+ svg.replaceWith(document.createTextNode(title ? '[Image: ' + title.trim() + ']' : '[Image]'));
982
+ });
983
+
984
+ _rapierPrefixPortableAnchors(root, opts.baseName);
985
+
986
+ root.querySelectorAll('*').forEach(element => {
987
+ if (nativeElements.has(element)) return;
988
+ const lang = element.tagName === 'CODE' ? _rapierLanguageClass(element) : '';
989
+ const columnAlignment = /^(TH|TD)$/.test(element.tagName) && /^(left|center|right)$/.test(element.style.textAlign)
990
+ ? element.style.textAlign : '';
991
+ const alignment = globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-md-layout'))?.align || columnAlignment;
992
+ const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
993
+
994
+ const colorHex = element.tagName === 'SPAN' ? element.getAttribute('data-md-color') : null;
995
+
996
+ const highlightHex = element.tagName === 'MARK'
997
+ ? (RAPIER_PORTABLE_HIGHLIGHT_HEX[String(element.getAttribute('data-rapier-highlight') || '').toLowerCase()] || RAPIER_PORTABLE_HIGHLIGHT_HEX.yellow)
998
+ : null;
999
+
1000
+ const isTableCaption = element.tagName === 'P' && element.classList.contains('rapier-table-caption');
1001
+
1002
+ const isPageBreak = element.hasAttribute('data-md-break');
1003
+ Array.from(element.attributes).forEach(attribute => {
1004
+ const name = attribute.name.toLowerCase();
1005
+ if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-align')) return;
1006
+ if (name === 'class' || name === 'style' || name.startsWith('data-')
1007
+ || name === 'contenteditable' || name === 'spellcheck'
1008
+ || name === 'tabindex' || name === 'disabled' || name === 'aria-disabled') {
1009
+ element.removeAttribute(attribute.name);
1010
+ }
1011
+ });
1012
+ if (lang) element.className = 'language-' + lang;
1013
+ if (alignment) element.style.textAlign = alignment;
1014
+ if (imageLayout && !keepLayoutData) {
1015
+ const pictureStyle = globalThis.RapierMarkdownLayout.imageStyle(imageLayout);
1016
+ if (imageLayout.width != null) element.removeAttribute('width');
1017
+ if (pictureStyle) element.style.cssText = pictureStyle;
1018
+ }
1019
+ if (colorHex && /^#[0-9a-f]{6}$/i.test(colorHex)) element.style.color = colorHex.toLowerCase();
1020
+ if (highlightHex) element.style.backgroundColor = highlightHex;
1021
+ if (isTableCaption) { element.style.fontStyle = 'italic'; element.style.color = '#555555'; }
1022
+
1023
+ if (isPageBreak) {
1024
+ const p = document.createElement('p');
1025
+ p.setAttribute('style', 'page-break-before: always');
1026
+ element.replaceWith(p);
1027
+ }
1028
+ });
1029
+
1030
+ const clean = document.createElement('div');
1031
+ clean.innerHTML = sanitizeRapierHtml(root.innerHTML, 'export');
1032
+ return clean;
1033
+ }
1034
+
1035
+ function _rapierPortableHtml(root) {
1036
+ return sanitizeRapierHtml(root ? root.innerHTML : '', 'export').trim();
1037
+ }
1038
+
1039
+ function _rapierNormalizeInlineText(value) {
1040
+ return String(value || '')
1041
+ .replace(/\u00a0/g, ' ')
1042
+ .replace(/[\t\f\v ]+/g, ' ')
1043
+ .replace(/ *\n */g, '\n')
1044
+ .trim();
1045
+ }
1046
+
1047
+ // What a text file says where a picture stands: its words, and its address unless the picture is carried in the document itself.
1048
+ function _rapierPictureWords(alt, src) {
1049
+ if (/^data:image\//i.test(src)) return alt ? '[Image: ' + alt + ']' : '[Image]';
1050
+ if (src) return alt ? '[Image: ' + alt + ' — ' + src + ']' : '[Image: ' + src + ']';
1051
+ return alt ? '[Image: ' + alt + ']' : '[Image]';
1052
+ }
1053
+
1054
+ // A remote picture written as Markdown stands in the editor, until the person allows remote content, as a placeholder: its words, its origin and a
1055
+ // button. The text file says the picture as it does once the person has allowed it, its words and its address, never the placeholder's controls.
1056
+ function _rapierHeldPicturesAsWords(root) {
1057
+ const copy = root.cloneNode(true);
1058
+ copy.querySelectorAll('span[data-rapier-remote-src]').forEach(held => held.replaceWith(document.createTextNode(
1059
+ _rapierPictureWords(String(held.getAttribute('data-rapier-remote-alt') || '').trim(), String(held.getAttribute('data-rapier-remote-src') || '').trim()))));
1060
+ return copy;
1061
+ }
1062
+
1063
+ function _rapierPortableInlineText(node, options) {
1064
+ const opts = options || {};
1065
+ if (!node) return '';
1066
+ if (node.nodeType === Node.TEXT_NODE) return node.nodeValue || '';
1067
+ if (node.nodeType !== Node.ELEMENT_NODE) return '';
1068
+ const tag = node.tagName;
1069
+ if (tag === 'BR') return '\n';
1070
+ // A task's box is what the person sees of it.
1071
+ if (tag === 'INPUT') return /^checkbox$/i.test(node.type || '') ? (node.checked || node.hasAttribute('checked') ? '☑ ' : '☐ ') : '';
1072
+ if (tag === 'IMG') {
1073
+ if (opts.visible) return '';
1074
+ const alt = String(node.getAttribute('alt') || '').trim();
1075
+ const src = String(node.getAttribute('src') || '').trim();
1076
+ return _rapierPictureWords(alt, src);
1077
+ }
1078
+ if (tag === 'A') {
1079
+ const label = _rapierNormalizeInlineText(Array.from(node.childNodes)
1080
+ .map(child => _rapierPortableInlineText(child, opts)).join(''));
1081
+ const href = String(node.getAttribute('href') || '').trim();
1082
+ if (opts.visible || !href || href.charAt(0) === '#') return label;
1083
+ if (!label || label === href) return href;
1084
+ return label + ' (' + href + ')';
1085
+ }
1086
+ if (tag === 'CODE' && !opts.inPre) {
1087
+ const source = String(node.textContent || '');
1088
+ return opts.visible ? source : source ? '`' + source + '`' : '';
1089
+ }
1090
+ return Array.from(node.childNodes)
1091
+ .map(child => _rapierPortableInlineText(child, opts)).join('');
1092
+ }
1093
+
1094
+ function _rapierPortableListText(list, depth, options) {
1095
+ const opts = options || {};
1096
+ const level = Math.max(0, depth | 0);
1097
+ const ordered = list.tagName === 'OL';
1098
+ let counter = ordered ? (parseInt(list.getAttribute('start'), 10) || 1) : 0;
1099
+ const lines = [];
1100
+ Array.from(list.children).filter(child => child.tagName === 'LI').forEach(item => {
1101
+ const nested = Array.from(item.children).filter(child => child.tagName === 'UL' || child.tagName === 'OL');
1102
+ const content = Array.from(item.childNodes)
1103
+ .filter(child => !(child.nodeType === Node.ELEMENT_NODE && (child.tagName === 'UL' || child.tagName === 'OL')))
1104
+ .map(child => _rapierPortableInlineText(child, opts)).join('');
1105
+ const normalized = _rapierNormalizeInlineText(content);
1106
+ const indent = ' '.repeat(level);
1107
+ // A task shows its box where a bullet would be.
1108
+ const marker = /^[☐☑☒]/.test(normalized) ? '' : ordered ? (counter++) + '. ' : '• ';
1109
+ const parts = (normalized || '').split('\n');
1110
+ lines.push(indent + marker + (parts.shift() || ''));
1111
+ parts.forEach(part => lines.push(indent + ' ' + part));
1112
+ nested.forEach(child => {
1113
+ const nestedText = _rapierPortableListText(child, level + 1, opts);
1114
+ if (nestedText) lines.push(nestedText);
1115
+ });
1116
+ });
1117
+ return lines.join('\n');
1118
+ }
1119
+
1120
+ // A table, or a part of one a selection clipped out (a head, a body, a row): its rows.
1121
+ function _rapierPortableTableText(table, options) {
1122
+ const opts = options || {};
1123
+ const rows = table.tagName === 'TR' ? [table] : Array.from(table.querySelectorAll('tr'));
1124
+ return rows.map(row => {
1125
+ return Array.from(row.children)
1126
+ .filter(cell => cell.tagName === 'TH' || cell.tagName === 'TD')
1127
+ .map(cell => _rapierNormalizeInlineText(_rapierPortableInlineText(cell, opts)))
1128
+ .join(opts.visible ? '\t' : ' | ');
1129
+ }).filter(Boolean).join('\n');
1130
+ }
1131
+
1132
+ function _rapierPortablePlainText(root, options) {
1133
+ const opts = options || {};
1134
+ const blocks = [];
1135
+ const blockTags = new Set([
1136
+ 'ADDRESS','ARTICLE','ASIDE','BLOCKQUOTE','DETAILS','DIV','DL','FIELDSET','FIGURE','FOOTER',
1137
+ 'FORM','H1','H2','H3','H4','H5','H6','HEADER','HR','MAIN','NAV','OL','P','PRE','SECTION','TABLE','UL'
1138
+ ]);
1139
+
1140
+ const pushBlock = (text, preserve) => {
1141
+ let value = String(text == null ? '' : text).replace(/\r\n?/g, '\n');
1142
+ if (preserve) value = value.replace(/\n+$/, '');
1143
+ else value = value.replace(/[ \t]+$/gm, '').replace(/\n{3,}/g, '\n\n').trim();
1144
+ if (value) blocks.push({ text: value, preserve: !!preserve });
1145
+ };
1146
+
1147
+ const collectChildren = parent => {
1148
+ let inline = [];
1149
+ const flush = () => {
1150
+ if (!inline.length) return;
1151
+ pushBlock(_rapierNormalizeInlineText(inline.map(node => _rapierPortableInlineText(node, opts)).join('')), false);
1152
+ inline = [];
1153
+ };
1154
+ const tablePart = node => node.nodeType === Node.ELEMENT_NODE && /^(THEAD|TBODY|TFOOT|TR)$/.test(node.tagName);
1155
+ const blankText = node => node.nodeType === Node.TEXT_NODE && !String(node.nodeValue || '').trim();
1156
+ const children = Array.from(parent.childNodes);
1157
+ const inTableRun = index => { for (let at = index - 1; at >= 0; at--) { if (!blankText(children[at])) return tablePart(children[at]); } return false; };
1158
+ children.forEach((child, index) => {
1159
+ if (tablePart(child)) {
1160
+ // The parts of a table a selection clipped out of it: one block of rows, whichever parts arrived.
1161
+ if (inTableRun(index)) return;
1162
+ flush();
1163
+ const parts = [];
1164
+ for (let at = index; at < children.length && (tablePart(children[at]) || blankText(children[at])); at++) {
1165
+ if (tablePart(children[at])) parts.push(_rapierPortableTableText(children[at], opts));
1166
+ }
1167
+ pushBlock(parts.filter(Boolean).join('\n'), false);
1168
+ } else if (blankText(child) && inTableRun(index)) {
1169
+ return;
1170
+ } else if (child.nodeType === Node.ELEMENT_NODE && blockTags.has(child.tagName)) {
1171
+ flush();
1172
+ collectBlock(child);
1173
+ } else if (opts.visible && child.nodeType === Node.ELEMENT_NODE && child.tagName === 'CODE' && String(child.textContent || '').includes('\n')) {
1174
+ // Lines of a code block taken without their block: the code as shown, every space kept.
1175
+ flush();
1176
+ pushBlock(String(child.textContent || ''), true);
1177
+ } else {
1178
+ inline.push(child);
1179
+ }
1180
+ });
1181
+ flush();
1182
+ };
1183
+
1184
+ const collectBlock = node => {
1185
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
1186
+ const tag = node.tagName;
1187
+ if (/^H[1-6]$/.test(tag) || tag === 'P') {
1188
+ const text = _rapierNormalizeInlineText(_rapierPortableInlineText(node, opts));
1189
+ // 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.
1190
+ if (tag === 'P' && !text && node.childNodes.length && !node.hasAttribute('data-md-break')) blocks.push({ text: '', blank: true });
1191
+ else pushBlock(text, false);
1192
+ return;
1193
+ }
1194
+ if (tag === 'HR') { pushBlock('────────', false); return; }
1195
+ if (tag === 'PRE') {
1196
+ const code = node.querySelector(':scope > code') || node;
1197
+ const lang = _rapierLanguageClass(code);
1198
+ const source = String(code.textContent || '').replace(/\r\n?/g, '\n').replace(/\n$/, '');
1199
+ if (opts.visible) { pushBlock(source, true); return; }
1200
+ const label = lang ? lang.charAt(0).toUpperCase() + lang.slice(1) + ':\n\n' : '';
1201
+ pushBlock(label + source.split('\n').map(line => ' ' + line).join('\n'), true);
1202
+ return;
1203
+ }
1204
+ if (tag === 'UL' || tag === 'OL') {
1205
+ pushBlock(_rapierPortableListText(node, 0, opts), false);
1206
+ return;
1207
+ }
1208
+ if (tag === 'TABLE') {
1209
+ pushBlock(_rapierPortableTableText(node, opts), false);
1210
+ return;
1211
+ }
1212
+ if (tag === 'BLOCKQUOTE') {
1213
+ const before = blocks.length;
1214
+ collectChildren(node);
1215
+ const quoted = blocks.splice(before).filter(block => !block.blank).map(block => block.text).join('\n\n');
1216
+ if (quoted) pushBlock(opts.visible ? quoted : quoted.split('\n').map(line => line ? '> ' + line : '>').join('\n'), true);
1217
+ return;
1218
+ }
1219
+ if (tag === 'DL') {
1220
+ const children = Array.from(node.children);
1221
+ for (let i = 0; i < children.length; i++) {
1222
+ if (children[i].tagName !== 'DT') continue;
1223
+ const term = _rapierNormalizeInlineText(_rapierPortableInlineText(children[i], opts));
1224
+ const defs = [];
1225
+ while (children[i + 1] && children[i + 1].tagName === 'DD') {
1226
+ defs.push(_rapierNormalizeInlineText(_rapierPortableInlineText(children[++i], opts)));
1227
+ }
1228
+ pushBlock(term + (defs.length ? ' — ' + defs.join('\n ') : ''), false);
1229
+ }
1230
+ return;
1231
+ }
1232
+ if (tag === 'DETAILS') {
1233
+ const summary = node.querySelector(':scope > summary');
1234
+ if (summary) pushBlock(_rapierNormalizeInlineText(_rapierPortableInlineText(summary, opts)), false);
1235
+ Array.from(node.children).filter(child => child !== summary).forEach(collectBlock);
1236
+ return;
1237
+ }
1238
+ if (tag === 'FIGURE') {
1239
+ const image = node.querySelector(':scope > img');
1240
+ if (image) pushBlock(_rapierPortableInlineText(image, opts), false);
1241
+ const caption = node.querySelector(':scope > figcaption');
1242
+ if (caption) pushBlock(_rapierNormalizeInlineText(_rapierPortableInlineText(caption, opts)), false);
1243
+ return;
1244
+ }
1245
+ collectChildren(node);
1246
+ };
1247
+
1248
+ collectChildren(root);
1249
+ // Each block stands a blank line from the next, and each empty paragraph between them adds one newline more.
1250
+ let joined = '', blanks = 0;
1251
+ for (const block of blocks) {
1252
+ if (block.blank) { blanks++; continue; }
1253
+ joined += (joined ? '\n\n' + '\n'.repeat(blanks) : '') + block.text;
1254
+ blanks = 0;
1255
+ }
1256
+ const text = joined.trim();
1257
+ return opts.finalNewline && text ? text + '\n' : text;
1258
+ }
1259
+
1260
+ // The text file Export writes: a Markdown document's portable words and one final newline, any other document's own text.
1261
+ // The editor's Export and the door's txt both end here.
1262
+ function _rapierPlainTextFile(context) {
1263
+ return context.metadata.docKind === 'markdown'
1264
+ ? _rapierPortablePlainText(_rapierProjectPortableRoot(_rapierHeldPicturesAsWords(context.semanticRoot), { baseName: context.baseName }), { finalNewline: true })
1265
+ : context.canonical;
1266
+ }
1267
+
1268
+ function _rapierSourceTokenValue(node) {
1269
+ if (!node?.hasAttribute('data-rapier-source') || !node.hasAttribute('data-rapier-visible') ||
1270
+ Array.from(node.childNodes).some(child => child.nodeType !== Node.TEXT_NODE)) return null;
1271
+ try {
1272
+ return node.textContent === decodeURIComponent(node.getAttribute('data-rapier-visible'))
1273
+ ? decodeURIComponent(node.getAttribute('data-rapier-source')) : null;
1274
+ } catch (_) { return null; }
1275
+ }
1276
+
1277
+ function _rapierResolveSoftBreakTokens(root) {
1278
+ root.querySelectorAll('.rapier-source-token--softbreak').forEach(token => {
1279
+ if (_rapierSourceTokenValue(token) === '\n') token.replaceWith(document.createTextNode(' '));
1280
+ else token.replaceWith(...token.childNodes);
1281
+ });
1282
+ return root;
1283
+ }
1284
+
1285
+ function _rapierUnwrapElement(element) {
1286
+ while (element.firstChild) element.parentNode.insertBefore(element.firstChild, element);
1287
+ element.remove();
1288
+ }
1289
+
1290
+ function _rapierBidiStrong(cp) {
1291
+ const ch = String.fromCodePoint(cp);
1292
+ if (/\p{Nd}/u.test(ch)) return null;
1293
+ if (_RAPIER_RTL_SCRIPT.test(ch)) return 'R';
1294
+ if (/\p{L}/u.test(ch)) return 'L';
1295
+ return null;
1296
+ }
1297
+
1298
+ return {_rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierUnwrapElement, _rapierBidiStrong, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
822
1299
  }
823
- export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, createMarkdownRenderer};
1300
+ export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};