rapier-markdown-kit 1.2.40 → 1.2.43
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/dist/agent/will.mjs +2 -2
- package/dist/kit/ledger/data.mjs +59 -2
- package/dist/kit/ledger/format.mjs +65 -22
- package/dist/kit/ledger/history.mjs +1 -1
- package/dist/kit/ledger/inverse-source.mjs +9 -0
- package/dist/kit/ledger/journal-records.mjs +197 -106
- package/dist/kit/render-markdown.mjs +131 -32
- package/dist/kit/render-sanitize.mjs +1 -1
- package/dist/kit/render.mjs +15 -2
- package/dist/spec/md-assets.mjs +7 -10
- package/package.json +1 -1
- package/style.css +8 -0
package/dist/agent/will.mjs
CHANGED
|
@@ -215,7 +215,7 @@ export function willTransition(beforeText, afterText, splices, markerSplices = s
|
|
|
215
215
|
return null;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
export function evaluateWill(beforeText, splices) {
|
|
218
|
+
export function evaluateWill(beforeText, splices, {restores = null} = {}) {
|
|
219
219
|
const invalid = rule => ({outcome: 'invalid', reason: 'precondition', rule});
|
|
220
220
|
if (typeof beforeText !== 'string' || !beforeText.isWellFormed()) return invalid('invalid_unicode');
|
|
221
221
|
if (!Array.isArray(splices)) return invalid('malformed_splice');
|
|
@@ -231,7 +231,7 @@ export function evaluateWill(beforeText, splices) {
|
|
|
231
231
|
if (afterText.slice(row.pos, end) !== row.removed) return invalid('source_mismatch');
|
|
232
232
|
afterText = afterText.slice(0, row.pos) + row.inserted + afterText.slice(end);
|
|
233
233
|
}
|
|
234
|
-
const refusal = splices.length && willTransition(beforeText, afterText, splices);
|
|
234
|
+
const refusal = splices.length && willTransition(beforeText, afterText, splices, splices, {restores});
|
|
235
235
|
return refusal ? {outcome: 'refused', reason: 'document_law', ...refusal} : {outcome: 'applied'};
|
|
236
236
|
}
|
|
237
237
|
|
package/dist/kit/ledger/data.mjs
CHANGED
|
@@ -44,11 +44,13 @@ function* quoted(text, whole = true) {
|
|
|
44
44
|
}
|
|
45
45
|
yield '"';
|
|
46
46
|
}
|
|
47
|
-
function* dataParts(value, depth, active, sorted) {
|
|
47
|
+
function* dataParts(value, depth, active, sorted, known = null) {
|
|
48
48
|
if (typeof value === 'string') { yield* quoted(value); return; }
|
|
49
49
|
if (value === null || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value) && !Object.is(value, -0)) {
|
|
50
50
|
yield JSON.stringify(value); return;
|
|
51
51
|
}
|
|
52
|
+
const text = known?.(value, depth);
|
|
53
|
+
if (text !== undefined) { yield text; return; }
|
|
52
54
|
const {array, descriptors, keys} = fields(value, depth, active, sorted);
|
|
53
55
|
active.add(value);
|
|
54
56
|
yield array ? '[' : '{';
|
|
@@ -56,14 +58,69 @@ function* dataParts(value, depth, active, sorted) {
|
|
|
56
58
|
if (i) yield ',';
|
|
57
59
|
const key = keys[i];
|
|
58
60
|
if (!array) { yield* quoted(key, false); yield ':'; }
|
|
59
|
-
yield* dataParts(descriptors[key].value, depth + 1, active, sorted);
|
|
61
|
+
yield* dataParts(descriptors[key].value, depth + 1, active, sorted, known);
|
|
60
62
|
}
|
|
61
63
|
yield array ? ']' : '}';
|
|
62
64
|
active.delete(value);
|
|
63
65
|
}
|
|
64
66
|
export function canonicalParts(value, depth = 0, active = new Set()) { return dataParts(value, depth, active, true); }
|
|
65
67
|
export function jsonParts(value) { return dataParts(value, 0, new Set(), false); }
|
|
68
|
+
export function jsonPartsKnown(value, known) { return dataParts(value, 0, new Set(), false, known); }
|
|
69
|
+
export function jsonTextAt(value, depth) { return Array.from(dataParts(value, depth, new Set(), false)).join(''); }
|
|
66
70
|
export function canonicalJSON(value, depth = 0, active = new Set()) { return Array.from(canonicalParts(value, depth, active)).join(''); }
|
|
71
|
+
const ownedRows = new WeakSet(), rowCanonical = new Map();
|
|
72
|
+
let rowCanonicalChars = 0;
|
|
73
|
+
const ROW_CANONICAL_LIMIT = 32 * 1024 * 1024;
|
|
74
|
+
function freezeData(value) {
|
|
75
|
+
if (!value || typeof value !== 'object' || ownedRows.has(value) || ownedLedgers.has(value)) return value;
|
|
76
|
+
for (const key of Object.keys(value)) freezeData(value[key]);
|
|
77
|
+
return Object.freeze(value);
|
|
78
|
+
}
|
|
79
|
+
export function ownRow(row) { if (!ownedRows.has(row)) { freezeData(row); ownedRows.add(row); } return row; }
|
|
80
|
+
const ownedLedgers = new WeakSet();
|
|
81
|
+
export function ownLedger(ledger) {
|
|
82
|
+
if (ledger && typeof ledger === 'object' && !ownedLedgers.has(ledger)) { freezeData(ledger); ownedLedgers.add(ledger); }
|
|
83
|
+
return ledger;
|
|
84
|
+
}
|
|
85
|
+
export function isOwnedLedger(ledger) { return !!ledger && ownedLedgers.has(ledger); }
|
|
86
|
+
const rowText = new WeakMap();
|
|
87
|
+
function canonicalRow(row, depth) {
|
|
88
|
+
if (!ownedRows.has(row)) return canonicalJSON(row, depth);
|
|
89
|
+
const own = rowText.get(row);
|
|
90
|
+
if (own?.depth === depth) return own.json;
|
|
91
|
+
const json = keyedRow(row, depth);
|
|
92
|
+
rowText.set(row, {depth, json});
|
|
93
|
+
return json;
|
|
94
|
+
}
|
|
95
|
+
function keyedRow(row, depth) {
|
|
96
|
+
const id = typeof row.transaction?.id === 'string' ? row.transaction.id : null, seen = JSON.stringify(row);
|
|
97
|
+
const hit = id === null ? null : rowCanonical.get(id);
|
|
98
|
+
if (hit && hit.depth === depth && hit.seen.includes(seen)) return hit.json;
|
|
99
|
+
const json = canonicalJSON(row, depth);
|
|
100
|
+
if (id !== null && 2 * seen.length + json.length <= ROW_CANONICAL_LIMIT / 4) {
|
|
101
|
+
const size = entry => entry.seen.reduce((sum, text) => sum + text.length, entry.json.length);
|
|
102
|
+
const seenAlso = hit && hit.depth === depth && hit.json === json ? [hit.seen.at(-1), seen] : [seen];
|
|
103
|
+
if (hit) { rowCanonical.delete(id); rowCanonicalChars -= size(hit); }
|
|
104
|
+
const entry = {depth, seen: seenAlso, json};
|
|
105
|
+
rowCanonical.set(id, entry); rowCanonicalChars += size(entry);
|
|
106
|
+
for (const [key, value] of rowCanonical) {
|
|
107
|
+
if (rowCanonicalChars <= ROW_CANONICAL_LIMIT) break;
|
|
108
|
+
rowCanonical.delete(key); rowCanonicalChars -= size(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return json;
|
|
112
|
+
}
|
|
113
|
+
const plainRowArrays = new WeakSet();
|
|
114
|
+
export function* canonicalRowsParts(rows, depth = 0) {
|
|
115
|
+
if (!(depth <= 64 && plainRowArrays.has(rows))) {
|
|
116
|
+
const {array} = fields(rows, depth, new Set());
|
|
117
|
+
if (!array) { yield* canonicalParts(rows, depth); return; }
|
|
118
|
+
if (Object.isFrozen(rows) && depth <= 64) plainRowArrays.add(rows);
|
|
119
|
+
}
|
|
120
|
+
yield '[';
|
|
121
|
+
for (let i = 0; i < rows.length; i++) { if (i) yield ','; yield canonicalRow(rows[i], depth + 1); }
|
|
122
|
+
yield ']';
|
|
123
|
+
}
|
|
67
124
|
export function exactKeys(value, keys) {
|
|
68
125
|
return value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
69
126
|
}
|
|
@@ -4,16 +4,16 @@ import {pictureHistorySplices} from './history.mjs';
|
|
|
4
4
|
import {HistorySource} from './source.mjs';
|
|
5
5
|
import {authoredHistory} from './transport.mjs';
|
|
6
6
|
import {textRoot, rootAfter, sha256, sha256PartsAsync, createSHA256} from './hash.mjs';
|
|
7
|
-
import {canonicalJSON, canonicalParts, plainData, documentText, utf8Length, exactKeys, nonnegative, fault} from './data.mjs';
|
|
7
|
+
import {canonicalJSON, canonicalParts, canonicalRowsParts, ownRow, ownLedger, isOwnedLedger, plainData, documentText, utf8Length, exactKeys, nonnegative, fault} from './data.mjs';
|
|
8
8
|
import {diff} from '../../packages/rapier-diff/diff.mjs';
|
|
9
9
|
import {minimalHistorySplice} from './inverse-source.mjs';
|
|
10
10
|
import {evaluateWill} from '../../agent/will.mjs';
|
|
11
11
|
export {textRoot, rootAfter};
|
|
12
12
|
const rootShape = value => typeof value === 'string' && /^\d+:[a-z0-9]+:[a-z0-9]+$/.test(value);
|
|
13
|
-
function* checkedRecords(ledger, prepared) {
|
|
13
|
+
function* checkedRecords(ledger, prepared, from = null) {
|
|
14
14
|
if (!prepared.ok || ledger.records.length !== prepared.records.length) throw fault('record chain differs');
|
|
15
|
-
let root = ledger.start.root, revision = ledger.start.revision;
|
|
16
|
-
for (let index = 0; index < prepared.records.length; index++) {
|
|
15
|
+
let root = from ? from.root : ledger.start.root, revision = from ? from.revision : ledger.start.revision;
|
|
16
|
+
for (let index = from ? from.index : 0; index < prepared.records.length; index++) {
|
|
17
17
|
const record = ledger.records[index];
|
|
18
18
|
if (!Array.isArray(record.splices) || !prepared.validRecord(index,
|
|
19
19
|
ledger.documentAuthority, 64 * 1024 * 1024, utf8Length, !ledger.complete) ||
|
|
@@ -27,32 +27,47 @@ function* checkedRecords(ledger, prepared) {
|
|
|
27
27
|
}
|
|
28
28
|
if (ledger.head.revision !== revision || ledger.head.root !== root) throw fault('history does not describe this document');
|
|
29
29
|
}
|
|
30
|
+
function* envelopeParts(envelope) {
|
|
31
|
+
const keys = Object.keys(envelope).sort();
|
|
32
|
+
yield '{';
|
|
33
|
+
for (let i = 0; i < keys.length; i++) {
|
|
34
|
+
if (i) yield ',';
|
|
35
|
+
yield JSON.stringify(keys[i]); yield ':';
|
|
36
|
+
yield* keys[i] === 'records' ? canonicalRowsParts(envelope.records, 1) : canonicalParts(envelope[keys[i]], 1, new Set());
|
|
37
|
+
}
|
|
38
|
+
yield '}';
|
|
39
|
+
}
|
|
30
40
|
const proofs = new WeakMap();
|
|
41
|
+
function ownedRows(ledger) {
|
|
42
|
+
if (ledger && Array.isArray(ledger.records)) for (const row of ledger.records) if (row && typeof row === 'object') ownRow(row);
|
|
43
|
+
return ledger;
|
|
44
|
+
}
|
|
31
45
|
function copiedLedger(input, proof) {
|
|
32
|
-
if (!proof || !input || typeof input !== 'object') return plainData(input);
|
|
46
|
+
if (!proof || !input || typeof input !== 'object') return ownedRows(plainData(input));
|
|
33
47
|
const fields = Object.getOwnPropertyDescriptors(input), start = fields.start;
|
|
34
|
-
if (!start || !Object.hasOwn(start, 'value') || !start.value || typeof start.value !== 'object') return plainData(input);
|
|
48
|
+
if (!start || !Object.hasOwn(start, 'value') || !start.value || typeof start.value !== 'object') return ownedRows(plainData(input));
|
|
35
49
|
const parts = Object.getOwnPropertyDescriptors(start.value), text = parts.text;
|
|
36
|
-
if (!text || !Object.hasOwn(text, 'value') || text.value !== proof.startText) return plainData(input);
|
|
50
|
+
if (!text || !Object.hasOwn(text, 'value') || text.value !== proof.startText) return ownedRows(plainData(input));
|
|
37
51
|
const shell = Object.create(Object.getPrototypeOf(input), {...fields, start: {...start,
|
|
38
52
|
value: Object.create(Object.getPrototypeOf(start.value), {...parts, text: {...text, value: ''}})}});
|
|
39
|
-
const ledger = plainData(shell);
|
|
53
|
+
const ledger = ownedRows(plainData(shell));
|
|
40
54
|
ledger.start.text = proof.startText;
|
|
41
55
|
return ledger;
|
|
42
56
|
}
|
|
43
57
|
function ledgerAdmission(input) {
|
|
44
58
|
const held = input && typeof input === 'object' ? proofs.get(input) : undefined;
|
|
59
|
+
if (held && isOwnedLedger(input)) return {ledger: input, shell: held.shell, proof: held};
|
|
45
60
|
const ledger = copiedLedger(input, held);
|
|
46
61
|
if (!exactKeys(ledger, ['format','documentAuthority','start','records','head','complete','sha256']) || ledger.format !== 'rapier-ledger/2' ||
|
|
47
62
|
typeof ledger.documentAuthority !== 'string' || !ledger.documentAuthority || ledger.documentAuthority.length > 256 ||
|
|
48
63
|
typeof ledger.complete !== 'boolean' || !Array.isArray(ledger.records) ||
|
|
49
64
|
!exactKeys(ledger.start, ['text','revision','root','sha256','metadata']) || !exactKeys(ledger.head, ['revision','root','sha256','metadata'])) throw fault('unsupported or invalid envelope');
|
|
50
|
-
const shell =
|
|
65
|
+
const shell = Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join('');
|
|
51
66
|
const proof = held?.shell === shell && held.startText === ledger.start.text ? held : null;
|
|
52
67
|
return {ledger, shell, proof};
|
|
53
68
|
}
|
|
54
69
|
function ledgerResult(ledger, proof, expectedText, expectedMetadata) {
|
|
55
|
-
proofs.set(ledger, proof);
|
|
70
|
+
proofs.set(ledger, proof); ownLedger(ledger);
|
|
56
71
|
const {text, metadata, root, revision, prepared, source} = proof;
|
|
57
72
|
if (expectedMetadata !== undefined && (!_rapierValidMetadata(expectedMetadata) || canonicalJSON(metadata) !== canonicalJSON(expectedMetadata)))
|
|
58
73
|
throw fault('metadata does not describe this document');
|
|
@@ -67,7 +82,7 @@ function readLedgerState(input, expectedText, expectedMetadata) {
|
|
|
67
82
|
}
|
|
68
83
|
function* proveLedger(ledger, native = false) {
|
|
69
84
|
const {sha256: claimed, ...payload} = ledger;
|
|
70
|
-
if (claimed !== (yield* checksum(
|
|
85
|
+
if (claimed !== (yield* checksum(envelopeParts(payload), native))) throw fault('envelope checksum differs');
|
|
71
86
|
let text = ledger.start.text, root = ledger.start.root, revision = ledger.start.revision;
|
|
72
87
|
const startFacts = yield* sourceFacts(text, native);
|
|
73
88
|
if (!_rapierValidMetadata(ledger.start.metadata) || !_rapierValidMetadata(ledger.head.metadata)) throw fault('invalid document metadata');
|
|
@@ -151,6 +166,11 @@ function* sourceFacts(text, native = false) {
|
|
|
151
166
|
return {utf8Bytes: bytes, sha256: digest, contentRoot: text.length + ':' +
|
|
152
167
|
(fnv >>> 0).toString(36) + ':' + (((b << 16) | a) >>> 0).toString(36)};
|
|
153
168
|
}
|
|
169
|
+
const expandedRows = new WeakMap();
|
|
170
|
+
const sameSplices = (a, b) => a === b || a.length === b.length &&
|
|
171
|
+
a.every((row, i) => row.pos === b[i].pos && row.removed === b[i].removed && row.inserted === b[i].inserted);
|
|
172
|
+
const sameEffect = (a, b) => a === b || !!a && !!b && canonicalJSON(a) === canonicalJSON(b);
|
|
173
|
+
let recentLedgers = [];
|
|
154
174
|
function* produceLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}, native = false) {
|
|
155
175
|
if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
|
|
156
176
|
metadata = {...metadata};
|
|
@@ -162,23 +182,40 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
162
182
|
const expanded = [];
|
|
163
183
|
for (const {record, splices: rows, metadata: effect} of prepared.rows) {
|
|
164
184
|
if (!rows || effect === undefined) throw fault('navigation target was not retained');
|
|
165
|
-
|
|
185
|
+
const held = expandedRows.get(record);
|
|
186
|
+
if (held && sameSplices(held.rows, rows) && sameEffect(held.effect, effect)) { expanded.push(held.row); yield; continue; }
|
|
187
|
+
expanded.push(ownRow({transaction: plainData(record.transaction), splices: rows.map(row => ({pos: row.pos, removed: row.removed, inserted: row.inserted})),
|
|
166
188
|
beforeHash: record.beforeHash, afterHash: record.afterHash, ...(Object.hasOwn(record, 'authored') ? {authored: plainData(record.authored)} : {}), ...(effect ? {metadata: plainData(effect)} : {}),
|
|
167
189
|
...(record.changeSet && Object.hasOwn(record.changeSet, 'label') ? {changeSet: {label: typeof record.changeSet.label === 'string' ? record.changeSet.label : plainData(record.changeSet.label)}} : {}),
|
|
168
|
-
...(record.derivedCommentIndex == null ? {} : {derivedCommentIndex: typeof record.derivedCommentIndex === 'string' ? record.derivedCommentIndex : plainData(record.derivedCommentIndex)})});
|
|
190
|
+
...(record.derivedCommentIndex == null ? {} : {derivedCommentIndex: typeof record.derivedCommentIndex === 'string' ? record.derivedCommentIndex : plainData(record.derivedCommentIndex)})}));
|
|
191
|
+
expandedRows.set(record, {row: expanded.at(-1), rows, effect});
|
|
169
192
|
yield;
|
|
170
193
|
}
|
|
171
194
|
const headBytes = bytes;
|
|
195
|
+
let base = recentLedgers.find(row => row.rows.length <= expanded.length && row.rows.every((value, at) => value === expanded[at]));
|
|
172
196
|
let startSource = new HistorySource(text), startMetadata = {...metadata};
|
|
173
|
-
for (let i = expanded.length - 1; i >= 0; i--) {
|
|
197
|
+
for (let i = expanded.length - 1; i >= (base ? base.rows.length : 0); i--) {
|
|
174
198
|
startSource = startSource.apply(expanded[i].splices, true);
|
|
175
|
-
startMetadata = _rapierTransformMetadata(startMetadata, expanded[i].metadata, true);
|
|
199
|
+
if (expanded[i].metadata) startMetadata = _rapierTransformMetadata(startMetadata, expanded[i].metadata, true);
|
|
176
200
|
if (startSource === null || !startMetadata) throw fault('history does not reverse from this document');
|
|
177
201
|
for (const row of expanded[i].splices) bytes += utf8Length(row.removed) - utf8Length(row.inserted);
|
|
178
202
|
if (bytes > 64 * 1024 * 1024) throw fault('document exceeds the editor text limit');
|
|
179
203
|
yield;
|
|
180
204
|
}
|
|
181
|
-
|
|
205
|
+
if (base && (bytes !== base.headBytes || startSource.length !== base.headText.length || !startSource.matches(0, base.headText) ||
|
|
206
|
+
canonicalJSON(startMetadata) !== base.headMetadata)) {
|
|
207
|
+
base = null; bytes = headBytes; startSource = new HistorySource(text); startMetadata = {...metadata};
|
|
208
|
+
for (let i = expanded.length - 1; i >= 0; i--) {
|
|
209
|
+
startSource = startSource.apply(expanded[i].splices, true);
|
|
210
|
+
if (expanded[i].metadata) startMetadata = _rapierTransformMetadata(startMetadata, expanded[i].metadata, true);
|
|
211
|
+
if (startSource === null || !startMetadata) throw fault('history does not reverse from this document');
|
|
212
|
+
for (const row of expanded[i].splices) bytes += utf8Length(row.removed) - utf8Length(row.inserted);
|
|
213
|
+
if (bytes > 64 * 1024 * 1024) throw fault('document exceeds the editor text limit');
|
|
214
|
+
yield;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (base) { startMetadata = {...base.startMetadata}; bytes = base.startBytes; }
|
|
218
|
+
const startText = base ? base.startText : startSource.slice();
|
|
182
219
|
const unchangedText = expanded.every(row => !row.splices.length);
|
|
183
220
|
const contentRoot = headFacts.contentRoot;
|
|
184
221
|
revision ??= expanded.at(-1)?.transaction.revision ?? 0;
|
|
@@ -187,20 +224,26 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
187
224
|
const startRevision = expanded[0]?.transaction.baseRevision ?? revision, startRoot = expanded[0]?.beforeHash ?? root;
|
|
188
225
|
if (typeof documentAuthority !== 'string' || !documentAuthority || documentAuthority.length > 256) throw fault('unsupported or invalid envelope');
|
|
189
226
|
if (!nonnegative(startRevision) || !rootShape(startRoot)) throw fault('invalid start');
|
|
190
|
-
const startFacts = unchangedText ? headFacts : yield* sourceFacts(startText, native);
|
|
227
|
+
const startFacts = unchangedText ? headFacts : base ? base.startFacts : yield* sourceFacts(startText, native);
|
|
191
228
|
if (startRevision === 0 && startRoot !== startFacts.contentRoot) throw fault('invalid start');
|
|
192
229
|
const headSHA = headFacts.sha256, startSHA = startFacts.sha256;
|
|
193
230
|
const payload = {format: 'rapier-ledger/2', documentAuthority,
|
|
194
231
|
start: {text: startText, revision: startRevision, root: startRoot, sha256: startSHA, metadata: startMetadata},
|
|
195
232
|
records: expanded, head: {revision, root, sha256: headSHA, metadata: {...metadata}}, complete: complete === true && startRevision === 0 && expanded.every((row, i) => !i || row.transaction.baseRevision === expanded[i - 1].transaction.revision)};
|
|
196
|
-
|
|
233
|
+
const from = base && base.documentAuthority === documentAuthority && base.complete === payload.complete &&
|
|
234
|
+
base.startRoot === startRoot && base.startRevision === startRevision ? {index: base.rows.length, root: base.chainRoot, revision: base.chainRevision} : null;
|
|
235
|
+
for (const _record of checkedRecords(payload, prepared, from)) yield;
|
|
197
236
|
if (expanded.some(row => Object.hasOwn(row, 'authored'))) {
|
|
198
237
|
try { authoredHistory(startText, expanded); } catch (_) { throw fault('authored origin differs'); }
|
|
199
238
|
}
|
|
200
239
|
const replayedMetadata = prepared.replayMetadata(startMetadata);
|
|
201
240
|
if (!replayedMetadata || canonicalJSON(replayedMetadata) !== canonicalJSON(metadata)) throw fault('metadata does not describe this document');
|
|
202
|
-
const ledger = {...payload, sha256: yield* checksum(
|
|
203
|
-
|
|
241
|
+
const ledger = {...payload, sha256: yield* checksum(envelopeParts(payload), native)};
|
|
242
|
+
recentLedgers = [{rows: expanded, headText: text, headMetadata: canonicalJSON(metadata), headBytes, startText, startMetadata: {...startMetadata},
|
|
243
|
+
startFacts, startBytes: bytes, documentAuthority, complete: payload.complete, startRoot, startRevision, chainRoot: payload.head.root,
|
|
244
|
+
chainRevision: payload.head.revision}, ...recentLedgers.filter(row => row.rows[0] !== expanded[0] && row.documentAuthority === documentAuthority)].slice(0, 4);
|
|
245
|
+
ownLedger(ledger);
|
|
246
|
+
proofs.set(ledger, {shell: Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join(''), startText,
|
|
204
247
|
text, metadata: {...metadata}, root, revision, prepared: _rapierPrepareHistoryRecords(ledger.records),
|
|
205
248
|
source: new HistorySource(text)});
|
|
206
249
|
return {ledger, contentRoot, utf8Bytes: headBytes};
|
|
@@ -250,7 +293,7 @@ function historyNavigation(ledger, text, metadata, prepared, source) {
|
|
|
250
293
|
let traversed = source, traversedMetadata = {...metadata};
|
|
251
294
|
for (let i = cursor - 1; i >= 0; i--) {
|
|
252
295
|
traversed = step(traversed, branch[i], false);
|
|
253
|
-
traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata, true);
|
|
296
|
+
if (byId.get(branch[i]).metadata) traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata, true);
|
|
254
297
|
if (traversed === null || !traversedMetadata) throw fault('undo branch does not reverse');
|
|
255
298
|
}
|
|
256
299
|
if (ledger.complete) {
|
|
@@ -261,7 +304,7 @@ function historyNavigation(ledger, text, metadata, prepared, source) {
|
|
|
261
304
|
traversed = source; traversedMetadata = {...metadata};
|
|
262
305
|
for (let i = cursor; i < branch.length; i++) {
|
|
263
306
|
traversed = step(traversed, branch[i], true);
|
|
264
|
-
traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata);
|
|
307
|
+
if (byId.get(branch[i]).metadata) traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata);
|
|
265
308
|
if (traversed === null || !traversedMetadata) throw fault('redo branch does not replay');
|
|
266
309
|
}
|
|
267
310
|
if (ledger.complete && pictures.size) {
|
|
@@ -363,7 +363,7 @@ export function selectiveUndo(input, actIds, options = {}) {
|
|
|
363
363
|
const result = projectInverseSource(transported ? {...prepared, rows, records: transported.records} : prepared, selected, projectedEffects, options);
|
|
364
364
|
if (!result.ok) return result;
|
|
365
365
|
if (options.actor?.kind === 'agent' && prepared.metadata.docKind === 'markdown' &&
|
|
366
|
-
evaluateWill(prepared.source, result.splices).outcome !== 'applied')
|
|
366
|
+
evaluateWill(prepared.source, result.splices, {restores: result.splices}).outcome !== 'applied')
|
|
367
367
|
return historyFailure('will_refused');
|
|
368
368
|
return {...result, actIds: [...requested], sourceTransactionIds: [...selected], metadata, afterMetadata,
|
|
369
369
|
effectChanged, unchanged: result.after === prepared.source && !metadata && !effectChanged};
|
|
@@ -32,6 +32,8 @@ function historyHunks(row) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
const differentActors = (a, b) => !!a && !!b && (a.kind !== b.kind || a.id !== b.id);
|
|
36
|
+
|
|
35
37
|
export function projectInverseSource(prepared, selected, projectedEffects, options = {}) {
|
|
36
38
|
const records = prepared.records || prepared.rows.map(({record, splices}) => record.splices === splices ? record : {...record, splices});
|
|
37
39
|
if (!options.origins && !records.some(record => !prepared.effects.references.get(record.transaction.id)?.length &&
|
|
@@ -40,6 +42,7 @@ export function projectInverseSource(prepared, selected, projectedEffects, optio
|
|
|
40
42
|
try {
|
|
41
43
|
let pieces = prepared.initialSource ? [{text: prepared.initialSource, owner: null, lineage: []}] : [];
|
|
42
44
|
const all = [], byId = new Map(), byAct = new Map(), processed = [], keptInverseAncestors = new Set();
|
|
45
|
+
const actorOf = new Map(records.map(record => [record.transaction.id, record.transaction.actor]));
|
|
43
46
|
const recordsForCover = prepared.records || prepared.rows.map(({record, splices}) => record.splices === splices ? record : {...record, splices});
|
|
44
47
|
const coverGroups = new Map();
|
|
45
48
|
let coverOrigins = null;
|
|
@@ -280,6 +283,12 @@ export function projectInverseSource(prepared, selected, projectedEffects, optio
|
|
|
280
283
|
const id = tx.id + '#' + rowIndex + ':' + index;
|
|
281
284
|
const lineage = new Set([id]);
|
|
282
285
|
for (const piece of removed) for (const parent of piece.lineage || piece.mark?.lineage || []) lineage.add(parent);
|
|
286
|
+
if (!row.removed.length && !count && at > 0 && /[\p{L}\p{N}\p{M}]/u.test(row.inserted)) {
|
|
287
|
+
const left = pieces[at - 1], right = pieces[at];
|
|
288
|
+
const inner = left?.text && right?.text && left.owner === right.owner ? byId.get(left.owner) : null;
|
|
289
|
+
if (inner && inner.actId !== tx.id && inner.removed && differentActors(actorOf.get(inner.actId), tx.actor) && /[\p{L}\p{N}\p{M}]$/u.test(left.text) && /^[\p{L}\p{N}\p{M}]/u.test(right.text))
|
|
290
|
+
for (const parent of [inner.id, ...(left.lineage || [])]) lineage.add(parent);
|
|
291
|
+
}
|
|
283
292
|
const hunk = {id, actId: tx.id, removed: row.removed, inserted: row.inserted, before: removed, derived,
|
|
284
293
|
chosen, kept: selected.has(tx.id) && !chosen, lineage: [...lineage], supersededBy: new Set(), active: true,
|
|
285
294
|
...(coverIdsFor(tx.id) ? {coverIds: coverIdsFor(tx.id)} : {})};
|
|
@@ -76,7 +76,7 @@ function _rapierValidAuthored(origin) {
|
|
|
76
76
|
!/[\uD800-\uDFFF]/u.test(row.removed) && !/[\uD800-\uDFFF]/u.test(row.inserted));
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function _rapierRecordIndex(records) {
|
|
79
|
+
function _rapierRecordIndex(records, shared = false) {
|
|
80
80
|
const byId = new Map(), splices = new Map(), metadata = new Map();
|
|
81
81
|
for (const record of records) {
|
|
82
82
|
const id = record?.transaction?.id;
|
|
@@ -100,20 +100,41 @@ function _rapierRecordIndex(records) {
|
|
|
100
100
|
}
|
|
101
101
|
cache.set(current, value);
|
|
102
102
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
103
|
-
if (path[i].transaction.reverts && value != null)
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
if (path[i].transaction.reverts && value != null) {
|
|
104
|
+
value = effects
|
|
105
|
+
? Object.fromEntries(Object.entries(value).map(([key, pair]) => [key, {before: pair.after, after: pair.before}]))
|
|
106
|
+
: value.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed}));
|
|
107
|
+
if (shared) { for (const row of Object.values(value)) Object.freeze(row); Object.freeze(value); }
|
|
108
|
+
}
|
|
106
109
|
cache.set(path[i], value);
|
|
107
110
|
}
|
|
108
111
|
return value;
|
|
109
112
|
};
|
|
110
|
-
|
|
113
|
+
const extend = more => { for (const record of more) { const id = record?.transaction?.id; if (!byId.has(id)) byId.set(id, []); byId.get(id).push(record); } };
|
|
114
|
+
return {byId, extend, splices: record => resolve(record, false), metadata: record => resolve(record, true)};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const _rapierLedgerIndexes = new WeakMap();
|
|
118
|
+
function _rapierLedgerIndex(ledger) {
|
|
119
|
+
const records = ledger || [];
|
|
120
|
+
if (!Array.isArray(records)) return _rapierRecordIndex(records);
|
|
121
|
+
const held = _rapierLedgerIndexes.get(records);
|
|
122
|
+
if (held && held.records.length === records.length && held.records.at(-1) === records.at(-1) &&
|
|
123
|
+
(held.checked || held.records.every((record, at) => record === records[at]))) {
|
|
124
|
+
if (!held.checked) { held.checked = true; queueMicrotask(() => { held.checked = false; }); }
|
|
125
|
+
return held.index;
|
|
126
|
+
}
|
|
127
|
+
if (!records.every(record => record && typeof record === 'object' && Object.isFrozen(record) && Object.isFrozen(record.transaction ?? {})))
|
|
128
|
+
return _rapierRecordIndex(records);
|
|
129
|
+
const index = _rapierRecordIndex(records, true);
|
|
130
|
+
_rapierLedgerIndexes.set(records, {records: records.slice(), index});
|
|
131
|
+
return index;
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
function _rapierRecordSplices(entry, ledger) {
|
|
114
135
|
try {
|
|
115
136
|
if (entry && Array.isArray(entry.splices)) return entry.splices;
|
|
116
|
-
return
|
|
137
|
+
return _rapierLedgerIndex(ledger).splices(entry);
|
|
117
138
|
} catch (_) { return null; }
|
|
118
139
|
}
|
|
119
140
|
|
|
@@ -163,7 +184,7 @@ function _rapierRecordMetadata(entry, ledger) {
|
|
|
163
184
|
try {
|
|
164
185
|
if (Object.hasOwn(entry, 'metadata')) return _rapierValidMetadataEffect(entry.metadata) ? entry.metadata : undefined;
|
|
165
186
|
if (Array.isArray(entry.splices)) return null;
|
|
166
|
-
return
|
|
187
|
+
return _rapierLedgerIndex(ledger).metadata(entry);
|
|
167
188
|
} catch (_) { return undefined; }
|
|
168
189
|
}
|
|
169
190
|
|
|
@@ -213,87 +234,89 @@ function _rapierPictureFinish(record, original, earlier, lookup) {
|
|
|
213
234
|
});
|
|
214
235
|
}
|
|
215
236
|
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
inactive.add(ref);
|
|
266
|
-
}
|
|
237
|
+
function _rapierHistoryWalker(records, lookup, visit = null, observe = null) {
|
|
238
|
+
let nodes = new Map(), references = new Map(), pictures = new Map();
|
|
239
|
+
const byId = new Map(), finished = new Set(), earlier = [];
|
|
240
|
+
const update = (target, delta) => {
|
|
241
|
+
const pending = [[target, delta]];
|
|
242
|
+
while (pending.length) {
|
|
243
|
+
const [id, change] = pending.pop(), node = nodes.get(id), was = node.active;
|
|
244
|
+
node.blockers += change;
|
|
245
|
+
if (node.blockers < 0) throw Error('effect');
|
|
246
|
+
node.active = node.blockers === 0;
|
|
247
|
+
if (was !== node.active) for (const ref of node.references) pending.push([ref, node.active ? 1 : -1]);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const activity = new Map();
|
|
251
|
+
const active = id => {
|
|
252
|
+
if (visit) return nodes.get(id).active;
|
|
253
|
+
const pending = [[id, 0]];
|
|
254
|
+
while (pending.length) {
|
|
255
|
+
const frame = pending.at(-1), target = frame[0];
|
|
256
|
+
if (activity.has(target)) { pending.pop(); continue; }
|
|
257
|
+
const dependents = nodes.get(target).dependents;
|
|
258
|
+
if (frame[1] === dependents.length) { activity.set(target, true); pending.pop(); continue; }
|
|
259
|
+
const next = dependents[frame[1]];
|
|
260
|
+
if (!activity.has(next)) pending.push([next, 0]);
|
|
261
|
+
else if (activity.get(next)) { activity.set(target, false); pending.pop(); }
|
|
262
|
+
else frame[1]++;
|
|
263
|
+
}
|
|
264
|
+
return activity.get(id);
|
|
265
|
+
};
|
|
266
|
+
const lastUndo = new Map(), contexts = new Map(), order = [];
|
|
267
|
+
const contextOf = origin => {
|
|
268
|
+
if (!origin) return null;
|
|
269
|
+
if (!_rapierValidAuthored(origin)) throw Error('origin');
|
|
270
|
+
const known = new Set(), pending = origin.basis.slice();
|
|
271
|
+
while (pending.length) {
|
|
272
|
+
const id = pending.pop(), context = contexts.get(id);
|
|
273
|
+
if (!context) throw Error('origin');
|
|
274
|
+
if (known.has(id)) continue;
|
|
275
|
+
known.add(id);
|
|
276
|
+
if (context.basis) pending.push(...context.basis);
|
|
277
|
+
else for (let index = 0; index < context.index; index++) known.add(order[index]);
|
|
278
|
+
}
|
|
279
|
+
const inactive = new Set();
|
|
280
|
+
for (let index = order.length - 1; index >= 0; index--) {
|
|
281
|
+
const id = order[index];
|
|
282
|
+
if (!known.has(id) || inactive.has(id)) continue;
|
|
283
|
+
for (const ref of references.get(id)) {
|
|
284
|
+
if (!known.has(ref)) throw Error('origin');
|
|
285
|
+
inactive.add(ref);
|
|
267
286
|
}
|
|
268
|
-
|
|
269
|
-
};
|
|
270
|
-
|
|
287
|
+
}
|
|
288
|
+
return {known, inactive};
|
|
289
|
+
};
|
|
290
|
+
const walker = {observe, failed: false};
|
|
291
|
+
walker.add = record => {
|
|
292
|
+
if (walker.failed) return false;
|
|
293
|
+
try {
|
|
271
294
|
const tx = record?.transaction;
|
|
272
|
-
if (!tx || typeof tx.id !== 'string' || !tx.id || nodes.has(tx.id) || (tx.reverts && tx.reapplies)) return
|
|
295
|
+
if (!tx || typeof tx.id !== 'string' || !tx.id || nodes.has(tx.id) || (tx.reverts && tx.reapplies)) { walker.failed = true; return false; }
|
|
273
296
|
const supplied = tx.sourceTransactionIds || (tx.sourceTransactionId ? [tx.sourceTransactionId] : []);
|
|
274
297
|
if (!Array.isArray(supplied) || new Set(supplied).size !== supplied.length ||
|
|
275
|
-
(tx.sourceTransactionIds && tx.sourceTransactionId && (supplied.length !== 1 || supplied[0] !== tx.sourceTransactionId))) return
|
|
298
|
+
(tx.sourceTransactionIds && tx.sourceTransactionId && (supplied.length !== 1 || supplied[0] !== tx.sourceTransactionId))) { walker.failed = true; return false; }
|
|
276
299
|
let refs = supplied;
|
|
277
300
|
if (tx.operation === 'document.finish-picture' && tx.sourceTransactionId) {
|
|
278
301
|
const owner = tx.sourceTransactionId;
|
|
279
302
|
if (!nodes.has(owner) || !active(owner) || finished.has(owner) ||
|
|
280
|
-
!_rapierPictureFinish(record, byId.get(owner), earlier, lookup ||= _rapierRecordIndex(records))) return
|
|
303
|
+
!_rapierPictureFinish(record, byId.get(owner), earlier, lookup ||= _rapierRecordIndex(records))) { walker.failed = true; return false; }
|
|
281
304
|
pictures.set(tx.id, owner); finished.add(owner); refs = [];
|
|
282
305
|
}
|
|
283
306
|
if (tx.reverts || tx.reapplies) {
|
|
284
307
|
const target = tx.reverts || tx.reapplies;
|
|
285
|
-
if (!nodes.has(target) || (supplied.length && (supplied.length !== 1 || supplied[0] !== target))) return
|
|
308
|
+
if (!nodes.has(target) || (supplied.length && (supplied.length !== 1 || supplied[0] !== target))) { walker.failed = true; return false; }
|
|
286
309
|
if (tx.reverts) refs = [target];
|
|
287
310
|
else {
|
|
288
311
|
const context = contextOf(record.authored || tx.authored);
|
|
289
312
|
const undo = (lastUndo.get(target) || []).findLast(id => context
|
|
290
313
|
? context.known.has(id) && !context.inactive.has(id) : active(id));
|
|
291
|
-
if (!undo) return
|
|
314
|
+
if (!undo) { walker.failed = true; return false; }
|
|
292
315
|
refs = [undo];
|
|
293
316
|
}
|
|
294
317
|
}
|
|
295
|
-
if (refs.some(id => typeof id !== 'string' || !nodes.has(id) || pictures.has(id))) return
|
|
296
|
-
if (tx.actor?.kind === 'agent' && refs.some(id => byId.get(id).transaction.actor?.kind !== 'agent')) return
|
|
318
|
+
if (refs.some(id => typeof id !== 'string' || !nodes.has(id) || pictures.has(id))) { walker.failed = true; return false; }
|
|
319
|
+
if (tx.actor?.kind === 'agent' && refs.some(id => byId.get(id).transaction.actor?.kind !== 'agent')) { walker.failed = true; return false; }
|
|
297
320
|
nodes.set(tx.id, {active: true, blockers: 0, references: refs, dependents: []}); references.set(tx.id, refs.slice());
|
|
298
321
|
byId.set(tx.id, record); earlier.push(record);
|
|
299
322
|
for (const ref of refs) {
|
|
@@ -308,23 +331,45 @@ function _rapierWalkHistoryEffects(records, lookup, visit = null, observe = null
|
|
|
308
331
|
lastUndo.get(tx.reverts).push(tx.id);
|
|
309
332
|
}
|
|
310
333
|
if (visit) visit(record, nodes, refs);
|
|
311
|
-
if (observe) observe(record, refs, active);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
if (walker.observe) walker.observe(record, refs, active);
|
|
335
|
+
return true;
|
|
336
|
+
} catch (_) { walker.failed = true; return false; }
|
|
337
|
+
};
|
|
338
|
+
walker.finish = () => {
|
|
339
|
+
if (walker.failed) return {ok: false};
|
|
340
|
+
const blockers = new Map(), effectiveActIds = [];
|
|
341
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
342
|
+
const id = order[i], node = nodes.get(id), on = visit ? node.active : !(blockers.get(id) > 0);
|
|
343
|
+
if (!visit && on) for (const ref of node.references) blockers.set(ref, (blockers.get(ref) || 0) + 1);
|
|
344
|
+
if (on && !pictures.has(id)) effectiveActIds.push(id);
|
|
316
345
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
346
|
+
effectiveActIds.reverse();
|
|
347
|
+
const result = {ok: true, effectiveActIds, active: new Set(effectiveActIds), references, pictures};
|
|
348
|
+
walker.shared = true;
|
|
349
|
+
return result;
|
|
350
|
+
};
|
|
351
|
+
const add = walker.add;
|
|
352
|
+
walker.add = record => {
|
|
353
|
+
if (walker.shared) { references = new Map(references); pictures = new Map(pictures); walker.shared = false; }
|
|
354
|
+
return add(record);
|
|
355
|
+
};
|
|
356
|
+
return walker;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _rapierWalkHistoryEffects(records, lookup, visit = null, observe = null) {
|
|
360
|
+
if (!Array.isArray(records)) return {ok: false};
|
|
361
|
+
const walker = _rapierHistoryWalker(records, lookup, visit, observe);
|
|
362
|
+
for (const record of records) if (!walker.add(record)) return {ok: false};
|
|
363
|
+
return walker.finish();
|
|
320
364
|
}
|
|
321
365
|
|
|
322
366
|
function _rapierHistoryEffects(records, visit = null) {
|
|
323
367
|
return _rapierWalkHistoryEffects(records, null, visit);
|
|
324
368
|
}
|
|
325
369
|
|
|
326
|
-
function _rapierMetadataProof(lookup) {
|
|
327
|
-
const values = {}, required = {},
|
|
370
|
+
function _rapierMetadataProof(lookup, from = null) {
|
|
371
|
+
const values = {...from?.values}, required = {...from?.required},
|
|
372
|
+
assignments = {filename: [...(from?.assignments.filename || [])], docKind: [...(from?.assignments.docKind || [])]};
|
|
328
373
|
const requireInitial = (key, value) => {
|
|
329
374
|
if (Object.hasOwn(required, key) && required[key] !== value) throw Error('metadata_effect');
|
|
330
375
|
required[key] = value;
|
|
@@ -351,7 +396,7 @@ function _rapierMetadataProof(lookup) {
|
|
|
351
396
|
if (!_rapierValidMetadata(initial) || Object.keys(required).some(key => initial[key] !== required[key])) return null;
|
|
352
397
|
return {...initial, ...values};
|
|
353
398
|
};
|
|
354
|
-
return {observe, replay};
|
|
399
|
+
return {observe, replay, fork: other => _rapierMetadataProof(other || lookup, {values, required, assignments})};
|
|
355
400
|
}
|
|
356
401
|
|
|
357
402
|
function _rapierMetadataState(initial, records, effects = _rapierHistoryEffects(records)) {
|
|
@@ -458,16 +503,79 @@ const _rapierPrepareHistoryRecords = (() => {
|
|
|
458
503
|
frame.descriptors = descriptors; frame.fields = fields;
|
|
459
504
|
frame.leaving = true; active.add(value); pending.push(frame);
|
|
460
505
|
for (let at = fields.length - 1; at >= 0; at--) {
|
|
461
|
-
const key = fields[at]
|
|
506
|
+
const key = fields[at], child = descriptors[key].value;
|
|
507
|
+
const prior = array && child && typeof child === 'object' ? copies.get(child) : undefined;
|
|
508
|
+
if (prior?.immutable && !active.has(child) && frame.depth + 1 + prior.height <= 64) {
|
|
509
|
+
finish({parent: frame, key}, prior.copy, true, prior.height); continue;
|
|
510
|
+
}
|
|
511
|
+
pending.push({value: child, parent: frame, key, depth: frame.depth + 1});
|
|
462
512
|
}
|
|
463
513
|
}
|
|
464
514
|
return result.value;
|
|
465
515
|
};
|
|
516
|
+
let resumable = [];
|
|
517
|
+
const rowOf = (lookup, record) => {
|
|
518
|
+
const splices = lookup.splices(record), metadata = lookup.metadata(record);
|
|
519
|
+
if (splices && !Object.isFrozen(splices)) {
|
|
520
|
+
for (const row of splices) Object.freeze(row);
|
|
521
|
+
Object.freeze(splices);
|
|
522
|
+
}
|
|
523
|
+
if (metadata && !Object.isFrozen(metadata)) {
|
|
524
|
+
for (const pair of Object.values(metadata)) Object.freeze(pair);
|
|
525
|
+
Object.freeze(metadata);
|
|
526
|
+
}
|
|
527
|
+
return Object.freeze({record, splices, metadata});
|
|
528
|
+
};
|
|
529
|
+
const observe = (records, basis, byId, rows, effects, proof) => {
|
|
530
|
+
for (const refs of effects.references.values()) Object.freeze(refs);
|
|
531
|
+
const effectView = Object.freeze({ok: true, effectiveActIds: Object.freeze(effects.effectiveActIds),
|
|
532
|
+
active: view(effects.active), references: view(effects.references), pictures: view(effects.pictures)});
|
|
533
|
+
const observation = Object.freeze({ok: effects.ok, records, rows: Object.freeze(rows), effects: effectView, byId: view(byId),
|
|
534
|
+
replayMetadata: initial => effects.ok ? proof.replay(initial) : null,
|
|
535
|
+
validRecord: (index, documentAuthority, maxDocumentBytes, utf8Length, allowGap = false) => {
|
|
536
|
+
if (!effects.ok || !Number.isSafeInteger(index) || index < 0 || index >= records.length) return false;
|
|
537
|
+
const row = rows[index], tx = row.record.transaction;
|
|
538
|
+
return _rapierCheckLedgerRecord(row.record, records[index - 1] || null, documentAuthority, maxDocumentBytes, utf8Length, allowGap,
|
|
539
|
+
{...row, duplicate: false, semantic: !!(tx.reverts || tx.reapplies || tx.sourceTransactionId || tx.sourceTransactionIds?.length)});
|
|
540
|
+
}});
|
|
541
|
+
prepared = new WeakMap([[records, {basis, observation}]]);
|
|
542
|
+
return observation;
|
|
543
|
+
};
|
|
544
|
+
const authorityOf = state => state.records[0]?.transaction?.documentAuthority;
|
|
545
|
+
const keep = state => { resumable = [state, ...resumable.filter(row => row !== state && row.records[0] !== state.records[0] &&
|
|
546
|
+
authorityOf(row) === authorityOf(state))].slice(0, 4); };
|
|
547
|
+
const extend = records => {
|
|
548
|
+
const base = resumable.find(row => row.records.length < records.length && row.records[0] === records[0] &&
|
|
549
|
+
row.records.every((record, at) => record === records[at]));
|
|
550
|
+
if (!base) return null;
|
|
551
|
+
resumable = resumable.filter(row => row !== base);
|
|
552
|
+
const more = records.slice(base.records.length), byId = new Map(base.byId);
|
|
553
|
+
for (const record of more) {
|
|
554
|
+
const id = record?.transaction?.id;
|
|
555
|
+
if (typeof id !== 'string' || !id || byId.has(id)) return null;
|
|
556
|
+
byId.set(id, record);
|
|
557
|
+
}
|
|
558
|
+
const {lookup, walker} = base;
|
|
559
|
+
lookup.extend(more);
|
|
560
|
+
const rows = base.rows.concat(more.map(record => rowOf(lookup, record)));
|
|
561
|
+
const proof = base.proof.fork(lookup);
|
|
562
|
+
walker.observe = proof.observe;
|
|
563
|
+
for (const record of more) if (!walker.add(record)) return null;
|
|
564
|
+
const effects = walker.finish();
|
|
565
|
+
if (!effects.ok) return null;
|
|
566
|
+
if (rows.every(row => row.splices && row.metadata !== undefined)) keep({records, lookup, walker, proof, byId, rows});
|
|
567
|
+
return observe(records, records, byId, rows, effects, proof);
|
|
568
|
+
};
|
|
466
569
|
return (input, earlier = input) => {
|
|
467
570
|
try {
|
|
468
571
|
if (!Array.isArray(input) || !Array.isArray(earlier)) return {ok: false};
|
|
469
572
|
const records = capture(input), basis = input === earlier ? records : capture(earlier), previous = prepared.get(records);
|
|
470
573
|
if (previous?.basis === basis) return previous.observation;
|
|
574
|
+
if (basis === records) {
|
|
575
|
+
let grown = null;
|
|
576
|
+
try { grown = extend(records); } catch (_) { grown = null; }
|
|
577
|
+
if (grown) return grown;
|
|
578
|
+
}
|
|
471
579
|
const byId = new Map();
|
|
472
580
|
for (const record of records) {
|
|
473
581
|
const id = record?.transaction?.id;
|
|
@@ -475,41 +583,24 @@ const _rapierPrepareHistoryRecords = (() => {
|
|
|
475
583
|
byId.set(id, record);
|
|
476
584
|
}
|
|
477
585
|
const lookup = _rapierRecordIndex(basis), proof = _rapierMetadataProof(lookup);
|
|
478
|
-
const rows = records.map(record =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
Object.freeze(splices);
|
|
483
|
-
}
|
|
484
|
-
if (metadata && !Object.isFrozen(metadata)) {
|
|
485
|
-
for (const pair of Object.values(metadata)) Object.freeze(pair);
|
|
486
|
-
Object.freeze(metadata);
|
|
487
|
-
}
|
|
488
|
-
return Object.freeze({record, splices, metadata});
|
|
489
|
-
});
|
|
490
|
-
const effects = _rapierWalkHistoryEffects(records, lookup, null, proof.observe);
|
|
586
|
+
const rows = records.map(record => rowOf(lookup, record));
|
|
587
|
+
const walker = _rapierHistoryWalker(records, lookup, null, proof.observe);
|
|
588
|
+
for (const record of records) if (!walker.add(record)) return {ok: false};
|
|
589
|
+
const effects = walker.finish();
|
|
491
590
|
if (!effects.ok) return {ok: false};
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const observation = Object.freeze({ok: effects.ok, records, rows: Object.freeze(rows), effects: effectView, byId: view(byId),
|
|
496
|
-
replayMetadata: initial => effects.ok ? proof.replay(initial) : null,
|
|
497
|
-
validRecord: (index, documentAuthority, maxDocumentBytes, utf8Length, allowGap = false) => {
|
|
498
|
-
if (!effects.ok || !Number.isSafeInteger(index) || index < 0 || index >= records.length) return false;
|
|
499
|
-
const row = rows[index], tx = row.record.transaction;
|
|
500
|
-
return _rapierCheckLedgerRecord(row.record, records[index - 1] || null, documentAuthority, maxDocumentBytes, utf8Length, allowGap,
|
|
501
|
-
{...row, duplicate: false, semantic: !!(tx.reverts || tx.reapplies || tx.sourceTransactionId || tx.sourceTransactionIds?.length)});
|
|
502
|
-
}});
|
|
503
|
-
prepared = new WeakMap([[records, {basis, observation}]]); return observation;
|
|
591
|
+
if (basis === records && rows.every(row => row.splices && row.metadata !== undefined))
|
|
592
|
+
keep({records, lookup, walker, proof, byId, rows});
|
|
593
|
+
return observe(records, basis, byId, rows, effects, proof);
|
|
504
594
|
} catch (_) { return {ok: false}; }
|
|
505
595
|
};
|
|
506
596
|
})();
|
|
507
597
|
|
|
508
598
|
const _rapierJournalRecord = (() => {
|
|
509
599
|
const copies = new WeakMap();
|
|
600
|
+
const fixed = value => { if (value && typeof value === 'object') { for (const key of Object.keys(value)) fixed(value[key]); Object.freeze(value); } return value; };
|
|
510
601
|
return record => {
|
|
511
602
|
let copy = copies.get(record);
|
|
512
|
-
if (!copy) { copy = JSON.parse(JSON.stringify(record)); copies.set(record, copy); }
|
|
603
|
+
if (!copy) { copy = fixed(JSON.parse(JSON.stringify(record))); copies.set(record, copy); }
|
|
513
604
|
return copy;
|
|
514
605
|
};
|
|
515
606
|
})();
|
|
@@ -55,18 +55,23 @@ function createMarkdownRenderer(runtime) {
|
|
|
55
55
|
window?.addEventListener?.('rapier:original-file-ready', scheduleWikiEmbeds);
|
|
56
56
|
const wikiMode = options => options.wikiEmbedding ? 'links' : options.wikiLinks !== undefined ? options.wikiLinks
|
|
57
57
|
: globalThis.RapierOriginalFolderCore?.currentOriginal?.() ? 'label' : false;
|
|
58
|
+
let codeLeft = Infinity;
|
|
58
59
|
function initMarkdownIt() {
|
|
59
60
|
if (typeof window.markdownit !== 'function') throw new Error('markdownit');
|
|
60
61
|
if (typeof _rapierMarkdownPreview === 'function') globalThis.rapierRenderPreview = _rapierMarkdownPreview;
|
|
61
62
|
|
|
62
63
|
md = window.markdownit({
|
|
63
64
|
...RAPIER_MARKDOWN_SPEC.options,
|
|
64
|
-
highlight: (code, lang) => lang && _rapierHighlightAdmitted(code) ? _rapierCodeHtml(code, lang) : '',
|
|
65
|
+
highlight: (code, lang) => lang && _rapierHighlightAdmitted(code) && (codeLeft -= code.length) >= 0 ? _rapierCodeHtml(code, lang) : '',
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
_rapierApplyMarkdownSpec(md, window);
|
|
68
69
|
globalThis.RapierImageAssets.installMarkdownImages(md);
|
|
69
70
|
_rapierInstallSemanticProbe(md);
|
|
71
|
+
md.core.ruler.after('block', 'rapier_part_decorations', state => {
|
|
72
|
+
if (state.env.rapierPartDecorations && !/[<$\\]/.test(state.src) && !state.src.includes('^['))
|
|
73
|
+
state.tokens = _rapierPartDecorationGroups(state.tokens).flat();
|
|
74
|
+
});
|
|
70
75
|
_rapierInstallMarkdownMath(md, RAPIER_MARKDOWN_SPEC.rules.mathBlock);
|
|
71
76
|
|
|
72
77
|
const markOpenDefault = md.renderer.rules.mark_open ||
|
|
@@ -138,7 +143,7 @@ function initMarkdownIt() {
|
|
|
138
143
|
'" data-rapier-image-source="' + esc(encodeURIComponent(token.content)) + '">' + esc(label) + '</span>';
|
|
139
144
|
}
|
|
140
145
|
const note = embed && !env.rapierWikiEmbedding;
|
|
141
|
-
if (note) scheduleWikiEmbeds();
|
|
146
|
+
if (note && !env.rapierPassive) scheduleWikiEmbeds();
|
|
142
147
|
return '<span class="rapier-source-token rapier-source-token--wiki' + (note ? '-embed' : '') +
|
|
143
148
|
'" data-rapier-source="' + esc(encodeURIComponent(token.content)) + '" data-rapier-visible="' + esc(encodeURIComponent(label)) +
|
|
144
149
|
'"><a href="' + esc(href) + '">' + esc(label) + '</a></span>';
|
|
@@ -340,6 +345,7 @@ function initMarkdownIt() {
|
|
|
340
345
|
};
|
|
341
346
|
|
|
342
347
|
const _fenceDefault = md.renderer.rules.fence;
|
|
348
|
+
let _mathPromptedOnce = false;
|
|
343
349
|
{
|
|
344
350
|
let _diagramPromptedOnce = false;
|
|
345
351
|
md.renderer.rules.fence = function (tokens, idx, opts, env, self) {
|
|
@@ -366,8 +372,16 @@ function initMarkdownIt() {
|
|
|
366
372
|
}
|
|
367
373
|
const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
|
|
368
374
|
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true;
|
|
369
|
-
const
|
|
370
|
-
|
|
375
|
+
const diagramReady = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
|
|
376
|
+
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
377
|
+
const mathNeeded = provider?.requiresMath?.(src) === true;
|
|
378
|
+
const mathReady = !mathNeeded || mathProvider?.status === 'ready';
|
|
379
|
+
if (!mathReady && !env.rapierPassive && !mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
380
|
+
_mathPromptedOnce = true;
|
|
381
|
+
try { _rapierUiMath.request(); } catch (_) {}
|
|
382
|
+
}
|
|
383
|
+
const ready = diagramReady && mathReady;
|
|
384
|
+
if (!diagramReady && !env.rapierPassive && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
371
385
|
_diagramPromptedOnce = true;
|
|
372
386
|
try { _rapierUiDiagram.request(); } catch (_) {}
|
|
373
387
|
}
|
|
@@ -401,7 +415,6 @@ function initMarkdownIt() {
|
|
|
401
415
|
preserveEmphasisMarkup('strong_open', 'data-rapier-strong');
|
|
402
416
|
|
|
403
417
|
{
|
|
404
|
-
let _mathPromptedOnce = false;
|
|
405
418
|
const _renderMath = (src, displayMode, env, authored) => {
|
|
406
419
|
const marker = displayMode ? '$$' : '$';
|
|
407
420
|
const source = authored || marker + src + marker, literal = md.utils.escapeHtml(source);
|
|
@@ -412,7 +425,7 @@ function initMarkdownIt() {
|
|
|
412
425
|
if (env.rapierPage) return sourceHtml;
|
|
413
426
|
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
414
427
|
if (!mathProvider || mathProvider.status !== 'ready' || typeof mathProvider.renderToString !== 'function') {
|
|
415
|
-
if (!mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
428
|
+
if (!env.rapierPassive && !mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
416
429
|
_mathPromptedOnce = true;
|
|
417
430
|
try { _rapierUiMath.request(); } catch (_) {}
|
|
418
431
|
}
|
|
@@ -444,6 +457,10 @@ function initMarkdownIt() {
|
|
|
444
457
|
}
|
|
445
458
|
globalThis.RapierMarkdownLayout.installMarkdownLayout(md);
|
|
446
459
|
globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
|
|
460
|
+
const render = md.renderer.render;
|
|
461
|
+
md.renderer.render = function (tokens, options, env) {
|
|
462
|
+
return render.call(this, _rapierCloseOpenDetails(tokens), options, env);
|
|
463
|
+
};
|
|
447
464
|
}
|
|
448
465
|
|
|
449
466
|
function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
@@ -464,42 +481,56 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
464
481
|
}
|
|
465
482
|
|
|
466
483
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
467
|
-
const env = {rapierImagePresentation: options.imagePresentation === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
484
|
+
const env = {rapierImagePresentation: options.imagePresentation === true, rapierPassive: options.passive === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
468
485
|
if (options.wikiEmbedding) env.rapierWikiEmbedding = true;
|
|
469
486
|
const wikiLinks = wikiMode(options);
|
|
470
487
|
if (wikiLinks) env.rapierWikiLinks = wikiLinks;
|
|
471
488
|
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
472
489
|
const nesting = md && options.maxNesting > 0 ? options.maxNesting : 0;
|
|
473
|
-
let rendered;
|
|
490
|
+
let cutAt = -1, rendered;
|
|
491
|
+
codeLeft = options.codeBudget >= 0 ? options.codeBudget : Infinity;
|
|
492
|
+
try {
|
|
474
493
|
if (nesting || md && options.closeDetails) {
|
|
475
494
|
const saved = md.options.maxNesting;
|
|
476
495
|
if (nesting) md.options.maxNesting = nesting;
|
|
477
496
|
try {
|
|
478
497
|
const tokens = md.parse(markdown, env);
|
|
498
|
+
if (nesting) cutAt = _rapierNestingCutAt(tokens, nesting);
|
|
479
499
|
rendered = md.renderer.render(options.closeDetails ? _rapierCloseOpenDetails(tokens) : tokens, md.options, env);
|
|
480
500
|
} finally { md.options.maxNesting = saved; }
|
|
481
501
|
} else rendered = md
|
|
482
502
|
? md.render(markdown, env)
|
|
483
503
|
: '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
504
|
+
} finally { codeLeft = Infinity; }
|
|
484
505
|
if (md && env.footnotes && Array.isArray(env.footnotes.list) && env.footnotes.list.length) {
|
|
485
506
|
const labels = env.footnotes.list.map((item, index) =>
|
|
486
507
|
String(item && item.label || ('inline-' + (index + 1))));
|
|
487
508
|
rendered = _relabelFootnoteHtml(rendered, labels);
|
|
488
509
|
}
|
|
489
510
|
root.innerHTML = sanitizeRapierHtml(rendered, options.page ? 'source' : 'export');
|
|
511
|
+
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
490
512
|
_rapierApplyBlockDirection(root);
|
|
491
513
|
|
|
492
|
-
_rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false);
|
|
514
|
+
_rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false, options.carry?.slugs, options.carry?.headingIds);
|
|
493
515
|
_rapierDisambiguateRenderedAnchors(root);
|
|
494
516
|
|
|
495
517
|
root.querySelectorAll('pre > code').forEach(_rapierNormalizeCodeElement);
|
|
496
|
-
_rapierMarkTableCaptions(root);
|
|
518
|
+
_rapierMarkTableCaptions(root, options.carry);
|
|
497
519
|
_rapierMarkFigureCaptions(root);
|
|
498
520
|
_rapierMarkListContinuations(_rapierChildElements(root));
|
|
499
|
-
_rapierDecorateJumpLists(root);
|
|
521
|
+
_rapierDecorateJumpLists(root, options.carry);
|
|
500
522
|
return root;
|
|
501
523
|
}
|
|
502
524
|
|
|
525
|
+
function _rapierAppendNestingCut(root, markdown, cutAt) {
|
|
526
|
+
const note = root.ownerDocument.createElement('p'), pre = root.ownerDocument.createElement('pre'), code = root.ownerDocument.createElement('code');
|
|
527
|
+
note.setAttribute('data-rapier-nesting-cut', 'true');
|
|
528
|
+
note.textContent = 'Nesting is too deep to show here. The rest of the document follows as plain text.';
|
|
529
|
+
code.textContent = String(markdown).split('\n').slice(cutAt).join('\n');
|
|
530
|
+
pre.append(code);
|
|
531
|
+
root.append(note, pre);
|
|
532
|
+
}
|
|
533
|
+
const _rapierNestingCutAt = (tokens, nesting) => { const deepest = tokens.find(token => token.level >= nesting - 1 && token.map); return deepest ? deepest.map[0] + 1 : -1; };
|
|
503
534
|
async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {}) {
|
|
504
535
|
const work = options.work || options;
|
|
505
536
|
check(work);
|
|
@@ -517,15 +548,16 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
517
548
|
let parsed;
|
|
518
549
|
try { parsed = !wikiLinks && options.parsed || (runtime.parseAsync && !wikiLinks && !nesting ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv}); }
|
|
519
550
|
finally { md.options.maxNesting = saved; }
|
|
520
|
-
let {tokens, env} = parsed;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
env = {...env, rapierImagePresentation: options.imagePresentation === true};
|
|
551
|
+
let {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
|
|
552
|
+
const cutAt = nesting ? _rapierNestingCutAt(tokens, nesting) : -1;
|
|
553
|
+
tokens = _rapierCloseOpenDetails(tokens);
|
|
554
|
+
env = {...env, rapierImagePresentation: options.imagePresentation === true, rapierPassive: options.passive === true};
|
|
524
555
|
await pause(work, .3);
|
|
525
556
|
const inert = document.createElement('template').content.ownerDocument;
|
|
526
557
|
const root = inert.createElement('div'), part = inert.createElement('div'), boundary = htmlBoundary();
|
|
527
558
|
root.setAttribute('data-rapier-semantic-root', 'true');
|
|
528
559
|
let html = '', began = performance.now(), complete = false;
|
|
560
|
+
codeLeft = options.codeBudget >= 0 ? options.codeBudget : Infinity;
|
|
529
561
|
const append = () => {
|
|
530
562
|
if (md.renderer.rapierTransformHtml) html = md.renderer.rapierTransformHtml(html);
|
|
531
563
|
if (labels?.length) html = _relabelFootnoteHtml(html, labels);
|
|
@@ -546,23 +578,72 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
546
578
|
}
|
|
547
579
|
}
|
|
548
580
|
if (html) append();
|
|
581
|
+
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
549
582
|
parsed = tokens = env = labels = null;
|
|
550
|
-
await finishAsync(_rapierSemanticDecorations(root), work);
|
|
583
|
+
await finishAsync(_rapierSemanticDecorations(root, options.carry), work);
|
|
551
584
|
await pause(work, .6);
|
|
552
585
|
complete = true;
|
|
553
586
|
return root;
|
|
554
|
-
} finally { if (!complete) { root.replaceChildren(); part.replaceChildren(); } }
|
|
587
|
+
} finally { codeLeft = Infinity; if (!complete) { root.replaceChildren(); part.replaceChildren(); } }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function _rapierPartDecorationGroups(tokens) {
|
|
591
|
+
const groups = [];
|
|
592
|
+
for (let at = 0; at < tokens.length; at++) {
|
|
593
|
+
const first = tokens[at];
|
|
594
|
+
if (first.type !== 'heading_open' && first.type !== 'table_open') continue;
|
|
595
|
+
const close = first.type === 'heading_open' ? 'heading_close' : 'table_close';
|
|
596
|
+
let end = at + 1;
|
|
597
|
+
while (end < tokens.length && !(tokens[end].type === close && tokens[end].level === first.level)) end++;
|
|
598
|
+
end++;
|
|
599
|
+
if (first.type === 'table_open' && tokens[end]?.type === 'paragraph_open' && tokens[end].level === first.level) {
|
|
600
|
+
while (end < tokens.length && !(tokens[end].type === 'paragraph_close' && tokens[end].level === first.level)) end++;
|
|
601
|
+
end++;
|
|
602
|
+
}
|
|
603
|
+
groups.push(tokens.slice(at, end));
|
|
604
|
+
at = end - 1;
|
|
605
|
+
}
|
|
606
|
+
return groups;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function _rapierReadPartDecorations(canonical, metadata, options = {}) {
|
|
610
|
+
const carry = options.carry || {slugs: Object.create(null), captions: 0, continued: true}, captionStart = carry.captions || 0;
|
|
611
|
+
const markdown = _rapierSplitOpeningFrontmatter(canonical).body, env = {rapierImagePresentation: options.imagePresentation === true, rapierPassive: true, rapierPartDecorations: true,
|
|
612
|
+
...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
613
|
+
if (options.wikiEmbedding) env.rapierWikiEmbedding = true;
|
|
614
|
+
const wikiLinks = wikiMode(options);
|
|
615
|
+
if (wikiLinks) env.rapierWikiLinks = wikiLinks;
|
|
616
|
+
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
617
|
+
const saved = md.options.maxNesting;
|
|
618
|
+
let tokens;
|
|
619
|
+
try {
|
|
620
|
+
if (options.maxNesting > 0) md.options.maxNesting = options.maxNesting;
|
|
621
|
+
tokens = md.parse(markdown, env);
|
|
622
|
+
} finally { md.options.maxNesting = saved; }
|
|
623
|
+
if (tokens.some(token => token.type === 'html_block' || token.type === 'math_block' || token.type.startsWith('footnote_') ||
|
|
624
|
+
token.type === 'inline' && token.children?.some(child => child.type === 'html_inline' || child.type === 'math_inline' || child.type.startsWith('footnote_')))) {
|
|
625
|
+
const root = _rapierRenderSemanticRoot(canonical, metadata, {...options, carry, codeBudget: 0, passive: true});
|
|
626
|
+
return Object.freeze({headingIds: Object.freeze([...root.querySelectorAll(RAPIER_RENDERED_HEADING_SELECTOR)].map(heading => heading.id)), captionStart});
|
|
627
|
+
}
|
|
628
|
+
const html = [];
|
|
629
|
+
for (const group of _rapierPartDecorationGroups(tokens)) html.push('<div>', md.renderer.render(group, md.options, env), '</div>');
|
|
630
|
+
const root = document.createElement('template').content.ownerDocument.createElement('div');
|
|
631
|
+
root.innerHTML = sanitizeRapierHtml(html.join(''), 'export');
|
|
632
|
+
_rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false, carry.slugs);
|
|
633
|
+
const headingIds = Object.freeze([...root.querySelectorAll(RAPIER_RENDERED_HEADING_SELECTOR)].map(heading => heading.id));
|
|
634
|
+
_rapierMarkTableCaptions(root, carry);
|
|
635
|
+
return Object.freeze({headingIds, captionStart});
|
|
555
636
|
}
|
|
556
637
|
|
|
557
|
-
function* _rapierSemanticDecorations(root) {
|
|
638
|
+
function* _rapierSemanticDecorations(root, carry = null) {
|
|
558
639
|
yield* _rapierApplyBlockDirectionSteps(root); yield;
|
|
559
|
-
yield* _rapierAssignHeadingSlugsSteps(root, RAPIER_RENDERED_HEADING_SELECTOR, false); yield;
|
|
640
|
+
yield* _rapierAssignHeadingSlugsSteps(root, RAPIER_RENDERED_HEADING_SELECTOR, false, carry?.slugs, carry?.headingIds); yield;
|
|
560
641
|
yield* _rapierDisambiguateRenderedAnchorsSteps(root); yield;
|
|
561
642
|
for (const code of root.querySelectorAll('pre > code')) { _rapierNormalizeCodeElement(code); yield; }
|
|
562
|
-
yield* _rapierMarkTableCaptionsSteps(root); yield;
|
|
643
|
+
yield* _rapierMarkTableCaptionsSteps(root, carry); yield;
|
|
563
644
|
yield* _rapierMarkFigureCaptionsSteps(root); yield;
|
|
564
645
|
yield* _rapierMarkListContinuationsSteps(_rapierChildElements(root)); yield;
|
|
565
|
-
yield* _rapierDecorateJumpListsSteps(root); yield;
|
|
646
|
+
yield* _rapierDecorateJumpListsSteps(root, carry); yield;
|
|
566
647
|
}
|
|
567
648
|
|
|
568
649
|
function _rapierInstallSemanticProbe(instance) {
|
|
@@ -815,7 +896,18 @@ function _rapierCloseOpenDetails(tokens) {
|
|
|
815
896
|
const tags = token => {
|
|
816
897
|
const found = [];
|
|
817
898
|
if (token.type === 'html_inline') found.push(token.content);
|
|
818
|
-
else if (token.type === 'html_block')
|
|
899
|
+
else if (token.type === 'html_block') {
|
|
900
|
+
let raw = '';
|
|
901
|
+
_rapierRawHtmlFragment(token.content, tag => {
|
|
902
|
+
if (raw) { if (new RegExp('^</' + raw + '[\\t\\n\\f\\r ]*>$', 'i').test(tag)) raw = ''; }
|
|
903
|
+
else {
|
|
904
|
+
const literal = /^<(script|style|textarea|title|xmp|iframe|noembed|noframes|plaintext)(?=[\t\n\f\r />])/i.exec(tag);
|
|
905
|
+
if (literal) raw = literal[1];
|
|
906
|
+
else found.push(tag);
|
|
907
|
+
}
|
|
908
|
+
return tag;
|
|
909
|
+
}, md);
|
|
910
|
+
}
|
|
819
911
|
else if (token.type === 'inline') for (const child of token.children || []) if (child.type === 'html_inline') found.push(child.content);
|
|
820
912
|
return found;
|
|
821
913
|
};
|
|
@@ -896,17 +988,23 @@ function _rapierFirstStrongDir(text) {
|
|
|
896
988
|
|
|
897
989
|
const _RAPIER_DIR_BLOCKS = 'p,h1,h2,h3,h4,h5,h6,li,blockquote,ul,ol,dt,dd,td,th';
|
|
898
990
|
|
|
899
|
-
function _rapierAssignHeadingSlugs(root, selector, reserveExisting) { return finish(_rapierAssignHeadingSlugsSteps(root, selector, reserveExisting)); }
|
|
991
|
+
function _rapierAssignHeadingSlugs(root, selector, reserveExisting, taken, assigned) { return finish(_rapierAssignHeadingSlugsSteps(root, selector, reserveExisting, taken, assigned)); }
|
|
900
992
|
|
|
901
|
-
function* _rapierAssignHeadingSlugsSteps(root, selector = RAPIER_RENDERED_HEADING_SELECTOR, reserveExisting = true) {
|
|
993
|
+
function* _rapierAssignHeadingSlugsSteps(root, selector = RAPIER_RENDERED_HEADING_SELECTOR, reserveExisting = true, taken = null, assigned = null) {
|
|
902
994
|
if (!root || !root.querySelectorAll) return;
|
|
903
|
-
const used = Object.create(null);
|
|
995
|
+
const used = taken || Object.create(null);
|
|
904
996
|
const headings = _rapierDormantHeadings(root, selector, true);
|
|
997
|
+
if (assigned && assigned.length !== headings.length) {
|
|
998
|
+
try { console.warn('[rapier-reader] Part heading facts do not match its rendered headings; assigning ordinary heading IDs.'); } catch (_) {}
|
|
999
|
+
assigned = null;
|
|
1000
|
+
}
|
|
905
1001
|
const headingSet = new Set(headings);
|
|
906
1002
|
if (reserveExisting) for (const element of _rapierDormantHeadings(root.isConnected ? document : root, '[id]', true)) { yield;
|
|
907
1003
|
if (!headingSet.has(element) && element.id) used[element.id] = 0;
|
|
908
1004
|
}
|
|
1005
|
+
let index = 0;
|
|
909
1006
|
for (const heading of headings) { yield;
|
|
1007
|
+
if (assigned) { heading.id = assigned[index++]; continue; }
|
|
910
1008
|
const base = _rapierHeadingSlugBase(heading.textContent);
|
|
911
1009
|
const owned = typeof _rapierChromeOwnsId === 'function' && _rapierChromeOwnsId(base);
|
|
912
1010
|
heading.id = _rapierNextHeadingSlug(owned ? 'heading-' + base : base, used);
|
|
@@ -1007,9 +1105,9 @@ function _rapierDecodeHash(hash) {
|
|
|
1007
1105
|
catch (_) { return raw; }
|
|
1008
1106
|
}
|
|
1009
1107
|
|
|
1010
|
-
function _rapierDecorateJumpLists(root) { return finish(_rapierDecorateJumpListsSteps(root)); }
|
|
1108
|
+
function _rapierDecorateJumpLists(root, carry) { return finish(_rapierDecorateJumpListsSteps(root, carry)); }
|
|
1011
1109
|
|
|
1012
|
-
function* _rapierDecorateJumpListsSteps(root) {
|
|
1110
|
+
function* _rapierDecorateJumpListsSteps(root, carry = null) {
|
|
1013
1111
|
for (const list of root.querySelectorAll('ul')) { yield;
|
|
1014
1112
|
if (list.closest('li')) continue;
|
|
1015
1113
|
const items = Array.from(list.children).filter(item => item.tagName === 'LI');
|
|
@@ -1032,7 +1130,7 @@ function* _rapierDecorateJumpListsSteps(root) {
|
|
|
1032
1130
|
}
|
|
1033
1131
|
}
|
|
1034
1132
|
|
|
1035
|
-
const first = root.firstElementChild, only = first && !first.nextElementSibling ? first : null;
|
|
1133
|
+
const first = root.firstElementChild, only = !carry && first && !first.nextElementSibling ? first : null;
|
|
1036
1134
|
if (only && only.tagName === 'P' && /^(?:jump\s+to|contents|table\s+of\s+contents):?$/i.test(String(only.textContent || '').trim())) {
|
|
1037
1135
|
only.classList.add('rapier-jump-label-candidate');
|
|
1038
1136
|
}
|
|
@@ -1074,11 +1172,11 @@ function* _rapierMarkListContinuationsSteps(blocks, mark = (element, name) => el
|
|
|
1074
1172
|
}
|
|
1075
1173
|
}
|
|
1076
1174
|
|
|
1077
|
-
function _rapierMarkTableCaptions(root) { return finish(_rapierMarkTableCaptionsSteps(root)); }
|
|
1175
|
+
function _rapierMarkTableCaptions(root, carry) { return finish(_rapierMarkTableCaptionsSteps(root, carry)); }
|
|
1078
1176
|
|
|
1079
|
-
function* _rapierMarkTableCaptionsSteps(root) {
|
|
1177
|
+
function* _rapierMarkTableCaptionsSteps(root, carry = null) {
|
|
1080
1178
|
const used = new Set(Array.from(root.querySelectorAll('[id]'), element => element.id));
|
|
1081
|
-
let ordinal = 0;
|
|
1179
|
+
let ordinal = carry?.captions || 0;
|
|
1082
1180
|
for (const table of root.querySelectorAll('table')) { yield;
|
|
1083
1181
|
const sibling = table.nextElementSibling;
|
|
1084
1182
|
if (!sibling || sibling.tagName !== 'P') continue;
|
|
@@ -1091,6 +1189,7 @@ function* _rapierMarkTableCaptionsSteps(root) {
|
|
|
1091
1189
|
const existing = (table.getAttribute('aria-describedby') || '').trim();
|
|
1092
1190
|
table.setAttribute('aria-describedby', existing ? existing + ' ' + id : id);
|
|
1093
1191
|
}
|
|
1192
|
+
if (carry) carry.captions = ordinal;
|
|
1094
1193
|
}
|
|
1095
1194
|
|
|
1096
1195
|
|
|
@@ -1794,7 +1893,7 @@ function renderWithMathML(source, metadata = {}) {
|
|
|
1794
1893
|
return {html, equations};
|
|
1795
1894
|
}
|
|
1796
1895
|
|
|
1797
|
-
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, _rapierInertParse, _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};
|
|
1896
|
+
return {_rapierReadPartDecorations, renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierInertParse, _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};
|
|
1798
1897
|
}
|
|
1799
1898
|
export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};
|
|
1800
1899
|
|
|
@@ -52,7 +52,7 @@ const DIAGRAM_LABEL_CSS = new Set(('color fill stroke background-color font-fami
|
|
|
52
52
|
const DOCUMENT_STYLE_PROPERTIES = new Set([...DIAGRAM_LABEL_CSS, ...('background display visibility opacity '
|
|
53
53
|
+ 'border border-top border-right border-bottom border-left border-color border-style border-width border-collapse border-spacing '
|
|
54
54
|
+ 'border-top-color border-right-color border-bottom-color border-left-color border-top-width border-right-width border-bottom-width border-left-width '
|
|
55
|
-
+ 'table-layout caption-side empty-cells list-style-type list-style-position direction unicode-bidi '
|
|
55
|
+
+ 'table-layout caption-side empty-cells list-style-type list-style-position direction unicode-bidi hyphens '
|
|
56
56
|
+ 'stroke-width stroke-opacity stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit '
|
|
57
57
|
+ 'fill-opacity fill-rule clip-rule clip-path filter stop-color stop-opacity text-anchor dominant-baseline alignment-baseline '
|
|
58
58
|
+ 'paint-order vector-effect shape-rendering color-interpolation color-interpolation-filters').split(' ')]);
|
package/dist/kit/render.mjs
CHANGED
|
@@ -289,15 +289,21 @@ function* _rapierProjectStyledRootSteps(semanticRoot, options) {
|
|
|
289
289
|
if (code) {
|
|
290
290
|
const lang = _rapierLanguageClass(code) || String(metadata.codeLang || '');
|
|
291
291
|
code.classList.add('artifact-code');
|
|
292
|
-
if (metadata.docKind === 'code')
|
|
292
|
+
if (metadata.docKind === 'code') {
|
|
293
|
+
const colored = _rapierArtifactHighlight(code.textContent || '', lang);
|
|
294
|
+
_rapierPaintCode(code, opts.sanitized ? sanitizeRapierHtml(colored, 'export') : colored);
|
|
295
|
+
}
|
|
293
296
|
if (lang) code.classList.add('language-' + String(lang).replace(/[^a-z0-9_-]/gi, ''));
|
|
294
297
|
}
|
|
295
298
|
} else {
|
|
299
|
+
let codeLeft = opts.codeBudget >= 0 ? opts.codeBudget : Infinity;
|
|
296
300
|
for (const code of root.querySelectorAll('pre > code')) { yield;
|
|
297
301
|
if (code.parentElement?.classList.contains('diagram-source') &&
|
|
298
302
|
code.closest('.diagram-block')?.getAttribute('data-diagram-state') === 'ready') continue;
|
|
299
303
|
const lang = _rapierLanguageClass(code) || 'text';
|
|
300
|
-
|
|
304
|
+
if ((codeLeft -= code.textContent.length) < 0) { code.replaceChildren(document.createTextNode(code.textContent)); continue; }
|
|
305
|
+
const colored = _rapierArtifactHighlight(code.textContent || '', lang);
|
|
306
|
+
_rapierPaintCode(code, opts.sanitized ? sanitizeRapierHtml(colored, 'export') : colored);
|
|
301
307
|
}
|
|
302
308
|
|
|
303
309
|
for (const table of root.querySelectorAll('table')) { yield;
|
|
@@ -336,6 +342,13 @@ function* _rapierProjectStyledRootSteps(semanticRoot, options) {
|
|
|
336
342
|
|
|
337
343
|
yield* _rapierPrefixPortableAnchorsSteps(root, opts.baseName || metadata.filename || 'document');
|
|
338
344
|
|
|
345
|
+
if (opts.sanitized) {
|
|
346
|
+
for (const cache of root.querySelectorAll('.diagram-cache')) {
|
|
347
|
+
yield;
|
|
348
|
+
cache.innerHTML = sanitizeRapierHtml(cache.innerHTML, 'export');
|
|
349
|
+
}
|
|
350
|
+
return root;
|
|
351
|
+
}
|
|
339
352
|
return yield* cleanTree(root, sanitizeRapierHtml, 'export');
|
|
340
353
|
}
|
|
341
354
|
|
package/dist/spec/md-assets.mjs
CHANGED
|
@@ -100,7 +100,7 @@ export function* blockWindows(source, bodyOffset, parser, env, keepNul = false)
|
|
|
100
100
|
return end < body.length ? sourceOffset(end + 1) : undefined;
|
|
101
101
|
}});
|
|
102
102
|
let lines = 0;
|
|
103
|
-
for (let at =
|
|
103
|
+
for (let at = body.indexOf('\n'); at !== -1; at = body.indexOf('\n', at + 1)) lines++;
|
|
104
104
|
if (lines <= BLOCK_WINDOW) {
|
|
105
105
|
state = new parser.block.State(body, parser, env, tokens);
|
|
106
106
|
state.rapierSourceSlice = (from, to) => source.slice(sourceOffset(from), sourceOffset(to));
|
|
@@ -179,8 +179,7 @@ export function* blockWindows(source, bodyOffset, parser, env, keepNul = false)
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
const
|
|
183
|
-
code >= 97 && code <= 122 || '-._~:/?@+$,;='.includes(String.fromCharCode(code)));
|
|
182
|
+
const INVALID_PERCENT_PAYLOAD = /[^A-Za-z0-9._~:\/?@+$,;=%-]/;
|
|
184
183
|
const isHexUnit = code => code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
|
|
185
184
|
let lastScan = null;
|
|
186
185
|
export function dataImage(url) {
|
|
@@ -197,13 +196,11 @@ function scanDataImage(url) {
|
|
|
197
196
|
...(prepared.encoding ? {encoding: prepared.encoding} : {})};
|
|
198
197
|
if (/^data:image\/svg\+xml,/i.test(url)) {
|
|
199
198
|
const payloadStart = 19;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
at += 2;
|
|
206
|
-
} else if (!(code < 128 && PERCENT_PAYLOAD[code])) return null;
|
|
199
|
+
if (INVALID_PERCENT_PAYLOAD.test(url)) return null;
|
|
200
|
+
let byteLength = url.length - payloadStart;
|
|
201
|
+
for (let at = url.indexOf('%', payloadStart); at !== -1; at = url.indexOf('%', at + 3)) {
|
|
202
|
+
if (!isHexUnit(url.charCodeAt(at + 1)) || !isHexUnit(url.charCodeAt(at + 2))) return null;
|
|
203
|
+
byteLength -= 2;
|
|
207
204
|
}
|
|
208
205
|
return byteLength && byteLength <= IMAGE_LIMITS.bytes
|
|
209
206
|
? {codec: 'image/svg+xml', byteLength, payloadStart, encoding: 'percent'} : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.43",
|
|
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",
|
package/style.css
CHANGED
|
@@ -391,6 +391,14 @@
|
|
|
391
391
|
-webkit-mask: var(--md-glyph-task-box) center / 100% 100% no-repeat;
|
|
392
392
|
mask: var(--md-glyph-task-box) center / 100% 100% no-repeat;
|
|
393
393
|
}
|
|
394
|
+
/* The box is a background through a mask, and two modes drop backgrounds: forced colors (Windows High Contrast) paints them as the page,
|
|
395
|
+
and print without "background graphics" leaves them out, so the boxes vanished. In those two the same mask is painted in the system's
|
|
396
|
+
text color and printed as drawn; nowhere else does anything change. */
|
|
397
|
+
.md-render .task-list-item input[type="checkbox"] { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
398
|
+
@media (forced-colors: active) {
|
|
399
|
+
.md-render .task-list-item input[type="checkbox"] { forced-color-adjust: none; }
|
|
400
|
+
.md-render .task-list-item input[type="checkbox"]::before,.md-render .task-list-item input[type="checkbox"]:checked::after { background: CanvasText; }
|
|
401
|
+
}
|
|
394
402
|
.md-render .task-list-item input[type="checkbox"]:checked::before {
|
|
395
403
|
-webkit-mask-image: var(--md-glyph-task-open);
|
|
396
404
|
mask-image: var(--md-glyph-task-open);
|