@dojo-ng/rich-text-criticmarkup 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grammar.js +87 -8
- package/dist/resolution.js +68 -9
- package/fixtures/conformance.json +453 -62
- package/package.json +31 -5
package/dist/grammar.js
CHANGED
|
@@ -167,7 +167,14 @@ function resolve(text, mark, side) {
|
|
|
167
167
|
return text;
|
|
168
168
|
}
|
|
169
169
|
const end = mark.kind === "highlight" ? highlightEnd(text, mark) : mark.end;
|
|
170
|
-
|
|
170
|
+
// Decision 18: only an ACCEPT adjusts whitespace at a paragraph-break seam. A decline restores
|
|
171
|
+
// the original text verbatim — `run{++¶++}together` declines back to `runtogether`, not to
|
|
172
|
+
// `run together` — and a highlight is kept unchanged in both directions, so neither touches the
|
|
173
|
+
// seam. `settleSeams` is a no-op on a string with no sentinel in it, which is every other case.
|
|
174
|
+
const adjusts = side === "new" && mark.kind !== "highlight";
|
|
175
|
+
const kept = keptText(mark, side, adjusts ? BREAK_SEAM : PARAGRAPH_BREAK);
|
|
176
|
+
const dropped = adjusts && kept === "" && hasStructuralToken(droppedText(mark, side));
|
|
177
|
+
return settleSeams(text.slice(0, mark.start) + (dropped ? JOIN_SEAM : kept) + text.slice(end));
|
|
171
178
|
}
|
|
172
179
|
/**
|
|
173
180
|
* `mark`'s own `end`, extended to swallow an immediately adjacent `{>>...<<}` — CriticMarkup's
|
|
@@ -183,20 +190,34 @@ function highlightEnd(text, mark) {
|
|
|
183
190
|
}
|
|
184
191
|
return mark.end;
|
|
185
192
|
}
|
|
186
|
-
function keptText(mark, side) {
|
|
193
|
+
function keptText(mark, side, breakAs = PARAGRAPH_BREAK) {
|
|
187
194
|
switch (mark.kind) {
|
|
188
195
|
case "highlight":
|
|
189
|
-
return resolveParagraphTokens(mark.text);
|
|
196
|
+
return resolveParagraphTokens(mark.text, PARAGRAPH_TOKEN, breakAs);
|
|
190
197
|
case "substitution":
|
|
191
|
-
return resolveParagraphTokens(side === "new" ? mark.new : mark.old);
|
|
198
|
+
return resolveParagraphTokens(side === "new" ? mark.new : mark.old, PARAGRAPH_TOKEN, breakAs);
|
|
192
199
|
case "insertion":
|
|
193
|
-
return side === "new" ? resolveParagraphTokens(mark.text) : "";
|
|
200
|
+
return side === "new" ? resolveParagraphTokens(mark.text, PARAGRAPH_TOKEN, breakAs) : "";
|
|
194
201
|
case "deletion":
|
|
195
|
-
return side === "new" ? "" : resolveParagraphTokens(mark.text);
|
|
202
|
+
return side === "new" ? "" : resolveParagraphTokens(mark.text, PARAGRAPH_TOKEN, breakAs);
|
|
196
203
|
default:
|
|
197
204
|
throw new Error(`unknown mark kind ${mark.kind}`);
|
|
198
205
|
}
|
|
199
206
|
}
|
|
207
|
+
/** The text this resolution DISCARDS — the mirror of `keptText`, and the only place a break that is
|
|
208
|
+
* about to disappear can still be seen. */
|
|
209
|
+
function droppedText(mark, side) {
|
|
210
|
+
switch (mark.kind) {
|
|
211
|
+
case "insertion":
|
|
212
|
+
return side === "new" ? "" : mark.text;
|
|
213
|
+
case "deletion":
|
|
214
|
+
return side === "new" ? mark.text : "";
|
|
215
|
+
case "substitution":
|
|
216
|
+
return side === "new" ? mark.old : mark.new;
|
|
217
|
+
default:
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
200
221
|
/**
|
|
201
222
|
* Resolve every mark as `accept` would, nested ones included. Idempotent: a body with no marks is
|
|
202
223
|
* returned unchanged.
|
|
@@ -291,7 +312,7 @@ export function unescapeToken(text, token = PARAGRAPH_TOKEN) {
|
|
|
291
312
|
* and a doubled (escaped) token becomes the single literal character it stands for. Scans
|
|
292
313
|
* left-to-right so a doubled pair is consumed as a unit and never mistaken for two lone tokens.
|
|
293
314
|
*/
|
|
294
|
-
function resolveParagraphTokens(text, token = PARAGRAPH_TOKEN) {
|
|
315
|
+
function resolveParagraphTokens(text, token = PARAGRAPH_TOKEN, breakAs = PARAGRAPH_BREAK) {
|
|
295
316
|
if (token === "")
|
|
296
317
|
return text;
|
|
297
318
|
let out = "";
|
|
@@ -303,7 +324,7 @@ function resolveParagraphTokens(text, token = PARAGRAPH_TOKEN) {
|
|
|
303
324
|
i += token.length * 2;
|
|
304
325
|
}
|
|
305
326
|
else {
|
|
306
|
-
out +=
|
|
327
|
+
out += breakAs; // unpaired: a structural break
|
|
307
328
|
i += token.length;
|
|
308
329
|
}
|
|
309
330
|
}
|
|
@@ -354,6 +375,64 @@ export function tokenizeBlockSpanning(text, token = PARAGRAPH_TOKEN) {
|
|
|
354
375
|
export function toPortableCriticMarkup(text, token = PARAGRAPH_TOKEN) {
|
|
355
376
|
return normalizeBlockSpanning(resolveParagraphTokens(text, token));
|
|
356
377
|
}
|
|
378
|
+
// --- decision 18: whitespace at a paragraph-break seam ------------------------------------------
|
|
379
|
+
/** A real paragraph break, as this grammar spells one. */
|
|
380
|
+
const PARAGRAPH_BREAK = "\n\n";
|
|
381
|
+
// Two sentinels, placed by `resolve` and consumed by `settleSeams` within that same call. They exist
|
|
382
|
+
// so a seam adjustment can see the characters on BOTH sides of the mark — which a rule written over
|
|
383
|
+
// the mark's own span cannot — without a regex sweep that would touch whitespace elsewhere in the
|
|
384
|
+
// document that this resolution does not own (G2's "leaving all other text untouched"). Deliberately
|
|
385
|
+
// outside the U+E000..U+E007 block `maskNested` uses; nothing outside this file ever sees one.
|
|
386
|
+
const BREAK_SEAM = "\uE010"; // a break this resolution CREATES
|
|
387
|
+
const JOIN_SEAM = "\uE011"; // a break this resolution REMOVES, with nothing kept in its place
|
|
388
|
+
/** Horizontal whitespace only: a seam adjustment never eats a neighbouring line's newline. */
|
|
389
|
+
const HORIZONTAL_WS = /[ \t]*/;
|
|
390
|
+
/** True if `text` holds at least one UNPAIRED token — a structural break rather than a doubled
|
|
391
|
+
* literal. Scans in `resolveParagraphTokens`' left-to-right order so the two always agree on which
|
|
392
|
+
* tokens are structural. */
|
|
393
|
+
function hasStructuralToken(text, token = PARAGRAPH_TOKEN) {
|
|
394
|
+
if (token === "")
|
|
395
|
+
return false;
|
|
396
|
+
let i = 0;
|
|
397
|
+
while (i < text.length) {
|
|
398
|
+
if (text.startsWith(token, i)) {
|
|
399
|
+
if (text.startsWith(token, i + token.length)) {
|
|
400
|
+
i += token.length * 2;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
i++;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Turn the seam sentinels into real text, which is where decision 18's two rules actually live:
|
|
411
|
+
*
|
|
412
|
+
* - A break being CREATED absorbs the horizontal whitespace on either side of it, so accepting
|
|
413
|
+
* `here. {++¶++}The` does not leave a space stranded at the end of the first paragraph.
|
|
414
|
+
* - A break being REMOVED becomes a single space, so accepting `here.{--¶--}The` reads
|
|
415
|
+
* `here. The` rather than `here.The` — but only when real text sits hard against both sides, so a
|
|
416
|
+
* merge into text that already has whitespace (or at the very start or end of the body) adds
|
|
417
|
+
* nothing.
|
|
418
|
+
*/
|
|
419
|
+
function settleSeams(text) {
|
|
420
|
+
if (!text.includes(BREAK_SEAM) && !text.includes(JOIN_SEAM))
|
|
421
|
+
return text;
|
|
422
|
+
const broken = text.replace(new RegExp(`${HORIZONTAL_WS.source}${BREAK_SEAM}${HORIZONTAL_WS.source}`, "g"), PARAGRAPH_BREAK);
|
|
423
|
+
return settleJoinSeam(broken);
|
|
424
|
+
}
|
|
425
|
+
/** One join seam at a time, by index rather than by regex, so a character outside the Basic
|
|
426
|
+
* Multilingual Plane on either side of the seam is never split. */
|
|
427
|
+
function settleJoinSeam(text) {
|
|
428
|
+
const at = text.indexOf(JOIN_SEAM);
|
|
429
|
+
if (at < 0)
|
|
430
|
+
return text;
|
|
431
|
+
const before = text.slice(0, at);
|
|
432
|
+
const after = text.slice(at + JOIN_SEAM.length);
|
|
433
|
+
const needsSpace = before !== "" && after !== "" && !/\s$/.test(before) && !/^\s/.test(after);
|
|
434
|
+
return settleJoinSeam(before + (needsSpace ? " " : "") + after);
|
|
435
|
+
}
|
|
357
436
|
// --- decision 11: nesting is detected and masked, not mangled ----------------------------------
|
|
358
437
|
// One private-use sentinel per CriticMarkup delimiter character, so masking is a plain,
|
|
359
438
|
// length-preserving character substitution with a trivial exact inverse.
|
package/dist/resolution.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* No confirmation here — `dj-popup-confirmation` gating `confirmBulk` is a toolbar-level concern
|
|
15
15
|
* (Track T3), not baked into these functions themselves.
|
|
16
16
|
*/
|
|
17
|
-
import { $createParagraphNode, $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isRootOrShadowRoot } from "lexical";
|
|
17
|
+
import { $createParagraphNode, $createTextNode, $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isRootOrShadowRoot, $isTextNode } from "lexical";
|
|
18
18
|
import { $isBreakNode, $isCommentNode, $isCriticMark, $isDeletionNode, $isHighlightNode, $isInsertionNode, } from "./nodes.js";
|
|
19
19
|
import { SKIP_TAG } from "./suggestion-mode.js";
|
|
20
20
|
/** The nearest CriticMarkup mark (any of the four kinds) containing the selection's anchor, or
|
|
@@ -82,12 +82,14 @@ function resolveOne(editor, node, side, resolved) {
|
|
|
82
82
|
resolved?.add(pair.deletion.getKey());
|
|
83
83
|
resolved?.add(pair.insertion.getKey());
|
|
84
84
|
if (side === "new") {
|
|
85
|
+
// No join seam here even when the deletion held a break (decision 18): the insertion's own
|
|
86
|
+
// text lands in its place, so the two sides are not becoming adjacent.
|
|
85
87
|
pair.deletion.remove();
|
|
86
|
-
unwrapAndResolveBreaks(pair.insertion);
|
|
88
|
+
unwrapAndResolveBreaks(pair.insertion, true);
|
|
87
89
|
}
|
|
88
90
|
else {
|
|
89
91
|
pair.insertion.remove();
|
|
90
|
-
unwrapAndResolveBreaks(pair.deletion);
|
|
92
|
+
unwrapAndResolveBreaks(pair.deletion, false);
|
|
91
93
|
}
|
|
92
94
|
dispatchChange(editor, "substitution", side === "new" ? "accept" : "decline");
|
|
93
95
|
return;
|
|
@@ -104,10 +106,17 @@ function resolveOne(editor, node, side, resolved) {
|
|
|
104
106
|
}
|
|
105
107
|
else {
|
|
106
108
|
const keep = $isInsertionNode(node) ? side === "new" : side === "old";
|
|
107
|
-
if (keep)
|
|
108
|
-
unwrapAndResolveBreaks(node);
|
|
109
|
-
|
|
109
|
+
if (keep) {
|
|
110
|
+
unwrapAndResolveBreaks(node, side === "new");
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Decision 18: accepting a deletion that holds a break closes a real paragraph boundary, so
|
|
114
|
+
// the text on either side needs the space that boundary was providing. Declining an
|
|
115
|
+
// insertion drops a break that never existed in the document, so it changes nothing.
|
|
116
|
+
if (side === "new" && holdsBreak(node))
|
|
117
|
+
joinAtRemovedBreak(node);
|
|
110
118
|
node.remove();
|
|
119
|
+
}
|
|
111
120
|
}
|
|
112
121
|
dispatchChange(editor, kind, side === "new" ? "accept" : "decline");
|
|
113
122
|
}
|
|
@@ -149,14 +158,60 @@ function unwrap(node) {
|
|
|
149
158
|
* children — a kept paragraph-break proposal (decision 16) stops being a decorator and becomes an
|
|
150
159
|
* actual block boundary, mirroring what `grammar.accept`/`decline` do to the token at the string
|
|
151
160
|
* level. */
|
|
152
|
-
function unwrapAndResolveBreaks(node) {
|
|
161
|
+
function unwrapAndResolveBreaks(node, creating) {
|
|
153
162
|
const children = unwrap(node);
|
|
154
163
|
for (const child of children) {
|
|
155
164
|
if ($isBreakNode(child))
|
|
156
|
-
splitBlockAtBreak(child);
|
|
165
|
+
splitBlockAtBreak(child, creating);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** True if `node` holds a `BreakNode` — the tree-side reading of `grammar.hasStructuralToken`. */
|
|
169
|
+
function holdsBreak(node) {
|
|
170
|
+
return node.getChildren().some($isBreakNode);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Decision 18's join rule on the live tree: a single space where the removed break was, but only
|
|
174
|
+
* when real text sits hard against both sides of it. Inserted as its own text node rather than
|
|
175
|
+
* appended to the neighbour, so a formatted run keeps its own format and Lexical's own
|
|
176
|
+
* normalization merges the pair where they match.
|
|
177
|
+
*/
|
|
178
|
+
function joinAtRemovedBreak(node) {
|
|
179
|
+
const before = node.getPreviousSibling()?.getTextContent() ?? "";
|
|
180
|
+
const after = node.getNextSibling()?.getTextContent() ?? "";
|
|
181
|
+
if (before === "" || after === "")
|
|
182
|
+
return;
|
|
183
|
+
if (/\s$/.test(before) || /^\s/.test(after))
|
|
184
|
+
return;
|
|
185
|
+
node.insertBefore($createTextNode(" "));
|
|
186
|
+
}
|
|
187
|
+
/** Decision 18's absorb rule on the live tree: the horizontal whitespace on either side of a break
|
|
188
|
+
* being CREATED goes with it, so an accepted split does not strand a space at the end of the first
|
|
189
|
+
* block or open the second one with one. A break being RESTORED (a declined merge) is left exactly
|
|
190
|
+
* as the document had it. */
|
|
191
|
+
function trimSeam(block, edge) {
|
|
192
|
+
// Walks inward from the edge over a SNAPSHOT of the children, rather than re-reading the edge
|
|
193
|
+
// child on each pass. The loop's progress has to come from the walk itself and never from
|
|
194
|
+
// `remove()` having taken effect: Lexical reinstates an empty text node at a block edge in some
|
|
195
|
+
// reconciliation paths, and a loop that re-read `getLastChild()` would then be handed the same
|
|
196
|
+
// node forever — a hung page rather than a failed assertion, which is the kind of bug a headless
|
|
197
|
+
// test with a simpler tree can miss entirely.
|
|
198
|
+
const children = block.getChildren();
|
|
199
|
+
const ordered = edge === "end" ? [...children].reverse() : children;
|
|
200
|
+
for (const child of ordered) {
|
|
201
|
+
if (!$isTextNode(child))
|
|
202
|
+
return;
|
|
203
|
+
const text = child.getTextContent();
|
|
204
|
+
const trimmed = edge === "end" ? text.replace(/[ \t]+$/, "") : text.replace(/^[ \t]+/, "");
|
|
205
|
+
if (trimmed === text)
|
|
206
|
+
return;
|
|
207
|
+
if (trimmed !== "") {
|
|
208
|
+
child.setTextContent(trimmed);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
child.remove();
|
|
157
212
|
}
|
|
158
213
|
}
|
|
159
|
-
function splitBlockAtBreak(breakNode) {
|
|
214
|
+
function splitBlockAtBreak(breakNode, creating) {
|
|
160
215
|
const block = topLevelBlockOf(breakNode);
|
|
161
216
|
if (!block) {
|
|
162
217
|
breakNode.remove();
|
|
@@ -171,6 +226,10 @@ function splitBlockAtBreak(breakNode) {
|
|
|
171
226
|
}
|
|
172
227
|
block.insertAfter(newBlock);
|
|
173
228
|
breakNode.remove();
|
|
229
|
+
if (creating) {
|
|
230
|
+
trimSeam(block, "end");
|
|
231
|
+
trimSeam(newBlock, "start");
|
|
232
|
+
}
|
|
174
233
|
}
|
|
175
234
|
function topLevelBlockOf(node) {
|
|
176
235
|
let current = node;
|
|
@@ -1,67 +1,458 @@
|
|
|
1
1
|
[
|
|
2
|
-
{
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
{ "id": "parse-substitution", "op": "parse", "input": "{~~old~>new~~}",
|
|
7
|
-
"expected": [ { "kind": "substitution", "start": 0, "end": 14, "old": "old", "new": "new", "spansBlock": false, "nested": false } ] },
|
|
8
|
-
{ "id": "parse-comment", "op": "parse", "input": "{>>note<<}",
|
|
9
|
-
"expected": [ { "kind": "comment", "start": 0, "end": 10, "text": "note", "spansBlock": false, "nested": false } ] },
|
|
10
|
-
{ "id": "parse-highlight", "op": "parse", "input": "{==hl==}",
|
|
11
|
-
"expected": [ { "kind": "highlight", "start": 0, "end": 8, "text": "hl", "spansBlock": false, "nested": false } ] },
|
|
12
|
-
{ "id": "parse-nested-same-kind", "op": "parse", "input": "{--a {--b--} c--}",
|
|
2
|
+
{
|
|
3
|
+
"id": "parse-insertion",
|
|
4
|
+
"op": "parse",
|
|
5
|
+
"input": "{++foo++}",
|
|
13
6
|
"expected": [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
{
|
|
8
|
+
"kind": "insertion",
|
|
9
|
+
"start": 0,
|
|
10
|
+
"end": 9,
|
|
11
|
+
"text": "foo",
|
|
12
|
+
"spansBlock": false,
|
|
13
|
+
"nested": false
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "parse-deletion",
|
|
19
|
+
"op": "parse",
|
|
20
|
+
"input": "{--bar--}",
|
|
18
21
|
"expected": [
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
{
|
|
23
|
+
"kind": "deletion",
|
|
24
|
+
"start": 0,
|
|
25
|
+
"end": 9,
|
|
26
|
+
"text": "bar",
|
|
27
|
+
"spansBlock": false,
|
|
28
|
+
"nested": false
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "parse-substitution",
|
|
34
|
+
"op": "parse",
|
|
35
|
+
"input": "{~~old~>new~~}",
|
|
25
36
|
"expected": [
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
37
|
+
{
|
|
38
|
+
"kind": "substitution",
|
|
39
|
+
"start": 0,
|
|
40
|
+
"end": 14,
|
|
41
|
+
"old": "old",
|
|
42
|
+
"new": "new",
|
|
43
|
+
"spansBlock": false,
|
|
44
|
+
"nested": false
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "parse-comment",
|
|
50
|
+
"op": "parse",
|
|
51
|
+
"input": "{>>note<<}",
|
|
52
|
+
"expected": [
|
|
53
|
+
{
|
|
54
|
+
"kind": "comment",
|
|
55
|
+
"start": 0,
|
|
56
|
+
"end": 10,
|
|
57
|
+
"text": "note",
|
|
58
|
+
"spansBlock": false,
|
|
59
|
+
"nested": false
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "parse-highlight",
|
|
65
|
+
"op": "parse",
|
|
66
|
+
"input": "{==hl==}",
|
|
67
|
+
"expected": [
|
|
68
|
+
{
|
|
69
|
+
"kind": "highlight",
|
|
70
|
+
"start": 0,
|
|
71
|
+
"end": 8,
|
|
72
|
+
"text": "hl",
|
|
73
|
+
"spansBlock": false,
|
|
74
|
+
"nested": false
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "parse-nested-same-kind",
|
|
80
|
+
"op": "parse",
|
|
81
|
+
"input": "{--a {--b--} c--}",
|
|
82
|
+
"expected": [
|
|
83
|
+
{
|
|
84
|
+
"kind": "deletion",
|
|
85
|
+
"start": 0,
|
|
86
|
+
"end": 17,
|
|
87
|
+
"text": "a {--b--} c",
|
|
88
|
+
"spansBlock": false,
|
|
89
|
+
"nested": true
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"kind": "deletion",
|
|
93
|
+
"start": 5,
|
|
94
|
+
"end": 12,
|
|
95
|
+
"text": "b",
|
|
96
|
+
"spansBlock": false,
|
|
97
|
+
"nested": false
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "parse-nested-different-kind",
|
|
103
|
+
"op": "parse",
|
|
104
|
+
"input": "{++a {--b--} c++}",
|
|
105
|
+
"expected": [
|
|
106
|
+
{
|
|
107
|
+
"kind": "insertion",
|
|
108
|
+
"start": 0,
|
|
109
|
+
"end": 17,
|
|
110
|
+
"text": "a {--b--} c",
|
|
111
|
+
"spansBlock": false,
|
|
112
|
+
"nested": true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"kind": "deletion",
|
|
116
|
+
"start": 5,
|
|
117
|
+
"end": 12,
|
|
118
|
+
"text": "b",
|
|
119
|
+
"spansBlock": false,
|
|
120
|
+
"nested": false
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "parse-unterminated",
|
|
126
|
+
"op": "parse",
|
|
127
|
+
"input": "{++foo",
|
|
128
|
+
"expected": []
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "parse-fenced-code-skip",
|
|
132
|
+
"op": "parse",
|
|
133
|
+
"input": "```\n{--not tracked--}\n```",
|
|
134
|
+
"expected": []
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "parse-reading-order",
|
|
138
|
+
"op": "parse",
|
|
139
|
+
"input": "{++a++}{--b--}{==c==}",
|
|
140
|
+
"expected": [
|
|
141
|
+
{
|
|
142
|
+
"kind": "insertion",
|
|
143
|
+
"start": 0,
|
|
144
|
+
"end": 7,
|
|
145
|
+
"text": "a",
|
|
146
|
+
"spansBlock": false,
|
|
147
|
+
"nested": false
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"kind": "deletion",
|
|
151
|
+
"start": 7,
|
|
152
|
+
"end": 14,
|
|
153
|
+
"text": "b",
|
|
154
|
+
"spansBlock": false,
|
|
155
|
+
"nested": false
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"kind": "highlight",
|
|
159
|
+
"start": 14,
|
|
160
|
+
"end": 21,
|
|
161
|
+
"text": "c",
|
|
162
|
+
"spansBlock": false,
|
|
163
|
+
"nested": false
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "parse-spans-block",
|
|
169
|
+
"op": "parse",
|
|
170
|
+
"input": "{++A\n\nB++}",
|
|
171
|
+
"expected": [
|
|
172
|
+
{
|
|
173
|
+
"kind": "insertion",
|
|
174
|
+
"start": 0,
|
|
175
|
+
"end": 10,
|
|
176
|
+
"text": "A\n\nB",
|
|
177
|
+
"spansBlock": true,
|
|
178
|
+
"nested": false
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "accept-insertion",
|
|
184
|
+
"op": "accept",
|
|
185
|
+
"input": "{++foo++}",
|
|
186
|
+
"mark": 0,
|
|
187
|
+
"expected": "foo"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "decline-insertion",
|
|
191
|
+
"op": "decline",
|
|
192
|
+
"input": "{++foo++}",
|
|
193
|
+
"mark": 0,
|
|
194
|
+
"expected": ""
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "accept-deletion",
|
|
198
|
+
"op": "accept",
|
|
199
|
+
"input": "{--bar--}",
|
|
200
|
+
"mark": 0,
|
|
201
|
+
"expected": ""
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"id": "decline-deletion",
|
|
205
|
+
"op": "decline",
|
|
206
|
+
"input": "{--bar--}",
|
|
207
|
+
"mark": 0,
|
|
208
|
+
"expected": "bar"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "accept-substitution",
|
|
212
|
+
"op": "accept",
|
|
213
|
+
"input": "{~~old~>new~~}",
|
|
214
|
+
"mark": 0,
|
|
215
|
+
"expected": "new"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "decline-substitution",
|
|
219
|
+
"op": "decline",
|
|
220
|
+
"input": "{~~old~>new~~}",
|
|
221
|
+
"mark": 0,
|
|
222
|
+
"expected": "old"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "accept-highlight-bare",
|
|
226
|
+
"op": "accept",
|
|
227
|
+
"input": "{==hl==}",
|
|
228
|
+
"mark": 0,
|
|
229
|
+
"expected": "hl"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "decline-highlight-bare",
|
|
233
|
+
"op": "decline",
|
|
234
|
+
"input": "{==hl==}",
|
|
235
|
+
"mark": 0,
|
|
236
|
+
"expected": "hl"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "accept-comment-bare",
|
|
240
|
+
"op": "accept",
|
|
241
|
+
"input": "{>>note<<}",
|
|
242
|
+
"mark": 0,
|
|
243
|
+
"expected": "{>>note<<}"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "decline-comment-bare",
|
|
247
|
+
"op": "decline",
|
|
248
|
+
"input": "{>>note<<}",
|
|
249
|
+
"mark": 0,
|
|
250
|
+
"expected": "{>>note<<}"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "accept-highlight-anchored-comment",
|
|
254
|
+
"op": "accept",
|
|
255
|
+
"input": "{==hl==}{>>note<<}",
|
|
256
|
+
"mark": 0,
|
|
257
|
+
"expected": "hl"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"id": "decline-highlight-anchored-comment",
|
|
261
|
+
"op": "decline",
|
|
262
|
+
"input": "{==hl==}{>>note<<}",
|
|
263
|
+
"mark": 0,
|
|
264
|
+
"expected": "hl"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "acceptAll-nested-deletion",
|
|
268
|
+
"op": "acceptAll",
|
|
269
|
+
"input": "{--a {--b--} c--}",
|
|
270
|
+
"expected": ""
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "declineAll-nested-deletion",
|
|
274
|
+
"op": "declineAll",
|
|
275
|
+
"input": "{--a {--b--} c--}",
|
|
276
|
+
"expected": "a b c"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"id": "acceptAll-identity-no-marks",
|
|
280
|
+
"op": "acceptAll",
|
|
281
|
+
"input": "plain prose, no marks here.",
|
|
282
|
+
"expected": "plain prose, no marks here."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"id": "declineAll-identity-no-marks",
|
|
286
|
+
"op": "declineAll",
|
|
287
|
+
"input": "plain prose, no marks here.",
|
|
288
|
+
"expected": "plain prose, no marks here."
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "acceptAll-bare-comment-survives",
|
|
292
|
+
"op": "acceptAll",
|
|
293
|
+
"input": "{++x++}{>>note<<}",
|
|
294
|
+
"expected": "x{>>note<<}"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"id": "declineAll-bare-comment-survives",
|
|
298
|
+
"op": "declineAll",
|
|
299
|
+
"input": "{++x++}{>>note<<}",
|
|
300
|
+
"expected": "{>>note<<}"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"id": "stripComments-removes-bare-comment",
|
|
304
|
+
"op": "stripComments",
|
|
305
|
+
"input": "x{>>note<<}",
|
|
306
|
+
"expected": "x"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"id": "token-accept-insertion-break",
|
|
310
|
+
"op": "accept",
|
|
311
|
+
"input": "A{++¶++}B",
|
|
312
|
+
"mark": 0,
|
|
313
|
+
"expected": "A\n\nB"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"id": "token-decline-insertion-break",
|
|
317
|
+
"op": "decline",
|
|
318
|
+
"input": "A{++¶++}B",
|
|
319
|
+
"mark": 0,
|
|
320
|
+
"expected": "AB"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "token-accept-deletion-break",
|
|
324
|
+
"op": "accept",
|
|
325
|
+
"input": "A{--¶--}B",
|
|
326
|
+
"mark": 0,
|
|
327
|
+
"expected": "A B"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "token-decline-deletion-break",
|
|
331
|
+
"op": "decline",
|
|
332
|
+
"input": "A{--¶--}B",
|
|
333
|
+
"mark": 0,
|
|
334
|
+
"expected": "A\n\nB"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"id": "token-decline-insertion-no-orphan-blank-line",
|
|
338
|
+
"op": "decline",
|
|
339
|
+
"input": "{++A¶B++}",
|
|
340
|
+
"mark": 0,
|
|
341
|
+
"expected": ""
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"id": "token-accept-insertion-content-break",
|
|
345
|
+
"op": "accept",
|
|
346
|
+
"input": "{++A¶B++}",
|
|
347
|
+
"mark": 0,
|
|
348
|
+
"expected": "A\n\nB"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"id": "token-accept-doubled-pilcrow-literal",
|
|
352
|
+
"op": "accept",
|
|
353
|
+
"input": "{++A¶¶B++}",
|
|
354
|
+
"mark": 0,
|
|
355
|
+
"expected": "A¶B"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"id": "tokenize-insertion-block-spanning",
|
|
359
|
+
"op": "tokenize",
|
|
360
|
+
"input": "{++A\n\nB++}",
|
|
361
|
+
"expected": "{++A¶B++}"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"id": "tokenize-substitution-block-spanning",
|
|
365
|
+
"op": "tokenize",
|
|
366
|
+
"input": "{~~A\n\nB~>C~~}",
|
|
367
|
+
"expected": "{~~A¶B~>C~~}"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"id": "portable-insertion-tokenized-to-split",
|
|
371
|
+
"op": "portable",
|
|
372
|
+
"input": "{++A¶B++}",
|
|
373
|
+
"expected": "{++A++}\n\n{++B++}"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"id": "declineAll-portable-split-blank-line-cost",
|
|
377
|
+
"op": "declineAll",
|
|
378
|
+
"input": "{++A++}\n\n{++B++}",
|
|
379
|
+
"expected": "\n\n"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "seam-accept-merge-injects-space",
|
|
383
|
+
"op": "accept",
|
|
384
|
+
"input": "The first paragraph ends here.{--¶--}The second begins here.",
|
|
385
|
+
"mark": 0,
|
|
386
|
+
"expected": "The first paragraph ends here. The second begins here."
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"id": "seam-accept-merge-space-already-before",
|
|
390
|
+
"op": "accept",
|
|
391
|
+
"input": "A. {--¶--}B",
|
|
392
|
+
"mark": 0,
|
|
393
|
+
"expected": "A. B"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"id": "seam-accept-merge-space-already-after",
|
|
397
|
+
"op": "accept",
|
|
398
|
+
"input": "A.{--¶--} B",
|
|
399
|
+
"mark": 0,
|
|
400
|
+
"expected": "A. B"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "seam-accept-merge-at-body-start",
|
|
404
|
+
"op": "accept",
|
|
405
|
+
"input": "{--¶--}B",
|
|
406
|
+
"mark": 0,
|
|
407
|
+
"expected": "B"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"id": "seam-accept-merge-at-body-end",
|
|
411
|
+
"op": "accept",
|
|
412
|
+
"input": "A{--¶--}",
|
|
413
|
+
"mark": 0,
|
|
414
|
+
"expected": "A"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"id": "seam-accept-split-absorbs-space-before",
|
|
418
|
+
"op": "accept",
|
|
419
|
+
"input": "A. {++¶++}B",
|
|
420
|
+
"mark": 0,
|
|
421
|
+
"expected": "A.\n\nB"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"id": "seam-accept-split-absorbs-space-after",
|
|
425
|
+
"op": "accept",
|
|
426
|
+
"input": "A.{++¶++} B",
|
|
427
|
+
"mark": 0,
|
|
428
|
+
"expected": "A.\n\nB"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"id": "seam-decline-split-restores-verbatim",
|
|
432
|
+
"op": "decline",
|
|
433
|
+
"input": "run{++¶++}together",
|
|
434
|
+
"mark": 0,
|
|
435
|
+
"expected": "runtogether"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"id": "seam-decline-merge-restores-verbatim",
|
|
439
|
+
"op": "decline",
|
|
440
|
+
"input": "A. {--¶--}B",
|
|
441
|
+
"mark": 0,
|
|
442
|
+
"expected": "A. \n\nB"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"id": "seam-accept-deletion-without-break-joins-nothing",
|
|
446
|
+
"op": "accept",
|
|
447
|
+
"input": "A{--x--}B",
|
|
448
|
+
"mark": 0,
|
|
449
|
+
"expected": "AB"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"id": "seam-accept-substitution-replaces-the-break",
|
|
453
|
+
"op": "accept",
|
|
454
|
+
"input": "{~~A¶B~>C~~}",
|
|
455
|
+
"mark": 0,
|
|
456
|
+
"expected": "C"
|
|
457
|
+
}
|
|
67
458
|
]
|
package/package.json
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
1
|
+
{
|
|
2
|
+
"name": "@dojo-ng/rich-text-criticmarkup",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"description": "CriticMarkup tracked-changes plugin for @dojo-ng/rich-text",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.map",
|
|
13
|
+
"fixtures"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"clean": "rm -rf dist"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@dojo-ng/rich-text": "^0.1.0",
|
|
21
|
+
"@dojo-ng/i18n": "^0.1.0",
|
|
22
|
+
"@dojo-ng/popup-confirmation": "^0.1.0",
|
|
23
|
+
"@dojo-ng/popup": "^0.1.0",
|
|
24
|
+
"@dojo-ng/text-area": "^0.1.0",
|
|
25
|
+
"@dojo-ng/button": "^0.1.0",
|
|
26
|
+
"lexical": "^0.21.0",
|
|
27
|
+
"@lexical/markdown": "^0.21.0",
|
|
28
|
+
"@lexical/utils": "^0.21.0",
|
|
29
|
+
"lit": "^3.3.0"
|
|
30
|
+
}
|
|
31
|
+
}
|