rapier-markdown-kit 1.1.90 → 1.1.92
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 +71 -37
- package/dist/agent/diff.mjs +52 -0
- package/dist/kit/ledger/authorship.mjs +3 -3
- package/dist/kit/ledger/carried.mjs +10 -10
- package/dist/kit/ledger/format.mjs +44 -28
- package/dist/kit/ledger/history.mjs +526 -0
- package/dist/kit/ledger/journal-records.mjs +216 -45
- package/dist/kit/ledger/merge.mjs +191 -51
- package/dist/kit/ledger/transport.mjs +318 -0
- package/dist/kit/rapier-ledger.mjs +12 -2
- package/dist/kit/render-markdown.mjs +227 -109
- package/dist/kit/render-styles.mjs +5 -4
- package/dist/kit/render-work.mjs +145 -0
- package/dist/kit/render.mjs +249 -76
- package/package.json +1 -1
package/dist/kit/render.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {finish, finishAsync, cloneTree, cleanTree, serializeTree, encodeUtf8Steps, pause} from './render-work.mjs';
|
|
2
3
|
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
3
4
|
function createRenderer(runtime) {
|
|
4
|
-
const {RAPIER_COLOR_CLOSE, RapierLedgerCarried, RapierPageReturnAddress, RapierTextCodec, _rapierArtifactHighlight,
|
|
5
|
+
const {RAPIER_COLOR_CLOSE, RapierLedgerCarried, RapierPageReturnAddress, RapierTextCodec, _rapierArtifactHighlight, _rapierArtifactPreference, _rapierArtifactStyles, _rapierBlobDataUrl, _rapierBuildInterchangeContext, _rapierDocumentNameIsAdmissible, _rapierLedgerParts, _rapierDrawReadSVGRecipe, _rapierDrawShapeProfileFor, _rapierExportFontCss, _rapierFillDiagram, _rapierFinalizeExportCode, _rapierFormatColorOpen, _rapierLanguageClass, _rapierLineStartOffsets, _rapierMarkdownEnvironment, _rapierPortableHtml, _rapierPrepareInterchangeContext, _rapierProjectPortableRoot, _rapierProviders, _rapierSourceCharEscaped, _rapierSourceLineSpan, crypto, document, escapeRapierHtmlText, globalThis, rapierConfirm, sanitizeRapierHtml} = runtime;
|
|
5
6
|
let md = runtime.md;
|
|
6
7
|
async function render(source, options = {}) {
|
|
7
8
|
if (typeof source !== 'string') throw new TypeError('Document source must be a string');
|
|
@@ -18,13 +19,15 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
18
19
|
const context = providedContext || _rapierBuildInterchangeContext(opts);
|
|
19
20
|
const metadata = context.metadata;
|
|
20
21
|
const base = context.baseName;
|
|
22
|
+
const work = context.work || opts.work;
|
|
21
23
|
|
|
22
24
|
if (opts.kind === 'publishing' || opts.kind === 'fragment') {
|
|
23
25
|
for (const diagram of context.semanticRoot.querySelectorAll('.diagram-block[data-diagram-native]'))
|
|
24
26
|
await _rapierFillDiagram(diagram, true);
|
|
25
|
-
const portableRoot =
|
|
27
|
+
const portableRoot = work ? await runtime._rapierProjectPortableRootAsync(context.semanticRoot, {baseName: base, keepDiagrams: true, work})
|
|
28
|
+
: _rapierProjectPortableRoot(context.semanticRoot, { baseName: base, keepDiagrams: true });
|
|
26
29
|
return {
|
|
27
|
-
html: _rapierPortableHtml(portableRoot),
|
|
30
|
+
html: work ? await runtime._rapierPortableHtmlAsync(portableRoot, work) : _rapierPortableHtml(portableRoot),
|
|
28
31
|
filename: base + '-publishing.html',
|
|
29
32
|
metadata,
|
|
30
33
|
};
|
|
@@ -34,25 +37,39 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
34
37
|
// Await the same materialization owner before any projection clones or serializes it.
|
|
35
38
|
for (const diagram of opts.kind === 'page' ? [] : context.semanticRoot.querySelectorAll('.diagram-block[data-diagram-src]')) {
|
|
36
39
|
await _rapierFillDiagram(diagram, true);
|
|
40
|
+
if (opts.kind !== 'standalone' || diagram.hasAttribute('data-diagram-unclosed')) continue;
|
|
41
|
+
const state = diagram.getAttribute('data-diagram-state');
|
|
42
|
+
// A completed diagram must have its drawing. Syntax and renderer failures both refuse
|
|
43
|
+
// the file; an unclosed fence above is still authored source, not a completed diagram.
|
|
44
|
+
if (state !== 'ready' || !diagram.querySelector('.diagram-cache svg'))
|
|
45
|
+
throw Object.assign(new Error('Web page not written; a required diagram could not be rendered'), {code: 'EXPORT_DIAGRAM_UNAVAILABLE'});
|
|
37
46
|
}
|
|
38
47
|
|
|
39
|
-
if (opts.kind === 'standalone')
|
|
48
|
+
if (opts.kind === 'standalone') {
|
|
49
|
+
if (context.semanticRoot.querySelector('.math-placeholder'))
|
|
50
|
+
throw new Error('Web page not written; the math plug-in is required to include its equations');
|
|
51
|
+
// MathJax represents invalid TeX as a static error SVG. A source-only span means
|
|
52
|
+
// rendering itself failed, so publishing it would silently omit the equation.
|
|
53
|
+
if (context.semanticRoot.querySelector('span[data-rapier-math-source]'))
|
|
54
|
+
throw new Error('Web page not written; a required equation could not be rendered');
|
|
55
|
+
}
|
|
56
|
+
if (opts.kind === 'standalone' || opts.kind === 'page') await _rapierRequireOfflinePageImages(context.semanticRoot);
|
|
40
57
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
baseName: base,
|
|
46
|
-
});
|
|
58
|
+
const styledOptions = {metadata, print: !!opts.print, sourceCode: opts.kind === 'page', baseName: base, work};
|
|
59
|
+
const styledRoot = work ? await _rapierProjectStyledRootAsync(context.semanticRoot, styledOptions)
|
|
60
|
+
: _rapierProjectStyledRoot(context.semanticRoot, styledOptions);
|
|
61
|
+
if (opts.kind === 'standalone' && typeof _rapierFinalizeExportCode === 'function') await _rapierFinalizeExportCode(styledRoot);
|
|
47
62
|
// _rapierProjectStyledRoot already returns the sanitized projection.
|
|
48
63
|
// Print never emits the layout reflow script (see below), so a print artifact has no use for
|
|
49
64
|
// this annotation -- skip it there rather than pay the cost for numbers nothing will read.
|
|
50
65
|
if (!opts.print && opts.kind !== 'page') _rapierAnnotateExportBoxPolygons(styledRoot);
|
|
51
66
|
// Share adds its nearest-side no-script float and long-code cap here. Offline-image
|
|
52
67
|
// admission above and the page policy below belong to this common writer, not its callers.
|
|
53
|
-
if (typeof opts.afterRoot === 'function') opts.afterRoot(styledRoot);
|
|
68
|
+
if (typeof opts.afterRoot === 'function') await opts.afterRoot(styledRoot);
|
|
54
69
|
const carrier = opts.kind === 'standalone' ? await _rapierSharedSourceCarrier(context, styledRoot) : '';
|
|
55
|
-
const bodyHtml = opts.kind === 'page' ? _rapierPageHtml(styledRoot) :
|
|
70
|
+
const bodyHtml = opts.kind === 'page' ? _rapierPageHtml(styledRoot) : work
|
|
71
|
+
? await finishAsync(serializeTree(styledRoot), work) : styledRoot.innerHTML;
|
|
72
|
+
if (work) await pause(work, .95);
|
|
56
73
|
const theme = opts.print
|
|
57
74
|
? 'light'
|
|
58
75
|
: opts.kind === 'page' ? 'system'
|
|
@@ -68,21 +85,23 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
68
85
|
// .md-render set and the highlight rules, so a numbered list, a checkbox, a callout and a
|
|
69
86
|
// table are drawn in an exported page exactly as they are drawn here. The one caller with
|
|
70
87
|
// rules of its own -- Share, for the no-script float it alone writes -- appends them.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const diagramFonts = !!styledRoot.querySelector('svg.rapier-diagram,svg.rapier-native-flowchart');
|
|
74
|
-
const includeFonts = !!opts.print || diagramFonts;
|
|
75
|
-
const zenFontCss = styledRoot.querySelector('svg[aria-roledescription="zenuml"]')
|
|
88
|
+
const includeFonts = !!opts.print || opts.kind === 'standalone';
|
|
89
|
+
const zenFontCss = opts.print && styledRoot.querySelector('svg[aria-roledescription="zenuml"]')
|
|
76
90
|
? (_rapierProviders?.mermaid?.exportFontCss?.() || '') : '';
|
|
77
91
|
const willFont = opts.print && opts.willFont ? opts.willFont : null;
|
|
78
92
|
const settings = _rapierDocumentSettingsOf(context.canonical);
|
|
79
93
|
const settingsCss = _rapierDocumentSettingsCss(settings);
|
|
80
94
|
const pageTitle = settings && settings.title ? globalThis.RapierMarkdownSpec.documentTitle(settings) : metadata.filename;
|
|
81
|
-
|
|
95
|
+
let css = _rapierArtifactStyles(theme, includeFonts, !!opts.print || opts.kind === 'standalone', opts.kind === 'page', opts.kind === 'standalone' ? '' : null)
|
|
82
96
|
+ (zenFontCss ? '\n\n' + zenFontCss : '')
|
|
83
97
|
+ (opts.extraCss ? '\n\n' + opts.extraCss : '')
|
|
84
98
|
+ (willFont ? '\n\n@font-face{font-family:' + willFont.family + ';src:url(' + await _rapierBlobDataUrl(new Blob([willFont.bytes], { type: 'font/ttf' })) + ') format("truetype")}' : '')
|
|
85
99
|
+ (settingsCss ? '\n' + settingsCss : '');
|
|
100
|
+
// Match the document's measured faces with only the glyphs this immutable page uses.
|
|
101
|
+
if (opts.kind === 'standalone') {
|
|
102
|
+
const fonts = await _rapierExportFontCss(styledRoot, {settings, css, theme});
|
|
103
|
+
if (fonts) css += '\n' + fonts;
|
|
104
|
+
}
|
|
86
105
|
// Every written page is offline. Only the writer's nonce script may run; it, pictures,
|
|
87
106
|
// styles and other page resources get no network authority. Following an ordinary link
|
|
88
107
|
// is the reader's navigation, with no Referer, not a background resource request.
|
|
@@ -91,9 +110,6 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
91
110
|
// script outright. Declaring a nonce a page has no use for is a widening, small but real -- anything that could inject markup into the file could read the
|
|
92
111
|
// nonce out of it and be admitted.
|
|
93
112
|
const layoutScript = !opts.print && opts.kind !== 'page' ? _rapierArtifactLayoutScript(styledRoot, nonce) : '';
|
|
94
|
-
// The lexer for the page's code, under the same nonce, only when a block earned it: the CPU
|
|
95
|
-
// spans are the first paint, and where the reader's browser has WebGPU the lexer repaints them.
|
|
96
|
-
const lexerScript = !opts.print && opts.kind !== 'page' ? _rapierArtifactLexerScript(styledRoot, nonce) : '';
|
|
97
113
|
const inkScript = !opts.print && opts.kind !== 'page' ? _rapierArtifactInkScript(styledRoot, nonce) : '';
|
|
98
114
|
const page = '<!DOCTYPE html>\n'
|
|
99
115
|
+ '<html lang="en" data-rapier-theme="' + theme + '" data-highlights="' + highlights + '">\n'
|
|
@@ -104,13 +120,14 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
104
120
|
// someone's page shows whole, with no inner scroll. Any origin may read the height: the page is the
|
|
105
121
|
// document, published by the person who exported it.
|
|
106
122
|
+ '<meta name="responsive-embedded-sizing" content="allow-origins=*">\n'
|
|
107
|
-
+ '<meta http-equiv="Content-Security-Policy" content="' + _rapierExportedPageCsp(layoutScript ||
|
|
123
|
+
+ '<meta http-equiv="Content-Security-Policy" content="' + _rapierExportedPageCsp(layoutScript || inkScript ? nonce : '', includeFonts) + '">\n'
|
|
108
124
|
+ '<meta name="referrer" content="no-referrer">\n'
|
|
125
|
+
+ '<link rel="icon" href="data:,">\n'
|
|
109
126
|
+ '<meta name="generator" content="Rapier ' + escapeRapierHtmlText(version) + '">\n'
|
|
110
127
|
+ '<title>' + escapeRapierHtmlText(pageTitle) + '</title>\n'
|
|
111
128
|
+ '<style>' + css + '</style>\n</head>\n'
|
|
112
129
|
+ '<body class="' + bodyClass + '"><main class="' + docClass + '" data-md-theme="' + theme + '">'
|
|
113
|
-
+ bodyHtml + '</main>' + carrier + layoutScript +
|
|
130
|
+
+ bodyHtml + '</main>' + carrier + layoutScript + inkScript + '</body>\n</html>';
|
|
114
131
|
|
|
115
132
|
return {
|
|
116
133
|
html: page,
|
|
@@ -141,9 +158,12 @@ function _rapierPageHtml(root) {
|
|
|
141
158
|
return children(root);
|
|
142
159
|
}
|
|
143
160
|
|
|
144
|
-
function _rapierProjectStyledRoot(semanticRoot, options) {
|
|
161
|
+
function _rapierProjectStyledRoot(semanticRoot, options) { return finish(_rapierProjectStyledRootSteps(semanticRoot, options)); }
|
|
162
|
+
async function _rapierProjectStyledRootAsync(semanticRoot, options = {}) { return finishAsync(_rapierProjectStyledRootSteps(semanticRoot, options), options.work); }
|
|
163
|
+
|
|
164
|
+
function* _rapierProjectStyledRootSteps(semanticRoot, options) {
|
|
145
165
|
const opts = options || {};
|
|
146
|
-
const root = semanticRoot
|
|
166
|
+
const root = yield* cloneTree(semanticRoot);
|
|
147
167
|
const metadata = opts.metadata || {};
|
|
148
168
|
|
|
149
169
|
if (metadata.docKind !== 'markdown') {
|
|
@@ -157,55 +177,53 @@ function _rapierProjectStyledRoot(semanticRoot, options) {
|
|
|
157
177
|
if (code) {
|
|
158
178
|
const lang = _rapierLanguageClass(code) || String(metadata.codeLang || '');
|
|
159
179
|
code.classList.add('artifact-code');
|
|
160
|
-
if (metadata.docKind === 'code' && !opts.sourceCode)
|
|
180
|
+
if (metadata.docKind === 'code' && !opts.sourceCode) _rapierPaintCode(code, _rapierArtifactHighlight(code.textContent || '', lang));
|
|
161
181
|
if (lang) code.classList.add('language-' + String(lang).replace(/[^a-z0-9_-]/gi, ''));
|
|
162
182
|
}
|
|
163
183
|
} else {
|
|
164
|
-
root.querySelectorAll('pre > code')
|
|
165
|
-
if (opts.sourceCode)
|
|
184
|
+
for (const code of root.querySelectorAll('pre > code')) { yield;
|
|
185
|
+
if (opts.sourceCode || code.parentElement?.classList.contains('diagram-source') &&
|
|
186
|
+
code.closest('.diagram-block')?.getAttribute('data-diagram-state') === 'ready') continue;
|
|
166
187
|
const lang = _rapierLanguageClass(code) || 'text';
|
|
167
188
|
_rapierPaintCode(code, _rapierArtifactHighlight(code.textContent || '', lang));
|
|
168
|
-
|
|
169
|
-
});
|
|
189
|
+
}
|
|
170
190
|
|
|
171
|
-
root.querySelectorAll('table')
|
|
172
|
-
if (table.parentElement && table.parentElement.classList.contains('table-scroll-wrap'))
|
|
191
|
+
for (const table of root.querySelectorAll('table')) { yield;
|
|
192
|
+
if (table.parentElement && table.parentElement.classList.contains('table-scroll-wrap')) continue;
|
|
173
193
|
const wrap = document.createElement('div');
|
|
174
194
|
wrap.className = 'table-scroll-wrap';
|
|
175
195
|
table.parentNode.insertBefore(wrap, table);
|
|
176
196
|
wrap.appendChild(table);
|
|
177
|
-
}
|
|
197
|
+
}
|
|
178
198
|
|
|
179
|
-
root.querySelectorAll('math[display="block"]')
|
|
180
|
-
if (math.closest('svg') || math.closest('.math-display-wrap'))
|
|
199
|
+
for (const math of root.querySelectorAll('math[display="block"]')) { yield;
|
|
200
|
+
if (math.closest('svg') || math.closest('.math-display-wrap')) continue;
|
|
181
201
|
const wrap = document.createElement('span');
|
|
182
202
|
wrap.className = 'math-display-wrap';
|
|
183
203
|
math.parentNode.insertBefore(wrap, math);
|
|
184
204
|
wrap.appendChild(math);
|
|
185
|
-
}
|
|
205
|
+
}
|
|
186
206
|
|
|
187
|
-
root.querySelectorAll('input[type="checkbox"]')
|
|
207
|
+
for (const input of root.querySelectorAll('input[type="checkbox"]')) { yield;
|
|
188
208
|
const item = input.closest('.task-list-item');
|
|
189
209
|
input.disabled = !!opts.print || !item;
|
|
190
|
-
if (input.disabled) { input.setAttribute('aria-disabled', 'true');
|
|
210
|
+
if (input.disabled) { input.setAttribute('aria-disabled', 'true'); continue; }
|
|
191
211
|
// Native checked state changes this reader's view, never the carried source or disk.
|
|
192
212
|
input.removeAttribute('aria-disabled');
|
|
193
213
|
input.removeAttribute('aria-checked');
|
|
194
214
|
input.setAttribute('autocomplete', 'off');
|
|
195
215
|
input.setAttribute('title', 'Ticks change this view, not the saved file.');
|
|
196
216
|
const label = item.cloneNode(true);
|
|
197
|
-
label.querySelectorAll('input,ul,ol')
|
|
217
|
+
for (const node of label.querySelectorAll('input,ul,ol')) { yield;node.remove();}
|
|
198
218
|
input.setAttribute('aria-label', String(label.textContent || '').replace(/\s+/g, ' ').trim() || 'Task');
|
|
199
|
-
}
|
|
219
|
+
}
|
|
200
220
|
|
|
201
|
-
if (opts.print) root.querySelectorAll('details')
|
|
221
|
+
if (opts.print) for (const details of root.querySelectorAll('details')) { yield; details.open = true; }
|
|
202
222
|
}
|
|
203
223
|
|
|
204
|
-
|
|
224
|
+
yield* _rapierPrefixPortableAnchorsSteps(root, opts.baseName || metadata.filename || 'document');
|
|
205
225
|
|
|
206
|
-
|
|
207
|
-
clean.innerHTML = sanitizeRapierHtml(root.innerHTML, opts.sourceCode ? 'source' : 'export');
|
|
208
|
-
return clean;
|
|
226
|
+
return yield* cleanTree(root, sanitizeRapierHtml, opts.sourceCode ? 'source' : 'export');
|
|
209
227
|
}
|
|
210
228
|
|
|
211
229
|
function _rapierPaintCode(el, html) {
|
|
@@ -213,7 +231,9 @@ function _rapierPaintCode(el, html) {
|
|
|
213
231
|
el.classList.add('tok');
|
|
214
232
|
}
|
|
215
233
|
|
|
216
|
-
function _rapierPrefixPortableAnchors(root, baseName) {
|
|
234
|
+
function _rapierPrefixPortableAnchors(root, baseName) { return finish(_rapierPrefixPortableAnchorsSteps(root, baseName)); }
|
|
235
|
+
|
|
236
|
+
function* _rapierPrefixPortableAnchorsSteps(root, baseName) {
|
|
217
237
|
const slug = String(baseName || 'document').toLowerCase()
|
|
218
238
|
.replace(/[^a-z0-9]+/g, '-')
|
|
219
239
|
.replace(/^-+|-+$/g, '') || 'document';
|
|
@@ -231,9 +251,9 @@ function _rapierPrefixPortableAnchors(root, baseName) {
|
|
|
231
251
|
return svg;
|
|
232
252
|
};
|
|
233
253
|
|
|
234
|
-
root.querySelectorAll('[id]')
|
|
254
|
+
for (const element of root.querySelectorAll('[id]')) { yield;
|
|
235
255
|
const oldId = element.id;
|
|
236
|
-
if (!oldId)
|
|
256
|
+
if (!oldId) continue;
|
|
237
257
|
const base = oldId.replace(/[^a-z0-9_-]+/gi, '-')
|
|
238
258
|
.replace(/^-+|-+$/g, '') || 'anchor';
|
|
239
259
|
let nextId = prefix + base;
|
|
@@ -250,9 +270,9 @@ function _rapierPrefixPortableAnchors(root, baseName) {
|
|
|
250
270
|
if (!local.has(oldId)) local.set(oldId, nextId);
|
|
251
271
|
}
|
|
252
272
|
element.id = nextId;
|
|
253
|
-
}
|
|
273
|
+
}
|
|
254
274
|
|
|
255
|
-
root.querySelectorAll('*')
|
|
275
|
+
for (const element of root.querySelectorAll('*')) { yield;
|
|
256
276
|
const svg = scopeOf(element), local = svgIds.get(svg);
|
|
257
277
|
const rewriteId = value => (svg ? local?.get(value) : ids.get(value)) || '';
|
|
258
278
|
// URL fragments percent-decode once; ARIA/for/headers are literal IDREFs.
|
|
@@ -296,7 +316,7 @@ function _rapierPrefixPortableAnchors(root, baseName) {
|
|
|
296
316
|
if (next !== value) element.setAttribute(name, next);
|
|
297
317
|
}
|
|
298
318
|
}
|
|
299
|
-
}
|
|
319
|
+
}
|
|
300
320
|
}
|
|
301
321
|
|
|
302
322
|
function _rapierDocumentSettingsOf(canonical) {
|
|
@@ -315,7 +335,7 @@ function _rapierDocumentSettingsCss(settings) {
|
|
|
315
335
|
}
|
|
316
336
|
|
|
317
337
|
function _rapierExportedPageCsp(nonce, fonts) {
|
|
318
|
-
return "default-src 'none'; img-src data
|
|
338
|
+
return "default-src 'none'; connect-src 'none'; img-src data:; style-src 'unsafe-inline'; "
|
|
319
339
|
+ (fonts ? "font-src data:; " : '')
|
|
320
340
|
+ "script-src " + (nonce ? "'nonce-" + nonce + "'" : "'none'") + "; base-uri 'none'; form-action 'none'";
|
|
321
341
|
}
|
|
@@ -368,15 +388,20 @@ function _rapierAnnotateExportBoxPolygons(root) {
|
|
|
368
388
|
}
|
|
369
389
|
}
|
|
370
390
|
|
|
371
|
-
async function _rapierSharedSourceHash(source) {
|
|
372
|
-
const bytes = new TextEncoder().encode(source);
|
|
391
|
+
async function _rapierSharedSourceHash(source, work = null) {
|
|
392
|
+
const bytes = work ? await finishAsync(encodeUtf8Steps(source), work) : new TextEncoder().encode(source);
|
|
373
393
|
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
|
|
374
394
|
return Array.from(hash, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
375
395
|
}
|
|
376
396
|
|
|
377
|
-
function _rapierSharedSourceForms(source, root, substitutions) {
|
|
397
|
+
function _rapierSharedSourceForms(source, root, substitutions) { return finish(_rapierSharedSourceFormsSteps(source, root, substitutions)); }
|
|
398
|
+
async function _rapierSharedSourceFormsAsync(source, root, substitutions, work) {
|
|
399
|
+
const destinations = await _rapierDataImageDestinationsAsync(source, work);
|
|
400
|
+
return finishAsync(_rapierSharedSourceFormsSteps(source, root, substitutions, destinations), work);
|
|
401
|
+
}
|
|
402
|
+
function* _rapierSharedSourceFormsSteps(source, root, substitutions, preparedDestinations = null) {
|
|
378
403
|
const assets = globalThis.RapierImageAssets;
|
|
379
|
-
const destinations = _rapierImageDestinations(source, url => !!assets.dataImage(url)).sort((a, b) => b.start - a.start);
|
|
404
|
+
const destinations = (preparedDestinations || _rapierImageDestinations(source, url => !!assets.dataImage(url))).sort((a, b) => b.start - a.start);
|
|
380
405
|
const byUrl = new Map(), ids = new Map(), labels = new Map();
|
|
381
406
|
for (const node of root.querySelectorAll('[id]')) ids.set(node.id, (ids.get(node.id) || 0) + 1);
|
|
382
407
|
for (const match of source.matchAll(/^ {0,3}\[([^\]\r\n]+)\]:[ \t]*(data:image\/\S+)/gim)) if (!labels.has(match[2])) labels.set(match[2], match[1]);
|
|
@@ -389,6 +414,7 @@ function _rapierSharedSourceForms(source, root, substitutions) {
|
|
|
389
414
|
const carrierEdits = [];
|
|
390
415
|
const used = [], definitions = [];
|
|
391
416
|
for (const row of destinations) {
|
|
417
|
+
yield;
|
|
392
418
|
const shownUrl = substitutions?.get(row.destination) || row.destination;
|
|
393
419
|
const image = byUrl.get(shownUrl);
|
|
394
420
|
if (!image) { if (shownUrl !== row.destination) { resolved = resolved.slice(0, row.start) + shownUrl + resolved.slice(row.end); carrierEdits.push({start: row.start, end: row.end, text: _rapierSharedEncode(shownUrl)}); } continue; }
|
|
@@ -407,25 +433,27 @@ function _rapierSharedSourceForms(source, root, substitutions) {
|
|
|
407
433
|
}
|
|
408
434
|
let carried = '', cursor = source.length;
|
|
409
435
|
for (const edit of carrierEdits) {
|
|
410
|
-
|
|
436
|
+
yield;
|
|
437
|
+
carried = edit.text + (yield* _rapierSharedEncodeSteps(source.slice(edit.end, cursor))) + carried;
|
|
411
438
|
cursor = edit.start;
|
|
412
439
|
}
|
|
413
|
-
carried =
|
|
440
|
+
carried = (yield* _rapierSharedEncodeSteps(source.slice(0, cursor))) + carried;
|
|
414
441
|
return { resolved, carried, ids: used, definitions };
|
|
415
442
|
}
|
|
416
443
|
|
|
417
444
|
async function _rapierSharedSourceCarrier(context, root) {
|
|
418
445
|
const source = (context.metadata.bom ? '\uFEFF' : '') + context.canonical;
|
|
419
|
-
const forms =
|
|
446
|
+
const forms = context.work ? await _rapierSharedSourceFormsAsync(source, root, context.imageSubstitutions, context.work)
|
|
447
|
+
: _rapierSharedSourceForms(source, root, context.imageSubstitutions);
|
|
420
448
|
const esc = escapeRapierHtmlText;
|
|
421
449
|
return '<script type="' + _RAPIER_SHARED_SOURCE_TYPE + '" data-filename="' + esc(context.metadata.filename) +
|
|
422
|
-
'" data-kind="' + esc(context.metadata.docKind) + '" data-sha256="' + await _rapierSharedSourceHash(forms.resolved) +
|
|
450
|
+
'" data-kind="' + esc(context.metadata.docKind) + '" data-sha256="' + await _rapierSharedSourceHash(forms.resolved, context.work) +
|
|
423
451
|
'"' + (forms.ids.length ? ' data-images="' + forms.ids.join(' ') + '"' : '') +
|
|
424
452
|
(forms.definitions.length ? ' data-image-definitions="' + forms.definitions.map(encodeURIComponent).join(' ') + '"' : '') + '>\n' +
|
|
425
453
|
forms.carried + '\n</script>\n' +
|
|
426
454
|
// The optional edit ledger: carried only when the host binds its owner; a host without one writes none.
|
|
427
455
|
(RapierLedgerCarried ? RapierLedgerCarried.writeParts(forms.resolved, _rapierLedgerParts(forms.resolved, context.ledger, context.carried)) +
|
|
428
|
-
RapierLedgerCarried.writeBase(forms.resolved, context.
|
|
456
|
+
RapierLedgerCarried.writeBase(forms.resolved, context.comparisonBase) : '');
|
|
429
457
|
}
|
|
430
458
|
|
|
431
459
|
function _rapierSharedResolve(text, ids, images, definitions) {
|
|
@@ -482,8 +510,8 @@ async function _rapierReadSharedDocument(text, filename) {
|
|
|
482
510
|
const bom = source.charCodeAt(0) === 0xFEFF;
|
|
483
511
|
if (RapierTextCodec.normalizeDocument(source) !== (bom ? source.slice(1) : source)) return invalid();
|
|
484
512
|
const parts = RapierLedgerCarried ? RapierLedgerCarried.readParts(source, Array.from(template.content.querySelectorAll('#rapier-ledger, #rapier-authorship'))) : {};
|
|
485
|
-
const
|
|
486
|
-
return {source, filename: name, kind, bom, ...parts,
|
|
513
|
+
const comparisonBase = RapierLedgerCarried?.readBaseElements(Array.from(template.content.querySelectorAll('#rapier-base'))) || null;
|
|
514
|
+
return {source, filename: name, kind, bom, ...parts, comparisonBase};
|
|
487
515
|
}
|
|
488
516
|
|
|
489
517
|
function _rapierSharedPageFallbackCss() {
|
|
@@ -512,7 +540,8 @@ function _rapierSharePictureParagraph(image) {
|
|
|
512
540
|
return paragraph;
|
|
513
541
|
}
|
|
514
542
|
|
|
515
|
-
function _rapierSharedPageLayout(root) {
|
|
543
|
+
function _rapierSharedPageLayout(root) { return finish(_rapierSharedPageLayoutSteps(root)); }
|
|
544
|
+
function* _rapierSharedPageLayoutSteps(root) {
|
|
516
545
|
// Owner search (wrapNeighbour) reads the sibling chain live, and applying one assignment
|
|
517
546
|
// splices the tree -- moving the owner into a new container moves it out of the very chain
|
|
518
547
|
// a later picture's own search would walk, and a barrier-free skip past an earlier picture
|
|
@@ -523,6 +552,7 @@ function _rapierSharedPageLayout(root) {
|
|
|
523
552
|
// from one upfront `[...root.children]` before it changes anything about any of them.
|
|
524
553
|
const assignments = [];
|
|
525
554
|
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
555
|
+
yield;
|
|
526
556
|
const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
527
557
|
if (layout?.wrap !== 'around' && layout?.wrap !== 'box') continue;
|
|
528
558
|
const paragraph = _rapierSharePictureParagraph(image);
|
|
@@ -541,6 +571,7 @@ function _rapierSharedPageLayout(root) {
|
|
|
541
571
|
// still gets a sensible nearest-side float; the inlined planner (same owner as standalone)
|
|
542
572
|
// replaces it when scripts run.
|
|
543
573
|
for (const {image, paragraph, layout, owner} of assignments) {
|
|
574
|
+
yield;
|
|
544
575
|
const side = (layout.x ?? 0) < 50 ? 'left' : 'right';
|
|
545
576
|
paragraph.style.cssText = 'float:' + side + ';max-width:100%;margin:0 ' + (side === 'left' ? '1rem 1rem 0' : '0 1rem 1rem');
|
|
546
577
|
if (layout.y > 0) {
|
|
@@ -634,12 +665,14 @@ function _rapierSharedPageLayout(root) {
|
|
|
634
665
|
// removing the picture from flow costs the surrounding text nothing.
|
|
635
666
|
const positionedAssignments = [];
|
|
636
667
|
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
668
|
+
yield;
|
|
637
669
|
const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
638
670
|
if (layout?.wrap !== 'behind' && layout?.wrap !== 'front') continue;
|
|
639
671
|
const paragraph = _rapierSharePictureParagraph(image);
|
|
640
672
|
if (paragraph) positionedAssignments.push({image, paragraph, layout});
|
|
641
673
|
}
|
|
642
674
|
for (const {image, paragraph, layout} of positionedAssignments) {
|
|
675
|
+
yield;
|
|
643
676
|
paragraph.style.cssText = 'position:relative;height:0;margin:0;padding:0;line-height:0';
|
|
644
677
|
const x = layout.x ?? 50, naturalWidth = Number(image.getAttribute('width')) || 0;
|
|
645
678
|
if (layout.width != null) {
|
|
@@ -665,6 +698,7 @@ function _rapierSharedPageLayout(root) {
|
|
|
665
698
|
// Share's own float model has nothing else to say about it.
|
|
666
699
|
const positioned = new Set([...assignments, ...positionedAssignments].map(row => row.image));
|
|
667
700
|
for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
|
|
701
|
+
yield;
|
|
668
702
|
if (positioned.has(image)) continue;
|
|
669
703
|
const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
|
|
670
704
|
if (!layout?.rotate) continue;
|
|
@@ -682,50 +716,175 @@ function _rapierSharedPageLayout(root) {
|
|
|
682
716
|
}
|
|
683
717
|
}
|
|
684
718
|
|
|
685
|
-
function _rapierShareCapLongCodeBlocks(root) {
|
|
686
|
-
|
|
719
|
+
function _rapierShareCapLongCodeBlocks(root) { return finish(_rapierShareCapLongCodeBlocksSteps(root)); }
|
|
720
|
+
function* _rapierShareCapLongCodeBlocksSteps(root) {
|
|
721
|
+
for (const pre of root.querySelectorAll('pre')) { yield;
|
|
687
722
|
const code = pre.querySelector(':scope > code') || pre;
|
|
688
723
|
const text = String(code.textContent || '').replace(/\n$/, '');
|
|
689
724
|
const lineCount = text === '' ? 0 : text.split('\n').length;
|
|
690
|
-
if (lineCount <= RAPIER_SHARE_LONG_CODE_LINES)
|
|
725
|
+
if (lineCount <= RAPIER_SHARE_LONG_CODE_LINES) continue;
|
|
691
726
|
pre.setAttribute('data-rapier-code-lines', String(lineCount));
|
|
692
727
|
const note = document.createElement('span');
|
|
693
728
|
note.className = 'rapier-code-lines-note';
|
|
694
729
|
note.setAttribute('contenteditable', 'false');
|
|
695
730
|
note.textContent = lineCount + ' lines';
|
|
696
731
|
pre.appendChild(note);
|
|
697
|
-
}
|
|
732
|
+
}
|
|
698
733
|
}
|
|
699
734
|
|
|
700
735
|
async function _rapierRequireOfflinePageImages(root) {
|
|
701
736
|
const missing = [];
|
|
737
|
+
const embedded = value => globalThis.RapierImageAssets.dataImage(String(value || '').trim());
|
|
738
|
+
const requireResource = (value, label) => {
|
|
739
|
+
const source = String(value || '').trim();
|
|
740
|
+
if (!source || source.startsWith('#') || embedded(source)) return;
|
|
741
|
+
missing.push(label + ': ' + (source.startsWith('data:') ? '(unsupported embedded resource)' : source));
|
|
742
|
+
};
|
|
702
743
|
for (const [index, image] of [...root.querySelectorAll('img,[data-rapier-remote-src]')].entries()) {
|
|
703
744
|
const src = image.getAttribute('data-rapier-remote-src') ?? image.getAttribute('src') ?? '';
|
|
704
|
-
if (
|
|
745
|
+
if (embedded(src)) continue;
|
|
705
746
|
const alt = image.getAttribute('data-rapier-remote-alt') ?? image.getAttribute('alt') ?? '';
|
|
706
747
|
const destination = /^data:/i.test(src) ? '(unsupported embedded image)' : src || '(missing image source)';
|
|
707
748
|
missing.push('Picture ' + (index + 1) + (alt ? ' — ' + alt : '') + ': ' + destination);
|
|
708
749
|
}
|
|
750
|
+
// A source candidate or an SVG/CSS picture is still an automatic resource. Inspect
|
|
751
|
+
// the retained data; ordinary links remain navigation and local fragment references stay local.
|
|
752
|
+
const sourceTags = new Set(['audio', 'video', 'source', 'track', 'input', 'iframe', 'embed', 'script']);
|
|
753
|
+
const cssAttributes = new Set(['style', 'fill', 'stroke', 'filter', 'clip-path', 'mask', 'marker-start', 'marker-mid', 'marker-end', 'cursor', 'color-profile']);
|
|
754
|
+
for (const element of root.querySelectorAll('*')) {
|
|
755
|
+
const tag = String(element.localName || element.tagName || '').toLowerCase();
|
|
756
|
+
if (sourceTags.has(tag)) requireResource(element.getAttribute('src'), tag + ' source');
|
|
757
|
+
for (const attribute of ['poster', 'background']) requireResource(element.getAttribute(attribute), tag + ' ' + attribute);
|
|
758
|
+
if (tag === 'object') requireResource(element.getAttribute('data'), 'object source');
|
|
759
|
+
if (tag === 'link' || element.namespaceURI === 'http://www.w3.org/2000/svg' && tag !== 'a')
|
|
760
|
+
for (const attribute of ['href', 'xlink:href']) requireResource(element.getAttribute(attribute), tag + ' ' + attribute);
|
|
761
|
+
if (tag === 'img' || tag === 'source') for (const source of _rapierSrcsetUrls(element.getAttribute('srcset') || ''))
|
|
762
|
+
requireResource(source, tag + ' source candidate');
|
|
763
|
+
for (const attribute of element.attributes || []) if (cssAttributes.has(attribute.name.toLowerCase())) {
|
|
764
|
+
const urls = _rapierCssValueUrls(attribute.value);
|
|
765
|
+
if (urls === null) missing.push(tag + ' ' + attribute.name + ': (unresolved CSS resource)');
|
|
766
|
+
else for (const url of urls) requireResource(url, tag + ' ' + attribute.name);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
709
769
|
if (!missing.length) return;
|
|
710
770
|
// An acknowledged, scrollable sheet, not a disappearing toast or a partial-file opt-in.
|
|
711
771
|
// Both Close and Cancel below refuse; neither can authorize a page with missing pictures.
|
|
712
|
-
await rapierConfirm({
|
|
772
|
+
if (typeof rapierConfirm === 'function') await rapierConfirm({
|
|
713
773
|
title: 'pictures not included',
|
|
714
774
|
message: 'No web page was written. Embed these pictures before exporting or sharing an offline page. '
|
|
715
775
|
+ 'Your document is unchanged. ' + missing.join('; '),
|
|
716
776
|
confirmLabel: 'close',
|
|
717
777
|
});
|
|
718
|
-
throw new Error('Web page not written; embed the listed pictures and try again');
|
|
778
|
+
throw Object.assign(new Error('Web page not written; embed the listed pictures and try again'), {code: 'EXPORT_IMAGE_UNAVAILABLE'});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function _rapierSrcsetUrls(value) {
|
|
782
|
+
const urls = [];
|
|
783
|
+
let at = 0;
|
|
784
|
+
while (at < value.length) {
|
|
785
|
+
while (at < value.length && /[\t\n\f\r ,]/.test(value[at])) at++;
|
|
786
|
+
const start = at;
|
|
787
|
+
while (at < value.length && !/[\t\n\f\r ]/.test(value[at])) at++;
|
|
788
|
+
const source = value.slice(start, at);
|
|
789
|
+
if (!source) break;
|
|
790
|
+
// A data URL's comma belongs to its URL. Only trailing separators or the
|
|
791
|
+
// comma after a descriptor finish a candidate.
|
|
792
|
+
if (source.endsWith(',')) { urls.push(source.replace(/,+$/, '')); continue; }
|
|
793
|
+
urls.push(source);
|
|
794
|
+
let parentheses = 0;
|
|
795
|
+
while (at < value.length) {
|
|
796
|
+
const char = value[at++];
|
|
797
|
+
if (char === '(') parentheses++;
|
|
798
|
+
else if (char === ')' && parentheses) parentheses--;
|
|
799
|
+
else if (char === ',' && !parentheses) break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return urls;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Shared by live CSS admission and offline resource admission. Quoted strings are
|
|
806
|
+
// resource URLs only inside url() or as an image-set option, never arbitrary CSS text.
|
|
807
|
+
function _rapierCssValueUrls(value) {
|
|
808
|
+
const urls = [], functions = [];
|
|
809
|
+
let at = 0;
|
|
810
|
+
const space = char => char !== undefined && /[\t\n\f\r ]/.test(char);
|
|
811
|
+
const escape = () => {
|
|
812
|
+
at++;
|
|
813
|
+
if (value[at] === '\r' || value[at] === '\n' || value[at] === '\f') {
|
|
814
|
+
if (value[at++] === '\r' && value[at] === '\n') at++;
|
|
815
|
+
return '';
|
|
816
|
+
}
|
|
817
|
+
const hex = /^[\da-f]{1,6}/i.exec(value.slice(at));
|
|
818
|
+
if (!hex) return value[at++] || '';
|
|
819
|
+
at += hex[0].length;
|
|
820
|
+
if (space(value[at]) && value[at++] === '\r' && value[at] === '\n') at++;
|
|
821
|
+
const code = parseInt(hex[0], 16);
|
|
822
|
+
return String.fromCodePoint(!code || code > 0x10ffff || code >= 0xd800 && code <= 0xdfff ? 0xfffd : code);
|
|
823
|
+
};
|
|
824
|
+
const string = () => {
|
|
825
|
+
const quote = value[at++];
|
|
826
|
+
let text = '';
|
|
827
|
+
while (at < value.length) {
|
|
828
|
+
if (value[at] === quote) { at++; return text; }
|
|
829
|
+
if (/[\r\n\f]/.test(value[at])) return null;
|
|
830
|
+
text += value[at] === '\\' ? escape() : value[at++];
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
};
|
|
834
|
+
const whitespace = () => {
|
|
835
|
+
for (;;) {
|
|
836
|
+
while (space(value[at])) at++;
|
|
837
|
+
if (value.slice(at, at + 2) !== '/*') return;
|
|
838
|
+
const end = value.indexOf('*/', at + 2);
|
|
839
|
+
at = end < 0 ? value.length : end + 2;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
while (at < value.length) {
|
|
843
|
+
whitespace();
|
|
844
|
+
if (at >= value.length) break;
|
|
845
|
+
const parent = functions.at(-1), char = value[at];
|
|
846
|
+
if (char === '"' || char === "'") {
|
|
847
|
+
const text = string();
|
|
848
|
+
if (parent?.imageSet && parent.option) {
|
|
849
|
+
if (text === null) return null;
|
|
850
|
+
urls.push(text);
|
|
851
|
+
parent.option = false;
|
|
852
|
+
}
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (char === ',') { if (parent?.imageSet) parent.option = true; at++; continue; }
|
|
856
|
+
if (char === ')') { functions.pop(); at++; continue; }
|
|
857
|
+
if (parent?.imageSet) parent.option = false;
|
|
858
|
+
if (/[-\w\u0080-\uffff\\]/.test(char)) {
|
|
859
|
+
let name = '';
|
|
860
|
+
while (at < value.length && /[-\w\u0080-\uffff\\]/.test(value[at]))
|
|
861
|
+
name += value[at] === '\\' ? escape() : value[at++];
|
|
862
|
+
if (value[at] !== '(') continue;
|
|
863
|
+
at++;
|
|
864
|
+
name = name.toLowerCase();
|
|
865
|
+
if (name !== 'url') { functions.push({imageSet: name === 'image-set' || name === '-webkit-image-set', option: true}); continue; }
|
|
866
|
+
while (space(value[at])) at++;
|
|
867
|
+
let url = '';
|
|
868
|
+
if (value[at] === '"' || value[at] === "'") { url = string(); whitespace(); }
|
|
869
|
+
else while (at < value.length && value[at] !== ')') url += value[at] === '\\' ? escape() : value[at++];
|
|
870
|
+
if (url === null || value[at++] !== ')') return null;
|
|
871
|
+
urls.push(url.trim());
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (char === '(') functions.push({imageSet: false});
|
|
875
|
+
at++;
|
|
876
|
+
}
|
|
877
|
+
return urls;
|
|
719
878
|
}
|
|
720
879
|
|
|
721
880
|
async function _rapierBuildSharedPage(captured) {
|
|
722
|
-
const context = await _rapierPrepareInterchangeContext({kind: 'share'}, captured
|
|
881
|
+
const context = await _rapierPrepareInterchangeContext({kind: 'share'}, captured);
|
|
723
882
|
// One owner for every HTML file Rapier writes, including offline-image admission and CSP.
|
|
724
883
|
// Share adds only the nearest-side no-script float and the long-code treatment.
|
|
725
884
|
const artifact = await _rapierBuildArtifact({
|
|
726
885
|
kind: 'standalone',
|
|
727
886
|
extraCss: _rapierSharedPageFallbackCss(),
|
|
728
|
-
afterRoot(root) {
|
|
887
|
+
async afterRoot(root) {
|
|
729
888
|
/* Unlike TXT/DOCX (which route through _rapierProjectPortableRoot and have no
|
|
730
889
|
native disclosure widget to fall back on), the shared page is a real HTML
|
|
731
890
|
document a real browser renders — the same styled root the standalone HTML
|
|
@@ -735,8 +894,10 @@ async function _rapierBuildSharedPage(captured) {
|
|
|
735
894
|
only threw away a native, JS-free expand/collapse the architecture's own
|
|
736
895
|
fidelity table promises ("Complete shared/styled HTML: Exact recoverable
|
|
737
896
|
source"). Leave it as authored, exactly like standalone HTML export. */
|
|
738
|
-
|
|
739
|
-
|
|
897
|
+
if (context.work) {
|
|
898
|
+
await finishAsync(_rapierSharedPageLayoutSteps(root), context.work);
|
|
899
|
+
await finishAsync(_rapierShareCapLongCodeBlocksSteps(root), context.work);
|
|
900
|
+
} else { _rapierSharedPageLayout(root); _rapierShareCapLongCodeBlocks(root); }
|
|
740
901
|
},
|
|
741
902
|
}, context);
|
|
742
903
|
// No cap and no warning: the page is as large as the document is. A page over the editor's own 25
|
|
@@ -753,6 +914,12 @@ const _RAPIER_SHARED_DEFINITION = /^(?:[A-Za-z0-9\-_.!~*'()]|%[0-9A-Fa-f]{2})+$/
|
|
|
753
914
|
|
|
754
915
|
const _rapierSharedEncode = text => text.replace(/&/g, '&').replace(/</g, '<').replace(/#/g, '#').replace(/\r/g, ' ');
|
|
755
916
|
|
|
917
|
+
function* _rapierSharedEncodeSteps(text) {
|
|
918
|
+
const parts = [];
|
|
919
|
+
for (let at = 0; at < text.length; at += 16384) { parts.push(_rapierSharedEncode(text.slice(at, at + 16384))); yield; }
|
|
920
|
+
return parts.join('');
|
|
921
|
+
}
|
|
922
|
+
|
|
756
923
|
const _rapierSharedDecode = text => text.replace(/#/g, '#').replace(/ /g, '\r').replace(/</g, '<').replace(/&/g, '&');
|
|
757
924
|
|
|
758
925
|
const RAPIER_SHARE_LONG_CODE_LINES = 40;
|
|
@@ -769,6 +936,12 @@ function _rapierImageSources(tokens) {
|
|
|
769
936
|
return sources;
|
|
770
937
|
}
|
|
771
938
|
|
|
939
|
+
async function _rapierDataImageDestinationsAsync(source, work) {
|
|
940
|
+
if (!source.includes('![')) return [];
|
|
941
|
+
return runtime.dataImageDestinationsAsync ? runtime.dataImageDestinationsAsync(source, work)
|
|
942
|
+
: _rapierImageDestinations(source, url => !!globalThis.RapierImageAssets.dataImage(url));
|
|
943
|
+
}
|
|
944
|
+
|
|
772
945
|
function _rapierImageDestinations(source, accepts) {
|
|
773
946
|
// Only a rendered Markdown image consumes a definition; unused definitions stay literal source.
|
|
774
947
|
if (!source.includes('![')) return [];
|
|
@@ -1495,6 +1668,6 @@ function _rapierArtifactInkScript(root, nonce = '') {
|
|
|
1495
1668
|
return '<script' + (nonce ? ' nonce="' + nonce + '"' : '') + '>\n' + script.replace(/<\/script/gi, '<\\/script') + '\n</script>';
|
|
1496
1669
|
}
|
|
1497
1670
|
|
|
1498
|
-
return {_rapierBuildArtifact, _rapierProjectStyledRoot, _rapierPaintCode, _rapierPrefixPortableAnchors, _rapierDocumentSettingsOf, _rapierDocumentSettingsCss, _rapierExportedPageCsp, _rapierArtifactNonce, _rapierAnnotateExportBoxPolygons, _rapierSharedSourceHash, _rapierSharedSourceForms, _rapierSharedSourceCarrier, _rapierSharedResolve, _rapierReadSharedDocument, _rapierSharedPageFallbackCss, _rapierShareWrapKind, _rapierSharePictureParagraph, _rapierSharedPageLayout, _rapierShareCapLongCodeBlocks, _rapierRequireOfflinePageImages, _rapierBuildSharedPage, _rapierImageSources, _rapierImageDestinations, _rapierScanMarkdownImages, _rapierParseMarkdownImageDestination, _rapierImageOpenRunsList, _rapierArtifactFactoryModules, _rapierProjectArtifactLayout, _rapierArtifactLayoutScript, _rapierArtifactInkScript , render};
|
|
1671
|
+
return {_rapierDataImageDestinationsAsync, _rapierSharedSourceFormsAsync, _rapierPrefixPortableAnchorsSteps, _rapierProjectStyledRootAsync, _rapierBuildArtifact, _rapierProjectStyledRoot, _rapierPaintCode, _rapierPrefixPortableAnchors, _rapierDocumentSettingsOf, _rapierDocumentSettingsCss, _rapierExportedPageCsp, _rapierArtifactNonce, _rapierAnnotateExportBoxPolygons, _rapierSharedSourceHash, _rapierSharedSourceForms, _rapierSharedSourceCarrier, _rapierSharedResolve, _rapierReadSharedDocument, _rapierSharedPageFallbackCss, _rapierShareWrapKind, _rapierSharePictureParagraph, _rapierSharedPageLayout, _rapierShareCapLongCodeBlocks, _rapierRequireOfflinePageImages, _rapierCssValueUrls, _rapierBuildSharedPage, _rapierImageSources, _rapierImageDestinations, _rapierScanMarkdownImages, _rapierParseMarkdownImageDestination, _rapierImageOpenRunsList, _rapierArtifactFactoryModules, _rapierProjectArtifactLayout, _rapierArtifactLayoutScript, _rapierArtifactInkScript , render};
|
|
1499
1672
|
}
|
|
1500
1673
|
export {createRenderer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.92",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|