@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
package/dist/schema.js
CHANGED
|
@@ -166,6 +166,41 @@ export const modification = {
|
|
|
166
166
|
}
|
|
167
167
|
]
|
|
168
168
|
};
|
|
169
|
+
export const structure = {
|
|
170
|
+
inclusive: false,
|
|
171
|
+
excludes: "deletion insertion modification",
|
|
172
|
+
attrs: {
|
|
173
|
+
id: {
|
|
174
|
+
validate: suggestionIdValidate
|
|
175
|
+
},
|
|
176
|
+
data: {
|
|
177
|
+
default: null
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
toDOM (mark) {
|
|
181
|
+
return [
|
|
182
|
+
"div",
|
|
183
|
+
{
|
|
184
|
+
"data-type": "structure",
|
|
185
|
+
"data-id": JSON.stringify(mark.attrs["id"]),
|
|
186
|
+
"data-data": JSON.stringify(mark.attrs["data"])
|
|
187
|
+
},
|
|
188
|
+
0
|
|
189
|
+
];
|
|
190
|
+
},
|
|
191
|
+
parseDOM: [
|
|
192
|
+
{
|
|
193
|
+
tag: "div[data-type='structure']",
|
|
194
|
+
getAttrs (node) {
|
|
195
|
+
if (!node.dataset["id"]) return false;
|
|
196
|
+
return {
|
|
197
|
+
id: JSON.parse(node.dataset["id"]),
|
|
198
|
+
data: JSON.parse(node.dataset["data"] ?? "null")
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
};
|
|
169
204
|
/**
|
|
170
205
|
* Add the deletion, insertion, and modification marks to
|
|
171
206
|
* the provided MarkSpec map.
|
|
@@ -174,6 +209,7 @@ export const modification = {
|
|
|
174
209
|
...marks,
|
|
175
210
|
deletion: opts?.experimental_deletions === "hidden" ? hiddenDeletion : deletion,
|
|
176
211
|
insertion,
|
|
177
|
-
modification
|
|
212
|
+
modification,
|
|
213
|
+
structure
|
|
178
214
|
};
|
|
179
215
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Schema } from "prosemirror-model";
|
|
2
|
+
export declare function createSchema(deletionMarksVisibility?: "hidden" | "visible"): Schema<"blockquote" | "text" | "image" | "doc" | "orderedList" | "bulletList" | "listItem" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "hard_break" | "hardBreak", "insertion" | "deletion" | "modification" | "structure" | "code" | "em" | "link" | "strong">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema, type Node } from "prosemirror-model";
|
|
2
2
|
import { type MarkBuilder, type NodeBuilder } from "prosemirror-test-builder";
|
|
3
|
-
declare const schema: Schema<"blockquote" | "text" | "
|
|
3
|
+
export declare const schema: Schema<"blockquote" | "text" | "image" | "doc" | "orderedList" | "bulletList" | "listItem" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "hard_break", "insertion" | "deletion" | "modification" | "structure" | "code" | "em" | "link" | "strong" | "difficulty">;
|
|
4
4
|
export declare const testBuilders: { [NodeTypeName in keyof (typeof schema)["nodes"]]: NodeBuilder; } & { [MarkTypeName in keyof (typeof schema)["marks"]]: MarkBuilder; } & {
|
|
5
5
|
schema: typeof schema;
|
|
6
6
|
};
|
|
@@ -8,4 +8,3 @@ export type TaggedNode = Node & {
|
|
|
8
8
|
flat: Node;
|
|
9
9
|
tag: Record<string, number>;
|
|
10
10
|
};
|
|
11
|
-
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Node, type Schema } from "prosemirror-model";
|
|
2
|
+
import { type EditorState, type Transaction } from "prosemirror-state";
|
|
3
|
+
import { type SuggestionId } from "./generateId.js";
|
|
4
|
+
interface PreserveTransactionDataOptions {
|
|
5
|
+
selection?: "mapFromOriginalTransaction" | "currentDocument" | false;
|
|
6
|
+
preserveScroll?: boolean;
|
|
7
|
+
preserveStoredMarks?: boolean;
|
|
8
|
+
preserveMeta?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function preserveTransactionData(transaction: Transaction, originalTransaction: Transaction, options?: PreserveTransactionDataOptions): void;
|
|
11
|
+
/**
|
|
12
|
+
* Given a standard transaction from ProseMirror, produce
|
|
13
|
+
* a new transaction that tracks the changes from the original,
|
|
14
|
+
* rather than applying them.
|
|
15
|
+
*
|
|
16
|
+
* For each type of step, we implement custom behavior to prevent
|
|
17
|
+
* deletions from being removed from the document, instead adding
|
|
18
|
+
* deletion marks, and ensuring that all insertions have insertion
|
|
19
|
+
* marks.
|
|
20
|
+
*/
|
|
21
|
+
export declare function transformToSuggestionTransaction(originalTransaction: Transaction, state: EditorState, generateId?: (schema: Schema, doc?: Node) => SuggestionId): Transaction;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AddMarkStep, AddNodeMarkStep, AttrStep, Mapping, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep } from "prosemirror-transform";
|
|
2
|
+
import { trackAddMarkStep } from "./addMarkStep.js";
|
|
3
|
+
import { trackAddNodeMarkStep } from "./addNodeMarkStep.js";
|
|
4
|
+
import { trackAttrStep } from "./attrStep.js";
|
|
5
|
+
import { generateNextNumberId } from "./generateId.js";
|
|
6
|
+
import { suggestRemoveMarkStep } from "./removeMarkStep.js";
|
|
7
|
+
import { suggestRemoveNodeMarkStep } from "./removeNodeMarkStep.js";
|
|
8
|
+
import { suggestReplaceAroundStep } from "./replaceAroundStep.js";
|
|
9
|
+
import { suggestReplaceStep } from "./replaceStep.js";
|
|
10
|
+
import { getSuggestionMarks } from "./utils.js";
|
|
11
|
+
function getStepHandler(step) {
|
|
12
|
+
if (step instanceof ReplaceStep) {
|
|
13
|
+
return suggestReplaceStep;
|
|
14
|
+
}
|
|
15
|
+
if (step instanceof ReplaceAroundStep) {
|
|
16
|
+
return suggestReplaceAroundStep;
|
|
17
|
+
}
|
|
18
|
+
if (step instanceof AddMarkStep) {
|
|
19
|
+
return trackAddMarkStep;
|
|
20
|
+
}
|
|
21
|
+
if (step instanceof RemoveMarkStep) {
|
|
22
|
+
return suggestRemoveMarkStep;
|
|
23
|
+
}
|
|
24
|
+
if (step instanceof AddNodeMarkStep) {
|
|
25
|
+
return trackAddNodeMarkStep;
|
|
26
|
+
}
|
|
27
|
+
if (step instanceof RemoveNodeMarkStep) {
|
|
28
|
+
return suggestRemoveNodeMarkStep;
|
|
29
|
+
}
|
|
30
|
+
if (step instanceof AttrStep) {
|
|
31
|
+
return trackAttrStep;
|
|
32
|
+
}
|
|
33
|
+
// Default handler — simply rebase the step onto the
|
|
34
|
+
// tracked transaction and apply it.
|
|
35
|
+
return (trackedTransaction, _state, _doc, step, prevSteps)=>{
|
|
36
|
+
const reset = prevSteps.slice().reverse().reduce((acc, step)=>acc?.map(step.getMap().invert()) ?? null, step);
|
|
37
|
+
const rebased = trackedTransaction.steps.reduce((acc, step)=>acc?.map(step.getMap()) ?? null, reset);
|
|
38
|
+
if (rebased) {
|
|
39
|
+
trackedTransaction.step(rebased);
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function preserveTransactionData(transaction, originalTransaction, options = {}) {
|
|
45
|
+
const { selection = "mapFromOriginalTransaction", preserveScroll = true, preserveStoredMarks = true, preserveMeta = true } = options;
|
|
46
|
+
if (selection && originalTransaction.selectionSet && !transaction.selectionSet) {
|
|
47
|
+
if (selection === "currentDocument") {
|
|
48
|
+
transaction.setSelection(originalTransaction.selection.map(transaction.doc, new Mapping()));
|
|
49
|
+
} else {
|
|
50
|
+
const originalBaseDoc = originalTransaction.docs[0];
|
|
51
|
+
const base = originalBaseDoc ? originalTransaction.selection.map(originalBaseDoc, originalTransaction.mapping.invert()) : originalTransaction.selection;
|
|
52
|
+
transaction.setSelection(base.map(transaction.doc, transaction.mapping));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (preserveScroll && originalTransaction.scrolledIntoView) {
|
|
56
|
+
transaction.scrollIntoView();
|
|
57
|
+
}
|
|
58
|
+
if (preserveStoredMarks && originalTransaction.storedMarksSet) {
|
|
59
|
+
transaction.setStoredMarks(originalTransaction.storedMarks);
|
|
60
|
+
}
|
|
61
|
+
if (preserveMeta) {
|
|
62
|
+
// @ts-expect-error Preserve original transaction meta exactly as-is
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
64
|
+
transaction.meta = originalTransaction.meta;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Given a standard transaction from ProseMirror, produce
|
|
69
|
+
* a new transaction that tracks the changes from the original,
|
|
70
|
+
* rather than applying them.
|
|
71
|
+
*
|
|
72
|
+
* For each type of step, we implement custom behavior to prevent
|
|
73
|
+
* deletions from being removed from the document, instead adding
|
|
74
|
+
* deletion marks, and ensuring that all insertions have insertion
|
|
75
|
+
* marks.
|
|
76
|
+
*/ export function transformToSuggestionTransaction(originalTransaction, state, generateId) {
|
|
77
|
+
getSuggestionMarks(state.schema);
|
|
78
|
+
let suggestionId = generateId ? generateId(state.schema, originalTransaction.docs[0]) : generateNextNumberId(state.schema, originalTransaction.docs[0]);
|
|
79
|
+
// Create a new transaction from scratch. The original transaction
|
|
80
|
+
// is going to be dropped in favor of this one.
|
|
81
|
+
const trackedTransaction = state.tr;
|
|
82
|
+
for(let i = 0; i < originalTransaction.steps.length; i++){
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
84
|
+
const step = originalTransaction.steps[i];
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
86
|
+
const doc = originalTransaction.docs[i];
|
|
87
|
+
const stepTracker = getStepHandler(step);
|
|
88
|
+
if (stepTracker(trackedTransaction, state, doc, step, originalTransaction.steps.slice(0, i), suggestionId) && i < originalTransaction.steps.length - 1) {
|
|
89
|
+
// If the suggestionId was used by one of the step handlers,
|
|
90
|
+
// increment it so that it's not reused.
|
|
91
|
+
if (generateId) {
|
|
92
|
+
suggestionId = generateId(state.schema, trackedTransaction.doc);
|
|
93
|
+
} else if (typeof suggestionId === "number") {
|
|
94
|
+
suggestionId = suggestionId + 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
preserveTransactionData(trackedTransaction, originalTransaction);
|
|
100
|
+
return trackedTransaction;
|
|
101
|
+
}
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Get the suggestion mark types from a schema, with proper error handling.
|
|
3
3
|
* Throws an error if any of the required marks are not found.
|
|
4
4
|
*/ export function getSuggestionMarks(schema) {
|
|
5
|
-
const { insertion, deletion, modification } = schema.marks;
|
|
5
|
+
const { insertion, deletion, modification, structure } = schema.marks;
|
|
6
6
|
if (!insertion) {
|
|
7
7
|
throw new Error("Failed to find insertion mark in schema. Did you forget to add it?");
|
|
8
8
|
}
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
if (!modification) {
|
|
13
13
|
throw new Error("Failed to find modification mark in schema. Did you forget to add it?");
|
|
14
14
|
}
|
|
15
|
+
if (!structure) {
|
|
16
|
+
throw new Error("Failed to find structure mark in schema. Did you forget to add it?");
|
|
17
|
+
}
|
|
15
18
|
return {
|
|
16
19
|
insertion,
|
|
17
20
|
deletion,
|
|
18
|
-
modification
|
|
21
|
+
modification,
|
|
22
|
+
structure
|
|
19
23
|
};
|
|
20
24
|
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
1
|
+
import { type Node, type Schema } from "prosemirror-model";
|
|
2
|
+
import { type Transaction } from "prosemirror-state";
|
|
3
|
+
import { type Transform } from "prosemirror-transform";
|
|
3
4
|
import { type EditorView } from "prosemirror-view";
|
|
4
5
|
import { type SuggestionId } from "./generateId.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* rather than applying them.
|
|
9
|
-
*
|
|
10
|
-
* For each type of step, we implement custom behavior to prevent
|
|
11
|
-
* deletions from being removed from the document, instead adding
|
|
12
|
-
* deletion marks, and ensuring that all insertions have insertion
|
|
13
|
-
* marks.
|
|
14
|
-
*/
|
|
15
|
-
export declare function transformToSuggestionTransaction(originalTransaction: Transaction, state: EditorState, generateId?: (schema: Schema, doc?: Node) => SuggestionId): Transaction;
|
|
6
|
+
import { type StructuralContextPath } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export { transformToSuggestionTransaction } from "./transformToSuggestionTransaction.js";
|
|
8
|
+
export { suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
16
9
|
/**
|
|
17
10
|
* A `dispatchTransaction` decorator. Wrap your existing `dispatchTransaction`
|
|
18
11
|
* function with `withSuggestChanges`, or pass no arguments to use the default
|
|
@@ -24,4 +17,8 @@ export declare function transformToSuggestionTransaction(originalTransaction: Tr
|
|
|
24
17
|
* applying them, e.g. by marking a range with the deletion mark rather
|
|
25
18
|
* than removing it from the document.
|
|
26
19
|
*/
|
|
27
|
-
export declare function withSuggestChanges(dispatchTransaction?: EditorView["dispatch"], generateId?: (schema: Schema, doc?: Node) => SuggestionId
|
|
20
|
+
export declare function withSuggestChanges(dispatchTransaction?: EditorView["dispatch"], generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
21
|
+
experimental_trackStructureChanges?: boolean;
|
|
22
|
+
experimental_trackStructures?: StructuralContextPath[];
|
|
23
|
+
experimental_ensureUniqueNodeIds?: (transactions: Transaction[], oldDoc: Node, newDoc: Node) => Transform;
|
|
24
|
+
}): EditorView["dispatch"];
|
|
@@ -1,97 +1,15 @@
|
|
|
1
|
-
import { AddMarkStep, AddNodeMarkStep, AttrStep, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep } from "prosemirror-transform";
|
|
2
|
-
import { trackAddMarkStep } from "./addMarkStep.js";
|
|
3
|
-
import { trackAddNodeMarkStep } from "./addNodeMarkStep.js";
|
|
4
|
-
import { trackAttrStep } from "./attrStep.js";
|
|
5
|
-
import { suggestRemoveMarkStep } from "./removeMarkStep.js";
|
|
6
|
-
import { suggestRemoveNodeMarkStep } from "./removeNodeMarkStep.js";
|
|
7
|
-
import { suggestReplaceAroundStep } from "./replaceAroundStep.js";
|
|
8
|
-
import { suggestReplaceStep } from "./replaceStep.js";
|
|
9
1
|
import { isSuggestChangesEnabled, suggestChangesKey } from "./plugin.js";
|
|
10
|
-
import { generateNextNumberId } from "./generateId.js";
|
|
11
|
-
import { getSuggestionMarks } from "./utils.js";
|
|
12
2
|
import { prependDeletionsWithZWSP } from "./prependDeletionsWithZWSP.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (step instanceof RemoveMarkStep) {
|
|
24
|
-
return suggestRemoveMarkStep;
|
|
25
|
-
}
|
|
26
|
-
if (step instanceof AddNodeMarkStep) {
|
|
27
|
-
return trackAddNodeMarkStep;
|
|
28
|
-
}
|
|
29
|
-
if (step instanceof RemoveNodeMarkStep) {
|
|
30
|
-
return suggestRemoveNodeMarkStep;
|
|
31
|
-
}
|
|
32
|
-
if (step instanceof AttrStep) {
|
|
33
|
-
return trackAttrStep;
|
|
34
|
-
}
|
|
35
|
-
// Default handler — simply rebase the step onto the
|
|
36
|
-
// tracked transaction and apply it.
|
|
37
|
-
return (trackedTransaction, _state, _doc, step, prevSteps)=>{
|
|
38
|
-
const reset = prevSteps.slice().reverse().reduce((acc, step)=>acc?.map(step.getMap().invert()) ?? null, step);
|
|
39
|
-
const rebased = trackedTransaction.steps.reduce((acc, step)=>acc?.map(step.getMap()) ?? null, reset);
|
|
40
|
-
if (rebased) {
|
|
41
|
-
trackedTransaction.step(rebased);
|
|
42
|
-
}
|
|
43
|
-
return false;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Given a standard transaction from ProseMirror, produce
|
|
48
|
-
* a new transaction that tracks the changes from the original,
|
|
49
|
-
* rather than applying them.
|
|
50
|
-
*
|
|
51
|
-
* For each type of step, we implement custom behavior to prevent
|
|
52
|
-
* deletions from being removed from the document, instead adding
|
|
53
|
-
* deletion marks, and ensuring that all insertions have insertion
|
|
54
|
-
* marks.
|
|
55
|
-
*/ export function transformToSuggestionTransaction(originalTransaction, state, generateId) {
|
|
56
|
-
getSuggestionMarks(state.schema);
|
|
57
|
-
let suggestionId = generateId ? generateId(state.schema, originalTransaction.docs[0]) : generateNextNumberId(state.schema, originalTransaction.docs[0]);
|
|
58
|
-
// Create a new transaction from scratch. The original transaction
|
|
59
|
-
// is going to be dropped in favor of this one.
|
|
60
|
-
const trackedTransaction = state.tr;
|
|
61
|
-
for(let i = 0; i < originalTransaction.steps.length; i++){
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
63
|
-
const step = originalTransaction.steps[i];
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
65
|
-
const doc = originalTransaction.docs[i];
|
|
66
|
-
const stepTracker = getStepHandler(step);
|
|
67
|
-
if (stepTracker(trackedTransaction, state, doc, step, originalTransaction.steps.slice(0, i), suggestionId) && i < originalTransaction.steps.length - 1) {
|
|
68
|
-
// If the suggestionId was used by one of the step handlers,
|
|
69
|
-
// increment it so that it's not reused.
|
|
70
|
-
if (generateId) {
|
|
71
|
-
suggestionId = generateId(state.schema, trackedTransaction.doc);
|
|
72
|
-
} else if (typeof suggestionId === "number") {
|
|
73
|
-
suggestionId = suggestionId + 1;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (originalTransaction.selectionSet && !trackedTransaction.selectionSet) {
|
|
79
|
-
// Map the original selection backwards through the original transaction,
|
|
80
|
-
// and then forwards through the new one.
|
|
81
|
-
const originalBaseDoc = originalTransaction.docs[0];
|
|
82
|
-
const base = originalBaseDoc ? originalTransaction.selection.map(originalBaseDoc, originalTransaction.mapping.invert()) : originalTransaction.selection;
|
|
83
|
-
trackedTransaction.setSelection(base.map(trackedTransaction.doc, trackedTransaction.mapping));
|
|
84
|
-
}
|
|
85
|
-
if (originalTransaction.scrolledIntoView) {
|
|
86
|
-
trackedTransaction.scrollIntoView();
|
|
87
|
-
}
|
|
88
|
-
if (originalTransaction.storedMarksSet) {
|
|
89
|
-
trackedTransaction.setStoredMarks(originalTransaction.storedMarks);
|
|
90
|
-
}
|
|
91
|
-
// @ts-expect-error Preserve original transaction meta exactly as-is
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
93
|
-
trackedTransaction.meta = originalTransaction.meta;
|
|
94
|
-
return trackedTransaction;
|
|
3
|
+
import { getRequiredStructuralContextPaths, suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
4
|
+
import { handleSpecialTransactionShape } from "./features/transactionShaping/index.js";
|
|
5
|
+
import { transformToSuggestionTransaction } from "./transformToSuggestionTransaction.js";
|
|
6
|
+
export { transformToSuggestionTransaction } from "./transformToSuggestionTransaction.js";
|
|
7
|
+
export { suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
8
|
+
const TRACE_ENABLED = false;
|
|
9
|
+
function trace(...args) {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
11
|
+
if (!TRACE_ENABLED) return;
|
|
12
|
+
console.log("[withSuggestChanges]", ...args);
|
|
95
13
|
}
|
|
96
14
|
/**
|
|
97
15
|
* A `dispatchTransaction` decorator. Wrap your existing `dispatchTransaction`
|
|
@@ -103,13 +21,65 @@ function getStepHandler(step) {
|
|
|
103
21
|
* These modified transactions will suggest changes instead of directly
|
|
104
22
|
* applying them, e.g. by marking a range with the deletion mark rather
|
|
105
23
|
* than removing it from the document.
|
|
106
|
-
*/ export function withSuggestChanges(dispatchTransaction, generateId) {
|
|
24
|
+
*/ export function withSuggestChanges(dispatchTransaction, generateId, opts) {
|
|
107
25
|
const dispatch = dispatchTransaction ?? function(tr) {
|
|
108
26
|
this.updateState(this.state.apply(tr));
|
|
109
27
|
};
|
|
110
28
|
return function dispatchTransaction(tr) {
|
|
111
29
|
const ySyncMeta = tr.getMeta("y-sync$") ?? {};
|
|
112
|
-
const
|
|
30
|
+
const isEnabled = isSuggestChangesEnabled(this.state) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {}));
|
|
31
|
+
let transaction = tr;
|
|
32
|
+
if (isEnabled) {
|
|
33
|
+
let structureChangesResult = null;
|
|
34
|
+
const docBefore = transaction.docs[0];
|
|
35
|
+
const structuralContextPaths = opts?.experimental_trackStructureChanges ? getRequiredStructuralContextPaths(opts.experimental_trackStructures) : null;
|
|
36
|
+
const ensureUniqueNodeIds = opts?.experimental_ensureUniqueNodeIds;
|
|
37
|
+
const shapedTransaction = transaction.docChanged ? handleSpecialTransactionShape({
|
|
38
|
+
transaction,
|
|
39
|
+
state: this.state,
|
|
40
|
+
generateId,
|
|
41
|
+
structuralContextPaths,
|
|
42
|
+
ensureUniqueNodeIds
|
|
43
|
+
}) : null;
|
|
44
|
+
if (!shapedTransaction && transaction.docChanged && docBefore && structuralContextPaths && typeof ensureUniqueNodeIds === "function") {
|
|
45
|
+
trace("trying to track structure changes first...");
|
|
46
|
+
// after a transaction, some nodes may not yet have unique ids (they were just added, and the unique id plugin has not yet run)
|
|
47
|
+
// this hook allows to "post-process" the transaction and add the missing ids
|
|
48
|
+
// basically it allows to run the core logic of the unique ids plugin earlier
|
|
49
|
+
const perfUid = performance.now();
|
|
50
|
+
const uniqueNodeIdsTransform = ensureUniqueNodeIds([
|
|
51
|
+
transaction
|
|
52
|
+
], docBefore, transaction.doc);
|
|
53
|
+
trace("perf", "structure", "ensureUniqueNodsIds took", Number((performance.now() - perfUid).toFixed(2)), "ms");
|
|
54
|
+
const docAfter = uniqueNodeIdsTransform.doc;
|
|
55
|
+
trace("unique node ids set", docAfter);
|
|
56
|
+
// try running structure changes first
|
|
57
|
+
// if handled, then ignore the main plugin
|
|
58
|
+
// otherwise use the main plugin
|
|
59
|
+
const perfStructure = performance.now();
|
|
60
|
+
structureChangesResult = suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId);
|
|
61
|
+
trace("perf", "structure", "suggestStructureChanges took", Number((performance.now() - perfStructure).toFixed(2)), "ms");
|
|
62
|
+
trace("structure changes transform completed", structureChangesResult.transform);
|
|
63
|
+
if (structureChangesResult.handled) {
|
|
64
|
+
uniqueNodeIdsTransform.steps.forEach((step)=>{
|
|
65
|
+
transaction.step(step);
|
|
66
|
+
});
|
|
67
|
+
structureChangesResult.transform.steps.forEach((step)=>{
|
|
68
|
+
transaction.step(step);
|
|
69
|
+
});
|
|
70
|
+
trace("applied unique id transform and structure changes transform to the transaction", transaction);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (shapedTransaction) {
|
|
74
|
+
transaction = shapedTransaction;
|
|
75
|
+
} else if (transaction.docChanged && structureChangesResult?.handled !== true) {
|
|
76
|
+
trace("running the main suggestions plugin...");
|
|
77
|
+
const perfSuggestions = performance.now();
|
|
78
|
+
transaction = transformToSuggestionTransaction(tr, this.state, generateId);
|
|
79
|
+
trace("perf", "suggestions", "transformToSuggestionTransaction took", Number((performance.now() - perfSuggestions).toFixed(2)), "ms");
|
|
80
|
+
trace("main suggestions plugin completed", transaction);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
113
83
|
if (transaction.docChanged) {
|
|
114
84
|
prependDeletionsWithZWSP(transaction);
|
|
115
85
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { InputRule } from "prosemirror-inputrules";
|
|
2
|
+
import { type Node } from "prosemirror-model";
|
|
3
|
+
import { type Attrs, type NodeType } from "prosemirror-model";
|
|
4
|
+
export declare function wrappingInputRule(regexp: RegExp, nodeType: NodeType, getAttrs?: Attrs | null | ((matches: RegExpMatchArray) => Attrs | null), joinPredicate?: (match: RegExpMatchArray, node: Node) => boolean): InputRule;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InputRule } from "prosemirror-inputrules";
|
|
2
|
+
import { canJoin, findWrapping } from "prosemirror-transform";
|
|
3
|
+
import { getSuggestionMarks } from "./utils.js";
|
|
4
|
+
import { ZWSP } from "./constants.js";
|
|
5
|
+
/// return a boolean to indicate whether a join should happen.
|
|
6
|
+
export function wrappingInputRule(regexp, nodeType, getAttrs = null, joinPredicate) {
|
|
7
|
+
return new InputRule(regexp, (state, match, start, end)=>{
|
|
8
|
+
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
|
9
|
+
const tr = state.tr;
|
|
10
|
+
// check and try to preserve zwsp
|
|
11
|
+
const { insertion } = getSuggestionMarks(state.doc.type.schema);
|
|
12
|
+
let $start = tr.doc.resolve(start);
|
|
13
|
+
if (insertion.isInSet($start.nodeAfter?.marks ?? []) && match[0].startsWith(ZWSP)) {
|
|
14
|
+
// preserve a single ZWSP at the start
|
|
15
|
+
tr.delete(start + 1, end);
|
|
16
|
+
} else {
|
|
17
|
+
tr.delete(start, end);
|
|
18
|
+
}
|
|
19
|
+
// the rest of the rule unchanged
|
|
20
|
+
$start = tr.doc.resolve(start);
|
|
21
|
+
const range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs);
|
|
22
|
+
if (!wrapping) return null;
|
|
23
|
+
tr.wrap(range, wrapping);
|
|
24
|
+
const before = tr.doc.resolve(start - 1).nodeBefore;
|
|
25
|
+
if (before && before.type == nodeType && canJoin(tr.doc, start - 1) && (!joinPredicate || joinPredicate(match, before))) tr.join(start - 1);
|
|
26
|
+
return tr;
|
|
27
|
+
});
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
When nodes are joined using backspace or delete, let the join happen normally,
|
|
2
|
-
but mark former node boundaries with deletion mark of type="join".
|
|
3
|
-
|
|
4
|
-
Save information about what nodes were at each side of the boundary before the
|
|
5
|
-
join.
|
|
6
|
-
|
|
7
|
-
Then on restore, use marks at join points to split the node and restore the node
|
|
8
|
-
markup at each side of the split.
|
|
File without changes
|