rapier-html 1.2.11 → 1.2.13

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/BUILD.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "package": "rapier-html",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "path": "dist/rapier.html",
5
- "bytes": 6398250,
6
- "sha256": "98bc260ff28d3836d295c5eb8983b6aaa1998a93692b4f353f5da5de48205b93",
7
- "builtAt": "2026-10-18T01:39:57.785Z",
5
+ "bytes": 5253575,
6
+ "sha256": "0db6ae1ccd84043afdeb86afdeb957e86ebca235c93a5912b4dbd27a6d90f6b7",
7
+ "builtAt": "2026-10-18T20:08:08.332Z",
8
8
  "node": "v22.23.3",
9
9
  "canonical": true,
10
10
  "packing": "zopfli",
@@ -31,22 +31,32 @@
31
31
  "delivery": "built-in",
32
32
  "kind": "files",
33
33
  "element": "rapier-plugin-mermaid",
34
- "bytes": 7389640,
35
- "packedBytes": 1526485,
34
+ "bytes": 682005,
35
+ "packedBytes": 225116,
36
36
  "files": [
37
37
  {
38
38
  "id": "rapier-mermaid-core",
39
- "name": "mermaid.min.js",
40
- "version": "12.1.0",
41
- "bytes": 5493176,
42
- "sri": "EbBpjO7rlR6eqZEcG7GaPpyk9H9WrMyPWX4d3KvPYltgt8Z8l0z6R56B1qP40pR4"
43
- },
39
+ "name": "pele-1.6.0.js",
40
+ "version": "1.6.0",
41
+ "bytes": 682005,
42
+ "sri": "4lHoXtKDt+x/E+htiTc83bpFWyREQzJCMudNQHirqKiCTW7Mx790I/5Q7criNZ1G"
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ "id": "mermaid-zenuml",
48
+ "delivery": "built-in",
49
+ "kind": "files",
50
+ "element": "rapier-plugin-mermaid-zenuml",
51
+ "bytes": 1890429,
52
+ "packedBytes": 251583,
53
+ "files": [
44
54
  {
45
55
  "id": "rapier-mermaid-zenuml",
46
- "name": "rapier-zenuml-4.5.0-2.js",
47
- "version": "4.5.0-2",
48
- "bytes": 1896464,
49
- "sri": "8Ptjhh1zNTOvy1dxFHhXgYKfs290DSlpzow5rF9FvP38cohtbsBW381Whym1WI6E"
56
+ "name": "rapier-zenuml-4.5.0-3.js",
57
+ "version": "4.5.0-3",
58
+ "bytes": 1890429,
59
+ "sri": "0tZDV6uy2r9boZwCWcBSrF+Mku7c14LvwAewLbraLrR+tvinNck31H8xsLCa5k6y"
50
60
  }
51
61
  ]
52
62
  },
@@ -135,15 +145,15 @@
135
145
  "delivery": "built-in",
136
146
  "kind": "files",
137
147
  "element": "rapier-plugin-font-subset",
138
- "bytes": 1214356,
139
- "packedBytes": 441192,
148
+ "bytes": 1214770,
149
+ "packedBytes": 441535,
140
150
  "files": [
141
151
  {
142
152
  "id": "rapier-font-subset",
143
- "name": "rapier-font-subset-2.js",
144
- "version": "2",
145
- "bytes": 1214356,
146
- "sri": "q4mHA3+u3N3Yt8z1wisCEXQqWNo5UhHZ0nUESid8BGNZz6BtV8dSUlVOvyndc0Tj"
153
+ "name": "rapier-font-subset-3.js",
154
+ "version": "3",
155
+ "bytes": 1214770,
156
+ "sri": "gOi4uP0F7r23fF2YH3Rvb3NisqOzz8H2T1qAFknqbSKZVeOzRykk7vDc5NEnqVm+"
147
157
  }
148
158
  ]
149
159
  }
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  Turn Markdown into one offline HTML file that contains the full Rapier editor. It opens in any browser, on a phone or
4
4
  computer, with no account. People read, edit, draw and paint; agents hand over documents, show what changed and get
5
- the edited copy back. Start with `npx rapier-html@1.2.11 notes.md`.
5
+ the edited copy back. Start with `npx rapier-html@1.2.13 notes.md`.
6
6
 
