@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.
- package/dist/__tests__/playwrightHelpers.d.ts +2 -2
- package/dist/__tests__/playwrightPage.d.ts +2 -50
- package/dist/commands.js +43 -222
- package/dist/ensureSelectionPlugin.js +3 -3
- package/dist/features/joinOnDelete/index.d.ts +19 -4
- package/dist/features/joinOnDelete/index.js +53 -166
- package/dist/generateId.js +4 -31
- package/dist/index.d.ts +2 -5
- package/dist/index.js +2 -4
- package/dist/replaceStep.d.ts +1 -1
- package/dist/schema.d.ts +1 -2
- package/dist/schema.js +1 -37
- package/dist/testing/testBuilders.d.ts +2 -1
- package/dist/utils.d.ts +0 -1
- package/dist/utils.js +2 -6
- package/dist/withSuggestChanges.d.ts +14 -11
- package/dist/withSuggestChanges.js +94 -64
- package/package.json +1 -1
- package/src/features/joinOnDelete/README.md +8 -0
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +0 -1
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +0 -1
- package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +0 -1
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +0 -6
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +0 -24
- package/dist/features/joinOnDelete/types.d.ts +0 -36
- package/dist/features/joinOnDelete/types.js +0 -23
- package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +0 -3
- package/dist/features/transactionShaping/detectSpecialTransactionShape.js +0 -4
- package/dist/features/transactionShaping/index.d.ts +0 -3
- package/dist/features/transactionShaping/index.js +0 -11
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +0 -3
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +0 -48
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +0 -1
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +0 -188
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +0 -3
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +0 -64
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +0 -2
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +0 -2
- package/dist/features/transactionShaping/types.d.ts +0 -20
- package/dist/features/transactionShaping/types.js +0 -1
- package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +0 -1
- package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +0 -1
- package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +0 -1
- package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +0 -1
- package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +0 -1
- package/dist/features/wrapUnwrap/addIdAttr.d.ts +0 -2
- package/dist/features/wrapUnwrap/addIdAttr.js +0 -60
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +0 -10
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +0 -41
- package/dist/features/wrapUnwrap/apply/index.d.ts +0 -5
- package/dist/features/wrapUnwrap/apply/index.js +0 -21
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +0 -2
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +0 -17
- package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +0 -3
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +0 -82
- package/dist/features/wrapUnwrap/constants.d.ts +0 -3
- package/dist/features/wrapUnwrap/constants.js +0 -3
- package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +0 -1
- package/dist/features/wrapUnwrap/generateUniqueNodeId.js +0 -6
- package/dist/features/wrapUnwrap/getNodeId.d.ts +0 -2
- package/dist/features/wrapUnwrap/getNodeId.js +0 -5
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +0 -3
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +0 -37
- package/dist/features/wrapUnwrap/revert/index.d.ts +0 -5
- package/dist/features/wrapUnwrap/revert/index.js +0 -19
- package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +0 -4
- package/dist/features/wrapUnwrap/revert/revertAddOp.js +0 -4
- package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +0 -16
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +0 -122
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +0 -10
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +0 -236
- package/dist/features/wrapUnwrap/sameParentChain.d.ts +0 -2
- package/dist/features/wrapUnwrap/sameParentChain.js +0 -4
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +0 -17
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +0 -299
- package/dist/features/wrapUnwrap/types.d.ts +0 -54
- package/dist/features/wrapUnwrap/types.js +0 -23
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +0 -17
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +0 -106
- package/dist/listInputRules.d.ts +0 -2
- package/dist/listInputRules.js +0 -14
- package/dist/rebaseStep.d.ts +0 -9
- package/dist/rebaseStep.js +0 -11
- package/dist/testing/e2eTestSchema.d.ts +0 -2
- package/dist/transformToSuggestionTransaction.d.ts +0 -22
- package/dist/transformToSuggestionTransaction.js +0 -101
- package/dist/wrappingInputRule.d.ts +0 -4
- package/dist/wrappingInputRule.js +0 -28
|
@@ -1,15 +1,97 @@
|
|
|
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";
|
|
1
9
|
import { isSuggestChangesEnabled, suggestChangesKey } from "./plugin.js";
|
|
10
|
+
import { generateNextNumberId } from "./generateId.js";
|
|
11
|
+
import { getSuggestionMarks } from "./utils.js";
|
|
2
12
|
import { prependDeletionsWithZWSP } from "./prependDeletionsWithZWSP.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
function getStepHandler(step) {
|
|
14
|
+
if (step instanceof ReplaceStep) {
|
|
15
|
+
return suggestReplaceStep;
|
|
16
|
+
}
|
|
17
|
+
if (step instanceof ReplaceAroundStep) {
|
|
18
|
+
return suggestReplaceAroundStep;
|
|
19
|
+
}
|
|
20
|
+
if (step instanceof AddMarkStep) {
|
|
21
|
+
return trackAddMarkStep;
|
|
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;
|
|
13
95
|
}
|
|
14
96
|
/**
|
|
15
97
|
* A `dispatchTransaction` decorator. Wrap your existing `dispatchTransaction`
|
|
@@ -21,65 +103,13 @@ function trace(...args) {
|
|
|
21
103
|
* These modified transactions will suggest changes instead of directly
|
|
22
104
|
* applying them, e.g. by marking a range with the deletion mark rather
|
|
23
105
|
* than removing it from the document.
|
|
24
|
-
*/ export function withSuggestChanges(dispatchTransaction, generateId
|
|
106
|
+
*/ export function withSuggestChanges(dispatchTransaction, generateId) {
|
|
25
107
|
const dispatch = dispatchTransaction ?? function(tr) {
|
|
26
108
|
this.updateState(this.state.apply(tr));
|
|
27
109
|
};
|
|
28
110
|
return function dispatchTransaction(tr) {
|
|
29
111
|
const ySyncMeta = tr.getMeta("y-sync$") ?? {};
|
|
30
|
-
const
|
|
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
|
-
}
|
|
112
|
+
const transaction = isSuggestChangesEnabled(this.state) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {})) ? transformToSuggestionTransaction(tr, this.state, generateId) : tr;
|
|
83
113
|
if (transaction.docChanged) {
|
|
84
114
|
prependDeletionsWithZWSP(transaction);
|
|
85
115
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
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.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type JoinMarkAttrs } from "./types.js";
|
|
2
|
-
export declare const MAX_BLOCK_JOIN_DEPTH = 2;
|
|
3
|
-
export declare function normalizeJoinNodesMetadata(attrs: JoinMarkAttrs): false | {
|
|
4
|
-
leftNodes: import("./types.js").SerializedJoinNode[];
|
|
5
|
-
rightNodes: import("./types.js").SerializedJoinNode[];
|
|
6
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { isSerializedJoinNode } from "./types.js";
|
|
2
|
-
// Block join suggestion metadata/revert currently supports the depth TipTap uses
|
|
3
|
-
// when Backspace joins both list-item paragraphs and their parent list items.
|
|
4
|
-
export const MAX_BLOCK_JOIN_DEPTH = 2;
|
|
5
|
-
export function normalizeJoinNodesMetadata(attrs) {
|
|
6
|
-
const data = attrs.data;
|
|
7
|
-
// Normalize legacy metadata so revert can use the same array path.
|
|
8
|
-
const leftNodes = data.leftNodes ?? (data.leftNode ? [
|
|
9
|
-
data.leftNode
|
|
10
|
-
] : null);
|
|
11
|
-
const rightNodes = data.rightNodes ?? (data.rightNode ? [
|
|
12
|
-
data.rightNode
|
|
13
|
-
] : null);
|
|
14
|
-
if (!Array.isArray(leftNodes) || !Array.isArray(rightNodes)) return false;
|
|
15
|
-
if (leftNodes.length === 0 || leftNodes.length !== rightNodes.length) return false;
|
|
16
|
-
// Reject unsupported depths instead of partially reverting unknown structure.
|
|
17
|
-
if (leftNodes.length > MAX_BLOCK_JOIN_DEPTH) return false;
|
|
18
|
-
if (!leftNodes.every(isSerializedJoinNode)) return false;
|
|
19
|
-
if (!rightNodes.every(isSerializedJoinNode)) return false;
|
|
20
|
-
return {
|
|
21
|
-
leftNodes,
|
|
22
|
-
rightNodes
|
|
23
|
-
};
|
|
24
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type Mark, type Attrs, type Node } from "prosemirror-model";
|
|
2
|
-
import { type SuggestionId } from "../../generateId.js";
|
|
3
|
-
export interface SerializedJoinNode {
|
|
4
|
-
type: string;
|
|
5
|
-
attrs: object;
|
|
6
|
-
marks: {
|
|
7
|
-
attrs: Record<string, unknown>;
|
|
8
|
-
}[];
|
|
9
|
-
}
|
|
10
|
-
export interface JoinMarkAttrs {
|
|
11
|
-
id: SuggestionId;
|
|
12
|
-
type: "join";
|
|
13
|
-
data: {
|
|
14
|
-
leftNode?: SerializedJoinNode;
|
|
15
|
-
rightNode?: SerializedJoinNode;
|
|
16
|
-
leftNodes?: SerializedJoinNode[];
|
|
17
|
-
rightNodes?: SerializedJoinNode[];
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export interface JoinPair {
|
|
21
|
-
leftNode: Node;
|
|
22
|
-
rightNode: Node;
|
|
23
|
-
}
|
|
24
|
-
export interface JoinCandidate {
|
|
25
|
-
joinPos: number;
|
|
26
|
-
leftNodes: Node[];
|
|
27
|
-
rightNodes: Node[];
|
|
28
|
-
}
|
|
29
|
-
export declare function isSerializedJoinNode(node: unknown): node is SerializedJoinNode;
|
|
30
|
-
export declare function isJoinMarkAttrs(attrs: Attrs): attrs is JoinMarkAttrs;
|
|
31
|
-
export declare function isJoinMarkObject(mark: unknown): mark is Omit<Mark, "attrs"> & {
|
|
32
|
-
attrs: JoinMarkAttrs;
|
|
33
|
-
};
|
|
34
|
-
export declare function isJoinMark(mark: Mark): mark is Omit<Mark, "attrs"> & {
|
|
35
|
-
attrs: JoinMarkAttrs;
|
|
36
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { getSuggestionMarks } from "../../utils.js";
|
|
2
|
-
export function isSerializedJoinNode(node) {
|
|
3
|
-
if (node === null || typeof node !== "object") return false;
|
|
4
|
-
const data = node;
|
|
5
|
-
return typeof data.type === "string" && typeof data.attrs === "object" && data.attrs !== null && Array.isArray(data.marks);
|
|
6
|
-
}
|
|
7
|
-
export function isJoinMarkAttrs(attrs) {
|
|
8
|
-
if (typeof attrs["id"] !== "string" && typeof attrs["id"] !== "number") {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
if (attrs["type"] !== "join") return false;
|
|
12
|
-
if (attrs["data"] == null) return false;
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
export function isJoinMarkObject(mark) {
|
|
16
|
-
if (mark === null || typeof mark !== "object") return false;
|
|
17
|
-
if (!("attrs" in mark)) return false;
|
|
18
|
-
return isJoinMarkAttrs(mark.attrs);
|
|
19
|
-
}
|
|
20
|
-
export function isJoinMark(mark) {
|
|
21
|
-
const { deletion } = getSuggestionMarks(mark.type.schema);
|
|
22
|
-
return mark.type === deletion && isJoinMarkAttrs(mark.attrs);
|
|
23
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { handleTipTapParagraphIntoListJoin } from "./tipTapParagraphIntoListJoin/index.js";
|
|
2
|
-
const specialTransactionShapeHandlers = [
|
|
3
|
-
handleTipTapParagraphIntoListJoin
|
|
4
|
-
];
|
|
5
|
-
export function handleSpecialTransactionShape(args) {
|
|
6
|
-
for (const handler of specialTransactionShapeHandlers){
|
|
7
|
-
const transaction = handler(args);
|
|
8
|
-
if (transaction) return transaction;
|
|
9
|
-
}
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { ReplaceStep, Transform } from "prosemirror-transform";
|
|
2
|
-
import { getNodeId } from "../../wrapUnwrap/getNodeId.js";
|
|
3
|
-
// detect a very specific TipTap pattern where joining a paragraph into a list above it
|
|
4
|
-
// causes a 3-step transaction, which is different from normal prosemirror where you just get 1 step
|
|
5
|
-
// see unit test for details
|
|
6
|
-
export function detectTipTapParagraphIntoListJoin(transaction) {
|
|
7
|
-
if (transaction.steps.length !== 3) return null;
|
|
8
|
-
const [deleteStep, insertStep, joinStep] = transaction.steps;
|
|
9
|
-
if (!(deleteStep instanceof ReplaceStep) || !(insertStep instanceof ReplaceStep) || !(joinStep instanceof ReplaceStep)) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
if (deleteStep.slice.content.size !== 0) return null;
|
|
13
|
-
if (insertStep.from !== insertStep.to) return null;
|
|
14
|
-
if (insertStep.slice.openStart !== 0 || insertStep.slice.openEnd !== 0) return null;
|
|
15
|
-
if (insertStep.slice.content.childCount !== 1) return null;
|
|
16
|
-
if (joinStep.slice.content.size !== 0) return null;
|
|
17
|
-
if (joinStep.structure !== true) return null;
|
|
18
|
-
const docBefore = transaction.docs[0];
|
|
19
|
-
if (!docBefore) return null;
|
|
20
|
-
const movedNode = docBefore.nodeAt(deleteStep.from);
|
|
21
|
-
if (!movedNode || !movedNode.isTextblock || movedNode.nodeSize !== deleteStep.to - deleteStep.from) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
const insertedNode = insertStep.slice.content.firstChild;
|
|
25
|
-
if (!insertedNode?.isTextblock) return null;
|
|
26
|
-
if (insertedNode.type !== movedNode.type) return null;
|
|
27
|
-
if (insertedNode.textContent !== movedNode.textContent) return null;
|
|
28
|
-
const movedNodeId = getNodeId(movedNode);
|
|
29
|
-
if (!movedNodeId || getNodeId(insertedNode) !== movedNodeId) return null;
|
|
30
|
-
const previousSibling = docBefore.resolve(deleteStep.from).nodeBefore;
|
|
31
|
-
if (!previousSibling || previousSibling.isInline) return null;
|
|
32
|
-
try {
|
|
33
|
-
const preview = new Transform(docBefore);
|
|
34
|
-
preview.step(deleteStep);
|
|
35
|
-
preview.step(insertStep);
|
|
36
|
-
new Transform(preview.doc).step(joinStep);
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
console.warn("[prosemirror-suggest-changes]", "detected TipTap paragraph into list join shape");
|
|
41
|
-
return {
|
|
42
|
-
type: "tipTapParagraphIntoListJoin",
|
|
43
|
-
deleteStep,
|
|
44
|
-
insertStep,
|
|
45
|
-
joinStep,
|
|
46
|
-
movedNode
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,188 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { EditorState } 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: "currentDocument",
|
|
55
|
-
preserveScroll: false,
|
|
56
|
-
preserveStoredMarks: false,
|
|
57
|
-
preserveMeta: false
|
|
58
|
-
});
|
|
59
|
-
preserveTransactionData(trackedTransaction, args.transaction);
|
|
60
|
-
return trackedTransaction;
|
|
61
|
-
}
|
|
62
|
-
function isTipTapParagraphIntoListJoinShape(shape) {
|
|
63
|
-
return shape?.type === "tipTapParagraphIntoListJoin";
|
|
64
|
-
}
|