@magic-marker/prosemirror-suggest-changes 0.4.0 → 0.4.1-wrap-unwrap.2

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 (94) hide show
  1. package/dist/__tests__/playwrightHelpers.d.ts +2 -2
  2. package/dist/__tests__/playwrightPage.d.ts +53 -2
  3. package/dist/commands.js +222 -43
  4. package/dist/{ensureSelectionPlugin.js → features/ensureValidSelection/ensureSelectionPlugin.js} +44 -77
  5. package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.d.ts +1 -0
  6. package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.js +112 -0
  7. package/dist/features/ensureValidSelection/selectionPosition.d.ts +3 -0
  8. package/dist/features/ensureValidSelection/selectionPosition.js +50 -0
  9. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +1 -0
  10. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +1 -0
  11. package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +1 -0
  12. package/dist/features/joinOnDelete/index.d.ts +4 -19
  13. package/dist/features/joinOnDelete/index.js +166 -53
  14. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +6 -0
  15. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +24 -0
  16. package/dist/features/joinOnDelete/types.d.ts +36 -0
  17. package/dist/features/joinOnDelete/types.js +23 -0
  18. package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +3 -0
  19. package/dist/features/transactionShaping/detectSpecialTransactionShape.js +4 -0
  20. package/dist/features/transactionShaping/index.d.ts +3 -0
  21. package/dist/features/transactionShaping/index.js +11 -0
  22. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +3 -0
  23. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +48 -0
  24. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +1 -0
  25. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +188 -0
  26. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +3 -0
  27. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +69 -0
  28. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +2 -0
  29. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +2 -0
  30. package/dist/features/transactionShaping/types.d.ts +20 -0
  31. package/dist/features/transactionShaping/types.js +1 -0
  32. package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
  33. package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +1 -0
  34. package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +1 -0
  35. package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +1 -0
  36. package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +1 -0
  37. package/dist/features/wrapUnwrap/addIdAttr.d.ts +2 -0
  38. package/dist/features/wrapUnwrap/addIdAttr.js +60 -0
  39. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +10 -0
  40. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +41 -0
  41. package/dist/features/wrapUnwrap/apply/index.d.ts +5 -0
  42. package/dist/features/wrapUnwrap/apply/index.js +21 -0
  43. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +2 -0
  44. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +17 -0
  45. package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +3 -0
  46. package/dist/features/wrapUnwrap/buildMaterializedPaths.js +82 -0
  47. package/dist/features/wrapUnwrap/constants.d.ts +3 -0
  48. package/dist/features/wrapUnwrap/constants.js +3 -0
  49. package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +1 -0
  50. package/dist/features/wrapUnwrap/generateUniqueNodeId.js +6 -0
  51. package/dist/features/wrapUnwrap/getNodeId.d.ts +2 -0
  52. package/dist/features/wrapUnwrap/getNodeId.js +5 -0
  53. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +3 -0
  54. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +37 -0
  55. package/dist/features/wrapUnwrap/revert/index.d.ts +5 -0
  56. package/dist/features/wrapUnwrap/revert/index.js +19 -0
  57. package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +4 -0
  58. package/dist/features/wrapUnwrap/revert/revertAddOp.js +4 -0
  59. package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +16 -0
  60. package/dist/features/wrapUnwrap/revert/revertMoveOp.js +122 -0
  61. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +10 -0
  62. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +236 -0
  63. package/dist/features/wrapUnwrap/sameParentChain.d.ts +2 -0
  64. package/dist/features/wrapUnwrap/sameParentChain.js +4 -0
  65. package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +17 -0
  66. package/dist/features/wrapUnwrap/structureChangesPlugin.js +299 -0
  67. package/dist/features/wrapUnwrap/types.d.ts +54 -0
  68. package/dist/features/wrapUnwrap/types.js +23 -0
  69. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +17 -0
  70. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +106 -0
  71. package/dist/generateId.js +31 -4
  72. package/dist/index.d.ts +6 -3
  73. package/dist/index.js +5 -3
  74. package/dist/listInputRules.d.ts +2 -0
  75. package/dist/listInputRules.js +14 -0
  76. package/dist/rebaseStep.d.ts +9 -0
  77. package/dist/rebaseStep.js +11 -0
  78. package/dist/replaceStep.d.ts +1 -1
  79. package/dist/replaceStep.js +1 -0
  80. package/dist/schema.d.ts +2 -1
  81. package/dist/schema.js +37 -1
  82. package/dist/testing/e2eTestSchema.d.ts +2 -0
  83. package/dist/testing/testBuilders.d.ts +1 -2
  84. package/dist/transformToSuggestionTransaction.d.ts +22 -0
  85. package/dist/transformToSuggestionTransaction.js +101 -0
  86. package/dist/utils.d.ts +1 -0
  87. package/dist/utils.js +6 -2
  88. package/dist/withSuggestChanges.d.ts +11 -14
  89. package/dist/withSuggestChanges.js +64 -94
  90. package/dist/wrappingInputRule.d.ts +4 -0
  91. package/dist/wrappingInputRule.js +28 -0
  92. package/package.json +1 -1
  93. package/src/features/joinOnDelete/README.md +0 -8
  94. /package/dist/{ensureSelectionPlugin.d.ts → features/ensureValidSelection/ensureSelectionPlugin.d.ts} +0 -0
