@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.30 → 0.4.0

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.
Files changed (88) hide show
  1. package/dist/__tests__/playwrightHelpers.d.ts +2 -2
  2. package/dist/__tests__/playwrightPage.d.ts +2 -50
  3. package/dist/commands.js +43 -222
  4. package/dist/ensureSelectionPlugin.js +3 -3
  5. package/dist/features/joinOnDelete/index.d.ts +19 -4
  6. package/dist/features/joinOnDelete/index.js +53 -166
  7. package/dist/generateId.js +4 -31
  8. package/dist/index.d.ts +2 -5
  9. package/dist/index.js +2 -4
  10. package/dist/replaceStep.d.ts +1 -1
  11. package/dist/schema.d.ts +1 -2
  12. package/dist/schema.js +1 -37
  13. package/dist/testing/testBuilders.d.ts +2 -1
  14. package/dist/utils.d.ts +0 -1
  15. package/dist/utils.js +2 -6
  16. package/dist/withSuggestChanges.d.ts +14 -11
  17. package/dist/withSuggestChanges.js +94 -64
  18. package/package.json +1 -1
  19. package/src/features/joinOnDelete/README.md +8 -0
  20. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +0 -1
  21. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +0 -1
  22. package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +0 -1
  23. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +0 -6
  24. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +0 -24
  25. package/dist/features/joinOnDelete/types.d.ts +0 -36
  26. package/dist/features/joinOnDelete/types.js +0 -23
  27. package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +0 -3
  28. package/dist/features/transactionShaping/detectSpecialTransactionShape.js +0 -4
  29. package/dist/features/transactionShaping/index.d.ts +0 -3
  30. package/dist/features/transactionShaping/index.js +0 -11
  31. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +0 -3
  32. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +0 -48
  33. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +0 -1
  34. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +0 -188
  35. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +0 -3
  36. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +0 -64
  37. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +0 -2
  38. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +0 -2
  39. package/dist/features/transactionShaping/types.d.ts +0 -20
  40. package/dist/features/transactionShaping/types.js +0 -1
  41. package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +0 -1
  42. package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +0 -1
  43. package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +0 -1
  44. package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +0 -1
  45. package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +0 -1
  46. package/dist/features/wrapUnwrap/addIdAttr.d.ts +0 -2
  47. package/dist/features/wrapUnwrap/addIdAttr.js +0 -60
  48. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +0 -10
  49. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +0 -41
  50. package/dist/features/wrapUnwrap/apply/index.d.ts +0 -5
  51. package/dist/features/wrapUnwrap/apply/index.js +0 -21
  52. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +0 -2
  53. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +0 -17
  54. package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +0 -3
  55. package/dist/features/wrapUnwrap/buildMaterializedPaths.js +0 -82
  56. package/dist/features/wrapUnwrap/constants.d.ts +0 -3
  57. package/dist/features/wrapUnwrap/constants.js +0 -3
  58. package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +0 -1
  59. package/dist/features/wrapUnwrap/generateUniqueNodeId.js +0 -6
  60. package/dist/features/wrapUnwrap/getNodeId.d.ts +0 -2
  61. package/dist/features/wrapUnwrap/getNodeId.js +0 -5
  62. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +0 -3
  63. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +0 -37
  64. package/dist/features/wrapUnwrap/revert/index.d.ts +0 -5
  65. package/dist/features/wrapUnwrap/revert/index.js +0 -19
  66. package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +0 -4
  67. package/dist/features/wrapUnwrap/revert/revertAddOp.js +0 -4
  68. package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +0 -16
  69. package/dist/features/wrapUnwrap/revert/revertMoveOp.js +0 -122
  70. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +0 -10
  71. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +0 -236
  72. package/dist/features/wrapUnwrap/sameParentChain.d.ts +0 -2
  73. package/dist/features/wrapUnwrap/sameParentChain.js +0 -4
  74. package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +0 -17
  75. package/dist/features/wrapUnwrap/structureChangesPlugin.js +0 -299
  76. package/dist/features/wrapUnwrap/types.d.ts +0 -54
  77. package/dist/features/wrapUnwrap/types.js +0 -23
  78. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +0 -17
  79. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +0 -106
  80. package/dist/listInputRules.d.ts +0 -2
  81. package/dist/listInputRules.js +0 -14
  82. package/dist/rebaseStep.d.ts +0 -9
  83. package/dist/rebaseStep.js +0 -11
  84. package/dist/testing/e2eTestSchema.d.ts +0 -2
  85. package/dist/transformToSuggestionTransaction.d.ts +0 -22
  86. package/dist/transformToSuggestionTransaction.js +0 -101
  87. package/dist/wrappingInputRule.d.ts +0 -4
  88. package/dist/wrappingInputRule.js +0 -28
