@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.
- package/dist/__tests__/playwrightHelpers.d.ts +2 -2
- package/dist/__tests__/playwrightPage.d.ts +53 -2
- package/dist/commands.js +222 -43
- package/dist/{ensureSelectionPlugin.js → features/ensureValidSelection/ensureSelectionPlugin.js} +44 -77
- package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.d.ts +1 -0
- package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.js +112 -0
- package/dist/features/ensureValidSelection/selectionPosition.d.ts +3 -0
- package/dist/features/ensureValidSelection/selectionPosition.js +50 -0
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/index.d.ts +4 -19
- package/dist/features/joinOnDelete/index.js +166 -53
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +6 -0
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +24 -0
- package/dist/features/joinOnDelete/types.d.ts +36 -0
- package/dist/features/joinOnDelete/types.js +23 -0
- package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +3 -0
- package/dist/features/transactionShaping/detectSpecialTransactionShape.js +4 -0
- package/dist/features/transactionShaping/index.d.ts +3 -0
- package/dist/features/transactionShaping/index.js +11 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +3 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +48 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +1 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +188 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +3 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +69 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +2 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +2 -0
- package/dist/features/transactionShaping/types.d.ts +20 -0
- package/dist/features/transactionShaping/types.js +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/addIdAttr.d.ts +2 -0
- package/dist/features/wrapUnwrap/addIdAttr.js +60 -0
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +10 -0
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +41 -0
- package/dist/features/wrapUnwrap/apply/index.d.ts +5 -0
- package/dist/features/wrapUnwrap/apply/index.js +21 -0
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +2 -0
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +17 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +3 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +82 -0
- package/dist/features/wrapUnwrap/constants.d.ts +3 -0
- package/dist/features/wrapUnwrap/constants.js +3 -0
- package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +1 -0
- package/dist/features/wrapUnwrap/generateUniqueNodeId.js +6 -0
- package/dist/features/wrapUnwrap/getNodeId.d.ts +2 -0
- package/dist/features/wrapUnwrap/getNodeId.js +5 -0
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +3 -0
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +37 -0
- package/dist/features/wrapUnwrap/revert/index.d.ts +5 -0
- package/dist/features/wrapUnwrap/revert/index.js +19 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +4 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.js +4 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +16 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +122 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +10 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +236 -0
- package/dist/features/wrapUnwrap/sameParentChain.d.ts +2 -0
- package/dist/features/wrapUnwrap/sameParentChain.js +4 -0
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +17 -0
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +299 -0
- package/dist/features/wrapUnwrap/types.d.ts +54 -0
- package/dist/features/wrapUnwrap/types.js +23 -0
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +17 -0
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +106 -0
- package/dist/generateId.js +31 -4
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/listInputRules.d.ts +2 -0
- package/dist/listInputRules.js +14 -0
- package/dist/rebaseStep.d.ts +9 -0
- package/dist/rebaseStep.js +11 -0
- package/dist/replaceStep.d.ts +1 -1
- package/dist/replaceStep.js +1 -0
- package/dist/schema.d.ts +2 -1
- package/dist/schema.js +37 -1
- package/dist/testing/e2eTestSchema.d.ts +2 -0
- package/dist/testing/testBuilders.d.ts +1 -2
- package/dist/transformToSuggestionTransaction.d.ts +22 -0
- package/dist/transformToSuggestionTransaction.js +101 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +6 -2
- package/dist/withSuggestChanges.d.ts +11 -14
- package/dist/withSuggestChanges.js +64 -94
- package/dist/wrappingInputRule.d.ts +4 -0
- package/dist/wrappingInputRule.js +28 -0
- package/package.json +1 -1
- package/src/features/joinOnDelete/README.md +0 -8
- /package/dist/{ensureSelectionPlugin.d.ts → features/ensureValidSelection/ensureSelectionPlugin.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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,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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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,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 @@
|
|
|
1
|
+
export declare function generateUniqueNodeId(): string;
|
|
@@ -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;
|