rapier-markdown-kit 1.2.40 → 1.2.42
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 +55 -2
- package/dist/kit/ledger/format.mjs +35 -12
- package/dist/kit/ledger/history.mjs +1 -1
- package/dist/kit/ledger/inverse-source.mjs +9 -0
- package/dist/kit/ledger/journal-records.mjs +28 -7
- package/dist/kit/render-markdown.mjs +43 -8
- package/dist/kit/render-sanitize.mjs +1 -1
- 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,65 @@ 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
|
+
export function* canonicalRowsParts(rows, depth = 0) {
|
|
114
|
+
const {array, keys} = fields(rows, depth, new Set());
|
|
115
|
+
if (!array) { yield* canonicalParts(rows, depth); return; }
|
|
116
|
+
yield '[';
|
|
117
|
+
for (let i = 0; i < keys.length; i++) { if (i) yield ','; yield canonicalRow(rows[keys[i]], depth + 1); }
|
|
118
|
+
yield ']';
|
|
119
|
+
}
|
|
67
120
|
export function exactKeys(value, keys) {
|
|
68
121
|
return value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
69
122
|
}
|
|
@@ -4,7 +4,7 @@ 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';
|
|
@@ -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,10 @@ 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);
|
|
154
173
|
function* produceLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}, native = false) {
|
|
155
174
|
if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
|
|
156
175
|
metadata = {...metadata};
|
|
@@ -162,10 +181,13 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
162
181
|
const expanded = [];
|
|
163
182
|
for (const {record, splices: rows, metadata: effect} of prepared.rows) {
|
|
164
183
|
if (!rows || effect === undefined) throw fault('navigation target was not retained');
|
|
165
|
-
|
|
184
|
+
const held = expandedRows.get(record);
|
|
185
|
+
if (held && sameSplices(held.rows, rows) && sameEffect(held.effect, effect)) { expanded.push(held.row); yield; continue; }
|
|
186
|
+
expanded.push(ownRow({transaction: plainData(record.transaction), splices: rows.map(row => ({pos: row.pos, removed: row.removed, inserted: row.inserted})),
|
|
166
187
|
beforeHash: record.beforeHash, afterHash: record.afterHash, ...(Object.hasOwn(record, 'authored') ? {authored: plainData(record.authored)} : {}), ...(effect ? {metadata: plainData(effect)} : {}),
|
|
167
188
|
...(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)})});
|
|
189
|
+
...(record.derivedCommentIndex == null ? {} : {derivedCommentIndex: typeof record.derivedCommentIndex === 'string' ? record.derivedCommentIndex : plainData(record.derivedCommentIndex)})}));
|
|
190
|
+
expandedRows.set(record, {row: expanded.at(-1), rows, effect});
|
|
169
191
|
yield;
|
|
170
192
|
}
|
|
171
193
|
const headBytes = bytes;
|
|
@@ -199,8 +221,9 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
199
221
|
}
|
|
200
222
|
const replayedMetadata = prepared.replayMetadata(startMetadata);
|
|
201
223
|
if (!replayedMetadata || canonicalJSON(replayedMetadata) !== canonicalJSON(metadata)) throw fault('metadata does not describe this document');
|
|
202
|
-
const ledger = {...payload, sha256: yield* checksum(
|
|
203
|
-
|
|
224
|
+
const ledger = {...payload, sha256: yield* checksum(envelopeParts(payload), native)};
|
|
225
|
+
ownLedger(ledger);
|
|
226
|
+
proofs.set(ledger, {shell: Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join(''), startText,
|
|
204
227
|
text, metadata: {...metadata}, root, revision, prepared: _rapierPrepareHistoryRecords(ledger.records),
|
|
205
228
|
source: new HistorySource(text)});
|
|
206
229
|
return {ledger, contentRoot, utf8Bytes: headBytes};
|
|
@@ -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,9 +100,12 @@ 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;
|
|
@@ -110,10 +113,27 @@ function _rapierRecordIndex(records) {
|
|
|
110
113
|
return {byId, splices: record => resolve(record, false), metadata: record => resolve(record, true)};
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
const _rapierLedgerIndexes = new WeakMap();
|
|
117
|
+
function _rapierLedgerIndex(ledger) {
|
|
118
|
+
const records = ledger || [];
|
|
119
|
+
if (!Array.isArray(records)) return _rapierRecordIndex(records);
|
|
120
|
+
const held = _rapierLedgerIndexes.get(records);
|
|
121
|
+
if (held && held.records.length === records.length && held.records.at(-1) === records.at(-1) &&
|
|
122
|
+
(held.checked || held.records.every((record, at) => record === records[at]))) {
|
|
123
|
+
if (!held.checked) { held.checked = true; queueMicrotask(() => { held.checked = false; }); }
|
|
124
|
+
return held.index;
|
|
125
|
+
}
|
|
126
|
+
if (!records.every(record => record && typeof record === 'object' && Object.isFrozen(record) && Object.isFrozen(record.transaction ?? {})))
|
|
127
|
+
return _rapierRecordIndex(records);
|
|
128
|
+
const index = _rapierRecordIndex(records, true);
|
|
129
|
+
_rapierLedgerIndexes.set(records, {records: records.slice(), index});
|
|
130
|
+
return index;
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
function _rapierRecordSplices(entry, ledger) {
|
|
114
134
|
try {
|
|
115
135
|
if (entry && Array.isArray(entry.splices)) return entry.splices;
|
|
116
|
-
return
|
|
136
|
+
return _rapierLedgerIndex(ledger).splices(entry);
|
|
117
137
|
} catch (_) { return null; }
|
|
118
138
|
}
|
|
119
139
|
|
|
@@ -163,7 +183,7 @@ function _rapierRecordMetadata(entry, ledger) {
|
|
|
163
183
|
try {
|
|
164
184
|
if (Object.hasOwn(entry, 'metadata')) return _rapierValidMetadataEffect(entry.metadata) ? entry.metadata : undefined;
|
|
165
185
|
if (Array.isArray(entry.splices)) return null;
|
|
166
|
-
return
|
|
186
|
+
return _rapierLedgerIndex(ledger).metadata(entry);
|
|
167
187
|
} catch (_) { return undefined; }
|
|
168
188
|
}
|
|
169
189
|
|
|
@@ -507,9 +527,10 @@ const _rapierPrepareHistoryRecords = (() => {
|
|
|
507
527
|
|
|
508
528
|
const _rapierJournalRecord = (() => {
|
|
509
529
|
const copies = new WeakMap();
|
|
530
|
+
const fixed = value => { if (value && typeof value === 'object') { for (const key of Object.keys(value)) fixed(value[key]); Object.freeze(value); } return value; };
|
|
510
531
|
return record => {
|
|
511
532
|
let copy = copies.get(record);
|
|
512
|
-
if (!copy) { copy = JSON.parse(JSON.stringify(record)); copies.set(record, copy); }
|
|
533
|
+
if (!copy) { copy = fixed(JSON.parse(JSON.stringify(record))); copies.set(record, copy); }
|
|
513
534
|
return copy;
|
|
514
535
|
};
|
|
515
536
|
})();
|
|
@@ -340,6 +340,7 @@ function initMarkdownIt() {
|
|
|
340
340
|
};
|
|
341
341
|
|
|
342
342
|
const _fenceDefault = md.renderer.rules.fence;
|
|
343
|
+
let _mathPromptedOnce = false;
|
|
343
344
|
{
|
|
344
345
|
let _diagramPromptedOnce = false;
|
|
345
346
|
md.renderer.rules.fence = function (tokens, idx, opts, env, self) {
|
|
@@ -366,8 +367,16 @@ function initMarkdownIt() {
|
|
|
366
367
|
}
|
|
367
368
|
const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
|
|
368
369
|
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true;
|
|
369
|
-
const
|
|
370
|
-
|
|
370
|
+
const diagramReady = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
|
|
371
|
+
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
372
|
+
const mathNeeded = provider?.requiresMath?.(src) === true;
|
|
373
|
+
const mathReady = !mathNeeded || mathProvider?.status === 'ready';
|
|
374
|
+
if (!mathReady && !mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
375
|
+
_mathPromptedOnce = true;
|
|
376
|
+
try { _rapierUiMath.request(); } catch (_) {}
|
|
377
|
+
}
|
|
378
|
+
const ready = diagramReady && mathReady;
|
|
379
|
+
if (!diagramReady && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
371
380
|
_diagramPromptedOnce = true;
|
|
372
381
|
try { _rapierUiDiagram.request(); } catch (_) {}
|
|
373
382
|
}
|
|
@@ -401,7 +410,6 @@ function initMarkdownIt() {
|
|
|
401
410
|
preserveEmphasisMarkup('strong_open', 'data-rapier-strong');
|
|
402
411
|
|
|
403
412
|
{
|
|
404
|
-
let _mathPromptedOnce = false;
|
|
405
413
|
const _renderMath = (src, displayMode, env, authored) => {
|
|
406
414
|
const marker = displayMode ? '$$' : '$';
|
|
407
415
|
const source = authored || marker + src + marker, literal = md.utils.escapeHtml(source);
|
|
@@ -444,6 +452,10 @@ function initMarkdownIt() {
|
|
|
444
452
|
}
|
|
445
453
|
globalThis.RapierMarkdownLayout.installMarkdownLayout(md);
|
|
446
454
|
globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
|
|
455
|
+
const render = md.renderer.render;
|
|
456
|
+
md.renderer.render = function (tokens, options, env) {
|
|
457
|
+
return render.call(this, _rapierCloseOpenDetails(tokens), options, env);
|
|
458
|
+
};
|
|
447
459
|
}
|
|
448
460
|
|
|
449
461
|
function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
@@ -470,12 +482,13 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
470
482
|
if (wikiLinks) env.rapierWikiLinks = wikiLinks;
|
|
471
483
|
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
472
484
|
const nesting = md && options.maxNesting > 0 ? options.maxNesting : 0;
|
|
473
|
-
let rendered;
|
|
485
|
+
let cutAt = -1, rendered;
|
|
474
486
|
if (nesting || md && options.closeDetails) {
|
|
475
487
|
const saved = md.options.maxNesting;
|
|
476
488
|
if (nesting) md.options.maxNesting = nesting;
|
|
477
489
|
try {
|
|
478
490
|
const tokens = md.parse(markdown, env);
|
|
491
|
+
if (nesting) cutAt = _rapierNestingCutAt(tokens, nesting);
|
|
479
492
|
rendered = md.renderer.render(options.closeDetails ? _rapierCloseOpenDetails(tokens) : tokens, md.options, env);
|
|
480
493
|
} finally { md.options.maxNesting = saved; }
|
|
481
494
|
} else rendered = md
|
|
@@ -487,6 +500,7 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
487
500
|
rendered = _relabelFootnoteHtml(rendered, labels);
|
|
488
501
|
}
|
|
489
502
|
root.innerHTML = sanitizeRapierHtml(rendered, options.page ? 'source' : 'export');
|
|
503
|
+
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
490
504
|
_rapierApplyBlockDirection(root);
|
|
491
505
|
|
|
492
506
|
_rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false);
|
|
@@ -500,6 +514,15 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
500
514
|
return root;
|
|
501
515
|
}
|
|
502
516
|
|
|
517
|
+
function _rapierAppendNestingCut(root, markdown, cutAt) {
|
|
518
|
+
const note = root.ownerDocument.createElement('p'), pre = root.ownerDocument.createElement('pre'), code = root.ownerDocument.createElement('code');
|
|
519
|
+
note.setAttribute('data-rapier-nesting-cut', 'true');
|
|
520
|
+
note.textContent = 'Nesting is too deep to show here. The rest of the document follows as plain text.';
|
|
521
|
+
code.textContent = String(markdown).split('\n').slice(cutAt).join('\n');
|
|
522
|
+
pre.append(code);
|
|
523
|
+
root.append(note, pre);
|
|
524
|
+
}
|
|
525
|
+
const _rapierNestingCutAt = (tokens, nesting) => { const deepest = tokens.find(token => token.level >= nesting - 1 && token.map); return deepest ? deepest.map[0] + 1 : -1; };
|
|
503
526
|
async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {}) {
|
|
504
527
|
const work = options.work || options;
|
|
505
528
|
check(work);
|
|
@@ -517,9 +540,9 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
517
540
|
let parsed;
|
|
518
541
|
try { parsed = !wikiLinks && options.parsed || (runtime.parseAsync && !wikiLinks && !nesting ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv}); }
|
|
519
542
|
finally { md.options.maxNesting = saved; }
|
|
520
|
-
let {tokens, env} = parsed;
|
|
521
|
-
|
|
522
|
-
|
|
543
|
+
let {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
|
|
544
|
+
const cutAt = nesting ? _rapierNestingCutAt(tokens, nesting) : -1;
|
|
545
|
+
tokens = _rapierCloseOpenDetails(tokens);
|
|
523
546
|
env = {...env, rapierImagePresentation: options.imagePresentation === true};
|
|
524
547
|
await pause(work, .3);
|
|
525
548
|
const inert = document.createElement('template').content.ownerDocument;
|
|
@@ -546,6 +569,7 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
546
569
|
}
|
|
547
570
|
}
|
|
548
571
|
if (html) append();
|
|
572
|
+
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
549
573
|
parsed = tokens = env = labels = null;
|
|
550
574
|
await finishAsync(_rapierSemanticDecorations(root), work);
|
|
551
575
|
await pause(work, .6);
|
|
@@ -815,7 +839,18 @@ function _rapierCloseOpenDetails(tokens) {
|
|
|
815
839
|
const tags = token => {
|
|
816
840
|
const found = [];
|
|
817
841
|
if (token.type === 'html_inline') found.push(token.content);
|
|
818
|
-
else if (token.type === 'html_block')
|
|
842
|
+
else if (token.type === 'html_block') {
|
|
843
|
+
let raw = '';
|
|
844
|
+
_rapierRawHtmlFragment(token.content, tag => {
|
|
845
|
+
if (raw) { if (new RegExp('^</' + raw + '[\\t\\n\\f\\r ]*>$', 'i').test(tag)) raw = ''; }
|
|
846
|
+
else {
|
|
847
|
+
const literal = /^<(script|style|textarea|title|xmp|iframe|noembed|noframes|plaintext)(?=[\t\n\f\r />])/i.exec(tag);
|
|
848
|
+
if (literal) raw = literal[1];
|
|
849
|
+
else found.push(tag);
|
|
850
|
+
}
|
|
851
|
+
return tag;
|
|
852
|
+
}, md);
|
|
853
|
+
}
|
|
819
854
|
else if (token.type === 'inline') for (const child of token.children || []) if (child.type === 'html_inline') found.push(child.content);
|
|
820
855
|
return found;
|
|
821
856
|
};
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.42",
|
|
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);
|