rapier-markdown-kit 1.1.104 → 1.2.3
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/LICENSE +5 -2
- package/README.md +34 -3
- package/dist/images/gif.mjs +47 -0
- package/dist/images/playback.mjs +101 -0
- package/dist/kit/conformance/03-wrap-around-silhouette.md +1 -1
- package/dist/kit/conformance/04-wrap-box.md +1 -1
- package/dist/kit/conformance/05-behind.md +1 -1
- package/dist/kit/conformance/06-front.md +1 -1
- package/dist/kit/conformance/07-rotate-raster.md +1 -1
- package/dist/kit/conformance/08-drawing-rotation.md +1 -1
- package/dist/kit/conformance/09-drawing-ring-interior.expected.json +18 -18
- package/dist/kit/conformance/09-drawing-ring-interior.md +1 -1
- package/dist/kit/conformance/10-both-sides.md +1 -1
- package/dist/kit/conformance/11-neighbour-skips-image.md +1 -1
- package/dist/kit/conformance/{12-heading-barrier.expected.json → 12-rule-barrier.expected.json} +1 -1
- package/dist/kit/conformance/{12-heading-barrier.md → 12-rule-barrier.md} +2 -2
- package/dist/kit/conformance/README.md +19 -28
- package/dist/kit/conformance/run.mjs +9 -7
- package/dist/kit/ledger/carried.mjs +10 -5
- package/dist/kit/ledger/format.mjs +12 -3
- package/dist/kit/ledger/hash.mjs +31 -15
- package/dist/kit/ledger/history.mjs +17 -5
- package/dist/kit/ledger/merge.mjs +4 -3
- package/dist/kit/pdf-vectors.mjs +4 -1
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-ink.mjs +1 -1
- package/dist/kit/render-markdown.mjs +146 -31
- package/dist/kit/render-print.mjs +131 -55
- package/dist/kit/render-sanitize.mjs +6 -3
- package/dist/kit/render.mjs +34 -77
- package/dist/layout/model.mjs +10 -8
- package/dist/packages/rapier-diff/index.mjs +75 -32
- package/dist/spec/document-fonts.mjs +1 -1
- package/dist/spec/document-settings.mjs +11 -164
- package/dist/spec/html-blocks.mjs +618 -79
- package/dist/spec/md-assets.mjs +29 -6
- package/dist/spec/md-layout.mjs +8 -0
- package/dist/spec/md-marks.mjs +25 -147
- package/dist/spec/md-source.mjs +54 -19
- package/package.json +1 -1
- package/style.css +7 -51
|
@@ -62,6 +62,15 @@ function readLedgerState(input, expectedText, expectedMetadata) {
|
|
|
62
62
|
(expectedText !== undefined && text !== expectedText)) throw fault('history does not describe this document');
|
|
63
63
|
return {value: {ledger, text, metadata, root, revision, complete: ledger.complete}, prepared, source};
|
|
64
64
|
}
|
|
65
|
+
// A stored copy may hold the head text alone. Its start is that text with every record reversed; readLedger proves it.
|
|
66
|
+
export function ledgerWithStart(input, text) {
|
|
67
|
+
let source = new HistorySource(documentText(text));
|
|
68
|
+
for (let i = input.records.length - 1; i >= 0; i--) {
|
|
69
|
+
source = source.apply(input.records[i].splices, true);
|
|
70
|
+
if (source === null) throw fault('history does not reverse from this document');
|
|
71
|
+
}
|
|
72
|
+
return {...input, start: {...input.start, text: source.slice()}};
|
|
73
|
+
}
|
|
65
74
|
export function readLedger(input, expectedText, expectedMetadata) {
|
|
66
75
|
return readLedgerState(input, expectedText, expectedMetadata).value;
|
|
67
76
|
}
|
|
@@ -182,8 +191,8 @@ export async function prepareLedgerAsync(input, {yield: yieldWork, signal} = {})
|
|
|
182
191
|
return step.value;
|
|
183
192
|
}
|
|
184
193
|
// Derived navigation, never a second history. A target older than start is intentionally unavailable.
|
|
185
|
-
export function historyEnvelope(input, segmentIdentity = []) {
|
|
186
|
-
const {value: {ledger, text, metadata}, prepared, source} = readLedgerState(input);
|
|
194
|
+
export function historyEnvelope(input, segmentIdentity = [], expectedText, expectedMetadata) {
|
|
195
|
+
const {value: {ledger, text, metadata}, prepared, source} = readLedgerState(input, expectedText, expectedMetadata);
|
|
187
196
|
const {byId, effects: {pictures}} = prepared;
|
|
188
197
|
let branch = [], cursor = 0;
|
|
189
198
|
for (const {transaction: tx} of ledger.records) {
|
|
@@ -198,7 +207,7 @@ export function historyEnvelope(input, segmentIdentity = []) {
|
|
|
198
207
|
}
|
|
199
208
|
const step = (source, id, redo) => {
|
|
200
209
|
const target = byId.get(id), folded = pictures.size
|
|
201
|
-
? pictureHistorySplices({source
|
|
210
|
+
? pictureHistorySplices({get source() { return source.slice(); }, records: prepared.records}, target, redo) : null;
|
|
202
211
|
return source.apply(folded || target.splices, !folded && !redo);
|
|
203
212
|
};
|
|
204
213
|
let traversed = source, traversedMetadata = {...metadata};
|
package/dist/kit/ledger/hash.mjs
CHANGED
|
@@ -30,13 +30,11 @@ const K = new Uint32Array([
|
|
|
30
30
|
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
|
|
31
31
|
]);
|
|
32
32
|
const rotate = (x, n) => (x >>> n) | (x << (32 - n));
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
export function createSHA256() {
|
|
33
|
+
// One bounded SHA-256 compression state for file bytes and encoded source text.
|
|
34
|
+
export function sha256State() {
|
|
36
35
|
const h = new Uint32Array([0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19]);
|
|
37
|
-
const w = new Uint32Array(64), block = new Uint8Array(64)
|
|
38
|
-
|
|
39
|
-
let used = 0, bytes = 0, high = '', finished = false;
|
|
36
|
+
const w = new Uint32Array(64), block = new Uint8Array(64);
|
|
37
|
+
let used = 0, bytes = 0, finished = false;
|
|
40
38
|
const compress = (input, offset) => {
|
|
41
39
|
for (let i = 0; i < 16; i++) {
|
|
42
40
|
const at = offset + i * 4;
|
|
@@ -65,13 +63,37 @@ export function createSHA256() {
|
|
|
65
63
|
for (; at + 64 <= size; at += 64) compress(input, at);
|
|
66
64
|
if (at < size) { block.set(input.subarray(at, size), 0); used = size - at; }
|
|
67
65
|
};
|
|
66
|
+
return {
|
|
67
|
+
update(input) {
|
|
68
|
+
if (finished || !(input instanceof Uint8Array)) throw new TypeError('SHA-256 needs bytes before finish');
|
|
69
|
+
if (!Number.isSafeInteger(bytes + input.length)) throw new Error('SHA-256 input length is not exact');
|
|
70
|
+
feed(input, input.length);
|
|
71
|
+
},
|
|
72
|
+
finish() {
|
|
73
|
+
if (finished) throw new Error('SHA-256 is already finished');
|
|
74
|
+
finished = true;
|
|
75
|
+
block[used++] = 0x80; block.fill(0, used);
|
|
76
|
+
if (used > 56) { compress(block, 0); block.fill(0); }
|
|
77
|
+
const view = new DataView(block.buffer);
|
|
78
|
+
view.setUint32(56, Math.floor(bytes / 0x20000000)); view.setUint32(60, (bytes * 8) >>> 0);
|
|
79
|
+
compress(block, 0);
|
|
80
|
+
return Array.from(h, x => x.toString(16).padStart(8, '0')).join('');
|
|
81
|
+
},
|
|
82
|
+
get bytes() { return bytes; },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// UTF-16 chunks share that byte state. Retain a trailing high surrogate so a
|
|
86
|
+
// caller can split a character between updates without changing its UTF-8 digest.
|
|
87
|
+
export function createSHA256() {
|
|
88
|
+
const hash = sha256State(), encoded = new Uint8Array(65536), encoder = new TextEncoder();
|
|
89
|
+
let high = '', finished = false;
|
|
68
90
|
const write = text => {
|
|
69
91
|
for (let at = 0; at < text.length;) {
|
|
70
92
|
// Bound both the substring and byte allocation; encodeInto never cuts a scalar.
|
|
71
93
|
let end = Math.min(text.length, at + 32768);
|
|
72
94
|
if (end < text.length && (text.charCodeAt(end - 1) & 0xfc00) === 0xd800 && (text.charCodeAt(end) & 0xfc00) === 0xdc00) end--;
|
|
73
95
|
const part = encoder.encodeInto(text.slice(at, end), encoded);
|
|
74
|
-
|
|
96
|
+
hash.update(encoded.subarray(0, part.written)); at += part.read;
|
|
75
97
|
}
|
|
76
98
|
};
|
|
77
99
|
return {
|
|
@@ -85,15 +107,9 @@ export function createSHA256() {
|
|
|
85
107
|
if (finished) throw new Error('SHA-256 is finished');
|
|
86
108
|
if (high) { write(high); high = ''; }
|
|
87
109
|
finished = true;
|
|
88
|
-
|
|
89
|
-
block[used++] = 0x80; block.fill(0, used);
|
|
90
|
-
if (used > 56) { compress(block, 0); block.fill(0); }
|
|
91
|
-
const view = new DataView(block.buffer);
|
|
92
|
-
view.setUint32(56, Math.floor(bits / 0x100000000)); view.setUint32(60, bits >>> 0);
|
|
93
|
-
compress(block, 0);
|
|
94
|
-
return Array.from(h, x => x.toString(16).padStart(8, '0')).join('');
|
|
110
|
+
return hash.finish();
|
|
95
111
|
},
|
|
96
|
-
get bytes() { return bytes; },
|
|
112
|
+
get bytes() { return hash.bytes; },
|
|
97
113
|
};
|
|
98
114
|
}
|
|
99
115
|
export function sha256Parts(parts) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Transient projections and selective inverses of the existing canonical journal.
|
|
3
|
-
import {_rapierTransformSplices,
|
|
3
|
+
import {_rapierTransformSplices, _rapierValidMetadata, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierPrepareHistoryRecords} from './journal-records.mjs';
|
|
4
4
|
import {projectInverseSource, HistorySource} from './inverse-source.mjs';
|
|
5
5
|
import {textRootParts, rootAfter} from './hash.mjs';
|
|
6
6
|
import {authoredHistory} from './transport.mjs';
|
|
@@ -43,11 +43,15 @@ export function groupHistoryActs(acts) {
|
|
|
43
43
|
|
|
44
44
|
// A picture's codec replacement travels with its original navigation step.
|
|
45
45
|
// Match every complete replacement exactly before moving any source bytes.
|
|
46
|
-
export function pictureHistorySplices(
|
|
46
|
+
export function pictureHistorySplices(input, target, redo) {
|
|
47
|
+
const {records} = input;
|
|
47
48
|
const id = target?.transaction?.id, at = records.indexOf(target);
|
|
48
49
|
if (!id || at < 0 || target.transaction.operation === 'document.finish-picture' || !Array.isArray(target.splices)) return null;
|
|
49
50
|
const later = records.slice(at + 1).filter(row => row?.transaction?.operation === 'document.finish-picture' && Array.isArray(row.splices) && row.splices.length);
|
|
50
51
|
if (!later.length) return null;
|
|
52
|
+
// A caller may hold a persistent source tree: request its text only when
|
|
53
|
+
// a later picture replacement can actually affect this navigation step.
|
|
54
|
+
const source = input.source;
|
|
51
55
|
const own = new Set(later.filter(row => row.transaction.sourceTransactionId === id));
|
|
52
56
|
const step = redo ? target.splices : target.splices.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed}));
|
|
53
57
|
const plan = moveOthers => {
|
|
@@ -116,7 +120,9 @@ function prepareHistory(input) {
|
|
|
116
120
|
row.beforeSource = initial; row.beforeMetadata = initialMetadata;
|
|
117
121
|
}
|
|
118
122
|
const earliestRevision = records[0]?.transaction.baseRevision ?? revision;
|
|
119
|
-
|
|
123
|
+
// The window's owner may name its root (the source store mints it once per text); otherwise hash the initial text.
|
|
124
|
+
let root = earliestRevision === 0 ? (typeof input.earliestRoot === 'string' && input.earliestRoot || textRootParts(initial.parts())) : null,
|
|
125
|
+
rootRevision = earliestRevision;
|
|
120
126
|
for (const {record, splices} of rows) {
|
|
121
127
|
const hashed = Object.hasOwn(record, 'beforeHash') || Object.hasOwn(record, 'afterHash');
|
|
122
128
|
if (hashed && (![record.beforeHash, record.afterHash].every(value =>
|
|
@@ -281,9 +287,15 @@ export function historyPlaces(input, blocks) {
|
|
|
281
287
|
}
|
|
282
288
|
touched.set(node, after);
|
|
283
289
|
});
|
|
290
|
+
// Blocks one splice covers share its range (a whole-document delete maps every block to all it removed): slice each range once.
|
|
291
|
+
const slices = new Map(), cut = (source, side, from, to) => {
|
|
292
|
+
const key = side + from + ':' + to;
|
|
293
|
+
if (!slices.has(key)) slices.set(key, source.slice(from, to));
|
|
294
|
+
return slices.get(key);
|
|
295
|
+
};
|
|
284
296
|
for (const [node, afterRange] of touched) {
|
|
285
|
-
const beforeRange = ranges.range(node), previous = {...beforeRange, source: beforeSource
|
|
286
|
-
after = {...afterRange, source: afterSource
|
|
297
|
+
const beforeRange = ranges.range(node), previous = {...beforeRange, source: cut(beforeSource, 'b', beforeRange.from, beforeRange.to)},
|
|
298
|
+
after = {...afterRange, source: cut(afterSource, 'a', afterRange.from, afterRange.to)};
|
|
287
299
|
if (previous.source !== after.source) node.block.acts.push({...historyAct(record), before: previous, after});
|
|
288
300
|
}
|
|
289
301
|
}
|
|
@@ -190,7 +190,9 @@ export function merge(first, second) {
|
|
|
190
190
|
const local = localIds.get(record.transaction.id);
|
|
191
191
|
if (local) mappedIds.set(record.transaction.id, local.transaction.id);
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
// Kept text arriving from the other copy waits for the person; this copy's own kept edits merge like any other.
|
|
194
|
+
const incomingKept = canonicalJSON(kept(theirs.text));
|
|
195
|
+
const keepDiffers = incomingKept !== canonicalJSON(kept(ours.text)) && incomingKept !== canonicalJSON(kept(common.text));
|
|
194
196
|
let conflicted = keepDiffers && unseen.length > 0, incomingSource = common.text;
|
|
195
197
|
if (!conflicted) for (const record of incoming) {
|
|
196
198
|
const beforeSource = incomingSource;
|
|
@@ -240,8 +242,7 @@ export function merge(first, second) {
|
|
|
240
242
|
let conflicts = [];
|
|
241
243
|
if (conflicted) {
|
|
242
244
|
if (unseen.length !== incoming.length) return refused('history', 'record_transport_conflict');
|
|
243
|
-
if (incomingFields.size
|
|
244
|
-
return metadataConflict('record_transport_conflict', {fields: [...incomingFields]});
|
|
245
|
+
if (incomingFields.size) return metadataConflict('record_transport_conflict', {fields: [...incomingFields]});
|
|
245
246
|
// Restart from the original local head: partially transported records never leak into a conflict result.
|
|
246
247
|
records.splice(ours.ledger.records.length); text = ours.text; metadata = {...ours.metadata}; root = ours.root;
|
|
247
248
|
const merged = mergeText(keepDiffers ? null : common.text, ours.text, theirs.text, {oursId: 'copy-' + ours.ledger.sha256.slice(0, 16), theirsId: 'copy-' + theirs.ledger.sha256.slice(0, 16)});
|
package/dist/kit/pdf-vectors.mjs
CHANGED
|
@@ -151,7 +151,10 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
151
151
|
const left = Math.max(0, bounds.left - box.left - 2), top = Math.max(0, bounds.top - box.top - 2);
|
|
152
152
|
const right = Math.min(box.width, bounds.right - box.left + 2), bottom = Math.min(box.height, bounds.bottom - box.top + 2);
|
|
153
153
|
if (!(right > left && bottom > top)) return;
|
|
154
|
-
const result = await capture({root: wrap, clip: {x: left, y: top, width: right - left, height: bottom - top}, transparent: true, scale: 2, signal, current}); check();
|
|
154
|
+
const result = await capture({root: wrap, clip: {x: left, y: top, width: right - left, height: bottom - top}, transparent: true, scale: 2, signal, current, pixels: true}); check();
|
|
155
|
+
if (result?.rgb instanceof Uint8Array && result?.alpha instanceof Uint8Array) {
|
|
156
|
+
return {image: {rgb: await compress(result.rgb), alpha: await compress(result.alpha), width: result.width, height: result.height}, x: box.left - origin.left + left, y: box.top - origin.top + top, width: right - left, height: bottom - top};
|
|
157
|
+
}
|
|
155
158
|
const image = new window.Image(); image.src = 'data:' + result.mimeType + ';base64,' + result.data; await image.decode(); check();
|
|
156
159
|
const canvas = document.createElement('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight;
|
|
157
160
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {exportLedger, replaceLedgerText, readLedger, textRoot, rootAfter, historyEnvelope, agentActorId} from './ledger/format.mjs';
|
|
3
|
-
export {exportLedger, replaceLedgerText, readLedger, textRoot, rootAfter, historyEnvelope, agentActorId};
|
|
2
|
+
import {exportLedger, replaceLedgerText, readLedger, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId} from './ledger/format.mjs';
|
|
3
|
+
export {exportLedger, replaceLedgerText, readLedger, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId};
|
|
4
4
|
import {authorship, readAuthorship, ledgerFromAuthorship} from './ledger/authorship.mjs';
|
|
5
5
|
export {authorship, readAuthorship, ledgerFromAuthorship};
|
|
6
6
|
import {merge, transportInterval, transportTouchedInterval} from './ledger/merge.mjs';
|
package/dist/kit/render-ink.mjs
CHANGED
|
@@ -63,7 +63,7 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
|
|
|
63
63
|
frame.srcdoc = '<!doctype html><html lang="' + documentLanguage(settings, language) + '" data-rapier-theme="' + theme + '"><head><meta charset="utf-8">'
|
|
64
64
|
+ '<meta http-equiv="Content-Security-Policy" content="' + policy + '"><meta name="referrer" content="no-referrer">'
|
|
65
65
|
+ '<style>' + css + '</style></head><body class="' + (theme === 'light' ? 'light' : '')
|
|
66
|
-
+ '"><main class="rapier-page md-render" data-md-theme="' + theme + '"
|
|
66
|
+
+ '"><main class="rapier-page md-render" data-md-theme="' + theme + '">' + root.innerHTML + '</main></body></html>';
|
|
67
67
|
doc.body.appendChild(frame);
|
|
68
68
|
await wait(loaded);
|
|
69
69
|
const paper = frame.contentDocument, main = paper?.querySelector('main.rapier-page');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {htmlPlaceholder, mountHtmlBlocks, htmlImageRecord, htmlLayoutEdit} from '../spec/html-blocks.mjs';
|
|
2
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {htmlPlaceholder, mountHtmlBlocks, materializeObjects, objectPosterLabel, htmlImageRecord, htmlLayoutEdit, htmlModeEdit, mergeHtmlObjectBlocks} from '../spec/html-blocks.mjs';
|
|
3
3
|
import {finish, finishAsync, cloneTree, cleanTree, serializeTree, htmlParts, admitHtml, htmlBoundary, pause, check} from './render-work.mjs';
|
|
4
4
|
const RAPIER_TABLE_CAPTION_PREFIX_RE = /^(?:Table:|:) /;
|
|
5
5
|
const RAPIER_PORTABLE_HIGHLIGHT_HEX = Object.freeze({
|
|
@@ -72,10 +72,8 @@ function initMarkdownIt() {
|
|
|
72
72
|
'data-md-typography-close="' + md.utils.escapeHtml(encodeURIComponent(held.close)) + '"');
|
|
73
73
|
return '<span ' + attrs.join(' ') + ' style="' + md.utils.escapeHtml(css.join(';')) + '">';
|
|
74
74
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
md.renderer.rules['rapier_' + kind + '_close'] = () => '</span>';
|
|
78
|
-
}
|
|
75
|
+
md.renderer.rules.rapier_typography_open = renderTypography;
|
|
76
|
+
md.renderer.rules.rapier_typography_close = () => '</span>';
|
|
79
77
|
// Ink: the span carries its opener's body (the grammar's inkOpenBody: a sanitizer drops an attribute holding the
|
|
80
78
|
// comment's close); the ink layer reads it back through the one grammar and draws the stroke over the words' own boxes.
|
|
81
79
|
// The span itself shows nothing.
|
|
@@ -138,8 +136,8 @@ function initMarkdownIt() {
|
|
|
138
136
|
const asset = globalThis.RapierImageAssets.dataImage(src), reference = token.meta?.mdImage?.reference;
|
|
139
137
|
// A connected host's own asset; a host without one (the server) supplies no port.
|
|
140
138
|
if (!env.rapierPage && typeof globalThis.RapierEmbeddedImages?.imageHtml === 'function' &&
|
|
141
|
-
(_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
|
|
142
|
-
reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src);
|
|
139
|
+
(_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/gif' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
|
|
140
|
+
reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src, env.rapierImagePresentation !== false);
|
|
143
141
|
const drawingAlt = typeof rawAlt === 'string' && alt === '' && asset?.codec === 'image/svg+xml' ? globalThis.RapierImageAssets.drawingImageAlt(src) : null;
|
|
144
142
|
const shownAlt = drawingAlt ?? alt;
|
|
145
143
|
let loadable = false;
|
|
@@ -158,19 +156,28 @@ function initMarkdownIt() {
|
|
|
158
156
|
return html.replace('<span ', '<span data-rapier-markdown-image="" ' +
|
|
159
157
|
(token.meta?.mdImage?.source ? 'data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '" ' : ''));
|
|
160
158
|
}
|
|
159
|
+
let animation = null;
|
|
160
|
+
try {animation = globalThis.RapierImageAssets.imageAnimation?.(src);} catch (_) {}
|
|
161
161
|
const attrs = [
|
|
162
162
|
'data-rapier-markdown-image=""',
|
|
163
|
-
'src="' + md.utils.escapeHtml(src) + '"',
|
|
163
|
+
'src="' + md.utils.escapeHtml(animation?.first || src) + '"',
|
|
164
164
|
'alt="' + md.utils.escapeHtml(shownAlt) + '"',
|
|
165
165
|
'loading="lazy"',
|
|
166
166
|
'decoding="async"',
|
|
167
167
|
];
|
|
168
|
+
if (animation) attrs.push('data-rapier-animation="' + md.utils.escapeHtml(src) + '"',
|
|
169
|
+
'data-rapier-natural-width="' + animation.width + '"', 'data-rapier-natural-height="' + animation.height + '"');
|
|
168
170
|
if (token.meta?.mdImage?.source && token.meta.mdImage.source.length <= 4096)
|
|
169
171
|
attrs.push('data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '"');
|
|
170
172
|
if (title) attrs.push('title="' + md.utils.escapeHtml(title) + '"');
|
|
171
173
|
if (shownAlt !== alt || size && !token.meta?.mdLayout?.layout?.width) attrs.push('data-rapier-image-alt-source="' + md.utils.escapeHtml(rawAlt) + '"');
|
|
172
174
|
if (layout) attrs.push('data-rapier-image-layout="' + md.utils.escapeHtml(encodeURIComponent(layout)) + '"');
|
|
173
|
-
|
|
175
|
+
// Portable semantic consumers keep their existing native image attributes. Only
|
|
176
|
+
// live editor/reader presentation needs provisional geometry and its load consumer.
|
|
177
|
+
if (env.rapierImagePresentation === false) return '<img ' + attrs.join(' ') + _rapierImageSizeAttributes(size, layout) + '>';
|
|
178
|
+
const geometry = !env.rapierPage && globalThis.RapierEmbeddedImages?.geometry
|
|
179
|
+
? globalThis.RapierEmbeddedImages.geometry(src) : globalThis.RapierImageAssets.imagePlaceholderDimensions(src);
|
|
180
|
+
return '<img ' + attrs.join(' ') + _rapierImageSizeAttributes(size, layout, geometry || {}) + '>';
|
|
174
181
|
};
|
|
175
182
|
|
|
176
183
|
md.renderer.rules.hr = function (tokens, idx) {
|
|
@@ -186,7 +193,9 @@ function initMarkdownIt() {
|
|
|
186
193
|
// markup reaches DOMPurify: sanitizing each HTML token closes containers before their body.
|
|
187
194
|
// The harness runs this installer alone for the parser's acceptance; without the page's sanitizer the tokens pass.
|
|
188
195
|
const inPage = typeof sanitizeRapierHtml === 'function' && typeof _rapierChromeOwnsId === 'function';
|
|
189
|
-
|
|
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
|
+
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;
|
|
190
199
|
// The id is read as HTML reads it: a numeric character reference (`source-textarea` is `source-textarea`,
|
|
191
200
|
// `s` the same) and the named references that spell a character an id can hold are decoded before the
|
|
192
201
|
// chrome's ids are asked; every other name stays literal (no named reference spells a letter, a digit or a
|
|
@@ -196,7 +205,7 @@ function initMarkdownIt() {
|
|
|
196
205
|
equals: '=', comma: ',', lpar: '(', rpar: ')', lsqb: '[', lbrack: '[', rsqb: ']', rbrack: ']', lcub: '{', lbrace: '{',
|
|
197
206
|
rcub: '}', rbrace: '}', verbar: '|', vert: '|', VerticalLine: '|', bsol: '\\', Hat: '^', grave: '`', DiacriticalGrave: '`', Tab: '\t', NewLine: '\n' };
|
|
198
207
|
const attributeText = value => String(value || '').replace(/^["']|["']$/g, '')
|
|
199
|
-
.replace(/&(?:#(\d
|
|
208
|
+
.replace(/&(?:#(\d+)|#[xX]([0-9a-fA-F]+)|([A-Za-z][A-Za-z0-9]{0,31}));?/g, (whole, dec, hex, name) => {
|
|
200
209
|
const code = dec ? Number(dec) : hex ? parseInt(hex, 16) : -1;
|
|
201
210
|
if (code >= 0) return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : '\ufffd';
|
|
202
211
|
return Object.hasOwn(RAW_ID_NAMED, name) ? RAW_ID_NAMED[name] : whole;
|
|
@@ -213,9 +222,15 @@ function initMarkdownIt() {
|
|
|
213
222
|
return key.startsWith('data-') || key === 'for' || key === 'id' && _rapierChromeOwnsId(attributeText(value)) ? '' : whole;
|
|
214
223
|
}) : tag;
|
|
215
224
|
md.renderer.rules.html_inline = (tokens, idx) => cleanTag(tokens[idx].content);
|
|
216
|
-
md.renderer.rules.html_block = (tokens, idx
|
|
217
|
-
tag => env?.rapierDocumentSpace ? cleanTag(tag).replace(/^<([A-Za-z][A-Za-z0-9:-]*)(?=[\s/>])/, '<$1 data-md-html-block="' + idx + '"') : cleanTag(tag), md);
|
|
225
|
+
md.renderer.rules.html_block = (tokens, idx) => _rapierRawHtmlFragment(tokens[idx].content, cleanTag, md);
|
|
218
226
|
}
|
|
227
|
+
// A highlight written in one of the picker's colours is that swatch, as `==🟢x==` is, and follows the theme.
|
|
228
|
+
const inlineHtml = md.renderer.rules.html_inline || ((tokens, idx) => tokens[idx].content);
|
|
229
|
+
md.renderer.rules.html_inline = (tokens, idx, ...rest) => {
|
|
230
|
+
const swatch = /^<mark style="background-color:(#[0-9a-f]{6});?">$/i.exec(tokens[idx].content);
|
|
231
|
+
const colour = swatch && Object.keys(RAPIER_PORTABLE_HIGHLIGHT_HEX).find(name => RAPIER_PORTABLE_HIGHLIGHT_HEX[name] === swatch[1].toLowerCase());
|
|
232
|
+
return colour ? '<mark data-rapier-highlight="' + colour + '">' : inlineHtml(tokens, idx, ...rest);
|
|
233
|
+
};
|
|
219
234
|
|
|
220
235
|
const _ulOpenDefault = md.renderer.rules.bullet_list_open
|
|
221
236
|
|| function (tokens, idx, opts, _env, self) { return self.renderToken(tokens, idx, opts); };
|
|
@@ -308,8 +323,7 @@ function initMarkdownIt() {
|
|
|
308
323
|
'</code> fence. Text below is still inside the block.</p>' + sourceHtml + '</figure>\n';
|
|
309
324
|
}
|
|
310
325
|
const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
|
|
311
|
-
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true
|
|
312
|
-
provider?.status === 'ready' && globalThis.RapierDiagrams?.parseDiagram(src).ok === true;
|
|
326
|
+
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true;
|
|
313
327
|
const ready = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
|
|
314
328
|
if (!ready && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
315
329
|
_diagramPromptedOnce = true;
|
|
@@ -360,11 +374,33 @@ function initMarkdownIt() {
|
|
|
360
374
|
+ literal + '</span>';
|
|
361
375
|
}
|
|
362
376
|
try {
|
|
363
|
-
|
|
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
|
+
let rendered;
|
|
380
|
+
if (env.rapierMathEquations) {
|
|
381
|
+
const combined = mathProvider.renderWithMathML(src, {displayMode});
|
|
382
|
+
if (!combined || typeof combined.svg !== 'string' || !combined.semantics ||
|
|
383
|
+
typeof combined.semantics !== 'object' || Array.isArray(combined.semantics) || !Array.isArray(combined.errors))
|
|
384
|
+
throw new TypeError('The Rapier Math shared result is invalid.');
|
|
385
|
+
rendered = combined.svg;
|
|
386
|
+
env.rapierMathEquations.push({source, tex: src, displayMode,
|
|
387
|
+
semantics: combined.semantics, errors: combined.errors});
|
|
388
|
+
} 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
|
+
}
|
|
364
397
|
return '<span class="math-rendered" data-math-src="'
|
|
365
|
-
+
|
|
398
|
+
+ encoded + '">' + rendered + '</span>';
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (env.rapierMathEquations) throw error;
|
|
402
|
+
return '<span data-rapier-math-source="">' + literal + '</span>';
|
|
366
403
|
}
|
|
367
|
-
catch (_) { return '<span data-rapier-math-source="">' + literal + '</span>'; }
|
|
368
404
|
};
|
|
369
405
|
md.renderer.rules.math_inline = (tokens, idx, _opts, env) => _renderMath(tokens[idx].content, false, env, tokens[idx].meta?.mathSource);
|
|
370
406
|
md.renderer.rules.math_block = (tokens, idx, _opts, env) => wrapDisplayMath(_renderMath(tokens[idx].content, true, env, tokens[idx].meta?.mathSource)) + '\n';
|
|
@@ -393,9 +429,10 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
393
429
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
394
430
|
// Source-semantic output is independent of installed rendering providers. Complete
|
|
395
431
|
// artifacts keep the default provider path and materialize their visual content.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
+
const env = {rapierImagePresentation: options.imagePresentation === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
435
|
+
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
399
436
|
let rendered = md
|
|
400
437
|
? md.render(markdown, env)
|
|
401
438
|
: '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
@@ -430,11 +467,10 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
430
467
|
const fallbackEnv = options.page ? {rapierPage: true} : {};
|
|
431
468
|
let parsed = options.parsed || (runtime.parseAsync ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv});
|
|
432
469
|
let {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
|
|
470
|
+
env = {...env, rapierImagePresentation: options.imagePresentation === true};
|
|
433
471
|
await pause(work, .3);
|
|
434
|
-
env.rapierDocumentSpace = !!globalThis.RapierMarkdownSpec?.readDocumentSettings?.(canonical)?.space;
|
|
435
472
|
const root = document.createElement('div'), part = document.createElement('div'), boundary = htmlBoundary();
|
|
436
473
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
437
|
-
if (env.rapierDocumentSpace) root.setAttribute('data-md-space', '');
|
|
438
474
|
let html = '', began = performance.now(), complete = false;
|
|
439
475
|
const append = () => {
|
|
440
476
|
if (md.renderer.rapierTransformHtml) html = md.renderer.rapierTransformHtml(html);
|
|
@@ -653,16 +689,81 @@ function _rapierImageAltText(value, env = {}) {
|
|
|
653
689
|
return md.renderer.renderInlineAsText(tokens[0]?.children || [], md.options, env);
|
|
654
690
|
}
|
|
655
691
|
|
|
656
|
-
|
|
692
|
+
// Intrinsic facts are a presentation input, never an authored width or a resource admission.
|
|
693
|
+
// Contain both edges within the column, sharing a 1.6-column sum-of-edges budget.
|
|
694
|
+
// Reciprocal portraits/landscapes have equal weight; the native-width cap never enlarges art.
|
|
695
|
+
function _rapierImageGeometryFacts(geometry, automatic) {
|
|
696
|
+
const w = Number(geometry?.width), h = Number(geometry?.height);
|
|
697
|
+
const known = Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && Number.isFinite(w / h) && w / h > 0;
|
|
698
|
+
const width = known ? w : 0, height = known ? h : 0;
|
|
699
|
+
const styles = known ? [['aspect-ratio', width + '/' + height]] : automatic ? [['aspect-ratio', '1/1']] : [];
|
|
700
|
+
if (automatic) {
|
|
701
|
+
const ratio = known ? width / height : 1;
|
|
702
|
+
const share = Math.min(1, ratio, 1.6 / (1 + 1 / ratio));
|
|
703
|
+
styles.push(['--rapier-image-auto-width', Number((share * 100).toFixed(6)) + '%'],
|
|
704
|
+
['--rapier-image-natural-width', known ? width + 'px' : '100%']);
|
|
705
|
+
}
|
|
706
|
+
return {width, height, styles};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Native loads supply their current element's dimensions synchronously. Embedded images
|
|
710
|
+
// supply an admitted row only after their existing document/resource ticket checks.
|
|
711
|
+
function _rapierApplyImageGeometry(image, geometry) {
|
|
712
|
+
if (image?.tagName !== 'IMG' || image.hasAttribute('data-rapier-html')) return false;
|
|
713
|
+
const mode = image.getAttribute('data-rapier-image-geometry');
|
|
714
|
+
if (!['auto', 'sized', 'natural'].includes(mode)) return false;
|
|
715
|
+
const native = geometry === undefined;
|
|
716
|
+
if (native) {
|
|
717
|
+
if (!image.isConnected || !image.complete || image.hasAttribute('data-rapier-asset') || image.hasAttribute('data-rapier-image-url')) return false;
|
|
718
|
+
geometry = {width: image.naturalWidth, height: image.naturalHeight};
|
|
719
|
+
}
|
|
720
|
+
const facts = _rapierImageGeometryFacts(geometry, mode === 'auto');
|
|
721
|
+
if (!facts.width || !facts.height) return false;
|
|
722
|
+
const attributes = [
|
|
723
|
+
['data-rapier-natural-width', String(facts.width)], ['data-rapier-natural-height', String(facts.height)],
|
|
724
|
+
...(!image.hasAttribute('data-rapier-image-size') ? [['width', String(facts.width)]] : []), ['height', String(facts.height)],
|
|
725
|
+
].filter(([name, value]) => image.getAttribute(name) !== value);
|
|
726
|
+
const styles = facts.styles.filter(([name, value]) => (name === 'aspect-ratio'
|
|
727
|
+
? image.style.getPropertyValue(name).replace(/\s/g, '') : image.style.getPropertyValue(name)) !== value);
|
|
728
|
+
// Ordinary mutations revisit mounted images. Settled facts need no source-URL
|
|
729
|
+
// read or parse (which can be the entire inline image), nor any DOM writes.
|
|
730
|
+
if (!attributes.length && !styles.length) return false;
|
|
731
|
+
if (native) {
|
|
732
|
+
const src = image.getAttribute('src'), current = image.currentSrc;
|
|
733
|
+
if (!src || !current) return false;
|
|
734
|
+
try { if (src !== current && new URL(src, image.ownerDocument.baseURI).href !== current) return false; }
|
|
735
|
+
catch (_) { return false; }
|
|
736
|
+
}
|
|
737
|
+
for (const [name, value] of attributes) image.setAttribute(name, value);
|
|
738
|
+
for (const [name, value] of styles) image.style.setProperty(name, value);
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function _rapierImageSizeAttributes(size, marker, geometry, automatic = true) {
|
|
657
743
|
const layout = globalThis.RapierMarkdownLayout.parseLayout(marker || '');
|
|
658
744
|
// Plain (rung 0, docs/standard-adoption.md): every picture renders at its natural width, the
|
|
659
745
|
// same as a reader with no layout facts at all -- Obsidian's `![alt|N]` alt-text width (the
|
|
660
746
|
// `size` fallback below) is a different, established convention and is unaffected.
|
|
661
|
-
|
|
662
|
-
|
|
747
|
+
let style = layout && !_rapierPlainLayout() ? globalThis.RapierMarkdownLayout.imageStyle(layout) : '';
|
|
748
|
+
let attributes = '';
|
|
749
|
+
if (layout?.width != null && style) attributes = ' data-md-image-width="' + layout.width + '"';
|
|
663
750
|
// A fade rides beside any width, the alt-text's own included.
|
|
664
|
-
|
|
665
|
-
'
|
|
751
|
+
else if (size) {
|
|
752
|
+
attributes = ' width="' + size.width + '" data-rapier-image-size="' + size.width + '"';
|
|
753
|
+
style = '--md-image-width:' + size.width + 'px' + (style ? ';' + style : '');
|
|
754
|
+
}
|
|
755
|
+
if (geometry !== undefined) {
|
|
756
|
+
const mode = size || layout?.width != null || layout?.lines != null ? 'sized' : automatic ? 'auto' : 'natural';
|
|
757
|
+
const facts = _rapierImageGeometryFacts(geometry, mode === 'auto');
|
|
758
|
+
attributes += ' data-rapier-image-geometry="' + mode + '"';
|
|
759
|
+
if (facts.width) {
|
|
760
|
+
attributes += ' data-rapier-natural-width="' + facts.width + '" data-rapier-natural-height="' + facts.height + '"';
|
|
761
|
+
if (!size || !attributes.includes(' width=')) attributes += ' width="' + facts.width + '"';
|
|
762
|
+
attributes += ' height="' + facts.height + '"';
|
|
763
|
+
}
|
|
764
|
+
style = [...facts.styles.map(([name, value]) => name + ':' + value), ...(style ? [style] : [])].join(';');
|
|
765
|
+
}
|
|
766
|
+
return attributes + (style ? ' style="' + style + '"' : '');
|
|
666
767
|
}
|
|
667
768
|
|
|
668
769
|
function _rapierRawHtmlFragment(source, rewrite, parser) {
|
|
@@ -1216,7 +1317,8 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1216
1317
|
(name === 'data-rapier-remote-src' || name === 'data-rapier-remote-alt')) continue;
|
|
1217
1318
|
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink'
|
|
1218
1319
|
|| name === 'data-md-first-line-end' || name === 'data-md-initial-font' || name === 'data-md-initial-size'
|
|
1219
|
-
|| name === 'data-md-picture-cap-height' || name === 'data-md-picture-cap-top'
|
|
1320
|
+
|| name === 'data-md-picture-cap-height' || name === 'data-md-picture-cap-top' || name === 'data-rapier-highlight' || name === 'data-rapier-animation'
|
|
1321
|
+
|| name === 'data-rapier-list-ordinal' || name === 'data-rapier-list-delimiter' || name === 'data-md-layout-tight')) continue;
|
|
1220
1322
|
if (name === 'class' || name === 'style' || name.startsWith('data-')
|
|
1221
1323
|
|| name === 'contenteditable' || name === 'spellcheck'
|
|
1222
1324
|
|| name === 'tabindex' || name === 'disabled' || name === 'aria-disabled') {
|
|
@@ -1627,8 +1729,21 @@ function _rapierBidiStrong(cp) {
|
|
|
1627
1729
|
return null;
|
|
1628
1730
|
}
|
|
1629
1731
|
|
|
1630
|
-
|
|
1732
|
+
// A consuming operation, not an editable DOM mode. Both outputs share one admitted
|
|
1733
|
+
// parse per equation; callers decide whether and how to expose the MathML to people.
|
|
1734
|
+
function renderWithMathML(source, metadata = {}) {
|
|
1735
|
+
const provider = _rapierProviders.math;
|
|
1736
|
+
if (!provider || provider.status !== 'ready' || typeof provider.renderToString !== 'function' ||
|
|
1737
|
+
typeof provider.renderWithMathML !== 'function')
|
|
1738
|
+
throw new Error('The Rapier Math shared-result renderer is unavailable.');
|
|
1739
|
+
if (!md) initMarkdownIt();
|
|
1740
|
+
const equations = [];
|
|
1741
|
+
const html = _rapierRenderSemanticRoot(source, metadata, {mathEquations: equations}).innerHTML;
|
|
1742
|
+
return {html, equations};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
return {renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _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, _rapierApplyImageGeometry, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
1631
1746
|
}
|
|
1632
1747
|
export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};
|
|
1633
1748
|
|
|
1634
|
-
export {mountHtmlBlocks, htmlImageRecord, htmlLayoutEdit};
|
|
1749
|
+
export {mountHtmlBlocks, materializeObjects, objectPosterLabel, htmlImageRecord, htmlLayoutEdit, htmlModeEdit, mergeHtmlObjectBlocks};
|