@@ -1,236 +0,0 @@
1
- import { getSuggestionMarks } from "../../../utils.js";
2
- import { getNodeId } from "../getNodeId.js";
3
- import { Transform } from "prosemirror-transform";
4
- import { guardStructureMarkAttrs } from "../types.js";
5
- import { sameParentChain } from "../sameParentChain.js";
6
- import { buildMaterializedPaths } from "../buildMaterializedPaths.js";
7
- import { revertAddOp } from "./revertAddOp.js";
8
- import { revertMoveOp } from "./revertMoveOp.js";
9
- const TRACE_ENABLED = false;
10
- function trace(...args) {
11
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
12
- if (!TRACE_ENABLED) return;
13
- console.log("[revertStructureSuggestions]", ...args);
14
- }
15
- export function revertStructureSuggestionsInDoc({ tr, suggestionId, from, to }) {
16
- if (suggestionId) {
17
- // when suggestionId is given, just revert it, no other logic required
18
- revertStructureSuggestionWithPrerequisites(tr, suggestionId);
19
- return;
20
- }
21
- if (from || to) {
22
- // when range is given, find suggestion ids inside that range,
23
- // but make sure to revert furthermost suggestions first
24
- // todo: most likely a better strategy would be to search for the next suggestion in the updated doc after each reversal (and map from and to)
25
- const { structure } = getSuggestionMarks(tr.doc.type.schema);
26
- const structureMarks = [];
27
- tr.doc.descendants((node, pos)=>{
28
- if (from !== undefined && pos < from) {
29
- return true;
30
- }
31
- if (to !== undefined && pos > to) {
32
- return false;
33
- }
34
- if (node.isText) return true;
35
- if (!structure.isInSet(node.marks)) return true;
36
- node.marks.forEach((mark)=>{
37
- if (mark.type !== structure) return;
38
- structureMarks.push({
39
- mark,
40
- node,
41
- pos
42
- });
43
- });
44
- return true;
45
- });
46
- structureMarks.sort((a, b)=>b.pos - a.pos);
47
- const suggestionIds = new Set();
48
- structureMarks.forEach(({ mark })=>{
49
- suggestionIds.add(mark.attrs["id"]);
50
- });
51
- for (const suggestionId of suggestionIds){
52
- revertStructureSuggestionWithPrerequisites(tr, suggestionId);
53
- }
54
- return;
55
- }
56
- // if no suggestion id nor range is given, revert all suggestions one by one, always take furthermost first
57
- // after each reversal, the next suggestion is searched in the new doc after the previous reversal
58
- let nextSuggestionId = findNextStructureSuggestion(tr.doc);
59
- while(nextSuggestionId != null){
60
- revertStructureSuggestionWithPrerequisites(tr, nextSuggestionId);
61
- nextSuggestionId = findNextStructureSuggestion(tr.doc);
62
- }
63
- }
64
- function revertStructureSuggestionWithPrerequisites(tr, suggestionId) {
65
- const suggestionIds = buildOrderedSuggestionIds(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
66
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
67
- if (TRACE_ENABLED) console.group("reverting structure suggestions", suggestionIds);
68
- for (const suggestionId of suggestionIds){
69
- revertOneStructureSuggestion(tr, suggestionId);
70
- }
71
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
72
- if (TRACE_ENABLED) console.groupEnd();
73
- }
74
- function revertOneStructureSuggestion(tr, suggestionId) {
75
- let count = 0;
76
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
77
- if (TRACE_ENABLED) console.groupCollapsed("reverting structure suggestion", suggestionId);
78
- let structureMark = findNextStructureMark(tr.doc, suggestionId);
79
- while(structureMark !== null){
80
- trace("reverting structure suggestion", suggestionId, "structure mark", structureMark.mark, "at pos", structureMark.pos, "at node", structureMark.node.toString());
81
- revertStructureMark(tr, structureMark.mark, structureMark.pos);
82
- structureMark = findNextStructureMark(tr.doc, suggestionId);
83
- count++;
84
- }
85
- trace("reverted", count, "structure marks for suggestion", suggestionId);
86
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
87
- if (TRACE_ENABLED) console.groupEnd();
88
- }
89
- export function revertStructureMark(tr, mark, pos) {
90
- const transform = new Transform(tr.doc);
91
- transform.removeNodeMark(pos, mark);
92
- const node = transform.doc.nodeAt(pos);
93
- if (!node) {
94
- throw new Error(`Node not found at position ${String(pos)}`);
95
- }
96
- const attrs = mark.attrs;
97
- if (!guardStructureMarkAttrs(attrs)) {
98
- console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
99
- attrs
100
- });
101
- return;
102
- }
103
- const op = attrs.data.op;
104
- trace("reverting structure mark with suggestion id", attrs.id, "at node", node.toString(), "at pos", pos, "with op", op.op, {
105
- mark,
106
- node,
107
- $pos: transform.doc.resolve(pos),
108
- op
109
- });
110
- switch(op.op){
111
- case "add":
112
- {
113
- revertAddOp(op, transform, node, pos);
114
- break;
115
- }
116
- case "move":
117
- {
118
- revertMoveOp(op, transform, node, pos);
119
- break;
120
- }
121
- default:
122
- console.warn("revertStructureMark", "unknown op", {
123
- op
124
- });
125
- break;
126
- }
127
- transform.steps.forEach((step)=>{
128
- tr.step(step);
129
- });
130
- }
131
- // given a suggestion id
132
- // return an array of suggestion ids to revert
133
- // resolve suggestion dependencies, meaning,
134
- // if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
135
- // it will return [3,2,1]
136
- // todo: this should probably use topological sort at some point
137
- function buildOrderedSuggestionIds(node, suggestionId, materializedPaths) {
138
- const { structure } = getSuggestionMarks(node.type.schema);
139
- // collect marks with the given suggestionId
140
- const markGroup = [];
141
- node.descendants((descendant, pos)=>{
142
- if (descendant.isText) return true;
143
- if (!structure.isInSet(descendant.marks)) return true;
144
- descendant.marks.forEach((mark)=>{
145
- if (mark.type !== structure) return;
146
- if (mark.attrs["id"] !== suggestionId) return;
147
- markGroup.push({
148
- mark,
149
- node: descendant,
150
- pos
151
- });
152
- });
153
- return true;
154
- });
155
- const suggestionIds = new Set();
156
- suggestionIds.add(suggestionId);
157
- // find first mark that doesn't have a matching op.to
158
- const mismatch = markGroup.find((mark)=>{
159
- const nodeId = getNodeId(mark.node);
160
- if (nodeId == null) return false;
161
- const parentChain = materializedPaths.get(nodeId);
162
- if (parentChain == null) return false;
163
- const { attrs } = mark.mark;
164
- if (!guardStructureMarkAttrs(attrs)) return false;
165
- if (attrs.data.op.op !== "move") return false;
166
- return !sameParentChain(attrs.data.op.to, parentChain.chain);
167
- });
168
- if (mismatch == null) {
169
- return Array.from(suggestionIds).reverse();
170
- }
171
- // find first mark on the node that does have a matching op.to
172
- const match = mismatch.node.marks.find((mark)=>{
173
- if (mark.type !== structure) return false;
174
- const { attrs } = mark;
175
- if (!guardStructureMarkAttrs(attrs)) return false;
176
- if (attrs.data.op.op !== "move") return false;
177
- const nodeId = getNodeId(mismatch.node);
178
- if (nodeId == null) return false;
179
- const parentChain = materializedPaths.get(nodeId);
180
- if (parentChain == null) return false;
181
- return sameParentChain(attrs.data.op.to, parentChain.chain);
182
- });
183
- if (match) {
184
- trace("suggestion", match.attrs["id"], "is a prerequisite for suggestion", suggestionId);
185
- suggestionIds.add(match.attrs["id"]);
186
- }
187
- return Array.from(suggestionIds).reverse();
188
- }
189
- function findNextStructureSuggestion(doc) {
190
- const { structure } = getSuggestionMarks(doc.type.schema);
191
- const structureMarks = [];
192
- doc.descendants((node, pos)=>{
193
- if (node.isText) return true;
194
- if (!structure.isInSet(node.marks)) return true;
195
- node.marks.forEach((mark)=>{
196
- if (mark.type !== structure) return;
197
- structureMarks.push({
198
- mark,
199
- node,
200
- pos
201
- });
202
- });
203
- return true;
204
- });
205
- structureMarks.sort((a, b)=>b.pos - a.pos);
206
- return structureMarks[0]?.mark.attrs["id"];
207
- }
208
- // given a suggestion id, find next structure mark to revert that belongs to that suggestion
209
- // always take furthermost mark first
210
- function findNextStructureMark(node, suggestionId) {
211
- const { structure } = getSuggestionMarks(node.type.schema);
212
- const structureMarks = [];
213
- node.descendants((descendant, pos)=>{
214
- // the assumption here is that a single node cannot have multiple structure marks with the same suggestion id
215
- // check the invariant
216
- const suggestionIds = new Set();
217
- descendant.marks.forEach((mark)=>{
218
- if (mark.type !== structure) return;
219
- const markSuggestionId = mark.attrs["id"];
220
- if (suggestionIds.has(markSuggestionId)) {
221
- console.warn("node", node, "has multiple structure marks with the same suggestion id", markSuggestionId);
222
- }
223
- suggestionIds.add(markSuggestionId);
224
- });
225
- const mark = descendant.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
226
- if (mark == null) return true;
227
- structureMarks.push({
228
- mark,
229
- node: descendant,
230
- pos
231
- });
232
- return true;
233
- });
234
- structureMarks.sort((a, b)=>b.pos - a.pos);
235
- return structureMarks[0] ?? null;
236
- }
@@ -1,2 +0,0 @@
1
- import { type Parent } from "./types.js";
2
- export declare function sameParentChain(parentChainA: Parent[], parentChainB: Parent[]): boolean;
@@ -1,4 +0,0 @@
1
- export function sameParentChain(parentChainA, parentChainB) {
2
- if (parentChainA.length !== parentChainB.length) return false;
3
- return parentChainA.every((parent, index)=>parent.nodeId === parentChainB[index]?.nodeId);
4
- }
@@ -1,17 +0,0 @@
1
- import { type Schema, type Node } from "prosemirror-model";
2
- import { Plugin, PluginKey } from "prosemirror-state";
3
- import { type SuggestionId } from "../../generateId.js";
4
- import { type StructuralContextPath } from "./types.js";
5
- import { Transform } from "prosemirror-transform";
6
- export declare const structureChangesKey: PluginKey<any>;
7
- export type SuggestStructureChangesReason = "split-derived-add";
8
- export interface SuggestStructureChangesResult {
9
- handled: boolean;
10
- transform: Transform;
11
- reason?: SuggestStructureChangesReason;
12
- }
13
- export declare function structureChangesPlugin(generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
14
- experimental_trackStructures?: StructuralContextPath[];
15
- }): Plugin<any>;
16
- export declare function suggestStructureChanges(docBefore: Node, docAfter: Node, structuralContextPaths: StructuralContextPath[], generateId?: (schema: Schema, doc?: Node) => SuggestionId): SuggestStructureChangesResult;
17
- export declare function getRequiredStructuralContextPaths(structuralContextPaths: StructuralContextPath[] | undefined): StructuralContextPath[];
@@ -1,299 +0,0 @@
1
- import { Plugin, PluginKey } from "prosemirror-state";
2
- import { getSuggestionMarks } from "../../utils.js";
3
- import { generateNextNumberId } from "../../generateId.js";
4
- import { getNodeId } from "./getNodeId.js";
5
- import { guardStructureMarkAttrs } from "./types.js";
6
- import { DOC_NODE_ID, STRUCTURE_CHANGES_ADD_MARKS } from "./constants.js";
7
- import { Transform } from "prosemirror-transform";
8
- import { isSuggestChangesEnabled, suggestChangesKey } from "../../plugin.js";
9
- import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
10
- import { sameParentChain } from "./sameParentChain.js";
11
- const TRACE_ENABLED = false;
12
- function trace(...args) {
13
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
14
- if (!TRACE_ENABLED) return;
15
- console.log("[structureChanges]", ...args);
16
- }
17
- export const structureChangesKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-structure-changes");
18
- export function structureChangesPlugin(generateId, opts) {
19
- const structuralContextPaths = getRequiredStructuralContextPaths(opts?.experimental_trackStructures);
20
- return new Plugin({
21
- key: structureChangesKey,
22
- appendTransaction (transactions, _oldState, newState) {
23
- if (transactions.some((tr)=>!isEnabled(tr, newState))) {
24
- console.warn("structureChangesPlugin", "tracking changes is disabled, skipping structure changes plugin", [
25
- ...transactions
26
- ]);
27
- return;
28
- }
29
- // do nothing if doc hasn't changed
30
- const docChanged = transactions.some((transaction)=>transaction.docChanged);
31
- if (!docChanged) {
32
- console.warn("structureChangesPlugin", "doc not changed, skipping", [
33
- ...transactions
34
- ]);
35
- return;
36
- }
37
- const firstTr = transactions[0];
38
- const lastTr = transactions[transactions.length - 1];
39
- const oldDoc = firstTr?.docs[0];
40
- const newDoc = lastTr?.doc;
41
- // do nothing if there isn't a pair of docs to compare
42
- if (!oldDoc || !newDoc) {
43
- console.warn("structureChangesPlugin", "old or new doc is missing, skipping", {
44
- oldDoc,
45
- newDoc,
46
- transactions: [
47
- ...transactions
48
- ]
49
- });
50
- return;
51
- }
52
- let idsSettled = true;
53
- newDoc.descendants((node, pos)=>{
54
- if (node.isText) return true;
55
- const nodeId = getNodeId(node);
56
- if (nodeId == null) {
57
- console.warn("structureChangesPlugin", "node", node.type.name, "at pos", pos, "is missing a unique id", {
58
- id: node.attrs["id"]
59
- });
60
- idsSettled = false;
61
- return true;
62
- }
63
- return true;
64
- });
65
- oldDoc.descendants((node, pos)=>{
66
- if (node.isText) return true;
67
- const nodeId = getNodeId(node);
68
- if (nodeId == null) {
69
- console.warn("structureChangesPlugin", "node", node.type.name, "at pos", pos, "is missing a unique id", {
70
- id: node.attrs["id"]
71
- });
72
- idsSettled = false;
73
- return true;
74
- }
75
- return true;
76
- });
77
- // do nothing if some nodes are missing unique ids - the diff is not possible then
78
- if (!idsSettled) {
79
- console.warn("structureChangesPlugin", "ids not settled, skipping", {
80
- oldDoc,
81
- newDoc,
82
- transactions: [
83
- ...transactions
84
- ]
85
- });
86
- return;
87
- }
88
- trace("structureChangesPlugin", "appendTransaction", [
89
- ...transactions
90
- ]);
91
- const { transform } = suggestStructureChanges(oldDoc, newDoc, structuralContextPaths, generateId);
92
- const tr = newState.tr;
93
- transform.steps.forEach((step)=>{
94
- tr.step(step);
95
- });
96
- if (!tr.steps.length) return;
97
- tr.setMeta(structureChangesKey, STRUCTURE_CHANGES_ADD_MARKS);
98
- return tr;
99
- }
100
- });
101
- }
102
- function isEnabled(tr, editorState) {
103
- const ySyncMeta = tr.getMeta("y-sync$") ?? {};
104
- const isEnabled = isSuggestChangesEnabled(editorState) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {}));
105
- return isEnabled;
106
- }
107
- export function suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId) {
108
- const suggestionId = generateId ? generateId(docBefore.type.schema, docBefore) : generateNextNumberId(docBefore.type.schema, docBefore);
109
- const pathsBefore = buildMaterializedPaths(docBefore);
110
- const pathsAfter = buildMaterializedPaths(docAfter);
111
- trace("suggestStructureChanges", "materialized paths", {
112
- pathsBefore: Object.fromEntries(pathsBefore.entries()),
113
- pathsAfter: Object.fromEntries(pathsAfter.entries())
114
- });
115
- const { ops, reason } = getOps(pathsBefore, pathsAfter, structuralContextPaths);
116
- trace("suggestStructureChanges", "ops", {
117
- ops: Object.fromEntries(ops.entries()),
118
- reason
119
- });
120
- const transform = new Transform(docAfter);
121
- if (reason) {
122
- return {
123
- handled: false,
124
- transform,
125
- reason
126
- };
127
- }
128
- addMarks(ops, transform, suggestionId);
129
- return {
130
- handled: ops.size > 0,
131
- transform
132
- };
133
- }
134
- export function getRequiredStructuralContextPaths(structuralContextPaths) {
135
- if (!structuralContextPaths?.length) {
136
- throw new Error("experimental_trackStructures must be provided when structure tracking is enabled");
137
- }
138
- return structuralContextPaths;
139
- }
140
- function addMarks(ops, tr, suggestionId) {
141
- const perfAddMarks = performance.now();
142
- const { structure } = getSuggestionMarks(tr.doc.type.schema);
143
- tr.doc.descendants((node, pos)=>{
144
- if (node.isText) return true;
145
- const nodeId = getNodeId(node);
146
- if (nodeId == null) return true;
147
- const op = ops.get(nodeId);
148
- if (op == null) return true;
149
- if (op.op === "move") {
150
- if (hasStructureAddMark(node)) return true;
151
- const inverseMoveMark = findInverseStructureMoveMark(node, op);
152
- if (inverseMoveMark) {
153
- tr.removeNodeMark(pos, inverseMoveMark);
154
- return true;
155
- }
156
- }
157
- tr.addNodeMark(pos, structure.create({
158
- id: suggestionId,
159
- data: {
160
- op
161
- }
162
- }));
163
- return true;
164
- });
165
- trace("perf", "addMarks", "took", Number((performance.now() - perfAddMarks).toFixed(2)), "ms");
166
- }
167
- function findInverseStructureMoveMark(node, op) {
168
- const { structure } = getSuggestionMarks(node.type.schema);
169
- return node.marks.find((mark)=>{
170
- if (mark.type !== structure) return false;
171
- if (!guardStructureMarkAttrs(mark.attrs)) return false;
172
- const existingOp = mark.attrs.data.op;
173
- if (existingOp.op !== "move") return false;
174
- return sameParentChain(existingOp.from, op.to) && sameParentChain(existingOp.to, op.from);
175
- });
176
- }
177
- function hasStructureAddMark(node) {
178
- const { structure } = getSuggestionMarks(node.type.schema);
179
- return node.marks.some((mark)=>{
180
- if (mark.type !== structure) return false;
181
- if (!guardStructureMarkAttrs(mark.attrs)) return false;
182
- return mark.attrs.data.op.op === "add";
183
- });
184
- }
185
- function getOps(beforePaths, afterPaths, structuralContextPaths) {
186
- const ops = new Map();
187
- const contextNodeTypes = getStructuralContextNodeTypes(structuralContextPaths);
188
- // first take care of nodes that exist in both
189
- for (const [id, beforePath] of beforePaths){
190
- if (contextNodeTypes.has(beforePath.nodeType)) continue;
191
- const afterPath = afterPaths.get(id);
192
- // node was removed - do nothing
193
- if (afterPath == null) continue;
194
- if (contextNodeTypes.has(afterPath.nodeType)) continue;
195
- const sameChain = sameParentChain(beforePath.chain, afterPath.chain);
196
- // node did not move anywhere - do nothing
197
- if (sameChain) continue;
198
- const hasStructuralContext = chainHasStructuralContext(beforePath.chain, structuralContextPaths) || chainHasStructuralContext(afterPath.chain, structuralContextPaths);
199
- // node is outside configured structural contexts
200
- if (!hasStructuralContext) continue;
201
- const op = {
202
- op: "move",
203
- from: beforePath.chain,
204
- to: afterPath.chain
205
- };
206
- ops.set(id, op);
207
- }
208
- // now take care of nodes that exist only in afterPaths
209
- // (we don't care about nodes that exist only in beforePaths - they were deleted)
210
- for (const [id, afterPath] of afterPaths){
211
- if (contextNodeTypes.has(afterPath.nodeType)) continue;
212
- // ignore nodes that also exist in beforePaths - they are already handled
213
- if (beforePaths.has(id)) continue;
214
- const hasStructuralContext = chainHasStructuralContext(afterPath.chain, structuralContextPaths);
215
- // node is outside configured structural contexts
216
- if (!hasStructuralContext) continue;
217
- // detect block split - if detected, bail out, and let the main plugin handle it
218
- if (isSplitDerivedAdd(id, beforePaths, afterPaths, contextNodeTypes)) {
219
- return {
220
- ops: new Map(),
221
- reason: "split-derived-add"
222
- };
223
- }
224
- // node was added
225
- const op = {
226
- op: "add"
227
- };
228
- ops.set(id, op);
229
- }
230
- return {
231
- ops
232
- };
233
- }
234
- function getStructuralContextNodeTypes(structuralContextPaths) {
235
- return new Set(structuralContextPaths.flat());
236
- }
237
- function chainHasStructuralContext(chain, structuralContextPaths) {
238
- const topDownChain = [
239
- ...chain
240
- ].reverse().filter((parent)=>parent.nodeType !== DOC_NODE_ID).map((parent)=>parent.nodeType);
241
- return structuralContextPaths.some((path)=>containsContiguousPath(topDownChain, path));
242
- }
243
- // does a chain like: nodeTypeA->nodeTypeB->nodeTypeC
244
- // end with a structural context path like nodeTypeB->nodeTypeC ?
245
- function containsContiguousPath(chainNodeTypes, structuralContextPath) {
246
- if (structuralContextPath.length > chainNodeTypes.length) return false;
247
- let structuralPathPointer = structuralContextPath.length - 1;
248
- let chainPointer = chainNodeTypes.length - 1;
249
- while(structuralPathPointer >= 0){
250
- if (structuralContextPath[structuralPathPointer] !== chainNodeTypes[chainPointer]) {
251
- return false;
252
- }
253
- structuralPathPointer--;
254
- chainPointer--;
255
- }
256
- return true;
257
- }
258
- // detect block split - try to look at the node above, concatenate two text contents,
259
- // and see if it combines into a single node in the old document
260
- function isSplitDerivedAdd(newNodeId, beforePaths, afterPaths, contextNodeTypes) {
261
- const newNode = afterPaths.get(newNodeId)?.node;
262
- if (!newNode || newNode.textContent === "") return false;
263
- if (matchesSplitDerivedPair(newNodeId, newNode.textContent, beforePaths, afterPaths)) {
264
- return true;
265
- }
266
- const afterPath = afterPaths.get(newNodeId);
267
- if (!afterPath) return false;
268
- for (const parent of afterPath.chain){
269
- if (parent.nodeType === DOC_NODE_ID) continue;
270
- if (!contextNodeTypes.has(parent.nodeType)) continue;
271
- const parentNode = afterPaths.get(parent.nodeId)?.node;
272
- if (!parentNode || parentNode.textContent === "") continue;
273
- if (matchesSplitDerivedPair(parent.nodeId, parentNode.textContent, beforePaths, afterPaths)) {
274
- return true;
275
- }
276
- }
277
- return false;
278
- }
279
- function matchesSplitDerivedPair(rightNodeId, rightText, beforePaths, afterPaths) {
280
- const rightPath = afterPaths.get(rightNodeId);
281
- const previousSiblingId = rightPath?.chain[0]?.childSiblingIds[0];
282
- if (!previousSiblingId) return false;
283
- const previousBefore = beforePaths.has(previousSiblingId) ? beforePaths.get(previousSiblingId)?.node : null;
284
- const previousAfter = afterPaths.has(previousSiblingId) ? afterPaths.get(previousSiblingId)?.node : null;
285
- if (!previousBefore || !previousAfter) return false;
286
- if (hasStructureAddMarkInSubtree(previousBefore)) return false;
287
- return previousBefore.textContent === previousAfter.textContent + rightText;
288
- }
289
- function hasStructureAddMarkInSubtree(node) {
290
- if (hasStructureAddMark(node)) return true;
291
- let found = false;
292
- node.descendants((descendant)=>{
293
- if (descendant.isText) return false;
294
- if (!hasStructureAddMark(descendant)) return true;
295
- found = true;
296
- return false;
297
- });
298
- return found;
299
- }
@@ -1,54 +0,0 @@
1
- import { type Attrs, type Node } from "prosemirror-model";
2
- import { type SuggestionId } from "../../generateId.js";
3
- import { DOC_NODE_ID } from "./constants.js";
4
- interface NodeParent {
5
- nodeId: string;
6
- nodeType: string;
7
- nodeAttrs: object;
8
- nodeMarks: object[];
9
- childSiblingIds: [string | null, string | null];
10
- childIndex: number;
11
- }
12
- interface StructureMoveMarkAttrs {
13
- id: SuggestionId;
14
- data: {
15
- op: MoveOp;
16
- };
17
- }
18
- interface StructureAddMarkAttrs {
19
- id: SuggestionId;
20
- data: {
21
- op: AddOp;
22
- };
23
- }
24
- export type StructureMarkAttrs = StructureMoveMarkAttrs | StructureAddMarkAttrs;
25
- export type StructuralContextPath = readonly [string, ...string[]];
26
- export interface DocParent extends Omit<NodeParent, "nodeId"> {
27
- nodeId: typeof DOC_NODE_ID;
28
- nodeType: typeof DOC_NODE_ID;
29
- }
30
- export type Parent = NodeParent | DocParent;
31
- export interface MoveOp {
32
- op: "move";
33
- from: Parent[];
34
- to: Parent[];
35
- }
36
- export interface AddOp {
37
- op: "add";
38
- }
39
- export type Op = MoveOp | AddOp;
40
- export type MaterializedPaths = Map<string, {
41
- chain: Parent[];
42
- nodeType: string;
43
- node: Node;
44
- }>;
45
- export declare function guardDocParent(parent: Parent | undefined): parent is DocParent;
46
- export declare function guardStructureMarkAttrs(attrs: Attrs): attrs is StructureMarkAttrs;
47
- export declare function guardStructureMoveMarkAttrs(attrs: Attrs): attrs is StructureMoveMarkAttrs;
48
- export declare function guardStructureAddMarkAttrs(attrs: Attrs): attrs is StructureAddMarkAttrs;
49
- export interface StructureMarkObject {
50
- type: "structure";
51
- attrs: StructureMarkAttrs;
52
- }
53
- export declare function guardStructureMarkObject(mark: unknown): mark is StructureMarkObject;
54
- export {};
@@ -1,23 +0,0 @@
1
- import { DOC_NODE_ID } from "./constants.js";
2
- export function guardDocParent(parent) {
3
- return parent != null && parent.nodeId === DOC_NODE_ID && parent.nodeType === DOC_NODE_ID;
4
- }
5
- export function guardStructureMarkAttrs(attrs) {
6
- if (!("data" in attrs)) return false;
7
- const data = attrs["data"];
8
- if (data === null || typeof data !== "object") return false;
9
- if (!("op" in data)) return false;
10
- return true;
11
- }
12
- export function guardStructureMoveMarkAttrs(attrs) {
13
- return guardStructureMarkAttrs(attrs) && attrs.data.op.op === "move";
14
- }
15
- export function guardStructureAddMarkAttrs(attrs) {
16
- return guardStructureMarkAttrs(attrs) && attrs.data.op.op === "add";
17
- }
18
- export function guardStructureMarkObject(mark) {
19
- if (mark === null || typeof mark !== "object") return false;
20
- if (!("type" in mark) || mark.type !== "structure") return false;
21
- if (!("attrs" in mark)) return false;
22
- return guardStructureMarkAttrs(mark.attrs);
23
- }
@@ -1,17 +0,0 @@
1
- import { type Node } from "prosemirror-model";
2
- import { Plugin, PluginKey, type Transaction } from "prosemirror-state";
3
- import { Transform } from "prosemirror-transform";
4
- export declare const uniqueNodeIdsPluginKey: PluginKey<{
5
- completedInitialRun: boolean;
6
- }>;
7
- export declare const UNIQUE_NODE_IDS_PLUGIN_META = "unique-node-ids-plugin";
8
- export declare function uniqueNodeIdsPlugin({ attributeName, generateID, }: {
9
- attributeName: string;
10
- generateID: () => string;
11
- }): Plugin<{
12
- completedInitialRun: boolean;
13
- }>;
14
- export declare function ensureUniqueNodeIds(_transactions: Transaction[], _oldDoc: Node, newDoc: Node, options: {
15
- attributeName: string;
16
- generateID: () => string;
17
- }): Transform;