rapier-markdown-kit 1.2.32 → 1.2.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/dist/agent/vendor/pretext/NOTICE +2 -2
- package/dist/agent/vendor/pretext/SOURCE.json +62 -78
- package/dist/agent/vendor/pretext/analysis.js +24 -6
- package/dist/agent/vendor/pretext/gecko-line-breaks.js +1 -1
- package/dist/agent/vendor/pretext/generated/engine-break-data.js +1 -1
- package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +1 -1
- package/dist/agent/vendor/pretext/graphemes.js +1 -1
- package/dist/agent/vendor/pretext/han-kerning.js +1 -1
- package/dist/agent/vendor/pretext/layout.js +14 -11
- package/dist/agent/vendor/pretext/line-break.js +170 -195
- package/dist/agent/vendor/pretext/line-breaks.js +1 -7
- package/dist/agent/vendor/pretext/line-text.js +2 -2
- package/dist/agent/vendor/pretext/measurement.js +454 -113
- package/dist/agent/vendor/pretext/prepare.js +164 -244
- package/dist/agent/vendor/pretext/rich-inline.js +57 -37
- package/dist/agent/will.mjs +73 -0
- package/dist/kit/ledger/data.mjs +2 -2
- package/dist/kit/ledger/format.mjs +86 -42
- package/dist/kit/ledger/hash.mjs +43 -2
- package/dist/kit/ledger/history.mjs +6 -0
- package/dist/kit/ledger/inverse-source.mjs +6 -2
- package/dist/kit/ledger/journal-records.mjs +4 -2
- package/dist/kit/ledger/merge.mjs +3 -17
- package/dist/kit/ledger/text-merge.mjs +191 -28
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-markdown.mjs +133 -10
- package/dist/kit/render-print.mjs +3 -3
- package/dist/kit/render-sanitize.mjs +66 -23
- package/dist/kit/render.mjs +10 -10
- package/dist/kit/wiki.mjs +34 -0
- package/dist/kit/will.d.mts +3 -0
- package/dist/layout/hyphenation.mjs +6 -4
- package/dist/layout/line-plan.mjs +96 -0
- package/dist/layout/model.mjs +2 -1
- package/dist/layout/paragraph-fit.mjs +4 -4
- package/dist/layout/prepared-paragraph.mjs +19 -2
- package/dist/layout/typography.mjs +57 -63
- package/dist/spec/document-fonts.mjs +1 -0
- package/dist/spec/document-settings.mjs +1 -0
- package/dist/spec/html-blocks.mjs +10 -7
- package/dist/spec/md-assets.mjs +52 -12
- package/package.json +1 -1
- package/style.css +37 -28
- package/dist/agent/vendor/pretext/entry-geometry.js +0 -183
|
@@ -5,6 +5,7 @@ import {sourceView, HistorySource} from './source.mjs';
|
|
|
5
5
|
import {textRootParts, rootAfter} from './hash.mjs';
|
|
6
6
|
import {authoredHistory, authoredPlacements} from './transport.mjs';
|
|
7
7
|
import {plainData, canonicalJSON} from './data.mjs';
|
|
8
|
+
import {evaluateWill} from '../../agent/will.mjs';
|
|
8
9
|
|
|
9
10
|
const historyFailure = reason => ({ok: false, reason});
|
|
10
11
|
const historyInteger = value => Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
@@ -323,6 +324,8 @@ export function selectiveUndo(input, actIds, options = {}) {
|
|
|
323
324
|
(options.splitSplice !== undefined && typeof options.splitSplice !== 'function')) return historyFailure('act_invalid');
|
|
324
325
|
const requested = new Set(actIds), available = prepared.byId;
|
|
325
326
|
if ([...requested].some(id => !available.has(id))) return historyFailure('act_unavailable');
|
|
327
|
+
if (options.actor?.kind === 'agent' && [...requested].some(id => available.get(id).record.transaction.actor.kind !== 'agent'))
|
|
328
|
+
return historyFailure('act_forbidden');
|
|
326
329
|
const selected = new Set([...requested].filter(id => prepared.effects.active.has(id)));
|
|
327
330
|
const records = prepared.records;
|
|
328
331
|
let inverseId = 'planned-inverse'; while (available.has(inverseId)) inverseId += ':';
|
|
@@ -359,6 +362,9 @@ export function selectiveUndo(input, actIds, options = {}) {
|
|
|
359
362
|
sourceIndices: transported.sourceIndices.get(record.transaction.id)})) : prepared.rows;
|
|
360
363
|
const result = projectInverseSource(transported ? {...prepared, rows, records: transported.records} : prepared, selected, projectedEffects, options);
|
|
361
364
|
if (!result.ok) return result;
|
|
365
|
+
if (options.actor?.kind === 'agent' && prepared.metadata.docKind === 'markdown' &&
|
|
366
|
+
evaluateWill(prepared.source, result.splices).outcome !== 'applied')
|
|
367
|
+
return historyFailure('will_refused');
|
|
362
368
|
return {...result, actIds: [...requested], sourceTransactionIds: [...selected], metadata, afterMetadata,
|
|
363
369
|
effectChanged, unchanged: result.after === prepared.source && !metadata && !effectChanged};
|
|
364
370
|
} catch (_) { return historyFailure('history_invalid'); }
|
|
@@ -10,7 +10,7 @@ const historyBoundary = (source, at) => Number.isSafeInteger(at) && at >= 0 && !
|
|
|
10
10
|
!(at > 0 && at < source.length && (source.charCodeAt(at - 1) & 0xFC00) === 0xD800 &&
|
|
11
11
|
(source.charCodeAt(at) & 0xFC00) === 0xDC00);
|
|
12
12
|
|
|
13
|
-
function minimalHistorySplice(before, after, pos = 0) {
|
|
13
|
+
export function minimalHistorySplice(before, after, pos = 0) {
|
|
14
14
|
let first = 0, a = before.length, b = after.length;
|
|
15
15
|
while (first < a && first < b && before[first] === after[first]) first++;
|
|
16
16
|
if (!historyBoundary(before, first) || !historyBoundary(after, first)) first--;
|
|
@@ -262,7 +262,11 @@ export function projectInverseSource(prepared, selected, projectedEffects, optio
|
|
|
262
262
|
const start = boundary(row.pos), end = boundary(row.pos + row.removed.length);
|
|
263
263
|
let at = start.last, count = Math.max(0, end.first - at);
|
|
264
264
|
if (!row.removed.length) {
|
|
265
|
-
const
|
|
265
|
+
const run = pieces.slice(start.first, start.last), next = pieces[start.last];
|
|
266
|
+
let before = start.first - 1; while (before >= 0 && !pieces[before].text) before--;
|
|
267
|
+
const leftAct = before >= 0 ? byId.get(pieces[before].owner)?.actId : undefined;
|
|
268
|
+
const sameAct = leftAct !== undefined && run.some(item => item.mark?.actId === leftAct);
|
|
269
|
+
const marker = run.findIndex(item => item.mark?.inserted.length && (next && owns(next, item.mark) || !sameAct));
|
|
266
270
|
at = marker < 0 ? start.last : start.first + marker; count = 0;
|
|
267
271
|
}
|
|
268
272
|
const removed = pieces.slice(at, at + count);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {fields, scalar, plainPrototype} from './data.mjs';
|
|
3
3
|
import {transformText} from './source.mjs';
|
|
4
|
+
import {dataImage} from '../../spec/md-assets.mjs';
|
|
4
5
|
|
|
5
6
|
const _RAPIER_TRANSACTION_ACTOR_LIMIT = 160;
|
|
6
7
|
const _RAPIER_TRANSACTION_OPERATION_LIMIT = 96;
|
|
@@ -175,10 +176,10 @@ function _rapierPictureFinish(record, original, earlier, lookup) {
|
|
|
175
176
|
if (!reference || !/^\]\[[a-z0-9-]+\]$/i.test(reference.inserted) || reference.removed === reference.inserted) return false;
|
|
176
177
|
const definition = rows.find(row => row !== reference), oldLabel = reference.removed.slice(2, -1), newLabel = reference.inserted.slice(2, -1);
|
|
177
178
|
const body = (text, label) => {
|
|
178
|
-
const prefix = '[' + label + ']:
|
|
179
|
+
const prefix = '[' + label + ']: ';
|
|
179
180
|
if (typeof text !== 'string' || !text.startsWith(prefix)) return null;
|
|
180
181
|
const value = text.slice(prefix.length), end = value.indexOf(' '), bytes = end < 0 ? value : value.slice(0, end), title = end < 0 ? '' : value.slice(end);
|
|
181
|
-
return
|
|
182
|
+
return dataImage(bytes)?.codec === 'image/svg+xml' && !/[\r\n]/.test(title) ? title : null;
|
|
182
183
|
};
|
|
183
184
|
const title = body(definition.removed, oldLabel);
|
|
184
185
|
if (title === null || body(definition.inserted, newLabel) !== title) return false;
|
|
@@ -292,6 +293,7 @@ function _rapierWalkHistoryEffects(records, lookup, visit = null, observe = null
|
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
if (refs.some(id => typeof id !== 'string' || !nodes.has(id) || pictures.has(id))) return {ok: false};
|
|
296
|
+
if (tx.actor?.kind === 'agent' && refs.some(id => byId.get(id).transaction.actor?.kind !== 'agent')) return {ok: false};
|
|
295
297
|
nodes.set(tx.id, {active: true, blockers: 0, references: refs, dependents: []}); references.set(tx.id, refs.slice());
|
|
296
298
|
byId.set(tx.id, record); earlier.push(record);
|
|
297
299
|
for (const ref of refs) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {readLedger, exportLedger, rootAfter} from './format.mjs';
|
|
3
3
|
import {_rapierTransformSplices, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState} from './journal-records.mjs';
|
|
4
|
-
import {mergeText} from './text-merge.mjs';
|
|
4
|
+
import {mergeText, willMergeConflict} from './text-merge.mjs';
|
|
5
5
|
import {transposeAuthored, authoredPlacements} from './transport.mjs';
|
|
6
|
-
import {parseWill, stripOneTerminator} from '../../agent/will.mjs';
|
|
7
6
|
import {sha256} from './hash.mjs';
|
|
8
7
|
import {canonicalJSON, fault} from './data.mjs';
|
|
9
8
|
export function transportInterval(start, end, splices) {
|
|
@@ -58,17 +57,6 @@ function checkpointList(read) {
|
|
|
58
57
|
}
|
|
59
58
|
return list;
|
|
60
59
|
}
|
|
61
|
-
function kept(text) {
|
|
62
|
-
const will = parseWill(text);
|
|
63
|
-
if (will.fault) return {fault: will.fault, text};
|
|
64
|
-
return will.regions.filter(region => region.law !== 'edit').map(({law, intent, start, end}) =>
|
|
65
|
-
law === 'keep' ? {law, intent, text: text.slice(start, end)} : {law, intent});
|
|
66
|
-
}
|
|
67
|
-
function appended(from, to) {
|
|
68
|
-
const regions = text => parseWill(text).regions.filter(region => region.law === 'append');
|
|
69
|
-
const was = regions(from), now = regions(to), core = (text, region) => stripOneTerminator(text.slice(region.start, region.end));
|
|
70
|
-
return was.length === now.length && was.every((region, index) => core(to, now[index]).startsWith(core(from, region)));
|
|
71
|
-
}
|
|
72
60
|
function expandRetainedStart(shorter, longer) {
|
|
73
61
|
if (shorter.ledger.documentAuthority !== longer.ledger.documentAuthority ||
|
|
74
62
|
shorter.ledger.start.revision <= longer.ledger.start.revision) return null;
|
|
@@ -191,10 +179,8 @@ export function merge(first, second) {
|
|
|
191
179
|
const local = localIds.get(record.transaction.id);
|
|
192
180
|
if (local) mappedIds.set(record.transaction.id, local.transaction.id);
|
|
193
181
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
!appended(common.text, theirs.text) && !appended(ours.text, theirs.text);
|
|
197
|
-
let conflicted = keepDiffers && unseen.length > 0, incomingSource = common.text;
|
|
182
|
+
const keepDiffers = willMergeConflict(common.text, ours.text, theirs.text);
|
|
183
|
+
let conflicted = keepDiffers, incomingSource = common.text;
|
|
198
184
|
if (!conflicted) for (const record of incoming) {
|
|
199
185
|
const beforeSource = incomingSource;
|
|
200
186
|
incomingSource = _rapierTransformSplices(incomingSource, record.splices);
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {parseWill, stripOneTerminator} from '../../agent/will.mjs';
|
|
3
|
+
|
|
4
|
+
export function willMergeConflict(base, ours, theirs) {
|
|
5
|
+
const snapshot = text => {
|
|
6
|
+
const will = parseWill(text);
|
|
7
|
+
return {fault: will.fault, regions: will.regions.filter(region => region.law !== 'edit').map(region => ({
|
|
8
|
+
law: region.law, intent: region.intent, text: text.slice(region.start, region.end),
|
|
9
|
+
}))};
|
|
10
|
+
};
|
|
11
|
+
const a = snapshot(ours), b = snapshot(theirs);
|
|
12
|
+
if (a.fault || b.fault) return ours !== theirs;
|
|
13
|
+
const kept = value => JSON.stringify(value.regions.map(({law, intent, text}) => ({law, intent, ...(law === 'keep' ? {text} : {})})));
|
|
14
|
+
if (kept(a) !== kept(b)) return true;
|
|
15
|
+
if (base === null) return false;
|
|
16
|
+
const prior = snapshot(base);
|
|
17
|
+
if (prior.fault) return base !== ours || base !== theirs;
|
|
18
|
+
const appended = value => {
|
|
19
|
+
const before = prior.regions.filter(region => region.law === 'append'), after = value.regions.filter(region => region.law === 'append');
|
|
20
|
+
return before.length === after.length && before.every((region, index) =>
|
|
21
|
+
stripOneTerminator(after[index].text).startsWith(stripOneTerminator(region.text)));
|
|
22
|
+
};
|
|
23
|
+
return !appended(a) || !appended(b);
|
|
24
|
+
}
|
|
25
|
+
|
|
2
26
|
function textFault(code, message) { return Object.assign(new Error(message), {code}); }
|
|
3
27
|
export function canonicalNote(text) {
|
|
4
28
|
if (typeof text !== 'string') throw textFault('note_text', 'A note must be text');
|
|
@@ -272,13 +296,168 @@ function textDeclarations(units) {
|
|
|
272
296
|
return [...definitions, ...continuations, ...(text.match(/<!--[\s\S]*?(?:-->|$)/g) || [])].join('\0');
|
|
273
297
|
}
|
|
274
298
|
|
|
299
|
+
function* textGroups(units, a, b) {
|
|
300
|
+
const changes = [...a.map(e => ({...e, side: 0})), ...b.map(e => ({...e, side: 1}))].sort((x, y) => x.start - y.start || x.end - y.end || x.side - y.side);
|
|
301
|
+
for (let i = 0; i < changes.length;) {
|
|
302
|
+
const group = [changes[i++]], start = group[0].start; let end = group[0].end;
|
|
303
|
+
while (i < changes.length && (changes[i].start < end || group.some(e => {
|
|
304
|
+
const next = changes[i];
|
|
305
|
+
const afterBlock = (edit, insert) => insert.start === insert.end && insert.start === edit.end && edit.start < edit.end && units.slice(edit.start, edit.end).every(unit => textEnvelopeAt(unit));
|
|
306
|
+
if (afterBlock(e, next) || afterBlock(next, e)) return false;
|
|
307
|
+
if (e.start === e.end && next.start <= e.start && e.start <= next.end) return true;
|
|
308
|
+
if (next.start === next.end && e.start <= next.start && next.start <= e.end) return true;
|
|
309
|
+
return next.start >= e.end && units.slice(e.start, e.end).every(textSpace) &&
|
|
310
|
+
units.slice(e.end, next.start).every(textSpace) && units.slice(next.start, next.end).every(textSpace);
|
|
311
|
+
}))) {
|
|
312
|
+
group.push(changes[i]); end = Math.max(end, changes[i++].end);
|
|
313
|
+
}
|
|
314
|
+
const before = units.slice(start, end).join('');
|
|
315
|
+
const version = side => {
|
|
316
|
+
let value = '', at = start;
|
|
317
|
+
for (const e of group.filter(e => e.side === side)) { value += units.slice(at, e.start).join('') + e.text; at = e.end; }
|
|
318
|
+
return value + units.slice(at, end).join('');
|
|
319
|
+
};
|
|
320
|
+
yield {start, end, before, av: version(0), bv: version(1)};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function textMoves(groups) {
|
|
325
|
+
const blank = value => !value.trim();
|
|
326
|
+
const side = (g, which) => which === 0 ? g.av : g.bv;
|
|
327
|
+
for (const g of groups) {
|
|
328
|
+
if (g.resolved !== undefined || blank(g.before)) continue;
|
|
329
|
+
for (const gone of [0, 1]) {
|
|
330
|
+
const removed = side(g, gone), other = side(g, 1 - gone);
|
|
331
|
+
if (!blank(removed) || other === g.before) continue;
|
|
332
|
+
if (other.split(g.before).length === 2) { g.resolved = other.replace(g.before, ''); break; }
|
|
333
|
+
const homes = groups.filter(h => h !== g && h.resolved === undefined && blank(h.before) && blank(side(h, 1 - gone)) && side(h, gone).trim() === g.before.trim());
|
|
334
|
+
if (homes.length === 1) {
|
|
335
|
+
const trailing = side(homes[0], gone).match(/\s*$/)[0], edited = other.replace(/\s*$/, '');
|
|
336
|
+
g.resolved = ''; homes[0].resolved = side(homes[0], gone).match(/^\s*/)[0] + edited + trailing; break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return groups;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const textLines = text => text.match(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g) || [];
|
|
344
|
+
const textWords = text => text.match(/\s+|\S+/g) || [];
|
|
345
|
+
const TEXT_ITEM = /^( {0,3})(?:[-+*]|\d{1,9}[.)])[ \t]+\S/;
|
|
346
|
+
const TEXT_TAGS = /^(tags|aliases):[ \t]*\[([^\[\]\r\n]*)\][ \t]*(\r\n|\r|\n|$)/;
|
|
347
|
+
function textCounts(text) {
|
|
348
|
+
const counts = new Map();
|
|
349
|
+
for (const word of text.match(/[\p{L}\p{N}_]+/gu) || []) counts.set(word, (counts.get(word) || 0) + 1);
|
|
350
|
+
return counts;
|
|
351
|
+
}
|
|
352
|
+
function textConserved(before, av, bv, merged) {
|
|
353
|
+
const [b, a, t, m] = [before, av, bv, merged].map(textCounts);
|
|
354
|
+
for (const word of new Set([...b.keys(), ...a.keys(), ...t.keys(), ...m.keys()])) {
|
|
355
|
+
const base = b.get(word) || 0, da = (a.get(word) || 0) - base, dt = (t.get(word) || 0) - base, got = (m.get(word) || 0) - base;
|
|
356
|
+
const both = [da + dt, da, dt];
|
|
357
|
+
if (da * dt > 0 ? got < Math.min(...both) || got > Math.max(...both) : got !== da + dt) return false;
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
function textRefine(before, av, bv, options, level = 'line') {
|
|
362
|
+
const merged = textRefineLevel(before, av, bv, options, level);
|
|
363
|
+
return merged !== null && textConserved(before, av, bv, merged) ? merged : null;
|
|
364
|
+
}
|
|
365
|
+
function textRefineLevel(before, av, bv, options, level) {
|
|
366
|
+
const split = level === 'line' ? textLines : textWords;
|
|
367
|
+
const units = split(before), oursUnits = split(av), theirsUnits = split(bv);
|
|
368
|
+
const reordered = textPermuted(units, oursUnits, theirsUnits);
|
|
369
|
+
if (reordered !== null) return reordered;
|
|
370
|
+
const a = textEdits(units, oursUnits), b = textEdits(units, theirsUnits);
|
|
371
|
+
if (!a || !b) return null;
|
|
372
|
+
let out = '', pos = 0;
|
|
373
|
+
for (const g of textGroups(units, a, b)) {
|
|
374
|
+
out += units.slice(pos, g.start).join('');
|
|
375
|
+
if (g.av === g.bv) out += g.av;
|
|
376
|
+
else if (g.av === g.before) out += g.bv;
|
|
377
|
+
else if (g.bv === g.before) out += g.av;
|
|
378
|
+
else {
|
|
379
|
+
const fine = level === 'line' ? textRefineGroup(g, options) : null;
|
|
380
|
+
if (fine === null) return null;
|
|
381
|
+
out += fine;
|
|
382
|
+
}
|
|
383
|
+
pos = g.end;
|
|
384
|
+
}
|
|
385
|
+
return out + units.slice(pos).join('');
|
|
386
|
+
}
|
|
387
|
+
function textPermuted(units, ours, theirs) {
|
|
388
|
+
const solid = list => list.filter(unit => !textSpace(unit));
|
|
389
|
+
const baseSolid = solid(units), known = new Map(baseSolid.map((unit, k) => [unit, k]));
|
|
390
|
+
if (known.size !== baseSolid.length) return null;
|
|
391
|
+
const reorders = side => { const x = solid(side); return x.length === baseSolid.length && x.every(unit => known.has(unit)) && new Set(x).size === x.length && side.join('') !== units.join(''); };
|
|
392
|
+
const [first, second] = [reorders(ours), reorders(theirs)];
|
|
393
|
+
if (first === second) return null;
|
|
394
|
+
const [moved, changed] = first ? [ours, theirs] : [theirs, ours];
|
|
395
|
+
const edits = textEdits(units, changed);
|
|
396
|
+
if (!edits) return null;
|
|
397
|
+
const replace = new Map(), after = new Map();
|
|
398
|
+
for (const e of edits) {
|
|
399
|
+
const inner = []; for (let i = e.start; i < e.end; i++) if (!textSpace(units[i])) inner.push(i);
|
|
400
|
+
if (!inner.length) {
|
|
401
|
+
if (!e.text.trim()) continue;
|
|
402
|
+
let k = -1; for (let i = e.start - 1; i >= 0; i--) if (!textSpace(units[i])) { k = known.get(units[i]); break; }
|
|
403
|
+
const gap = e.start > 0 && textSpace(units[e.start - 1]);
|
|
404
|
+
after.set(k, [...(after.get(k) || []), {text: e.text, gap}]);
|
|
405
|
+
} else if (inner.length === 1) {
|
|
406
|
+
if (!e.text.trim()) replace.set(known.get(units[inner[0]]), units.slice(e.start, e.end).some(textSpace) ? ' ' : '');
|
|
407
|
+
else if (units.slice(e.start, e.end).some(textSpace)) return null;
|
|
408
|
+
else replace.set(known.get(units[inner[0]]), e.text);
|
|
409
|
+
} else return null;
|
|
410
|
+
}
|
|
411
|
+
const out = [], place = (k, from) => {
|
|
412
|
+
const rows = after.get(k) || [];
|
|
413
|
+
return rows.filter(row => row.gap === from).map(row => row.text);
|
|
414
|
+
};
|
|
415
|
+
out.push(...place(-1, false));
|
|
416
|
+
for (let i = 0; i < moved.length; i++) {
|
|
417
|
+
const unit = moved[i];
|
|
418
|
+
if (textSpace(unit)) { out.push(unit); continue; }
|
|
419
|
+
const k = known.get(unit);
|
|
420
|
+
if (replace.get(k) === '' || replace.get(k) === ' ') {
|
|
421
|
+
if (replace.get(k) === ' ') { if (i + 1 < moved.length && textSpace(moved[i + 1])) i++; else if (out.length && textSpace(out[out.length - 1])) out.pop(); }
|
|
422
|
+
} else out.push(replace.has(k) ? replace.get(k) : unit);
|
|
423
|
+
out.push(...place(k, false));
|
|
424
|
+
if (after.get(k)?.some(row => row.gap)) { if (i + 1 < moved.length && textSpace(moved[i + 1])) out.push(moved[++i]); out.push(...place(k, true)); }
|
|
425
|
+
}
|
|
426
|
+
return out.join('');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function textRefineGroup({before, av, bv}, options) {
|
|
430
|
+
const [first, second] = textDeviceOptions(options), ids = first < second ? [0, 1] : [1, 0];
|
|
431
|
+
const added = (side, base) => side.startsWith(base) ? side.slice(base.length) : null;
|
|
432
|
+
const oursAdded = added(av, before), theirsAdded = added(bv, before);
|
|
433
|
+
if (oursAdded !== null && theirsAdded !== null && textLines(oursAdded + theirsAdded).every(line => TEXT_ITEM.test(line))) {
|
|
434
|
+
const tail = value => value && !/[\r\n]$/.test(value) ? value + (before.includes('\r\n') ? '\r\n' : '\n') : value;
|
|
435
|
+
const parts = [tail(oursAdded), tail(theirsAdded)];
|
|
436
|
+
const last = tail(before) === before ? '' : (before.includes('\r\n') ? '\r\n' : '\n');
|
|
437
|
+
return before + last + parts[ids[0]] + parts[ids[1]];
|
|
438
|
+
}
|
|
439
|
+
const lines = textLines(before);
|
|
440
|
+
if (lines.length === 1) {
|
|
441
|
+
const tags = TEXT_TAGS.exec(before), x = TEXT_TAGS.exec(av), y = TEXT_TAGS.exec(bv);
|
|
442
|
+
if (tags && x && y && x[1] === tags[1] && y[1] === tags[1]) {
|
|
443
|
+
const list = row => row[2].split(',').map(item => item.trim()).filter(Boolean);
|
|
444
|
+
const base = list(tags), ours = list(x), theirs = list(y);
|
|
445
|
+
const kept = base.filter(item => ours.includes(item) && theirs.includes(item));
|
|
446
|
+
const added = [ours, theirs].map(list => list.filter(item => !base.includes(item)));
|
|
447
|
+
const joined = [...new Set([...kept, ...added[ids[0]], ...added[ids[1]]])];
|
|
448
|
+
return tags[1] + ': [' + joined.join(', ') + ']' + tags[3];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return textRefineLevel(before, av, bv, options, 'word');
|
|
452
|
+
}
|
|
275
453
|
export function mergeText(base, ours, theirs, options = {}) {
|
|
276
454
|
canonicalNote(ours); canonicalNote(theirs); if (base !== null) canonicalNote(base);
|
|
277
455
|
const unchanged = value => ({text: value, clean: !value.includes(TEXT_CONFLICT), conflicts: value.includes(TEXT_CONFLICT) ? [{kind: 'existing-conflict', advisory: true}] : []});
|
|
278
456
|
if (ours === theirs) return unchanged(ours);
|
|
279
|
-
|
|
280
|
-
if (base ===
|
|
281
|
-
|
|
457
|
+
const willConflict = willMergeConflict(base, ours, theirs);
|
|
458
|
+
if (!willConflict && base === ours) return unchanged(theirs);
|
|
459
|
+
if (!willConflict && base === theirs) return unchanged(ours);
|
|
460
|
+
const conflicts = []; let text = '', a, b;
|
|
282
461
|
const conflict = (before, a, b, kind) => {
|
|
283
462
|
const ab = inspectTextConflicts(a), bb = inspectTextConflicts(b);
|
|
284
463
|
const au = textUnits(a), bu = textUnits(b); let first = 0, last = 0;
|
|
@@ -314,40 +493,24 @@ export function mergeText(base, ours, theirs, options = {}) {
|
|
|
314
493
|
const [units, oursUnits, theirsUnits] = base === null ? [] : [base, ours, theirs].map(textUnits);
|
|
315
494
|
const declared = base !== null && textDeclarations(units);
|
|
316
495
|
if (base === null) conflict(null, ours, theirs, 'no-ancestor');
|
|
496
|
+
else if (willConflict) conflict(base, ours, theirs, 'will');
|
|
317
497
|
else if (textDeclarations(oursUnits) !== declared || textDeclarations(theirsUnits) !== declared) conflict(base, ours, theirs, 'document-bindings');
|
|
318
498
|
else {
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
499
|
+
const reordered = textPermuted(units, oursUnits, theirsUnits);
|
|
500
|
+
if (reordered !== null && textConserved(base, ours, theirs, reordered)) text += reordered;
|
|
501
|
+
else if (!(a = textEdits(units, oursUnits)) || !(b = textEdits(units, theirsUnits))) conflict(base, ours, theirs, 'ambiguous-alignment-or-budget');
|
|
321
502
|
else {
|
|
322
|
-
const changes = [...a.map(e => ({...e, side: 0})), ...b.map(e => ({...e, side: 1}))].sort((x, y) => x.start - y.start || x.end - y.end || x.side - y.side);
|
|
323
503
|
let pos = 0;
|
|
324
|
-
for (
|
|
325
|
-
const group = [changes[i++]], start = group[0].start; let end = group[0].end;
|
|
326
|
-
while (i < changes.length && (changes[i].start < end || group.some(e => {
|
|
327
|
-
const next = changes[i];
|
|
328
|
-
const afterBlock = (edit, insert) => insert.start === insert.end && insert.start === edit.end && edit.start < edit.end && units.slice(edit.start, edit.end).every(unit => textEnvelopeAt(unit));
|
|
329
|
-
if (afterBlock(e, next) || afterBlock(next, e)) return false;
|
|
330
|
-
if (e.start === e.end && next.start <= e.start && e.start <= next.end) return true;
|
|
331
|
-
if (next.start === next.end && e.start <= next.start && next.start <= e.end) return true;
|
|
332
|
-
return next.start >= e.end && units.slice(e.start, e.end).every(textSpace) &&
|
|
333
|
-
units.slice(e.end, next.start).every(textSpace) && units.slice(next.start, next.end).every(textSpace);
|
|
334
|
-
}))) {
|
|
335
|
-
group.push(changes[i]); end = Math.max(end, changes[i++].end);
|
|
336
|
-
}
|
|
504
|
+
for (const {start, end, before, av, bv, resolved} of textMoves([...textGroups(units, a, b)])) {
|
|
337
505
|
text += units.slice(pos, start).join('');
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let value = '', at = start;
|
|
341
|
-
for (const e of group.filter(e => e.side === side)) { value += units.slice(at, e.start).join('') + e.text; at = e.end; }
|
|
342
|
-
return value + units.slice(at, end).join('');
|
|
343
|
-
};
|
|
344
|
-
const av = version(0), bv = version(1);
|
|
345
|
-
if (av === bv) text += av;
|
|
506
|
+
if (resolved !== undefined) text += resolved;
|
|
507
|
+
else if (av === bv) text += av;
|
|
346
508
|
else if (av === before) text += bv;
|
|
347
509
|
else if (bv === before) text += av;
|
|
348
510
|
else {
|
|
349
511
|
const tick = end === start + 1 ? textTick(before, av, bv) : null;
|
|
350
|
-
|
|
512
|
+
const fine = tick === null ? textRefine(before, av, bv, options) : null;
|
|
513
|
+
if (tick !== null) text += tick; else if (fine !== null) text += fine; else conflict(before, av, bv, 'text');
|
|
351
514
|
}
|
|
352
515
|
pos = end;
|
|
353
516
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {exportLedger, replaceLedgerText, readLedger, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId} from './ledger/format.mjs';
|
|
3
|
-
export {exportLedger, replaceLedgerText, readLedger, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId};
|
|
2
|
+
import {exportLedger, prepareLedgerAsync, replaceLedgerText, readLedger, readLedgerAsync, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId} from './ledger/format.mjs';
|
|
3
|
+
export {exportLedger, prepareLedgerAsync, replaceLedgerText, readLedger, readLedgerAsync, ledgerWithStart, textRoot, rootAfter, historyEnvelope, agentActorId};
|
|
4
4
|
import {authorship, readAuthorship, ledgerFromAuthorship} from './ledger/authorship.mjs';
|
|
5
5
|
export {authorship, readAuthorship, ledgerFromAuthorship};
|
|
6
6
|
import {merge, transportInterval, transportTouchedInterval} from './ledger/merge.mjs';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {htmlPlaceholder, mountHtmlBlocks, materializeObjects, objectPosterLabel, htmlImageRecord, htmlLayoutEdit, htmlModeEdit, mergeHtmlObjectBlocks} from '../spec/html-blocks.mjs';
|
|
3
3
|
import {finish, finishAsync, cloneTree, cleanTree, serializeTree, htmlParts, admitHtml, htmlBoundary, pause, check} from './render-work.mjs';
|
|
4
|
+
import {installWikiParser, wikiAddress} from './wiki.mjs';
|
|
4
5
|
const RAPIER_TABLE_CAPTION_PREFIX_RE = /^(?:Table:|:) /;
|
|
5
6
|
const RAPIER_PORTABLE_HIGHLIGHT_HEX = Object.freeze({
|
|
6
7
|
green: '#35c57a', red: '#f05a62', blue: '#4f91f7', yellow: '#e6b91e', purple: '#9b6dea',
|
|
@@ -9,6 +10,51 @@ const _RAPIER_RTL_SCRIPT = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}
|
|
|
9
10
|
function createMarkdownRenderer(runtime) {
|
|
10
11
|
const {Node, _rapierPrefixPortableAnchors, RAPIER_HIGHLIGHT_COLOR_BY_MARKER, RAPIER_MARKDOWN_SPEC, RAPIER_RENDERED_HEADING_SELECTOR, _rapierApplyMarkdownSpec, _rapierChromeOwnsId, _rapierCodeHtml, _rapierDeriveDarkColor, _rapierDormantHeadings, _rapierEmbedAssetSource, _rapierHeadingSlugBase, _rapierNextHeadingSlug, _rapierHighlightAdmitted, _rapierInstallMarkdownMath, _rapierMarkdownPreview, _rapierPlainLayout, _rapierProviders, _rapierRemoteContent, _rapierRemoteImagePlaceholder, _rapierRemoteSubresourceOrigin, _rapierRenderedAnchorOriginalIds, _rapierSourceCharEscaped, _rapierSplitOpeningFrontmatter, _rapierUiDiagram, _rapierUiMath, document, escapeRapierHtmlText, globalThis, sanitizeRapierHtml, window} = runtime;
|
|
11
12
|
let md = runtime.md;
|
|
13
|
+
let wikiTimer = null, wikiAttempt = 0;
|
|
14
|
+
const wikiBusy = new WeakSet();
|
|
15
|
+
function scheduleWikiEmbeds() {
|
|
16
|
+
if (!globalThis.RapierPlatform || !document || wikiTimer !== null) return;
|
|
17
|
+
wikiTimer = setTimeout(async () => {
|
|
18
|
+
wikiTimer = null;
|
|
19
|
+
const original = globalThis.RapierOriginalFolderCore?.currentOriginal();
|
|
20
|
+
if (!original) { if (++wikiAttempt < 8) scheduleWikiEmbeds(); return; }
|
|
21
|
+
wikiAttempt = 0;
|
|
22
|
+
for (const node of [...document.querySelectorAll('#editor-blocks .rapier-source-token--wiki-embed')].slice(0, 32)) {
|
|
23
|
+
if (wikiBusy.has(node) || node.hasAttribute('data-rapier-wiki-ready')) continue;
|
|
24
|
+
wikiBusy.add(node);
|
|
25
|
+
try {
|
|
26
|
+
const link = node.querySelector('a'), href = link?.getAttribute('href');
|
|
27
|
+
if (!href) continue;
|
|
28
|
+
const before = node.innerHTML;
|
|
29
|
+
const embedded = await original.embed(href);
|
|
30
|
+
if (!node.isConnected || node.innerHTML !== before || globalThis.RapierOriginalFolderCore?.currentOriginal() !== original) continue;
|
|
31
|
+
const holder = _rapierRenderSemanticRoot(embedded.text, {docKind: 'markdown'}, {wikiEmbedding: true});
|
|
32
|
+
let followed = 0;
|
|
33
|
+
for (const anchor of holder.querySelectorAll('a[href]')) {
|
|
34
|
+
const address = anchor.getAttribute('href');
|
|
35
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(address)) continue;
|
|
36
|
+
try {
|
|
37
|
+
if (++followed > 64) throw new Error('link limit');
|
|
38
|
+
anchor.setAttribute('href', await original.addressFrom(embedded.displayPath, address));
|
|
39
|
+
} catch (_) { anchor.replaceWith(...anchor.childNodes); }
|
|
40
|
+
}
|
|
41
|
+
if (!node.isConnected || node.innerHTML !== before || globalThis.RapierOriginalFolderCore?.currentOriginal() !== original) continue;
|
|
42
|
+
for (const image of holder.querySelectorAll('img,[data-rapier-remote-src]'))
|
|
43
|
+
image.replaceWith(document.createTextNode(image.getAttribute('alt') || image.textContent || ''));
|
|
44
|
+
for (const block of [...holder.querySelectorAll('p,h1,h2,h3,h4,h5,h6,div,pre,blockquote,ul,ol,li,table,tr,td,th')].reverse())
|
|
45
|
+
block.replaceWith(...block.childNodes, document.createElement('br'));
|
|
46
|
+
node.replaceChildren(...holder.childNodes);
|
|
47
|
+
node.setAttribute('data-rapier-visible', encodeURIComponent(node.textContent));
|
|
48
|
+
node.setAttribute('data-rapier-wiki-ready', '');
|
|
49
|
+
node.contentEditable = 'false';
|
|
50
|
+
} catch (_) { }
|
|
51
|
+
finally { wikiBusy.delete(node); }
|
|
52
|
+
}
|
|
53
|
+
}, 125);
|
|
54
|
+
}
|
|
55
|
+
window?.addEventListener?.('rapier:original-file-ready', scheduleWikiEmbeds);
|
|
56
|
+
const wikiMode = options => options.wikiEmbedding ? 'links' : options.wikiLinks !== undefined ? options.wikiLinks
|
|
57
|
+
: globalThis.RapierOriginalFolderCore?.currentOriginal?.() ? 'label' : false;
|
|
12
58
|
function initMarkdownIt() {
|
|
13
59
|
if (typeof window.markdownit !== 'function') throw new Error('markdownit');
|
|
14
60
|
if (typeof _rapierMarkdownPreview === 'function') globalThis.rapierRenderPreview = _rapierMarkdownPreview;
|
|
@@ -82,6 +128,21 @@ function initMarkdownIt() {
|
|
|
82
128
|
const sourceToken = (source, visible, className) =>
|
|
83
129
|
'<span class="rapier-source-token ' + className + '" data-rapier-source="' +
|
|
84
130
|
encodeURIComponent(source) + '" data-rapier-visible="' + encodeURIComponent(visible) + '">' + md.utils.escapeHtml(visible) + '</span>';
|
|
131
|
+
installWikiParser(md, (token, env) => {
|
|
132
|
+
const {target, label, embed} = token.meta, href = wikiAddress(target), esc = md.utils.escapeHtml;
|
|
133
|
+
if (env.rapierWikiLinks === 'label') return esc(label);
|
|
134
|
+
if (embed && /\.(?:png|jpe?g|gif|webp|jxl)(?:#.*)?$/i.test(target)) {
|
|
135
|
+
if (!env.rapierWikiEmbedding && globalThis.RapierPlatform?.files.relativeCandidate?.(href) && globalThis.RapierEmbeddedImages?.imageHtml)
|
|
136
|
+
return globalThis.RapierEmbeddedImages.imageHtml(null, label, '', null, '', label, token.content, href, env.rapierImagePresentation !== false);
|
|
137
|
+
return '<span data-rapier-remote-src="' + esc(href) + '" data-rapier-remote-alt="' + esc(label) +
|
|
138
|
+
'" data-rapier-image-source="' + esc(encodeURIComponent(token.content)) + '">' + esc(label) + '</span>';
|
|
139
|
+
}
|
|
140
|
+
const note = embed && !env.rapierWikiEmbedding;
|
|
141
|
+
if (note) scheduleWikiEmbeds();
|
|
142
|
+
return '<span class="rapier-source-token rapier-source-token--wiki' + (note ? '-embed' : '') +
|
|
143
|
+
'" data-rapier-source="' + esc(encodeURIComponent(token.content)) + '" data-rapier-visible="' + esc(encodeURIComponent(label)) +
|
|
144
|
+
'"><a href="' + esc(href) + '">' + esc(label) + '</a></span>';
|
|
145
|
+
});
|
|
85
146
|
md.renderer.rules.emoji = (tokens, idx) => {
|
|
86
147
|
const token = tokens[idx];
|
|
87
148
|
const source = token.markup ? ':' + token.markup + ':' : token.content;
|
|
@@ -114,7 +175,9 @@ function initMarkdownIt() {
|
|
|
114
175
|
|
|
115
176
|
md.renderer.rules.image = (tokens, idx, opts, env, self) => {
|
|
116
177
|
const token = tokens[idx];
|
|
117
|
-
const
|
|
178
|
+
const written = token.attrGet('src') || '';
|
|
179
|
+
const hosted = globalThis.RapierEmbedPictureBase?.(written) ?? null;
|
|
180
|
+
const src = hosted || written;
|
|
118
181
|
const title = token.attrGet('title') || '';
|
|
119
182
|
const rawAlt = String(token.content ?? token.attrGet('alt') ?? '');
|
|
120
183
|
const size = _rapierImageAltSize(rawAlt);
|
|
@@ -140,7 +203,7 @@ function initMarkdownIt() {
|
|
|
140
203
|
+ (token.meta?.mdImage?.source ? ' data-rapier-image-source="' + escape(encodeURIComponent(token.meta.mdImage.source)) + '"' : '')
|
|
141
204
|
+ '>' + escape(alt || src) + '</span>';
|
|
142
205
|
}
|
|
143
|
-
if (!env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
|
|
206
|
+
if (!hosted && !env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
|
|
144
207
|
const html = _rapierRemoteImagePlaceholder(src, alt, title, size, layout, rawAlt);
|
|
145
208
|
return html.replace('<span ', '<span data-rapier-markdown-image="" ' +
|
|
146
209
|
(token.meta?.mdImage?.source ? 'data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '" ' : ''));
|
|
@@ -402,8 +465,20 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
402
465
|
|
|
403
466
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
404
467
|
const env = {rapierImagePresentation: options.imagePresentation === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
|
|
468
|
+
if (options.wikiEmbedding) env.rapierWikiEmbedding = true;
|
|
469
|
+
const wikiLinks = wikiMode(options);
|
|
470
|
+
if (wikiLinks) env.rapierWikiLinks = wikiLinks;
|
|
405
471
|
if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
|
|
406
|
-
|
|
472
|
+
const nesting = md && options.maxNesting > 0 ? options.maxNesting : 0;
|
|
473
|
+
let rendered;
|
|
474
|
+
if (nesting || md && options.closeDetails) {
|
|
475
|
+
const saved = md.options.maxNesting;
|
|
476
|
+
if (nesting) md.options.maxNesting = nesting;
|
|
477
|
+
try {
|
|
478
|
+
const tokens = md.parse(markdown, env);
|
|
479
|
+
rendered = md.renderer.render(options.closeDetails ? _rapierCloseOpenDetails(tokens) : tokens, md.options, env);
|
|
480
|
+
} finally { md.options.maxNesting = saved; }
|
|
481
|
+
} else rendered = md
|
|
407
482
|
? md.render(markdown, env)
|
|
408
483
|
: '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
|
|
409
484
|
if (md && env.footnotes && Array.isArray(env.footnotes.list) && env.footnotes.list.length) {
|
|
@@ -435,8 +510,16 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
435
510
|
}
|
|
436
511
|
const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
|
|
437
512
|
const fallbackEnv = options.page ? {rapierPage: true} : {};
|
|
438
|
-
|
|
439
|
-
|
|
513
|
+
const wikiLinks = markdown.includes('[[') && wikiMode(options);
|
|
514
|
+
if (wikiLinks) fallbackEnv.rapierWikiLinks = wikiLinks;
|
|
515
|
+
const nesting = options.maxNesting > 0 ? options.maxNesting : 0, saved = md.options.maxNesting;
|
|
516
|
+
if (nesting) md.options.maxNesting = nesting;
|
|
517
|
+
let parsed;
|
|
518
|
+
try { parsed = !wikiLinks && options.parsed || (runtime.parseAsync && !wikiLinks && !nesting ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv}); }
|
|
519
|
+
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))));
|
|
440
523
|
env = {...env, rapierImagePresentation: options.imagePresentation === true};
|
|
441
524
|
await pause(work, .3);
|
|
442
525
|
const inert = document.createElement('template').content.ownerDocument;
|
|
@@ -727,6 +810,38 @@ function _rapierImageSizeAttributes(size, marker, geometry, automatic = true) {
|
|
|
727
810
|
return attributes + (style ? ' style="' + style + '"' : '');
|
|
728
811
|
}
|
|
729
812
|
|
|
813
|
+
function _rapierCloseOpenDetails(tokens) {
|
|
814
|
+
const opens = /^<details[\t\n\f\r />]/i, closes = /^<\/details[\t\n\f\r ]*>$/i;
|
|
815
|
+
const tags = token => {
|
|
816
|
+
const found = [];
|
|
817
|
+
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);
|
|
819
|
+
else if (token.type === 'inline') for (const child of token.children || []) if (child.type === 'html_inline') found.push(child.content);
|
|
820
|
+
return found;
|
|
821
|
+
};
|
|
822
|
+
const after = new Map(), add = (index, count) => after.set(index, (after.get(index) || 0) + count);
|
|
823
|
+
const waiting = [];
|
|
824
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
825
|
+
const token = tokens[index];
|
|
826
|
+
let depth = 0;
|
|
827
|
+
for (const tag of tags(token)) {
|
|
828
|
+
if (opens.test(tag)) { if (token.type === 'html_block') waiting.push(index); else depth++; }
|
|
829
|
+
else if (closes.test(tag)) { if (depth) depth--; else waiting.pop(); }
|
|
830
|
+
}
|
|
831
|
+
if (depth) add(index + 1 < tokens.length && tokens[index + 1].nesting === -1 ? index + 1 : index, depth);
|
|
832
|
+
}
|
|
833
|
+
for (const index of waiting) add(index, 1);
|
|
834
|
+
if (!after.size) return tokens;
|
|
835
|
+
const out = [];
|
|
836
|
+
tokens.forEach((token, index) => {
|
|
837
|
+
out.push(token);
|
|
838
|
+
const count = after.get(index);
|
|
839
|
+
if (count) out.push({type: 'html_block', tag: '', attrs: null, map: null, nesting: 0, level: token.level || 0, children: null,
|
|
840
|
+
content: '</details>'.repeat(count) + '\n', markup: '', info: '', meta: null, block: true, hidden: false});
|
|
841
|
+
});
|
|
842
|
+
return out;
|
|
843
|
+
}
|
|
844
|
+
|
|
730
845
|
function _rapierRawHtmlFragment(source, rewrite, parser) {
|
|
731
846
|
const state = new parser.inline.State(source, parser, {}, []);
|
|
732
847
|
const htmlRule = parser.inline.ruler.__rules__.find(rule => rule.name === 'html_inline').fn;
|
|
@@ -792,7 +907,9 @@ function* _rapierAssignHeadingSlugsSteps(root, selector = RAPIER_RENDERED_HEADIN
|
|
|
792
907
|
if (!headingSet.has(element) && element.id) used[element.id] = 0;
|
|
793
908
|
}
|
|
794
909
|
for (const heading of headings) { yield;
|
|
795
|
-
|
|
910
|
+
const base = _rapierHeadingSlugBase(heading.textContent);
|
|
911
|
+
const owned = typeof _rapierChromeOwnsId === 'function' && _rapierChromeOwnsId(base);
|
|
912
|
+
heading.id = _rapierNextHeadingSlug(owned ? 'heading-' + base : base, used);
|
|
796
913
|
}
|
|
797
914
|
}
|
|
798
915
|
|
|
@@ -1622,11 +1739,17 @@ async function _rapierPlainTextFileAsync(context) {
|
|
|
1622
1739
|
async function _rapierCloneRootAsync(root, work) { return finishAsync(cloneTree(root), work); }
|
|
1623
1740
|
|
|
1624
1741
|
function _rapierSourceTokenValue(node) {
|
|
1625
|
-
if (!node?.hasAttribute('data-rapier-source') || !node.hasAttribute('data-rapier-visible')
|
|
1626
|
-
Array.from(node.childNodes).some(child => child.nodeType !== Node.TEXT_NODE)) return null;
|
|
1742
|
+
if (!node?.hasAttribute('data-rapier-source') || !node.hasAttribute('data-rapier-visible')) return null;
|
|
1627
1743
|
try {
|
|
1744
|
+
const source = decodeURIComponent(node.getAttribute('data-rapier-source'));
|
|
1745
|
+
if (node.classList.contains('rapier-source-token--wiki') || node.classList.contains('rapier-source-token--wiki-embed')) {
|
|
1746
|
+
const token = md.parseInline(source, {rapierWikiLinks: 'links'})[0]?.children;
|
|
1747
|
+
if (token?.length !== 1 || token[0].type !== 'rapier_wiki') return null;
|
|
1748
|
+
if (!node.hasAttribute('data-rapier-wiki-ready') && (node.children.length !== 1 || node.firstElementChild.tagName !== 'A' ||
|
|
1749
|
+
node.firstElementChild.getAttribute('href') !== wikiAddress(token[0].meta.target))) return null;
|
|
1750
|
+
} else if (Array.from(node.childNodes).some(child => child.nodeType !== Node.TEXT_NODE)) return null;
|
|
1628
1751
|
return node.textContent === decodeURIComponent(node.getAttribute('data-rapier-visible'))
|
|
1629
|
-
?
|
|
1752
|
+
? source : null;
|
|
1630
1753
|
} catch (_) { return null; }
|
|
1631
1754
|
}
|
|
1632
1755
|
|
|
@@ -1634,7 +1757,7 @@ function _rapierResolveSoftBreakTokens(root) { return finish(_rapierResolveSoftB
|
|
|
1634
1757
|
|
|
1635
1758
|
function* _rapierResolveSoftBreakTokensSteps(root) {
|
|
1636
1759
|
for (const token of root.querySelectorAll('.rapier-source-token--softbreak')) { yield;
|
|
1637
|
-
if (_rapierSourceTokenValue(token) === '\n') token.replaceWith(document.createTextNode(' '));
|
|
1760
|
+
if (_rapierSourceTokenValue(token) === '\n') token.replaceWith(document.createTextNode(token.closest('pre') ? '\n' : ' '));
|
|
1638
1761
|
else token.replaceWith(...token.childNodes);
|
|
1639
1762
|
}
|
|
1640
1763
|
return root;
|
|
@@ -3,7 +3,7 @@ import {collectPdfVectors} from './pdf-vectors.mjs';
|
|
|
3
3
|
import {collectPdfGlyphs} from './pdf-glyphs.mjs';
|
|
4
4
|
import {pdfFontSources, waitForFonts} from './export-fonts.mjs';
|
|
5
5
|
import {apply as applyTypography} from '../layout/typography.mjs';
|
|
6
|
-
import {documentLanguage, pageSizeOf} from '../spec/document-settings.mjs';
|
|
6
|
+
import {documentLanguage, pageSizeOf, paperForLanguage} from '../spec/document-settings.mjs';
|
|
7
7
|
import {applyPrintCapitals} from './print-capitals.mjs';
|
|
8
8
|
import {stripOneTerminator as _rapierWillStripOneTerminator} from '../agent/will.mjs';
|
|
9
9
|
import {quotedWillSpan} from '../spec/md-marks.mjs';
|
|
@@ -220,7 +220,7 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
220
220
|
if (current?.() === false) refuse('document_changed');
|
|
221
221
|
};
|
|
222
222
|
if (!root?.ownerDocument || typeof capture !== 'function') refuse('pdf_render_unavailable');
|
|
223
|
-
const paper = settings?.papersize || (templatePage ? pageSizeOf(settings) :
|
|
223
|
+
const paper = settings?.papersize || (templatePage ? pageSizeOf(settings) : paperForLanguage(root.ownerDocument.defaultView?.navigator?.language));
|
|
224
224
|
const width = paper.width / 20, height = paper.height / 20;
|
|
225
225
|
const margins = Object.fromEntries(['top', 'right', 'bottom', 'left'].map(side =>
|
|
226
226
|
[side, settings?.geometry ? settings.geometry[side] / 20 : (side === 'top' ? 12 : 14) * 72 / 25.4]));
|
|
@@ -841,7 +841,7 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
841
841
|
const characters = fallbackCharacters(part, run.carrier); if (!characters.length) continue;
|
|
842
842
|
const {slots, count} = _rapierPrintSlots(part.text, part.rtl);
|
|
843
843
|
const advance = part.width * .75 / count;
|
|
844
|
-
const baseline = decimal(height - margins.top - (part.y + part.height * .8) * .75);
|
|
844
|
+
const baseline = decimal(height - margins.top - (part.baseline ?? part.y + part.height * .8) * .75);
|
|
845
845
|
for (let letter = 0; letter < characters.length; letter++) {
|
|
846
846
|
const size = Math.max(.1, advance * 2 * [...characters[letter].replace(/\n/g, '')].length), scaleY = Math.max(.1, part.height * .75) / size;
|
|
847
847
|
const value = (codes.get(characters[letter]) || codes.get(characters[letter].codePointAt(0))).toString(16).padStart(4, '0');
|