7
7
  Use it for a plan to rearrange, a guide to annotate, a draft to keep writing, or a revision to review. The file works
8
8
  without any hosted workspace.
9
9
 
10
10
  - The page writes, saves Markdown, exports Word, PDF and a web page, and prints. It needs no account and fetches
11
11
  nothing to open, edit or save. It is the base editor (about 1.7 MB) with the plug-ins the document displays with
12
- (Math, Mermaid); `--include draw` adds drawing and painting. Notes never travel.
12
+ (Math, Mermaid, and page layout for a template page); `--include draw` adds drawing and painting. Notes never travel.
13
13
  - `--return` adds a one-use Send back address from the agent door.
14
14
  - `--view draw` opens on Draw. `--compare original.md` opens the edit with its diff; `--by <name>` attributes it.
15
15
  - `writePage(text, name)` makes the page in one call; `unwrap` gives the document back, exactly.
@@ -17,14 +17,14 @@ without any hosted workspace.
17
17
  Needs Node 22 or newer.
18
18
 
19
19
  ```sh
20
- npx -- rapier-html@1.2.11 notes.md # the editor, notes.md inside it, written beside the file
21
- npx -- rapier-html@1.2.11 notes.md --view draw # opens on Draw: sketch and paint, the document behind it
22
- npx -- rapier-html@1.2.11 notes.md --include draw,math # carries Draw and Math (default: what the document needs)
23
- npx -- rapier-html@1.2.11 notes.md --drawing sketch.svg # carries a drawing, opened on Draw over the document
24
- npx -- rapier-html@1.2.11 revised.md --compare original.md # opens showing what changed
25
- npx -- rapier-html@1.2.11 notes.md --return "$RETURN_URL" --return-expires-at "$RETURN_EXPIRES_AT" # Send back
26
- npx -- rapier-html@1.2.11 notes.md out.html # a named output
27
- npm install rapier-html@1.2.11 # as a library: writePage, unwrap
20
+ npx -- rapier-html@1.2.13 notes.md # the editor, notes.md inside it, written beside the file
21
+ npx -- rapier-html@1.2.13 notes.md --view draw # opens on Draw: sketch and paint, the document behind it
22
+ npx -- rapier-html@1.2.13 notes.md --include draw,math # carries Draw and Math (default: what the document needs)
23
+ npx -- rapier-html@1.2.13 notes.md --drawing sketch.svg # carries a drawing, opened on Draw over the document
24
+ npx -- rapier-html@1.2.13 revised.md --compare original.md # opens showing what changed
25
+ npx -- rapier-html@1.2.13 notes.md --return "$RETURN_URL" --return-expires-at "$RETURN_EXPIRES_AT" # Send back
26
+ npx -- rapier-html@1.2.13 notes.md out.html # a named output
27
+ npm install rapier-html@1.2.13 # as a library: writePage, unwrap
28
28
  ```
29
29
 
30
30
  The first `--` is for npm, so `--help` reaches the command. It never overwrites a file.
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {readLedger, exportLedger, textRoot, rootAfter} from './format.mjs';
3
3
  import {_rapierTransformSplices, _rapierHistoryEffects} from './journal-records.mjs';
4
+ import {projectInverseSource} from './inverse-source.mjs';
4
5
  import {sha256} from './hash.mjs';
5
6
  import {plainData, documentText, nonnegative, exactKeys, fault} from './data.mjs';
