@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 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
- return text.slice(0, mark.start) + keptText(mark, side) + text.slice(end);
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 += "\n\n"; // unpaired: a structural break
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.
@@ -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
- else
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
- { "id": "parse-insertion", "op": "parse", "input": "{++foo++}",
3
- "expected": [ { "kind": "insertion", "start": 0, "end": 9, "text": "foo", "spansBlock": false, "nested": false } ] },
4
- { "id": "parse-deletion", "op": "parse", "input": "{--bar--}",
5
- "expected": [ { "kind": "deletion", "start": 0, "end": 9, "text": "bar", "spansBlock": false, "nested": false } ] },
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
- { "kind": "deletion", "start": 0, "end": 17, "text": "a {--b--} c", "spansBlock": false, "nested": true },
15
- { "kind": "deletion", "start": 5, "end": 12, "text": "b", "spansBlock": false, "nested": false }
16
- ] },
17
- { "id": "parse-nested-different-kind", "op": "parse", "input": "{++a {--b--} c++}",
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
- { "kind": "insertion", "start": 0, "end": 17, "text": "a {--b--} c", "spansBlock": false, "nested": true },
20
- { "kind": "deletion", "start": 5, "end": 12, "text": "b", "spansBlock": false, "nested": false }
21
- ] },
22
- { "id": "parse-unterminated", "op": "parse", "input": "{++foo", "expected": [] },
23
- { "id": "parse-fenced-code-skip", "op": "parse", "input": "```\n{--not tracked--}\n```", "expected": [] },
24
- { "id": "parse-reading-order", "op": "parse", "input": "{++a++}{--b--}{==c==}",
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
- { "kind": "insertion", "start": 0, "end": 7, "text": "a", "spansBlock": false, "nested": false },
27
- { "kind": "deletion", "start": 7, "end": 14, "text": "b", "spansBlock": false, "nested": false },
28
- { "kind": "highlight", "start": 14, "end": 21, "text": "c", "spansBlock": false, "nested": false }
29
- ] },
30
- { "id": "parse-spans-block", "op": "parse", "input": "{++A\n\nB++}",
31
- "expected": [ { "kind": "insertion", "start": 0, "end": 10, "text": "A\n\nB", "spansBlock": true, "nested": false } ] },
32
-
33
- { "id": "accept-insertion", "op": "accept", "input": "{++foo++}", "mark": 0, "expected": "foo" },
34
- { "id": "decline-insertion", "op": "decline", "input": "{++foo++}", "mark": 0, "expected": "" },
35
- { "id": "accept-deletion", "op": "accept", "input": "{--bar--}", "mark": 0, "expected": "" },
36
- { "id": "decline-deletion", "op": "decline", "input": "{--bar--}", "mark": 0, "expected": "bar" },
37
- { "id": "accept-substitution", "op": "accept", "input": "{~~old~>new~~}", "mark": 0, "expected": "new" },
38
- { "id": "decline-substitution", "op": "decline", "input": "{~~old~>new~~}", "mark": 0, "expected": "old" },
39
- { "id": "accept-highlight-bare", "op": "accept", "input": "{==hl==}", "mark": 0, "expected": "hl" },
40
- { "id": "decline-highlight-bare", "op": "decline", "input": "{==hl==}", "mark": 0, "expected": "hl" },
41
- { "id": "accept-comment-bare", "op": "accept", "input": "{>>note<<}", "mark": 0, "expected": "{>>note<<}" },
42
- { "id": "decline-comment-bare", "op": "decline", "input": "{>>note<<}", "mark": 0, "expected": "{>>note<<}" },
43
- { "id": "accept-highlight-anchored-comment", "op": "accept", "input": "{==hl==}{>>note<<}", "mark": 0, "expected": "hl" },
44
- { "id": "decline-highlight-anchored-comment", "op": "decline", "input": "{==hl==}{>>note<<}", "mark": 0, "expected": "hl" },
45
-
46
- { "id": "acceptAll-nested-deletion", "op": "acceptAll", "input": "{--a {--b--} c--}", "expected": "" },
47
- { "id": "declineAll-nested-deletion", "op": "declineAll", "input": "{--a {--b--} c--}", "expected": "a b c" },
48
- { "id": "acceptAll-identity-no-marks", "op": "acceptAll", "input": "plain prose, no marks here.", "expected": "plain prose, no marks here." },
49
- { "id": "declineAll-identity-no-marks", "op": "declineAll", "input": "plain prose, no marks here.", "expected": "plain prose, no marks here." },
50
- { "id": "acceptAll-bare-comment-survives", "op": "acceptAll", "input": "{++x++}{>>note<<}", "expected": "x{>>note<<}" },
51
- { "id": "declineAll-bare-comment-survives", "op": "declineAll", "input": "{++x++}{>>note<<}", "expected": "{>>note<<}" },
52
- { "id": "stripComments-removes-bare-comment", "op": "stripComments", "input": "x{>>note<<}", "expected": "x" },
53
-
54
- { "id": "token-accept-insertion-break", "op": "accept", "input": "A{++¶++}B", "mark": 0, "expected": "A\n\nB" },
55
- { "id": "token-decline-insertion-break", "op": "decline", "input": "A{++¶++}B", "mark": 0, "expected": "AB" },
56
- { "id": "token-accept-deletion-break", "op": "accept", "input": "A{--¶--}B", "mark": 0, "expected": "AB" },
57
- { "id": "token-decline-deletion-break", "op": "decline", "input": "A{--¶--}B", "mark": 0, "expected": "A\n\nB" },
58
- { "id": "token-decline-insertion-no-orphan-blank-line", "op": "decline", "input": "{++A¶B++}", "mark": 0, "expected": "" },
59
- { "id": "token-accept-insertion-content-break", "op": "accept", "input": "{++A¶B++}", "mark": 0, "expected": "A\n\nB" },
60
- { "id": "token-accept-doubled-pilcrow-literal", "op": "accept", "input": "{++A¶¶B++}", "mark": 0, "expected": "A¶B" },
61
-
62
- { "id": "tokenize-insertion-block-spanning", "op": "tokenize", "input": "{++A\n\nB++}", "expected": "{++A¶B++}" },
63
- { "id": "tokenize-substitution-block-spanning", "op": "tokenize", "input": "{~~A\n\nB~>C~~}", "expected": "{~~A¶B~>C~~}" },
64
-
65
- { "id": "portable-insertion-tokenized-to-split", "op": "portable", "input": "{++A¶B++}", "expected": "{++A++}\n\n{++B++}" },
66
- { "id": "declineAll-portable-split-blank-line-cost", "op": "declineAll", "input": "{++A++}\n\n{++B++}", "expected": "\n\n" }
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
- { "name": "@dojo-ng/rich-text-criticmarkup", "version": "0.1.0", "license": "BSD-3-Clause", "description": "CriticMarkup tracked-changes plugin for @dojo-ng/rich-text", "type": "module",
2
- "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts",
3
- "files": ["dist", "!dist/**/*.map", "fixtures"],
4
- "scripts": { "build": "tsc -p tsconfig.json", "clean": "rm -rf dist" },
5
- "dependencies": { "@dojo-ng/rich-text": "^0.1.0", "@dojo-ng/i18n": "^0.1.0", "@dojo-ng/popup-confirmation": "^0.1.0", "@dojo-ng/popup": "^0.1.0", "@dojo-ng/text-area": "^0.1.0", "@dojo-ng/button": "^0.1.0", "lexical": "^0.21.0", "@lexical/markdown": "^0.21.0", "@lexical/utils": "^0.21.0", "lit": "^3.3.0" } }
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
+ }