@@ -0,0 +1,188 @@
1
+ import { EditorState } from "prosemirror-state";
2
+ import { Step } from "prosemirror-transform";
3
+ import { describe, expect, it } from "vitest";
4
+ import { createSchema } from "../../../testing/e2eTestSchema.js";
5
+ import { detectTipTapParagraphIntoListJoin } from "./detectTipTapParagraphIntoListJoin.js";
6
+ const schema = createSchema();
7
+ // when joining a paragraph into a list above it,
8
+ // TipTap List extension overrides the default ProseMirror behavior
9
+ // by default, ProseMirror puts the paragraph to the end of list as a separate list item
10
+ // TipTap instead joins the paragraph with the paragraph of the last list item
11
+ const TIPTAP_PARAGRAPH_INTO_LIST_STEPS = [
12
+ // first step deletes the paragraph that we're joining
13
+ {
14
+ stepType: "replace",
15
+ from: 42,
16
+ to: 60
17
+ },
18
+ // second step inserts that paragraph into the last list item
19
+ {
20
+ stepType: "replace",
21
+ from: 40,
22
+ to: 40,
23
+ slice: {
24
+ content: [
25
+ {
26
+ type: "paragraph",
27
+ attrs: {
28
+ id: "node-9",
29
+ textAlign: null
30
+ },
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: "sample paragraph"
35
+ }
36
+ ]
37
+ }
38
+ ]
39
+ }
40
+ },
41
+ // third step joins the two paragraphs in the list item
42
+ {
43
+ stepType: "replace",
44
+ from: 39,
45
+ to: 41,
46
+ structure: true
47
+ }
48
+ ];
49
+ const TIPTAP_PARAGRAPH_INTO_LIST_DOC = {
50
+ type: "doc",
51
+ content: [
52
+ {
53
+ type: "orderedList",
54
+ attrs: {
55
+ order: 1,
56
+ id: "node-0"
57
+ },
58
+ content: [
59
+ {
60
+ type: "listItem",
61
+ attrs: {
62
+ id: "node-1"
63
+ },
64
+ content: [
65
+ {
66
+ type: "paragraph",
67
+ attrs: {
68
+ id: "node-2"
69
+ },
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: "Item 1"
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ },
79
+ {
80
+ type: "listItem",
81
+ attrs: {
82
+ id: "node-3"
83
+ },
84
+ content: [
85
+ {
86
+ type: "paragraph",
87
+ attrs: {
88
+ id: "node-4"
89
+ },
90
+ content: [
91
+ {
92
+ type: "text",
93
+ text: "Item 2"
94
+ }
95
+ ]
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ type: "listItem",
101
+ attrs: {
102
+ id: "node-5"
103
+ },
104
+ content: [
105
+ {
106
+ type: "paragraph",
107
+ attrs: {
108
+ id: "node-6"
109
+ },
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: "Item 3"
114
+ }
115
+ ]
116
+ }
117
+ ]
118
+ },
119
+ {
120
+ type: "listItem",
121
+ attrs: {
122
+ id: "node-7"
123
+ },
124
+ content: [
125
+ {
126
+ type: "paragraph",
127
+ attrs: {
128
+ id: "node-8"
129
+ },
130
+ content: [
131
+ {
132
+ type: "text",
133
+ text: "Item 4"
134
+ }
135
+ ]
136
+ }
137
+ ]
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ type: "paragraph",
143
+ attrs: {
144
+ id: "node-9"
145
+ },
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: "sample paragraph"
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ };
155
+ describe("detectTipTapParagraphIntoListJoin", ()=>{
156
+ it("detects TipTap's three-step paragraph-into-list join shape", ()=>{
157
+ const doc = schema.nodeFromJSON(TIPTAP_PARAGRAPH_INTO_LIST_DOC);
158
+ const state = EditorState.create({
159
+ doc
160
+ });
161
+ const transaction = state.tr;
162
+ TIPTAP_PARAGRAPH_INTO_LIST_STEPS.forEach((stepJSON)=>{
163
+ transaction.step(Step.fromJSON(schema, stepJSON));
164
+ });
165
+ const shape = detectTipTapParagraphIntoListJoin(transaction);
166
+ expect(shape).toMatchObject({
167
+ type: "tipTapParagraphIntoListJoin",
168
+ deleteStep: {
169
+ from: 42,
170
+ to: 60
171
+ },
172
+ insertStep: {
173
+ from: 40,
174
+ to: 40
175
+ },
176
+ joinStep: {
177
+ from: 39,
178
+ to: 41
179
+ },
180
+ movedNode: {
181
+ attrs: {
182
+ id: "node-9"
183
+ }
184
+ }
185
+ });
186
+ expect(shape?.movedNode.textContent).toBe("sample paragraph");
187
+ });
188
+ });
@@ -0,0 +1,3 @@
1
+ import { type Transaction } from "prosemirror-state";
2
+ import { type HandleSpecialTransactionShapeArgs } from "../types.js";
3
+ export declare function handleTipTapParagraphIntoListJoin(args: HandleSpecialTransactionShapeArgs): Transaction | null;
@@ -0,0 +1,69 @@
1
+ import { EditorState, Selection } from "prosemirror-state";
2
+ import { preserveTransactionData, transformToSuggestionTransaction } from "../../../transformToSuggestionTransaction.js";
3
+ import { generateNextNumberId } from "../../../generateId.js";
4
+ import { suggestStructureChanges } from "../../wrapUnwrap/structureChangesPlugin.js";
5
+ import { detectSpecialTransactionShape } from "../detectSpecialTransactionShape.js";
6
+ // handle the specific TipTap pattern when backspacing from a paragraph into a list above
7
+ // causes a transaction with 3 steps
8
+ // "slice" the transaction into two pieces, first piece is the first 2 steps, second piece is the last step
9
+ // first piece "moves" the paragraph - handled by structure change tracking - a structure "move" suggestion
10
+ // second piece joins two paragraphs - handled by the main plugin join-on-delete feature - deletion type="join" mark
11
+ export function handleTipTapParagraphIntoListJoin(args) {
12
+ const shape = detectSpecialTransactionShape(args.transaction);
13
+ if (!isTipTapParagraphIntoListJoinShape(shape)) return null;
14
+ if (!args.structuralContextPaths || !args.ensureUniqueNodeIds) return null;
15
+ const docBefore = args.transaction.docs[0];
16
+ if (!docBefore) return null;
17
+ const trackedTransaction = args.state.tr;
18
+ const sharedSuggestionId = args.generateId ? args.generateId(args.state.schema, docBefore) : generateNextNumberId(args.state.schema, docBefore);
19
+ const generateSharedSuggestionId = ()=>sharedSuggestionId;
20
+ try {
21
+ trackedTransaction.step(shape.deleteStep);
22
+ trackedTransaction.step(shape.insertStep);
23
+ } catch {
24
+ return null;
25
+ }
26
+ const uniqueNodeIdsTransform = args.ensureUniqueNodeIds([
27
+ args.transaction
28
+ ], docBefore, trackedTransaction.doc);
29
+ uniqueNodeIdsTransform.steps.forEach((step)=>{
30
+ trackedTransaction.step(step);
31
+ });
32
+ const structureChangesResult = suggestStructureChanges(docBefore, uniqueNodeIdsTransform.doc, args.structuralContextPaths, generateSharedSuggestionId);
33
+ if (!structureChangesResult.handled) {
34
+ return null;
35
+ }
36
+ structureChangesResult.transform.steps.forEach((step)=>{
37
+ trackedTransaction.step(step);
38
+ });
39
+ const intermediateState = EditorState.create({
40
+ schema: args.state.schema,
41
+ doc: trackedTransaction.doc
42
+ });
43
+ const joinTransaction = intermediateState.tr;
44
+ try {
45
+ joinTransaction.step(shape.joinStep);
46
+ } catch {
47
+ return null;
48
+ }
49
+ const trackedJoinTransaction = transformToSuggestionTransaction(joinTransaction, intermediateState, generateSharedSuggestionId);
50
+ trackedJoinTransaction.steps.forEach((step)=>{
51
+ trackedTransaction.step(step);
52
+ });
53
+ preserveTransactionData(trackedTransaction, trackedJoinTransaction, {
54
+ selection: false,
55
+ preserveScroll: false,
56
+ preserveStoredMarks: false,
57
+ preserveMeta: false
58
+ });
59
+ preserveTransactionData(trackedTransaction, args.transaction, {
60
+ selection: false
61
+ });
62
+ if (args.transaction.selectionSet) {
63
+ trackedTransaction.setSelection(Selection.fromJSON(trackedTransaction.doc, args.transaction.selection.toJSON()));
64
+ }
65
+ return trackedTransaction;
66
+ }
67
+ function isTipTapParagraphIntoListJoinShape(shape) {
68
+ return shape?.type === "tipTapParagraphIntoListJoin";
69
+ }
@@ -0,0 +1,2 @@
1
+ export { detectTipTapParagraphIntoListJoin } from "./detectTipTapParagraphIntoListJoin.js";
2
+ export { handleTipTapParagraphIntoListJoin } from "./handleTipTapParagraphIntoListJoin.js";
@@ -0,0 +1,2 @@
1
+ export { detectTipTapParagraphIntoListJoin } from "./detectTipTapParagraphIntoListJoin.js";
2
+ export { handleTipTapParagraphIntoListJoin } from "./handleTipTapParagraphIntoListJoin.js";
@@ -0,0 +1,20 @@
1
+ import { type Node, type Schema } from "prosemirror-model";
2
+ import { type EditorState, type Transaction } from "prosemirror-state";
3
+ import { type ReplaceStep, type Transform } from "prosemirror-transform";
4
+ import { type SuggestionId } from "../../generateId.js";
5
+ import { type StructuralContextPath } from "../wrapUnwrap/types.js";
6
+ export interface TipTapParagraphIntoListJoinShape {
7
+ type: "tipTapParagraphIntoListJoin";
8
+ deleteStep: ReplaceStep;
9
+ insertStep: ReplaceStep;
10
+ joinStep: ReplaceStep;
11
+ movedNode: Node;
12
+ }
13
+ export type SpecialTransactionShape = TipTapParagraphIntoListJoinShape;
14
+ export interface HandleSpecialTransactionShapeArgs {
15
+ transaction: Transaction;
16
+ state: EditorState;
17
+ generateId: ((schema: Schema, doc?: Node) => SuggestionId) | undefined;
18
+ structuralContextPaths: StructuralContextPath[] | null;
19
+ ensureUniqueNodeIds: ((transactions: Transaction[], oldDoc: Node, newDoc: Node) => Transform) | undefined;
20
+ }
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,2 @@
1
+ import { type NodeSpec } from "prosemirror-model";
2
+ export declare const addIdAttr: (nodeSpec: NodeSpec) => NodeSpec;
@@ -0,0 +1,60 @@
1
+ export const addIdAttr = (nodeSpec)=>{
2
+ const { toDOM, parseDOM } = nodeSpec;
3
+ if (!toDOM || !parseDOM) {
4
+ return nodeSpec;
5
+ }
6
+ const newNodeSpec = {
7
+ ...nodeSpec,
8
+ attrs: {
9
+ ...nodeSpec.attrs ?? {},
10
+ id: {
11
+ default: null
12
+ }
13
+ },
14
+ toDOM (node) {
15
+ const domOutputSpec = toDOM(node);
16
+ if (!Array.isArray(domOutputSpec)) {
17
+ console.warn("addIdAttr", "domOutputSpec is not an array, id is not added", domOutputSpec);
18
+ return domOutputSpec;
19
+ }
20
+ const id = typeof node.attrs["id"] === "string" ? node.attrs["id"] : null;
21
+ const attrs = domOutputSpec[1];
22
+ if (!Array.isArray(attrs) && typeof attrs === "object" && attrs !== null) {
23
+ const theRest = domOutputSpec.slice(2);
24
+ const result = [
25
+ domOutputSpec[0],
26
+ id ? {
27
+ ...attrs,
28
+ "data-id": id
29
+ } : attrs,
30
+ ...theRest
31
+ ];
32
+ return result;
33
+ }
34
+ const theRest = domOutputSpec.slice(1);
35
+ const result = [
36
+ domOutputSpec[0],
37
+ id ? {
38
+ "data-id": id
39
+ } : {},
40
+ ...theRest
41
+ ];
42
+ return result;
43
+ },
44
+ parseDOM: [
45
+ ...parseDOM.map((tagParseRule)=>({
46
+ ...tagParseRule,
47
+ getAttrs (dom) {
48
+ const id = dom.getAttribute("data-id");
49
+ return tagParseRule.getAttrs ? {
50
+ ...tagParseRule.getAttrs(dom),
51
+ id
52
+ } : {
53
+ id
54
+ };
55
+ }
56
+ }))
57
+ ]
58
+ };
59
+ return newNodeSpec;
60
+ };
@@ -0,0 +1,10 @@
1
+ import { type Node } from "prosemirror-model";
2
+ import { type Transform } from "prosemirror-transform";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to, }: {
5
+ tr: Transform;
6
+ node: Node;
7
+ suggestionId?: SuggestionId;
8
+ from?: number | undefined;
9
+ to?: number | undefined;
10
+ }): void;
@@ -0,0 +1,41 @@
1
+ import { getSuggestionMarks } from "../../../utils.js";
2
+ export function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to }) {
3
+ const { structure } = getSuggestionMarks(node.type.schema);
4
+ const suggestionIds = new Set();
5
+ node.descendants((node, pos)=>{
6
+ if (from !== undefined && pos < from) {
7
+ return true;
8
+ }
9
+ if (to !== undefined && pos > to) {
10
+ return false;
11
+ }
12
+ if (node.isText) return true;
13
+ if (!structure.isInSet(node.marks)) return true;
14
+ node.marks.forEach((mark)=>{
15
+ if (mark.type !== structure) return;
16
+ const markSuggestionId = mark.attrs["id"];
17
+ if (suggestionId != null && markSuggestionId !== suggestionId) return;
18
+ suggestionIds.add(markSuggestionId);
19
+ });
20
+ return true;
21
+ });
22
+ for (const suggestionId of suggestionIds){
23
+ applyOneStructureSuggestion(tr, suggestionId);
24
+ }
25
+ }
26
+ function applyOneStructureSuggestion(tr, suggestionId) {
27
+ const { structure } = getSuggestionMarks(tr.doc.type.schema);
28
+ tr.doc.descendants((node, pos)=>{
29
+ if (node.isText) return true;
30
+ if (!structure.isInSet(node.marks)) return true;
31
+ node.marks.forEach((mark)=>{
32
+ if (mark.type !== structure) return;
33
+ if (mark.attrs["id"] !== suggestionId) return;
34
+ applyStructureMark(tr, mark, pos);
35
+ });
36
+ return true;
37
+ });
38
+ }
39
+ function applyStructureMark(tr, mark, pos) {
40
+ tr.removeNodeMark(pos, mark);
41
+ }
@@ -0,0 +1,5 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function applyAllStructureSuggestions(node: Node, from?: number, to?: number): Transform;
5
+ export declare function applyStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
@@ -0,0 +1,21 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { applyStructureSuggestionsInNode } from "./applyStructureSuggestions.js";
3
+ export function applyAllStructureSuggestions(node, from, to) {
4
+ const tr = new Transform(node);
5
+ applyStructureSuggestionsInNode({
6
+ tr,
7
+ node: tr.doc,
8
+ from,
9
+ to
10
+ });
11
+ return tr;
12
+ }
13
+ export function applyStructureSuggestion(node, suggestionId) {
14
+ const tr = new Transform(node);
15
+ applyStructureSuggestionsInNode({
16
+ tr,
17
+ node: tr.doc,
18
+ suggestionId
19
+ });
20
+ return tr;
21
+ }
@@ -0,0 +1,2 @@
1
+ import { type Mark } from "prosemirror-model";
2
+ export declare function areEquivalentStructureMarks(a: Mark, b: Mark): boolean;
@@ -0,0 +1,17 @@
1
+ import { sameParentChain } from "./sameParentChain.js";
2
+ import { guardStructureMarkAttrs } from "./types.js";
3
+ export function areEquivalentStructureMarks(a, b) {
4
+ if (a.type !== b.type) return false;
5
+ if (!guardStructureMarkAttrs(a.attrs)) return false;
6
+ if (!guardStructureMarkAttrs(b.attrs)) return false;
7
+ const aOp = a.attrs.data.op;
8
+ const bOp = b.attrs.data.op;
9
+ if (aOp.op !== bOp.op) return false;
10
+ if (aOp.op === "add" && bOp.op === "add") {
11
+ return a.attrs.id === b.attrs.id;
12
+ }
13
+ if (aOp.op === "move" && bOp.op === "move") {
14
+ return sameParentChain(aOp.from, bOp.from) && sameParentChain(aOp.to, bOp.to);
15
+ }
16
+ return false;
17
+ }
@@ -0,0 +1,3 @@
1
+ import { type MaterializedPaths } from "./types.js";
2
+ import { type Node } from "prosemirror-model";
3
+ export declare function buildMaterializedPaths(doc: Node): MaterializedPaths;
@@ -0,0 +1,82 @@
1
+ import { getNodeId } from "./getNodeId.js";
2
+ import { DOC_NODE_ID } from "./constants.js";
3
+ const TRACE_ENABLED = false;
4
+ function trace(...args) {
5
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
6
+ if (!TRACE_ENABLED) return;
7
+ console.log("[structureChanges]", ...args);
8
+ }
9
+ export function buildMaterializedPaths(doc) {
10
+ const perfPaths = performance.now();
11
+ const paths = new Map();
12
+ // add direct doc children first since they have a specific parent signature due to doc not having an ID
13
+ doc.children.forEach((node, index, children)=>{
14
+ const nodeId = getNodeId(node);
15
+ if (nodeId == null) return;
16
+ const leftSibling = children[index - 1];
17
+ const leftSiblingId = leftSibling ? getNodeId(leftSibling) : null;
18
+ const rightSibling = children[index + 1];
19
+ const rightSiblingId = rightSibling ? getNodeId(rightSibling) : null;
20
+ const parent = {
21
+ nodeId: DOC_NODE_ID,
22
+ nodeType: DOC_NODE_ID,
23
+ nodeAttrs: {},
24
+ nodeMarks: [],
25
+ childSiblingIds: [
26
+ leftSiblingId,
27
+ rightSiblingId
28
+ ],
29
+ childIndex: index
30
+ };
31
+ paths.set(nodeId, {
32
+ nodeType: node.type.name,
33
+ node,
34
+ chain: [
35
+ parent
36
+ ]
37
+ });
38
+ });
39
+ // now add the rest of the nodes
40
+ doc.descendants((node, _pos, parent, childIndex)=>{
41
+ if (node.isInline) return false;
42
+ const nodeId = getNodeId(node);
43
+ if (nodeId == null) return true;
44
+ // this is to avoid processing direct doc children twice
45
+ if (paths.has(nodeId)) return true;
46
+ if (parent == null) return true;
47
+ const parentId = getNodeId(parent);
48
+ if (parentId == null) return true;
49
+ // by definition, for any node it's parent chain should already exist
50
+ // because we go downwards
51
+ const parentChain = paths.get(parentId);
52
+ if (parentChain == null) return true;
53
+ const leftSibling = parent.children[childIndex - 1];
54
+ const leftSiblingId = leftSibling ? getNodeId(leftSibling) : null;
55
+ const rightSibling = parent.children[childIndex + 1];
56
+ const rightSiblingId = rightSibling ? getNodeId(rightSibling) : null;
57
+ // (this node parent chain) is (parent chain of the parent node) + (the parent node itself)
58
+ const parentDesc = {
59
+ nodeId: parentId,
60
+ nodeType: parent.type.name,
61
+ nodeAttrs: parent.attrs,
62
+ nodeMarks: parent.marks.map((mark)=>mark.toJSON()),
63
+ childSiblingIds: [
64
+ leftSiblingId,
65
+ rightSiblingId
66
+ ],
67
+ childIndex: childIndex
68
+ };
69
+ const chain = [
70
+ parentDesc,
71
+ ...parentChain.chain
72
+ ];
73
+ paths.set(nodeId, {
74
+ nodeType: node.type.name,
75
+ node,
76
+ chain
77
+ });
78
+ return true;
79
+ });
80
+ trace("perf", "buildMaterializedPaths", "took", Number((performance.now() - perfPaths).toFixed(2)), "ms");
81
+ return paths;
82
+ }
@@ -0,0 +1,3 @@
1
+ export declare const DOC_NODE_ID = "__doc__";
2
+ export declare const STRUCTURE_CHANGES_ADD_MARKS = "structure-changes-add-mark-type";
3
+ export declare const STRUCTURE_CHANGES_REVERT_MARKS = "structure-changes-revert-mark-type";
@@ -0,0 +1,3 @@
1
+ export const DOC_NODE_ID = "__doc__";
2
+ export const STRUCTURE_CHANGES_ADD_MARKS = "structure-changes-add-mark-type";
3
+ export const STRUCTURE_CHANGES_REVERT_MARKS = "structure-changes-revert-mark-type";
@@ -0,0 +1 @@
1
+ export declare function generateUniqueNodeId(): string;
@@ -0,0 +1,6 @@
1
+ export function generateUniqueNodeId() {
2
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3
+ return crypto.randomUUID();
4
+ }
5
+ return `node-${Math.random().toString(36).slice(2)}`;
6
+ }
@@ -0,0 +1,2 @@
1
+ import { type Node } from "prosemirror-model";
2
+ export declare function getNodeId(node: Node): string | null;
@@ -0,0 +1,5 @@
1
+ export function getNodeId(node) {
2
+ const nodeId = node.attrs["id"];
3
+ if (typeof nodeId !== "string") return null;
4
+ return nodeId;
5
+ }
@@ -0,0 +1,3 @@
1
+ import { type Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ export declare function deleteNodeUpwards(transform: Transform, node: Node, pos: number): void;
@@ -0,0 +1,37 @@
1
+ import { getNodeId } from "../getNodeId.js";
2
+ const TRACE_ENABLED = false;
3
+ function trace(...args) {
4
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
5
+ if (!TRACE_ENABLED) return;
6
+ console.log("[deleteNodeUpwards]", "\n", ...args);
7
+ }
8
+ // delete a given node, and traverse upwards deleting parent nodes if they are now empty
9
+ export function deleteNodeUpwards(transform, node, pos) {
10
+ let $mappedPos = transform.doc.resolve(pos);
11
+ trace("attempting to delete node", node.type.name, getNodeId(node), "\n", "node subtree:\n", node.toString(), "\n", "node parent:", $mappedPos.parent.type.name, getNodeId($mappedPos.parent), "\n", {
12
+ node,
13
+ parent: $mappedPos.parent
14
+ });
15
+ let deleteFrom = $mappedPos.pos;
16
+ let deleteTo = $mappedPos.pos + node.nodeSize;
17
+ while($mappedPos.depth > 0){
18
+ const $nextMappedPos = transform.doc.resolve($mappedPos.before());
19
+ if ($nextMappedPos.nodeAfter?.childCount !== 1) {
20
+ trace("stopping deletion at non-empty node:", $nextMappedPos.nodeAfter?.type.name, $nextMappedPos.nodeAfter == null ? null : getNodeId($nextMappedPos.nodeAfter), "\n", "non-empty node parent:", $nextMappedPos.parent.type.name, getNodeId($nextMappedPos.parent), "\n", "non-empty node subtree:\n", $nextMappedPos.nodeAfter?.toString(), "\n", "non-empty node subtree direct children:\n", $nextMappedPos.nodeAfter?.children.map((child)=>`${child.type.name} ${getNodeId(child) ?? "null"}`).join(", "), {
21
+ nonEmptyNode: $nextMappedPos.nodeAfter,
22
+ nonEmptyNodeParent: $nextMappedPos.parent
23
+ });
24
+ break;
25
+ }
26
+ $mappedPos = $nextMappedPos;
27
+ deleteFrom = $nextMappedPos.pos;
28
+ deleteTo = $nextMappedPos.pos + $nextMappedPos.nodeAfter.nodeSize;
29
+ }
30
+ trace("deleting subtree:\n", $mappedPos.nodeAfter?.toString(), "\n", "subtree root node:", $mappedPos.nodeAfter?.type.name, $mappedPos.nodeAfter == null ? null : getNodeId($mappedPos.nodeAfter), "\n", "subtree root parent node:", $mappedPos.parent.type.name, getNodeId($mappedPos.parent), "\n", "non-empty node subtree direct children:\n", $mappedPos.nodeAfter?.children.map((child)=>`${child.type.name} (${getNodeId(child) ?? "null"}`).join(", "), "\n", {
31
+ node: $mappedPos.nodeAfter,
32
+ parent: $mappedPos.parent,
33
+ $deleteFrom: $mappedPos.doc.resolve(deleteFrom),
34
+ $deleteTo: $mappedPos.doc.resolve(deleteTo)
35
+ });
36
+ transform.delete(deleteFrom, deleteTo);
37
+ }
@@ -0,0 +1,5 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function revertAllStructureSuggestions(doc: Node, from?: number, to?: number): Transform;
5
+ export declare function revertStructureSuggestion(doc: Node, suggestionId: SuggestionId): Transform;