6
7
  function coalesce(runs) {
@@ -14,25 +15,15 @@ function coalesce(runs) {
14
15
  return out;
15
16
  }
16
17
  export function authorship(input) {
17
- const {ledger, text} = readLedger(input), references = _rapierHistoryEffects(ledger.records).references, removed = new Map();
18
- let runs = ledger.start.text ? [[0, ledger.start.text.length, 'system', 'unattributed', 0]] : [];
19
- for (const record of ledger.records) {
20
- const tx = record.transaction, gone = [], kept = (references?.get(tx.id) || []).flatMap(id => removed.get(id) || []);
21
- for (const row of record.splices) {
22
- const cut = row.pos + row.removed.length, delta = row.inserted.length - row.removed.length, before = [], after = [], inside = [];
23
- for (const run of runs) {
24
- if (run[0] < row.pos) before.push([run[0], Math.min(run[1], row.pos), ...run.slice(2)]);
25
- if (run[1] > cut) after.push([Math.max(run[0], cut) + delta, run[1] + delta, ...run.slice(2)]);
26
- if (run[0] < cut && run[1] > row.pos) inside.push([Math.max(run[0], row.pos) - row.pos, Math.min(run[1], cut) - row.pos, ...run.slice(2)]);
27
- }
28
- if (row.removed) gone.push({text: row.removed, runs: inside});
29
- const at = row.inserted ? kept.findIndex(entry => entry.text === row.inserted) : -1;
30
- const put = at < 0 ? [[row.pos, row.pos + row.inserted.length, tx.actor.kind, tx.actor.id, tx.createdAt]]
31
- : kept.splice(at, 1)[0].runs.map(run => [run[0] + row.pos, run[1] + row.pos, ...run.slice(2)]);
32
- runs = coalesce([...before, ...put, ...after]);
33
- }
34
- removed.set(tx.id, gone);
35
- }
18
+ const {ledger, text} = readLedger(input), effects = _rapierHistoryEffects(ledger.records);
19
+ const projection = projectInverseSource({source: text, initialSource: ledger.start.text, records: ledger.records,
20
+ rows: ledger.records.map(record => ({record, splices: record.splices})), effects}, new Set(), effects, {origins: true});
21
+ if (!projection.ok) throw fault('authorship source history is invalid');
22
+ const authors = new Map(ledger.records.map(({transaction}) => [transaction.id, transaction]));
23
+ const runs = coalesce(projection.origins.map(([from, to, id]) => {
24
+ const tx = authors.get(id);
25
+ return [from, to, tx?.actor.kind ?? 'system', tx?.actor.id ?? 'unattributed', tx ? tx.createdAt : 0];
26
+ }));
36
27
  return {format: 'rapier-authorship/1', sha256: sha256(text), runs};
37
28
  }
38
29
  export function readAuthorship(input, text) {
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {_rapierPrepareHistoryRecords, _rapierValidMetadata, _rapierTransformMetadata} from './journal-records.mjs';
3
3
  import {pictureHistorySplices} from './history.mjs';
4
- import {HistorySource} from './inverse-source.mjs';
4
+ import {HistorySource} from './source.mjs';
5
5
  import {authoredHistory} from './transport.mjs';
6
6
  import {textRoot, rootAfter, sha256, sha256Parts, createSHA256} from './hash.mjs';
7
7
  import {canonicalJSON, canonicalParts, plainData, documentText, utf8Length, exactKeys, nonnegative, fault} from './data.mjs';
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {_rapierTransformSplices, _rapierValidMetadata, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierPrepareHistoryRecords} from './journal-records.mjs';
3
- import {projectInverseSource, HistorySource} from './inverse-source.mjs';
3
+ import {projectInverseSource} from './inverse-source.mjs';
4
+ import {sourceView, HistorySource} from './source.mjs';
4
5
  import {textRootParts, rootAfter} from './hash.mjs';
5
6
  import {authoredHistory, authoredPlacements} from './transport.mjs';
6
7
  import {plainData, canonicalJSON} from './data.mjs';
@@ -74,6 +75,14 @@ export function pictureHistorySplices(input, target, redo) {
74
75
  }
75
76
 
76
77
  let preparedHistories = new WeakMap();
78
+ let initialRootObservation = null;
79
+ function initialRootOf(source) {
80
+ if (source.length <= 4096) return textRootParts(source.parts());
81
+ const previous = initialRootObservation?.source.deref();
82
+ const root = previous && source.sameSource(previous) ? initialRootObservation.root : textRootParts(source.parts());
83
+ initialRootObservation = {source: new WeakRef(source), root};
84
+ return root;
85
+ }
77
86
  function prepareHistory(input) {
78
87
  try {
79
88
  if (!input || typeof input.source !== 'string' || !Array.isArray(input.records) || !_rapierValidMetadata(input.metadata)) return historyFailure('history_invalid');
@@ -105,7 +114,7 @@ function prepareHistory(input) {
105
114
  rows.push(row); byId.set(tx.id, row); prior = tx.revision;
106
115
  }
107
116
  if (prior !== revision) return historyFailure('history_unavailable');
108
- let initial = new HistorySource(source), initialMetadata = {...metadata};
117
+ let initial = source.length <= 4096 ? new HistorySource(source) : sourceView(source), initialMetadata = {...metadata};
109
118
  for (let at = rows.length - 1; at >= 0; at--) {
110
119
  const row = rows[at];
111
120
  row.afterSource = initial; row.afterMetadata = initialMetadata;
@@ -115,7 +124,7 @@ function prepareHistory(input) {
115
124
  row.beforeSource = initial; row.beforeMetadata = initialMetadata;
116
125
  }
117
126
  const earliestRevision = records[0]?.transaction.baseRevision ?? revision;
118
- let root = earliestRevision === 0 ? (typeof input.earliestRoot === 'string' && input.earliestRoot || textRootParts(initial.parts())) : null,
127
+ let root = earliestRevision === 0 ? (typeof input.earliestRoot === 'string' && input.earliestRoot || initialRootOf(initial)) : null,
119
128
  rootRevision = earliestRevision;
120
129
  for (const {record, splices} of rows) {
121
130
  const hashed = Object.hasOwn(record, 'beforeHash') || Object.hasOwn(record, 'afterHash');
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {_rapierTransformSplices, _rapierHistoryEffects} from './journal-records.mjs';
3
+ import {HistorySource} from './source.mjs';
3
4
  import {authoredPlacements} from './authored-origin.mjs';
4
5
  import {canonicalJSON} from './data.mjs';
5
6
  import {diff} from './diff/index.mjs';
@@ -9,131 +10,6 @@ const historyBoundary = (source, at) => Number.isSafeInteger(at) && at >= 0 && !
9
10
  !(at > 0 && at < source.length && (source.charCodeAt(at - 1) & 0xFC00) === 0xD800 &&
10
11
  (source.charCodeAt(at) & 0xFC00) === 0xDC00);
11
12
 
12
- const sourceLeaf = (text, from = 0, to = text.length) => to > from
13
- ? {text, from, length: to - from, height: 1} : null;
14
- const sourceHeight = node => node?.height || 0;
15
- const sourceNode = (left, right) => !left ? right : !right ? left
16
- : {left, right, length: left.length + right.length, height: 1 + Math.max(left.height, right.height)};
17
- function sourceBalance(left, right) {
18
- if (sourceHeight(left) > sourceHeight(right) + 1) {
19
- if (sourceHeight(left.left) >= sourceHeight(left.right)) return sourceNode(left.left, sourceNode(left.right, right));
20
- return sourceNode(sourceNode(left.left, left.right.left), sourceNode(left.right.right, right));
21
- }
22
- if (sourceHeight(right) > sourceHeight(left) + 1) {
23
- if (sourceHeight(right.right) >= sourceHeight(right.left)) return sourceNode(sourceNode(left, right.left), right.right);
24
- return sourceNode(sourceNode(left, right.left.left), sourceNode(right.left.right, right.right));
25
- }
26
- return sourceNode(left, right);
27
- }
28
- function sourceJoin(left, right) {
29
- if (!left || !right) return left || right;
30
- if (left.height > right.height + 1) return sourceBalance(left.left, sourceJoin(left.right, right));
31
- if (right.height > left.height + 1) return sourceBalance(sourceJoin(left, right.left), right.right);
32
- return sourceNode(left, right);
33
- }
34
- function sourceSplit(node, at) {
35
- if (!node || at === 0) return [null, node];
36
- if (at === node.length) return [node, null];
37
- if (node.text !== undefined) return [sourceLeaf(node.text, node.from, node.from + at), sourceLeaf(node.text, node.from + at, node.from + node.length)];
38
- if (at < node.left.length) {
39
- const [left, right] = sourceSplit(node.left, at);
40
- return [left, sourceJoin(right, node.right)];
41
- }
42
- const [left, right] = sourceSplit(node.right, at - node.left.length);
43
- return [sourceJoin(node.left, left), right];
44
- }
45
- function* sourceParts(node, from, to) {
46
- if (!node || from >= to) return;
47
- if (node.text !== undefined) {
48
- yield node.from + from === 0 && node.from + to === node.text.length ? node.text : node.text.slice(node.from + from, node.from + to); return;
49
- }
50
- if (from < node.left.length) yield* sourceParts(node.left, from, Math.min(to, node.left.length));
51
- if (to > node.left.length) yield* sourceParts(node.right, Math.max(0, from - node.left.length), to - node.left.length);
52
- }
53
- function* sourceLeaves(node) {
54
- if (!node) return;
55
- if (node.text !== undefined) { yield node; return; }
56
- yield* sourceLeaves(node.left); yield* sourceLeaves(node.right);
57
- }
58
- export class HistorySource {
59
- #root;
60
- constructor(text, root = sourceLeaf(text)) { this.#root = root; }
61
- static fromParts(parts) {
62
- const leaves = Array.from(parts, text => sourceLeaf(text)).filter(Boolean);
63
- const build = (from, to) => from === to ? null : to - from === 1 ? leaves[from]
64
- : sourceNode(build(from, Math.floor((from + to) / 2)), build(Math.floor((from + to) / 2), to));
65
- return new HistorySource('', build(0, leaves.length));
66
- }
67
- get length() { return this.#root?.length || 0; }
68
- charCodeAt(at) {
69
- if (at < 0 || at >= this.length) return NaN;
70
- let node = this.#root;
71
- while (node.text === undefined) {
72
- if (at < node.left.length) node = node.left;
73
- else { at -= node.left.length; node = node.right; }
74
- }
75
- return node.text.charCodeAt(node.from + at);
76
- }
77
- parts(from = 0, to = this.length) { return sourceParts(this.#root, from, to); }
78
- slice(from = 0, to = this.length) {
79
- const root = this.#root;
80
- if (!root || from >= to) return '';
81
- if (root.text !== undefined) return root.from + from === 0 && root.from + to === root.text.length
82
- ? root.text : root.text.slice(root.from + from, root.from + to);
83
- return Array.from(this.parts(from, to)).join('');
84
- }
85
- matches(from, text) {
86
- if (from < 0 || from + text.length > this.length) return false;
87
- let at = 0;
88
- for (const part of this.parts(from, from + text.length)) {
89
- if (!text.startsWith(part, at)) return false;
90
- at += part.length;
91
- }
92
- return at === text.length;
93
- }
94
- sameParts(parts) {
95
- const leaves = sourceLeaves(this.#root);
96
- let leaf = leaves.next().value, offset = 0;
97
- for (const text of parts) for (let at = 0; at < text.length;) {
98
- if (!leaf) return false;
99
- const length = Math.min(leaf.length - offset, text.length - at), from = leaf.from + offset;
100
- if ((leaf.text !== text || from !== at) && leaf.text.slice(from, from + length) !== text.slice(at, at + length)) return false;
101
- offset += length; at += length;
102
- if (offset === leaf.length) { leaf = leaves.next().value; offset = 0; }
103
- }
104
- return !leaf;
105
- }
106
- replace(pos, length, text) {
107
- const [left, rest] = sourceSplit(this.#root, pos), [, right] = sourceSplit(rest, length);
108
- return new HistorySource('', sourceJoin(sourceJoin(left, sourceLeaf(text)), right));
109
- }
110
- before(splices) {
111
- let source = this;
112
- for (let index = splices.length - 1; index >= 0; index--) {
113
- const row = splices[index];
114
- if (!row || !Number.isSafeInteger(row.pos) || row.pos < 0 || Object.is(row.pos, -0) ||
115
- typeof row.removed !== 'string' || typeof row.inserted !== 'string' ||
116
- /[\uD800-\uDFFF]/u.test(row.removed) || /[\uD800-\uDFFF]/u.test(row.inserted) || !source.matches(row.pos, row.inserted)) return null;
117
- source = source.replace(row.pos, row.inserted.length, row.removed);
118
- if (!historyBoundary(source, row.pos) || !historyBoundary(source, row.pos + row.removed.length)) return null;
119
- }
120
- return source;
121
- }
122
- apply(splices, inverse = false) {
123
- if (!Array.isArray(splices)) return null;
124
- let source = this;
125
- for (let index = 0; index < splices.length; index++) {
126
- const row = splices[inverse ? splices.length - 1 - index : index];
127
- const removed = inverse ? row?.inserted : row?.removed, inserted = inverse ? row?.removed : row?.inserted;
128
- if (typeof removed !== 'string' || typeof inserted !== 'string' ||
129
- /[\uD800-\uDFFF]/u.test(removed) || /[\uD800-\uDFFF]/u.test(inserted) ||
130
- !historyBoundary(source, row.pos) || !historyBoundary(source, row.pos + removed.length) || !source.matches(row.pos, removed)) return null;
131
- source = source.replace(row.pos, removed.length, inserted);
132
- }
133
- return source;
134
- }
135
- }
136
-
137
13
  function minimalHistorySplice(before, after, pos = 0) {
138
14
  let first = 0, a = before.length, b = after.length;
139
15
  while (first < a && first < b && before[first] === after[first]) first++;
@@ -158,7 +34,7 @@ function historyHunks(row) {
158
34
 
159
35
  export function projectInverseSource(prepared, selected, projectedEffects, options = {}) {
160
36
  const records = prepared.records || prepared.rows.map(({record, splices}) => record.splices === splices ? record : {...record, splices});
161
- if (!records.some(record => !prepared.effects.references.get(record.transaction.id)?.length &&
37
+ if (!options.origins && !records.some(record => !prepared.effects.references.get(record.transaction.id)?.length &&
162
38
  prepared.effects.active.has(record.transaction.id) !== projectedEffects.active.has(record.transaction.id)))
163
39
  return {ok: true, splices: [], after: prepared.source, superseded: [], overlaps: []};
164
40
  try {
@@ -417,6 +293,13 @@ export function projectInverseSource(prepared, selected, projectedEffects, optio
417
293
  }
418
294
  }
419
295
  if (!new HistorySource(prepared.source).sameParts(texts(pieces))) throw Error('head');
296
+ if (options.origins) {
297
+ let at = 0;
298
+ return {ok: true, origins: pieces.filter(piece => piece.text).map(piece => {
299
+ const from = at; at += piece.text.length;
300
+ return [from, at, byId.get(piece.owner)?.actId ?? null];
301
+ })};
302
+ }
420
303
  const origins = new Map();
421
304
  let offset = 0;
422
305
  for (const piece of pieces) if (piece.text) { origins.set(piece, offset); offset += piece.text.length; }
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {fields, scalar, plainPrototype} from './data.mjs';
3
+ import {transformText} from './source.mjs';
3
4
 
4
5
  const _RAPIER_TRANSACTION_ACTOR_LIMIT = 160;
5
6
  const _RAPIER_TRANSACTION_OPERATION_LIMIT = 96;
@@ -8,23 +9,7 @@ const _RAPIER_TRANSACTION_REQUEST_LIMIT = 160;
8
9
  function _rapierTransformSplices(text, splices, inverse = false) {
9
10
  try {
10
11
  if (!Array.isArray(splices)) return null;
11
- let value = String(text == null ? '' : text);
12
- const whole = text => typeof text === 'string' && !/[\uD800-\uDFFF]/u.test(text);
13
- const splitsPair = at => at > 0 && at < value.length &&
14
- (value.charCodeAt(at - 1) & 0xFC00) === 0xD800 &&
15
- (value.charCodeAt(at) & 0xFC00) === 0xDC00;
16
- const rows = inverse ? splices.slice().reverse() : splices;
17
- for (const row of rows) {
18
- if (!row || !Number.isSafeInteger(row.pos) || row.pos < 0 || Object.is(row.pos, -0)) return null;
19
- const pos = row.pos;
20
- const removed = inverse ? row.inserted : row.removed;
21
- const inserted = inverse ? row.removed : row.inserted;
22
- if (!whole(removed) || !whole(inserted) || pos > value.length ||
23
- pos + removed.length > value.length || splitsPair(pos) || splitsPair(pos + removed.length) ||
24
- value.slice(pos, pos + removed.length) !== removed) return null;
25
- value = value.slice(0, pos) + inserted + value.slice(pos + removed.length);
26
- }
27
- return value;
12
+ return transformText(String(text == null ? '' : text), splices, inverse);
28
13
  } catch (_) { return null; }
29
14
  }
30
15