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.
@@ -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
 
@@ -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 = canonicalJSON({...ledger, start: {...ledger.start, text: ''}});
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(canonicalParts(payload), native))) throw fault('envelope checksum differs');
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
- expanded.push({transaction: plainData(record.transaction), splices: rows.map(row => ({pos: row.pos, removed: row.removed, inserted: row.inserted})),
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(canonicalParts(payload), native)};
203
- proofs.set(ledger, {shell: canonicalJSON({...ledger, start: {...ledger.start, text: ''}}), startText,
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) value = effects
104
- ? Object.fromEntries(Object.entries(value).map(([key, pair]) => [key, {before: pair.after, after: pair.before}]))
105
- : value.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed}));
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 _rapierRecordIndex(ledger || []).splices(entry);
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 _rapierRecordIndex(ledger || []).metadata(entry);
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 ready = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
370
- if (!ready && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
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
- if (options.closeDetails) tokens = _rapierCloseOpenDetails(tokens);
522
- let labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
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') _rapierRawHtmlFragment(token.content, tag => { found.push(tag); return tag; }, md);
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.40",
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);