@magic-marker/prosemirror-suggest-changes 0.4.0 → 0.4.1-wrap-unwrap.1
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 +50 -2
- package/dist/commands.js +222 -43
- package/dist/ensureSelectionPlugin.js +3 -3
- 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 +5 -2
- package/dist/index.js +4 -2
- 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
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { getNodeId } from "./getNodeId.js";
|
|
3
|
+
import { Transform } from "prosemirror-transform";
|
|
4
|
+
// unique ids plugin
|
|
5
|
+
// https://discuss.prosemirror.net/t/how-to-avoid-copying-attributes-to-new-paragraph/4568/2
|
|
6
|
+
// (also checks and fix duplicates that inevitably appear)
|
|
7
|
+
export const uniqueNodeIdsPluginKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-unique-node-ids");
|
|
8
|
+
export const UNIQUE_NODE_IDS_PLUGIN_META = "unique-node-ids-plugin";
|
|
9
|
+
const TRACE_ENABLED = false;
|
|
10
|
+
function trace(...args) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
12
|
+
if (!TRACE_ENABLED) return;
|
|
13
|
+
console.log("[uniqueNodeIdsPlugin]", ...args);
|
|
14
|
+
}
|
|
15
|
+
export function uniqueNodeIdsPlugin({ attributeName, generateID }) {
|
|
16
|
+
return new Plugin({
|
|
17
|
+
key: uniqueNodeIdsPluginKey,
|
|
18
|
+
appendTransaction (transactions, oldState, newState) {
|
|
19
|
+
trace("appendTransaction");
|
|
20
|
+
const pluginState = uniqueNodeIdsPluginKey.getState(newState);
|
|
21
|
+
// do nothing if doc hasn't changed (but make sure it runs initially)
|
|
22
|
+
const docChanged = transactions.some((transaction)=>transaction.docChanged);
|
|
23
|
+
if (!docChanged && pluginState?.completedInitialRun) {
|
|
24
|
+
trace("doc not changed, skipping", [
|
|
25
|
+
...transactions
|
|
26
|
+
]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
trace("appendTransaction", [
|
|
30
|
+
...transactions
|
|
31
|
+
]);
|
|
32
|
+
const tr = newState.tr;
|
|
33
|
+
const transform = ensureUniqueNodeIds(transactions, oldState.doc, newState.doc, {
|
|
34
|
+
attributeName,
|
|
35
|
+
generateID
|
|
36
|
+
});
|
|
37
|
+
transform.steps.forEach((step)=>{
|
|
38
|
+
tr.step(step);
|
|
39
|
+
});
|
|
40
|
+
trace("tr steps", tr.steps);
|
|
41
|
+
if (!tr.steps.length) return;
|
|
42
|
+
tr.setMeta(uniqueNodeIdsPluginKey, UNIQUE_NODE_IDS_PLUGIN_META);
|
|
43
|
+
return tr;
|
|
44
|
+
},
|
|
45
|
+
state: {
|
|
46
|
+
init () {
|
|
47
|
+
return {
|
|
48
|
+
completedInitialRun: false
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
apply (tr, value) {
|
|
52
|
+
const meta = tr.getMeta(uniqueNodeIdsPluginKey);
|
|
53
|
+
if (meta === UNIQUE_NODE_IDS_PLUGIN_META && !value.completedInitialRun) {
|
|
54
|
+
return {
|
|
55
|
+
completedInitialRun: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function ensureUniqueNodeIds(_transactions, _oldDoc, newDoc, options) {
|
|
64
|
+
const tr = new Transform(newDoc);
|
|
65
|
+
const nodeIds = new Set();
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
67
|
+
if (TRACE_ENABLED) console.groupCollapsed("ensureUniqueNodeIds");
|
|
68
|
+
tr.doc.descendants((node, pos)=>{
|
|
69
|
+
if (node.isText) return false;
|
|
70
|
+
const nodeId = getNodeId(node);
|
|
71
|
+
// nodeId is set and is not duplicated
|
|
72
|
+
if (nodeId != null && !nodeIds.has(nodeId)) {
|
|
73
|
+
nodeIds.add(nodeId);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// nodeId is set and it is duplicated
|
|
77
|
+
if (nodeId != null && nodeIds.has(nodeId)) {
|
|
78
|
+
const id = options.generateID();
|
|
79
|
+
nodeIds.add(id);
|
|
80
|
+
tr.setNodeMarkup(pos, node.type, {
|
|
81
|
+
...node.attrs,
|
|
82
|
+
[options.attributeName]: id
|
|
83
|
+
}, node.marks);
|
|
84
|
+
trace("fixed duplicate id", id, "for node", node.type.name, "at pos", pos, {
|
|
85
|
+
was: nodeId,
|
|
86
|
+
is: id
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// node id is not set
|
|
91
|
+
if (nodeId == null) {
|
|
92
|
+
const id = options.generateID();
|
|
93
|
+
nodeIds.add(id);
|
|
94
|
+
tr.setNodeMarkup(pos, node.type, {
|
|
95
|
+
...node.attrs,
|
|
96
|
+
[options.attributeName]: id
|
|
97
|
+
}, node.marks);
|
|
98
|
+
trace("set unique id", id, "for node", node.type.name, "at pos", pos);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
104
|
+
if (TRACE_ENABLED) console.groupEnd();
|
|
105
|
+
return tr;
|
|
106
|
+
}
|
package/dist/generateId.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getSuggestionMarks } from "./utils.js";
|
|
2
|
+
import { isJoinMark } from "./features/joinOnDelete/types.js";
|
|
3
|
+
import { normalizeJoinNodesMetadata } from "./features/joinOnDelete/normalizeJoinNodesMetadata.js";
|
|
2
4
|
export const suggestionIdValidate = "number|string";
|
|
3
5
|
export function parseSuggestionId(id) {
|
|
4
6
|
const parsed = parseInt(id, 10);
|
|
@@ -8,14 +10,39 @@ export function parseSuggestionId(id) {
|
|
|
8
10
|
return parsed;
|
|
9
11
|
}
|
|
10
12
|
export function generateNextNumberId(schema, doc) {
|
|
11
|
-
const { deletion, insertion, modification } = getSuggestionMarks(schema);
|
|
13
|
+
const { deletion, insertion, modification, structure } = getSuggestionMarks(schema);
|
|
12
14
|
// Find the highest change id in the document so far,
|
|
13
15
|
// and use that as the starting point for new changes
|
|
14
16
|
let suggestionId = 0;
|
|
15
17
|
doc?.descendants((node)=>{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// find max suggestion id across all suggestion marks and across all suggestion marks that are serialized into metadata of the join marks
|
|
19
|
+
const marks = node.marks.filter((mark)=>mark.type === insertion || mark.type === deletion || mark.type === modification || mark.type === structure);
|
|
20
|
+
const markIds = marks.map((mark)=>mark.attrs["id"]);
|
|
21
|
+
// collect suggestion ids of marks that are serialized into join marks metadata
|
|
22
|
+
const joinMarks = marks.filter((mark)=>isJoinMark(mark));
|
|
23
|
+
const joinMetadataMarkIds = [];
|
|
24
|
+
joinMarks.forEach((mark)=>{
|
|
25
|
+
const joinMetadata = normalizeJoinNodesMetadata(mark.attrs);
|
|
26
|
+
if (!joinMetadata) return;
|
|
27
|
+
joinMetadata.leftNodes.forEach((node)=>{
|
|
28
|
+
node.marks.forEach((mark)=>{
|
|
29
|
+
if (!mark.attrs["id"]) return;
|
|
30
|
+
joinMetadataMarkIds.push(mark.attrs["id"]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
joinMetadata.rightNodes.forEach((node)=>{
|
|
34
|
+
node.marks.forEach((mark)=>{
|
|
35
|
+
if (!mark.attrs["id"]) return;
|
|
36
|
+
joinMetadataMarkIds.push(mark.attrs["id"]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
const allMarkIds = [
|
|
41
|
+
...markIds,
|
|
42
|
+
...joinMetadataMarkIds
|
|
43
|
+
];
|
|
44
|
+
if (allMarkIds.length > 0) {
|
|
45
|
+
suggestionId = Math.max(suggestionId, ...allMarkIds);
|
|
19
46
|
return false;
|
|
20
47
|
}
|
|
21
48
|
return true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure, } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges, } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled, } from "./plugin.js";
|
|
4
|
-
export { withSuggestChanges, transformToSuggestionTransaction, } from "./withSuggestChanges.js";
|
|
4
|
+
export { withSuggestChanges, transformToSuggestionTransaction, suggestStructureChanges as experimental_suggestStructureChanges, } from "./withSuggestChanges.js";
|
|
5
5
|
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled, } from "./ensureSelectionPlugin.js";
|
|
6
|
+
export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export type { Op as StructureOp, StructureMarkAttrs, StructuralContextPath, } from "./features/wrapUnwrap/types.js";
|
|
8
|
+
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled } from "./plugin.js";
|
|
4
|
-
export { withSuggestChanges, transformToSuggestionTransaction } from "./withSuggestChanges.js";
|
|
4
|
+
export { withSuggestChanges, transformToSuggestionTransaction, suggestStructureChanges as experimental_suggestStructureChanges } from "./withSuggestChanges.js";
|
|
5
5
|
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled } from "./ensureSelectionPlugin.js";
|
|
6
|
+
export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { wrappingInputRule } from "./wrappingInputRule.js";
|
|
2
|
+
import { ZWSP } from "./constants.js";
|
|
3
|
+
export function listInputRules(bulletListNodeType, orderedListNodeType) {
|
|
4
|
+
const bulletListInputRule = wrappingInputRule(// ^ string start, [${ZWSP}\\s]* zero or more ZWSP or whitespace, ([-+*]) one of -+* , \\s one whitespace, $ end of string
|
|
5
|
+
// "u" flag treats \u as unicode code points instead of literal "u"
|
|
6
|
+
new RegExp(`^[${ZWSP}\\s]*([-+*])\\s$`, "u"), bulletListNodeType);
|
|
7
|
+
// ^ string start, [${ZWSP}\\s]* zero or more ZWSP or whitespace, ([0-9]+\\.) digit followed by dot, \\s one whitespace, $ end of string
|
|
8
|
+
// "u" flag treats \u as unicode code points instead of literal "u"
|
|
9
|
+
const orderedListInputRule = wrappingInputRule(new RegExp(`^[${ZWSP}\\s]*([0-9]+\\.)\\s$`, "u"), orderedListNodeType);
|
|
10
|
+
return [
|
|
11
|
+
bulletListInputRule,
|
|
12
|
+
orderedListInputRule
|
|
13
|
+
];
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Step } from "prosemirror-transform";
|
|
2
|
+
/**
|
|
3
|
+
* Rebase a step onto a new lineage of steps
|
|
4
|
+
*
|
|
5
|
+
* @param step The step to rebase
|
|
6
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
7
|
+
* @param forth The new steps to map through
|
|
8
|
+
*/
|
|
9
|
+
export declare function rebaseStep(step: Step, back: Step[], forth: Step[]): Step | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebase a step onto a new lineage of steps
|
|
3
|
+
*
|
|
4
|
+
* @param step The step to rebase
|
|
5
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
6
|
+
* @param forth The new steps to map through
|
|
7
|
+
*/ export function rebaseStep(step, back, forth) {
|
|
8
|
+
const reset = back.slice().reverse().reduce((acc, step)=>acc?.map(step.getMap().invert()) ?? null, step);
|
|
9
|
+
const rebased = forth.reduce((acc, step)=>acc?.map(step.getMap()) ?? null, reset);
|
|
10
|
+
return rebased;
|
|
11
|
+
}
|
package/dist/replaceStep.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Node } from "prosemirror-model";
|
|
2
2
|
import { type EditorState, type Transaction } from "prosemirror-state";
|
|
3
|
-
import { type
|
|
3
|
+
import { type ReplaceStep, type Step } from "prosemirror-transform";
|
|
4
4
|
import { type SuggestionId } from "./generateId.js";
|
|
5
5
|
/**
|
|
6
6
|
* Transform a replace step into its equivalent tracked steps.
|
package/dist/replaceStep.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getSuggestionMarks } from "./utils.js";
|
|
|
5
5
|
import { joinBlocks } from "./features/joinBlocks/index.js";
|
|
6
6
|
import { collapseZWSPNodes, findJoinMark, joinNodesAndMarkJoinPoints, removeZWSPDeletions } from "./features/joinOnDelete/index.js";
|
|
7
7
|
import { adjustForStartToStartTextblockDeletion } from "./features/startToStartTextblockDeletion/index.js";
|
|
8
|
+
// import { rebaseStep } from "./rebaseStep.js";
|
|
8
9
|
/**
|
|
9
10
|
* Transform a replace step into its equivalent tracked steps.
|
|
10
11
|
*
|
package/dist/schema.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export declare const deletion: MarkSpec;
|
|
|
3
3
|
export declare const hiddenDeletion: MarkSpec;
|
|
4
4
|
export declare const insertion: MarkSpec;
|
|
5
5
|
export declare const modification: MarkSpec;
|
|
6
|
+
export declare const structure: MarkSpec;
|
|
6
7
|
/**
|
|
7
8
|
* Add the deletion, insertion, and modification marks to
|
|
8
9
|
* the provided MarkSpec map.
|
|
9
10
|
*/
|
|
10
11
|
export declare function addSuggestionMarks<Marks extends string>(marks: Record<Marks, MarkSpec>, opts?: {
|
|
11
12
|
experimental_deletions?: "hidden" | "visible";
|
|
12
|
-
}): Record<Marks | "deletion" | "insertion" | "modification", MarkSpec>;
|
|
13
|
+
}): Record<Marks | "deletion" | "insertion" | "modification" | "structure", MarkSpec>;
|
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" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "image" | "hard_break" | "orderedList" | "bulletList" | "listItem" | "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" | "doc" | "paragraph" | "
|
|
3
|
+
export declare const schema: Schema<"blockquote" | "text" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "image" | "hard_break" | "orderedList" | "bulletList" | "listItem", "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"];
|