@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.7 → 0.3.3-wrap-unwrap.8

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/commands.js CHANGED
@@ -5,7 +5,8 @@ import { suggestChangesKey } from "./plugin.js";
5
5
  import { getSuggestionMarks } from "./utils.js";
6
6
  import { ZWSP } from "./constants.js";
7
7
  import { maybeRevertJoinMark } from "./features/joinOnDelete/index.js";
8
- import { applyAllStructureSuggestions, applyAllStructureSuggestionsOnNode, applyOneStructureSuggestion, revertAllStructureSuggestions, revertAllStructureSuggestionsOnNode, revertOneStructureSuggestion } from "./features/wrapUnwrap/revertStructureSuggestions.js";
8
+ import { revertAllStructureSuggestions, revertStructureSuggestion } from "./features/wrapUnwrap/revert/index.js";
9
+ import { applyAllStructureSuggestions, applyStructureSuggestion } from "./features/wrapUnwrap/apply/index.js";
9
10
  /**
10
11
  * Given a node and a transform, add a set of steps to the
11
12
  * transform that applies all marks of type markTypeToApply
@@ -136,7 +137,7 @@ function applyModificationsToTransform(node, tr, dir, suggestionId, from, to) {
136
137
  export function applySuggestionsToNode(node) {
137
138
  const { deletion, insertion } = getSuggestionMarks(node.type.schema);
138
139
  // first, create a structure transform that applies all structure changes on the given node
139
- const structureTransform = applyAllStructureSuggestionsOnNode(node);
140
+ const structureTransform = applyAllStructureSuggestions(node);
140
141
  // then start a clear transform from the document where the structure changes are applied
141
142
  const suggestionsTransform = new Transform(structureTransform.doc);
142
143
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion);
@@ -153,7 +154,7 @@ export function applySuggestionsToRange(doc, from, to) {
153
154
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154
155
  const nodeRange = doc.resolve(from).blockRange(doc.resolve(to));
155
156
  // create a structure transform that applies all structure changes on the given node range
156
- const structureTransform = applyAllStructureSuggestionsOnNode(doc, nodeRange.start, nodeRange.end);
157
+ const structureTransform = applyAllStructureSuggestions(doc, nodeRange.start, nodeRange.end);
157
158
  // then start a clear transform from the document where the structure changes are applied
158
159
  const suggestionsTransform = new Transform(structureTransform.doc);
159
160
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, nodeRange.start, nodeRange.end);
@@ -204,7 +205,7 @@ export function applySuggestionsToRange(doc, from, to) {
204
205
  return (state, dispatch)=>{
205
206
  const { deletion, insertion } = getSuggestionMarks(state.schema);
206
207
  // create a structure transform that applies all structure changes on the given node range
207
- const structureTransform = applyAllStructureSuggestionsOnNode(state.doc, from, to);
208
+ const structureTransform = applyAllStructureSuggestions(state.doc, from, to);
208
209
  // then start a clear transform from the document where the structure changes are applied
209
210
  const suggestionsTransform = new Transform(structureTransform.doc);
210
211
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, from, to);
@@ -236,7 +237,7 @@ export function applySuggestionsToRange(doc, from, to) {
236
237
  return (state, dispatch)=>{
237
238
  const { deletion, insertion } = getSuggestionMarks(state.schema);
238
239
  // create a structure transform that applies the given structure change on the given node
239
- const structureTransform = applyOneStructureSuggestion(state.doc, suggestionId);
240
+ const structureTransform = applyStructureSuggestion(state.doc, suggestionId);
240
241
  // then start a clear transform from the document where the structure changes are applied
241
242
  const suggestionsTransform = new Transform(structureTransform.doc);
242
243
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, suggestionId, from, to);
@@ -298,7 +299,7 @@ export function applySuggestionsToRange(doc, from, to) {
298
299
  return (state, dispatch)=>{
299
300
  const { deletion, insertion } = getSuggestionMarks(state.schema);
300
301
  // create a structure transform that reverts all structure changes on the given node range
301
- const structureTransform = revertAllStructureSuggestionsOnNode(state.doc, from, to);
302
+ const structureTransform = revertAllStructureSuggestions(state.doc, from, to);
302
303
  // then start a clear transform from the document where the structure changes are reverted
303
304
  const suggestionsTransform = new Transform(structureTransform.doc);
304
305
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, undefined, from, to);
@@ -330,7 +331,7 @@ export function applySuggestionsToRange(doc, from, to) {
330
331
  return (state, dispatch)=>{
331
332
  const { deletion, insertion } = getSuggestionMarks(state.schema);
332
333
  // create a structure transform that reverts the given structure change on the given node
333
- const structureTransform = revertOneStructureSuggestion(state.doc, suggestionId);
334
+ const structureTransform = revertStructureSuggestion(state.doc, suggestionId);
334
335
  // then start a clear transform from the document where the structure changes are reverted
335
336
  const suggestionsTransform = new Transform(structureTransform.doc);
336
337
  applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, suggestionId, from, to);
@@ -0,0 +1,10 @@
1
+ import { type Node } from "prosemirror-model";
2
+ import { type Transform } from "prosemirror-transform";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to, }: {
5
+ tr: Transform;
6
+ node: Node;
7
+ suggestionId?: SuggestionId;
8
+ from?: number | undefined;
9
+ to?: number | undefined;
10
+ }): void;
@@ -0,0 +1,41 @@
1
+ import { getSuggestionMarks } from "../../../utils.js";
2
+ export function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to }) {
3
+ const { structure } = getSuggestionMarks(node.type.schema);
4
+ const suggestionIds = new Set();
5
+ node.descendants((node, pos)=>{
6
+ if (from !== undefined && pos < from) {
7
+ return true;
8
+ }
9
+ if (to !== undefined && pos > to) {
10
+ return false;
11
+ }
12
+ if (node.isText) return true;
13
+ if (!structure.isInSet(node.marks)) return true;
14
+ node.marks.forEach((mark)=>{
15
+ if (mark.type !== structure) return;
16
+ const markSuggestionId = mark.attrs["id"];
17
+ if (suggestionId != null && markSuggestionId !== suggestionId) return;
18
+ suggestionIds.add(markSuggestionId);
19
+ });
20
+ return true;
21
+ });
22
+ for (const suggestionId of suggestionIds){
23
+ applyOneStructureSuggestion(tr, suggestionId);
24
+ }
25
+ }
26
+ function applyOneStructureSuggestion(tr, suggestionId) {
27
+ const { structure } = getSuggestionMarks(tr.doc.type.schema);
28
+ tr.doc.descendants((node, pos)=>{
29
+ if (node.isText) return true;
30
+ if (!structure.isInSet(node.marks)) return true;
31
+ node.marks.forEach((mark)=>{
32
+ if (mark.type !== structure) return;
33
+ if (mark.attrs["id"] !== suggestionId) return;
34
+ applyStructureMark(tr, mark, pos);
35
+ });
36
+ return true;
37
+ });
38
+ }
39
+ function applyStructureMark(tr, mark, pos) {
40
+ tr.removeNodeMark(pos, mark);
41
+ }
@@ -0,0 +1,5 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function applyAllStructureSuggestions(node: Node, from?: number, to?: number): Transform;
5
+ export declare function applyStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
@@ -0,0 +1,21 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { applyStructureSuggestionsInNode } from "./applyStructureSuggestions.js";
3
+ export function applyAllStructureSuggestions(node, from, to) {
4
+ const tr = new Transform(node);
5
+ applyStructureSuggestionsInNode({
6
+ tr,
7
+ node: tr.doc,
8
+ from,
9
+ to
10
+ });
11
+ return tr;
12
+ }
13
+ export function applyStructureSuggestion(node, suggestionId) {
14
+ const tr = new Transform(node);
15
+ applyStructureSuggestionsInNode({
16
+ tr,
17
+ node: tr.doc,
18
+ suggestionId
19
+ });
20
+ return tr;
21
+ }
@@ -0,0 +1,3 @@
1
+ import { type Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ export declare function deleteNodeUpwards(transform: Transform, node: Node, pos: number): void;
@@ -0,0 +1,31 @@
1
+ // delete a given node, and traverse upwards deleting parent nodes if they are now empty
2
+ export function deleteNodeUpwards(transform, node, pos) {
3
+ let $mappedPos = transform.doc.resolve(pos);
4
+ let deleteFrom = $mappedPos.pos;
5
+ let deleteTo = $mappedPos.pos + node.nodeSize;
6
+ console.log("deleteNodeUpwards", "initial delete range covers node", node.toString(), {
7
+ deleteFrom,
8
+ deleteTo,
9
+ $mappedPos
10
+ });
11
+ while($mappedPos.depth > 0){
12
+ const $nextMappedPos = transform.doc.resolve($mappedPos.before());
13
+ console.log("deleteNodeUpwards", "considering", $nextMappedPos.nodeAfter?.toString(), "for deletion", "childCount is", $nextMappedPos.nodeAfter?.childCount);
14
+ if ($nextMappedPos.nodeAfter?.childCount !== 1) break;
15
+ console.log("deleteNodeUpwards", "expanding to deleting node", $nextMappedPos.nodeAfter.toString());
16
+ $mappedPos = $nextMappedPos;
17
+ deleteFrom = $nextMappedPos.pos;
18
+ deleteTo = $nextMappedPos.pos + $nextMappedPos.nodeAfter.nodeSize;
19
+ console.log("deleteNodeUpwards", "expanded delete range to cover node", $nextMappedPos.nodeAfter.toString(), {
20
+ deleteFrom,
21
+ deleteTo,
22
+ $mappedPos: $nextMappedPos
23
+ });
24
+ }
25
+ console.log("deleteNodeUpwards", "final delete range covers node", $mappedPos.nodeAfter?.toString(), {
26
+ deleteFrom,
27
+ deleteTo,
28
+ $mappedPos
29
+ });
30
+ transform.delete(deleteFrom, deleteTo);
31
+ }
@@ -0,0 +1,5 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { type Node } from "prosemirror-model";
3
+ import { type SuggestionId } from "../../../generateId.js";
4
+ export declare function revertAllStructureSuggestions(node: Node, from?: number, to?: number): Transform;
5
+ export declare function revertStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
@@ -0,0 +1,21 @@
1
+ import { Transform } from "prosemirror-transform";
2
+ import { revertStructureSuggestionsInNode } from "./revertStructureSuggestions.js";
3
+ export function revertAllStructureSuggestions(node, from, to) {
4
+ const tr = new Transform(node);
5
+ revertStructureSuggestionsInNode({
6
+ tr,
7
+ node: tr.doc,
8
+ from,
9
+ to
10
+ });
11
+ return tr;
12
+ }
13
+ export function revertStructureSuggestion(node, suggestionId) {
14
+ const tr = new Transform(node);
15
+ revertStructureSuggestionsInNode({
16
+ tr,
17
+ node: tr.doc,
18
+ suggestionId
19
+ });
20
+ return tr;
21
+ }
@@ -0,0 +1,4 @@
1
+ import { type Transform } from "prosemirror-transform";
2
+ import { type AddOp } from "../types.js";
3
+ import { type Node } from "prosemirror-model";
4
+ export declare function revertAddOp(op: AddOp, tr: Transform, node: Node, pos: number): void;
@@ -0,0 +1,9 @@
1
+ import { deleteNodeUpwards } from "./deleteNodeUpwards.js";
2
+ export function revertAddOp(op, tr, node, pos) {
3
+ console.log("revertAddOp", "node", node.toString(), "was added at", pos, {
4
+ op,
5
+ node,
6
+ pos
7
+ });
8
+ deleteNodeUpwards(tr, node, pos);
9
+ }
@@ -0,0 +1,14 @@
1
+ import { type Transform } from "prosemirror-transform";
2
+ import { type MoveOp } from "../types.js";
3
+ import { type Node } from "prosemirror-model";
4
+ import { type NodeWithChildren, type Parent } from "../types.js";
5
+ export declare function revertMoveOp(op: MoveOp, tr: Transform, node: Node, pos: number): void;
6
+ export declare function getNodesWithChildren(doc: Node): Map<string, NodeWithChildren>;
7
+ export declare function getDeepestSurvivingParent(parentChain: Parent[], doc: Node): {
8
+ parent: Parent;
9
+ node: Node;
10
+ pos: number | null;
11
+ remainingChain: Parent[];
12
+ };
13
+ export declare function wrapNodeInParentChain(parentChain: Parent[], node: Node): Node;
14
+ export declare function findInsertionPos(node: Node, pos: number | null, parent: Parent): number;
@@ -0,0 +1,149 @@
1
+ import { deleteNodeUpwards } from "./deleteNodeUpwards.js";
2
+ import { getNodeId } from "../getNodeId.js";
3
+ import { guardDocParent, guardDocWithChildren } from "../types.js";
4
+ export function revertMoveOp(op, tr, node, pos) {
5
+ console.log("revertMoveOp", "node", node.toString(), "was moved from", op.from, {
6
+ op,
7
+ node
8
+ });
9
+ const parent = getDeepestSurvivingParent(op.from, tr.doc);
10
+ console.log("revertMoveOp", "deepest surviving parent is", parent.node.toString(), {
11
+ parent
12
+ });
13
+ const child = wrapNodeInParentChain(parent.remainingChain, node);
14
+ console.log("revertMoveOp", "wrapped node in parent chain is", child.toString(), {
15
+ child
16
+ });
17
+ const insertionPos = findInsertionPos(parent.node, parent.pos, parent.parent);
18
+ console.log("revertMoveOp", "insertion pos", {
19
+ insertionPos
20
+ });
21
+ tr.insert(insertionPos, child);
22
+ const mappedPos = tr.mapping.map(pos);
23
+ deleteNodeUpwards(tr, node, mappedPos);
24
+ }
25
+ export function getNodesWithChildren(doc) {
26
+ const nodesWithChildren = new Map();
27
+ const docWithChildren = {
28
+ node: doc,
29
+ pos: null,
30
+ children: new Set()
31
+ };
32
+ doc.children.forEach((child)=>{
33
+ const nodeId = getNodeId(child);
34
+ if (nodeId == null) return;
35
+ docWithChildren.children.add(nodeId);
36
+ });
37
+ nodesWithChildren.set("__doc__", docWithChildren);
38
+ doc.descendants((node, pos)=>{
39
+ if (node.isText) return true;
40
+ const nodeId = getNodeId(node);
41
+ if (nodeId == null) return true;
42
+ if (nodesWithChildren.has(nodeId)) return true;
43
+ const children = node.children.reduce((acc, child)=>{
44
+ const childId = getNodeId(child);
45
+ if (childId == null) return acc;
46
+ acc.add(childId);
47
+ return acc;
48
+ }, new Set());
49
+ nodesWithChildren.set(nodeId, {
50
+ node,
51
+ pos,
52
+ children
53
+ });
54
+ return true;
55
+ });
56
+ return nodesWithChildren;
57
+ }
58
+ // given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
59
+ // return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
60
+ // also return the remaining part of the chain
61
+ export function getDeepestSurvivingParent(parentChain, doc) {
62
+ const chain = [
63
+ ...parentChain
64
+ ].reverse();
65
+ const currentNodes = getNodesWithChildren(doc);
66
+ let currentNode = currentNodes.get("__doc__"); // get doc node with children from current doc
67
+ if (!guardDocWithChildren(currentNode)) {
68
+ throw new Error("doc not found in nodesWithChildren");
69
+ }
70
+ let parent = chain.shift(); // get doc node descriptor from parent chain
71
+ if (!guardDocParent(parent)) {
72
+ throw new Error("doc parent not found in op chain");
73
+ }
74
+ let remainingChain = [];
75
+ for (const [index, nextParent] of chain.entries()){
76
+ const nextNodeWithChildren = currentNodes.get(nextParent.nodeId);
77
+ // nextParent does not exist in the current document at all
78
+ if (nextNodeWithChildren == null) {
79
+ remainingChain = chain.slice(index);
80
+ break;
81
+ }
82
+ // nextParent exists in the document, but in a different parent chain
83
+ if (!currentNode.children.has(nextParent.nodeId)) {
84
+ remainingChain = chain.slice(index);
85
+ break;
86
+ }
87
+ currentNode = nextNodeWithChildren;
88
+ parent = nextParent;
89
+ }
90
+ return {
91
+ parent,
92
+ node: currentNode.node,
93
+ pos: currentNode.pos,
94
+ remainingChain: [
95
+ ...remainingChain
96
+ ].reverse()
97
+ };
98
+ }
99
+ // given a chain of parent node descriptors and a node
100
+ // wrap the node in the parent chain
101
+ export function wrapNodeInParentChain(parentChain, node) {
102
+ let child = node.copy(node.content);
103
+ for (const parent of parentChain){
104
+ const schema = node.type.schema;
105
+ const nodeType = schema.nodes[parent.nodeType];
106
+ if (!nodeType) {
107
+ throw new Error(`node type ${parent.nodeType} not found in schema`);
108
+ }
109
+ const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
110
+ child = nodeType.create(parent.nodeAttrs, child, marks);
111
+ }
112
+ return child;
113
+ }
114
+ // given a node, its position, and a parent descriptor of this node in some parent chain,
115
+ // use the info from the descriptor to find the insertion position in the node
116
+ // first try to find siblings, fallback to end of node
117
+ export function findInsertionPos(node, pos, parent) {
118
+ let leftSibling = null;
119
+ let rightSibling = null;
120
+ node.descendants((child, localChildPos)=>{
121
+ const childId = getNodeId(child);
122
+ if (childId == null) return false;
123
+ const globalChildPos = pos != null ? pos + 1 + localChildPos : localChildPos;
124
+ if (parent.childSiblingIds[0] === childId) {
125
+ leftSibling = {
126
+ node: child,
127
+ pos: globalChildPos
128
+ };
129
+ }
130
+ if (parent.childSiblingIds[1] === childId) {
131
+ rightSibling = {
132
+ node: child,
133
+ pos: globalChildPos
134
+ };
135
+ }
136
+ // iterate only direct children
137
+ return false;
138
+ });
139
+ if (rightSibling != null) {
140
+ // insert before right sibling
141
+ return rightSibling.pos;
142
+ }
143
+ if (leftSibling != null) {
144
+ // insert after left sibling
145
+ return leftSibling.pos + leftSibling.node.nodeSize;
146
+ }
147
+ // insert at end of node
148
+ return pos != null ? pos + node.nodeSize - 1 : node.nodeSize - 1;
149
+ }
@@ -0,0 +1,12 @@
1
+ import { type Node } from "prosemirror-model";
2
+ import { type Mark } from "prosemirror-model";
3
+ import { Transform } from "prosemirror-transform";
4
+ import { type SuggestionId } from "../../../generateId.js";
5
+ export declare function revertStructureSuggestionsInNode({ tr, node, suggestionId, from, to, }: {
6
+ tr: Transform;
7
+ node: Node;
8
+ suggestionId?: SuggestionId;
9
+ from?: number | undefined;
10
+ to?: number | undefined;
11
+ }): void;
12
+ export declare function revertStructureMark(tr: Transform, mark: Mark, pos: number): void;
@@ -0,0 +1,180 @@
1
+ import { getSuggestionMarks } from "../../../utils.js";
2
+ import { getNodeId } from "../getNodeId.js";
3
+ import { Transform } from "prosemirror-transform";
4
+ import { guardStructureMarkAttrs } from "../types.js";
5
+ import { sameParentChain } from "../sameParentChain.js";
6
+ import { buildMaterializedPaths } from "../buildMaterializedPaths.js";
7
+ import { revertAddOp } from "./revertAddOp.js";
8
+ import { revertMoveOp } from "./revertMoveOp.js";
9
+ export function revertStructureSuggestionsInNode({ tr, node, suggestionId, from, to }) {
10
+ const { structure } = getSuggestionMarks(node.type.schema);
11
+ // collect all structure mark ids
12
+ const suggestionIds = new Set();
13
+ node.descendants((node, pos)=>{
14
+ if (from !== undefined && pos < from) {
15
+ return true;
16
+ }
17
+ if (to !== undefined && pos > to) {
18
+ return false;
19
+ }
20
+ if (node.isText) return true;
21
+ if (!structure.isInSet(node.marks)) return true;
22
+ node.marks.forEach((mark)=>{
23
+ if (mark.type !== structure) return;
24
+ const markSuggestionId = mark.attrs["id"];
25
+ if (suggestionId != null && markSuggestionId !== suggestionId) return;
26
+ suggestionIds.add(markSuggestionId);
27
+ });
28
+ return true;
29
+ });
30
+ for (const suggestionId of suggestionIds){
31
+ revertStructureSuggestionWithPrerequisites(tr, suggestionId);
32
+ }
33
+ }
34
+ function revertStructureSuggestionWithPrerequisites(tr, suggestionId) {
35
+ console.group("revertStructureMarkGroupInOrder", "reverting structure mark group", suggestionId);
36
+ const suggestionIds = buildOrderedSuggestionIds(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
37
+ console.log("revertStructureMarkGroupInOrder", "suggestion groups to revert", suggestionIds);
38
+ for (const suggestionId of suggestionIds){
39
+ revertOneStructureSuggestion(tr, suggestionId);
40
+ }
41
+ console.groupEnd();
42
+ }
43
+ function revertOneStructureSuggestion(tr, suggestionId) {
44
+ console.group("revertStructureSuggestion", "reverting structure suggestion", suggestionId);
45
+ let structureMark = findNextStructureMark(tr.doc, suggestionId);
46
+ while(structureMark != null){
47
+ console.groupCollapsed("revertStructureSuggestion", "reverting structure mark", structureMark.mark.attrs["id"], "at pos", structureMark.$pos.pos, "at node", structureMark.node.toString(), {
48
+ structureMark
49
+ });
50
+ revertStructureMark(tr, structureMark.mark, structureMark.$pos.pos);
51
+ console.groupEnd();
52
+ structureMark = findNextStructureMark(tr.doc, suggestionId);
53
+ }
54
+ console.groupEnd();
55
+ }
56
+ export function revertStructureMark(tr, mark, pos) {
57
+ const transform = new Transform(tr.doc);
58
+ transform.removeNodeMark(pos, mark);
59
+ const node = transform.doc.nodeAt(pos);
60
+ if (!node) {
61
+ throw new Error(`Node not found at position ${String(pos)}`);
62
+ }
63
+ console.log("revertStructureMark", mark.attrs["id"], "at node", node.toString(), {
64
+ node,
65
+ mark,
66
+ pos
67
+ });
68
+ const attrs = mark.attrs;
69
+ if (!guardStructureMarkAttrs(attrs)) {
70
+ console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
71
+ attrs
72
+ });
73
+ return;
74
+ }
75
+ const op = attrs.data.op;
76
+ switch(op.op){
77
+ case "add":
78
+ {
79
+ revertAddOp(op, transform, node, pos);
80
+ break;
81
+ }
82
+ case "move":
83
+ {
84
+ revertMoveOp(op, transform, node, pos);
85
+ break;
86
+ }
87
+ default:
88
+ console.warn("revertStructureMark", "unknown op", {
89
+ op
90
+ });
91
+ break;
92
+ }
93
+ transform.steps.forEach((step)=>{
94
+ tr.step(step);
95
+ });
96
+ }
97
+ // given a suggestion id
98
+ // return an array of suggestion ids to revert
99
+ // resolve suggestion dependencies, meaning,
100
+ // if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
101
+ // it will return [3,2,1]
102
+ // todo: this should probably use topological sort at some point
103
+ function buildOrderedSuggestionIds(node, suggestionId, materializedPaths) {
104
+ const { structure } = getSuggestionMarks(node.type.schema);
105
+ // collect marks with the given suggestionId
106
+ const markGroup = [];
107
+ node.descendants((descendant, pos)=>{
108
+ if (descendant.isText) return true;
109
+ if (!structure.isInSet(descendant.marks)) return true;
110
+ descendant.marks.forEach((mark)=>{
111
+ if (mark.type !== structure) return;
112
+ if (mark.attrs["id"] !== suggestionId) return;
113
+ markGroup.push({
114
+ mark,
115
+ node: descendant,
116
+ pos
117
+ });
118
+ });
119
+ return true;
120
+ });
121
+ const suggestionIds = new Set();
122
+ suggestionIds.add(suggestionId);
123
+ // find first mark that doesn't have a matching op.to
124
+ const mismatch = markGroup.find((mark)=>{
125
+ const nodeId = getNodeId(mark.node);
126
+ if (nodeId == null) return false;
127
+ const parentChain = materializedPaths.get(nodeId);
128
+ if (parentChain == null) return false;
129
+ const { attrs } = mark.mark;
130
+ if (!guardStructureMarkAttrs(attrs)) return false;
131
+ if (attrs.data.op.op !== "move") return false;
132
+ return !sameParentChain(attrs.data.op.to, parentChain.chain);
133
+ });
134
+ if (mismatch == null) {
135
+ return Array.from(suggestionIds).reverse();
136
+ }
137
+ console.log("findOrderedSuggestionIdsToRevert", "suggestion", suggestionId, "contains mark with a mismatched 'to':", mismatch, "searching a different suggestion with the matching 'to'...");
138
+ // find first mark on the node that does have a matching op.to
139
+ const match = mismatch.node.marks.find((mark)=>{
140
+ if (mark.type !== structure) return false;
141
+ const { attrs } = mark;
142
+ if (!guardStructureMarkAttrs(attrs)) return false;
143
+ if (attrs.data.op.op !== "move") return false;
144
+ const nodeId = getNodeId(mismatch.node);
145
+ if (nodeId == null) return false;
146
+ const parentChain = materializedPaths.get(nodeId);
147
+ if (parentChain == null) return false;
148
+ return sameParentChain(attrs.data.op.to, parentChain.chain);
149
+ });
150
+ if (match) {
151
+ console.log("findOrderedSuggestionIdsToRevert", "found suggestin with matching 'to'", match);
152
+ suggestionIds.add(match.attrs["id"]);
153
+ }
154
+ return Array.from(suggestionIds).reverse();
155
+ }
156
+ function findNextStructureMark(node, suggestionId) {
157
+ console.log("findNextStructureMark", suggestionId);
158
+ const { structure } = getSuggestionMarks(node.type.schema);
159
+ const structureMarks = [];
160
+ node.descendants((descendant, pos)=>{
161
+ const mark = descendant.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
162
+ if (mark == null) return true;
163
+ structureMarks.push({
164
+ mark,
165
+ node: descendant,
166
+ $pos: node.resolve(pos)
167
+ });
168
+ return true;
169
+ });
170
+ structureMarks.sort((a, b)=>b.$pos.depth - a.$pos.depth);
171
+ if (structureMarks[0]) {
172
+ console.log("findStructureMark", "found structure mark with id", suggestionId, {
173
+ structureMark: structureMarks[0],
174
+ suggestionId
175
+ });
176
+ } else {
177
+ console.log("findStructureMark", "no structure mark found with id", suggestionId);
178
+ }
179
+ return structureMarks[0];
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-marker/prosemirror-suggest-changes",
3
- "version": "0.3.3-wrap-unwrap.7",
3
+ "version": "0.3.3-wrap-unwrap.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "dist/index.js",
@@ -1,9 +0,0 @@
1
- import { type Node } from "prosemirror-model";
2
- import { Transform } from "prosemirror-transform";
3
- import { type SuggestionId } from "../../generateId.js";
4
- export declare function applyAllStructureSuggestions(node: Node): Transform;
5
- export declare function revertAllStructureSuggestions(node: Node): Transform;
6
- export declare function applyOneStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
7
- export declare function revertOneStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
8
- export declare function applyAllStructureSuggestionsOnNode(node: Node, from?: number, to?: number): Transform;
9
- export declare function revertAllStructureSuggestionsOnNode(node: Node, from?: number, to?: number): Transform;
@@ -1,434 +0,0 @@
1
- import { getSuggestionMarks } from "../../utils.js";
2
- import { getNodeId } from "./getNodeId.js";
3
- import { Transform } from "prosemirror-transform";
4
- import { guardDocParent, guardDocWithChildren, guardStructureMarkAttrs } from "./types.js";
5
- import { sameParentChain } from "./sameParentChain.js";
6
- import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
7
- /* public */ export function applyAllStructureSuggestions(node) {
8
- const tr = new Transform(node);
9
- applyAllStructureMarks(tr);
10
- return tr;
11
- }
12
- export function revertAllStructureSuggestions(node) {
13
- const tr = new Transform(node);
14
- revertAllStructureMarks(tr);
15
- return tr;
16
- }
17
- export function applyOneStructureSuggestion(node, suggestionId) {
18
- const tr = new Transform(node);
19
- applyStructureMarkGroup(tr, suggestionId);
20
- return tr;
21
- }
22
- export function revertOneStructureSuggestion(node, suggestionId) {
23
- const tr = new Transform(node);
24
- revertStructureMarkGroupInOrder(tr, suggestionId);
25
- return tr;
26
- }
27
- export function applyAllStructureSuggestionsOnNode(node, from, to) {
28
- const tr = new Transform(node);
29
- applyAllStructureMarksOnNode(tr, node, from, to);
30
- return tr;
31
- }
32
- export function revertAllStructureSuggestionsOnNode(node, from, to) {
33
- const tr = new Transform(node);
34
- revertAllStructureMarksOnNode(tr, node, from, to);
35
- return tr;
36
- }
37
- /* private */ function applyAllStructureMarks(tr) {
38
- applyAllStructureMarksOnNode(tr, tr.doc);
39
- }
40
- function revertAllStructureMarks(tr) {
41
- revertAllStructureMarksOnNode(tr, tr.doc);
42
- }
43
- function applyAllStructureMarksOnNode(tr, node, from, to) {
44
- const { structure } = getSuggestionMarks(node.type.schema);
45
- const suggestionIds = new Set();
46
- node.descendants((node, pos)=>{
47
- if (from !== undefined && pos < from) {
48
- return true;
49
- }
50
- if (to !== undefined && pos > to) {
51
- return false;
52
- }
53
- if (node.isText) return true;
54
- if (!structure.isInSet(node.marks)) return true;
55
- node.marks.forEach((mark)=>{
56
- if (mark.type !== structure) return;
57
- const suggestionId = mark.attrs["id"];
58
- suggestionIds.add(suggestionId);
59
- });
60
- return true;
61
- });
62
- for (const suggestionId of suggestionIds){
63
- applyStructureMarkGroup(tr, suggestionId);
64
- }
65
- }
66
- function revertAllStructureMarksOnNode(tr, node, from, to) {
67
- const { structure } = getSuggestionMarks(node.type.schema);
68
- // collect all structure mark ids
69
- const suggestionIds = new Set();
70
- node.descendants((node, pos)=>{
71
- if (from !== undefined && pos < from) {
72
- return true;
73
- }
74
- if (to !== undefined && pos > to) {
75
- return false;
76
- }
77
- if (node.isText) return true;
78
- if (!structure.isInSet(node.marks)) return true;
79
- node.marks.forEach((mark)=>{
80
- if (mark.type !== structure) return;
81
- const suggestionId = mark.attrs["id"];
82
- suggestionIds.add(suggestionId);
83
- });
84
- return true;
85
- });
86
- for (const suggestionId of suggestionIds){
87
- revertStructureMarkGroupInOrder(tr, suggestionId);
88
- }
89
- }
90
- function applyStructureMarkGroup(tr, suggestionId) {
91
- const { structure } = getSuggestionMarks(tr.doc.type.schema);
92
- tr.doc.descendants((node, pos)=>{
93
- if (node.isText) return true;
94
- if (!structure.isInSet(node.marks)) return true;
95
- node.marks.forEach((mark)=>{
96
- if (mark.type !== structure) return;
97
- if (mark.attrs["id"] !== suggestionId) return;
98
- applyStructureMark(tr, mark, pos);
99
- });
100
- return true;
101
- });
102
- }
103
- function revertStructureMarkGroupInOrder(tr, suggestionId) {
104
- console.group("revertStructureMarkGroupInOrder", "reverting structure mark group", suggestionId);
105
- const suggestionIds = findOrderedSuggestionIdsToRevert(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
106
- console.log("revertStructureMarkGroupInOrder", "suggestion groups to revert", suggestionIds);
107
- for (const suggestionId of suggestionIds){
108
- revertStructureMarkGroup(tr, suggestionId);
109
- }
110
- console.groupEnd();
111
- }
112
- function revertStructureMarkGroup(tr, suggestionId) {
113
- console.group("revertStructureMarkGroup", "reverting structure suggestion", suggestionId);
114
- let structureMark = findNextStructureMark(tr.doc, suggestionId);
115
- while(structureMark !== null){
116
- console.groupCollapsed("revertStructureMarkGroup", "reverting structure mark", structureMark.mark.attrs["id"], "at pos", structureMark.pos, "at node", structureMark.node.toString(), {
117
- structureMark
118
- });
119
- revertStructureMark(tr, structureMark.mark, structureMark.pos);
120
- console.groupEnd();
121
- structureMark = findNextStructureMark(tr.doc, suggestionId);
122
- }
123
- console.groupEnd();
124
- }
125
- function applyStructureMark(tr, mark, pos) {
126
- tr.removeNodeMark(pos, mark);
127
- }
128
- function revertStructureMark(tr, mark, pos) {
129
- const transform = new Transform(tr.doc);
130
- transform.removeNodeMark(pos, mark);
131
- const node = transform.doc.nodeAt(pos);
132
- if (!node) {
133
- throw new Error(`Node not found at position ${String(pos)}`);
134
- }
135
- console.log("revertStructureMark", mark.attrs["id"], "at node", node.toString(), {
136
- node,
137
- mark,
138
- pos
139
- });
140
- const attrs = mark.attrs;
141
- if (!guardStructureMarkAttrs(attrs)) {
142
- console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
143
- attrs
144
- });
145
- return;
146
- }
147
- const op = attrs.data.op;
148
- switch(op.op){
149
- case "add":
150
- {
151
- revertAdd(op, transform, node, pos);
152
- break;
153
- }
154
- case "move":
155
- {
156
- revertMove(op, transform, node, pos);
157
- break;
158
- }
159
- default:
160
- console.warn("revertStructureMark", "unknown op", {
161
- op
162
- });
163
- break;
164
- }
165
- transform.steps.forEach((step)=>{
166
- tr.step(step);
167
- });
168
- }
169
- function revertAdd(op, tr, node, pos) {
170
- console.log("revertAdd", "node", node.toString(), "was added at", pos, {
171
- op,
172
- node,
173
- pos
174
- });
175
- deleteNodeWithParents(tr, node, pos);
176
- }
177
- function revertMove(op, tr, node, pos) {
178
- console.log("revertMove", "node", node.toString(), "was moved from", op.from, {
179
- op,
180
- node
181
- });
182
- const parent = getDeepestSurvivingParent(op.from, tr.doc);
183
- console.log("revertMove", "deepest surviving parent is", parent.node.toString(), {
184
- parent
185
- });
186
- const child = wrapNodeInParentChain(parent.remainingChain, node);
187
- console.log("revertMove", "wrapped node in parent chain is", child.toString(), {
188
- child
189
- });
190
- const insertionPos = findInsertionPos(parent.node, parent.pos, parent.parent);
191
- console.log("revertMove", "insertion pos", {
192
- insertionPos
193
- });
194
- tr.insert(insertionPos, child);
195
- const mappedPos = tr.mapping.map(pos);
196
- deleteNodeWithParents(tr, node, mappedPos);
197
- }
198
- // given a suggestion id
199
- // return an array of suggestion ids to revert
200
- // resolve suggestion dependencies, meaning,
201
- // if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
202
- // it will return [3,2,1]
203
- // todo: this should probably use topological sort at some point
204
- function findOrderedSuggestionIdsToRevert(node, suggestionId, materializedPaths) {
205
- const { structure } = getSuggestionMarks(node.type.schema);
206
- // collect marks with the given suggestionId
207
- const markGroup = [];
208
- node.descendants((descendant, pos)=>{
209
- if (descendant.isText) return true;
210
- if (!structure.isInSet(descendant.marks)) return true;
211
- descendant.marks.forEach((mark)=>{
212
- if (mark.type !== structure) return;
213
- if (mark.attrs["id"] !== suggestionId) return;
214
- markGroup.push({
215
- mark,
216
- node: descendant,
217
- pos
218
- });
219
- });
220
- return true;
221
- });
222
- const suggestionIds = new Set();
223
- suggestionIds.add(suggestionId);
224
- // find first mark that doesn't have a matching op.to
225
- const mismatch = markGroup.find((mark)=>{
226
- const nodeId = getNodeId(mark.node);
227
- if (nodeId == null) return false;
228
- const parentChain = materializedPaths.get(nodeId);
229
- if (parentChain == null) return false;
230
- const { attrs } = mark.mark;
231
- if (!guardStructureMarkAttrs(attrs)) return false;
232
- if (attrs.data.op.op !== "move") return false;
233
- return !sameParentChain(attrs.data.op.to, parentChain.chain);
234
- });
235
- if (mismatch == null) {
236
- return Array.from(suggestionIds).reverse();
237
- }
238
- console.log("findOrderedSuggestionIdsToRevert", "suggestion", suggestionId, "contains mark with a mismatched 'to':", mismatch, "searching a different suggestion with the matching 'to'...");
239
- // find first mark on the node that does have a matching op.to
240
- const match = mismatch.node.marks.find((mark)=>{
241
- if (mark.type !== structure) return false;
242
- const { attrs } = mark;
243
- if (!guardStructureMarkAttrs(attrs)) return false;
244
- if (attrs.data.op.op !== "move") return false;
245
- const nodeId = getNodeId(mismatch.node);
246
- if (nodeId == null) return false;
247
- const parentChain = materializedPaths.get(nodeId);
248
- if (parentChain == null) return false;
249
- return sameParentChain(attrs.data.op.to, parentChain.chain);
250
- });
251
- if (match) {
252
- console.log("findOrderedSuggestionIdsToRevert", "found suggestin with matching 'to'", match);
253
- suggestionIds.add(match.attrs["id"]);
254
- }
255
- return Array.from(suggestionIds).reverse();
256
- }
257
- function findNextStructureMark(doc, suggestionId) {
258
- console.log("findNextStructureMark", suggestionId);
259
- const { structure } = getSuggestionMarks(doc.type.schema);
260
- let structureMark = null;
261
- doc.nodesBetween(0, doc.content.size, (node, pos)=>{
262
- const mark = node.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
263
- if (mark) {
264
- structureMark = {
265
- mark,
266
- node,
267
- pos
268
- };
269
- }
270
- return structureMark === null;
271
- });
272
- if (structureMark) {
273
- console.log("findStructureMark", "found structure mark with id", suggestionId, {
274
- structureMark,
275
- suggestionId
276
- });
277
- } else {
278
- console.log("findStructureMark", "no structure mark found with id", suggestionId);
279
- }
280
- return structureMark;
281
- }
282
- function getNodesWithChildren(doc) {
283
- const nodesWithChildren = new Map();
284
- const docWithChildren = {
285
- node: doc,
286
- pos: null,
287
- children: new Set()
288
- };
289
- doc.children.forEach((child)=>{
290
- const nodeId = getNodeId(child);
291
- if (nodeId == null) return;
292
- docWithChildren.children.add(nodeId);
293
- });
294
- nodesWithChildren.set("__doc__", docWithChildren);
295
- doc.descendants((node, pos)=>{
296
- if (node.isText) return true;
297
- const nodeId = getNodeId(node);
298
- if (nodeId == null) return true;
299
- if (nodesWithChildren.has(nodeId)) return true;
300
- const children = node.children.reduce((acc, child)=>{
301
- const childId = getNodeId(child);
302
- if (childId == null) return acc;
303
- acc.add(childId);
304
- return acc;
305
- }, new Set());
306
- nodesWithChildren.set(nodeId, {
307
- node,
308
- pos,
309
- children
310
- });
311
- return true;
312
- });
313
- return nodesWithChildren;
314
- }
315
- // given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
316
- // return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
317
- // also return the remaining part of the chain
318
- function getDeepestSurvivingParent(parentChain, doc) {
319
- const chain = [
320
- ...parentChain
321
- ].reverse();
322
- const currentNodes = getNodesWithChildren(doc);
323
- let currentNode = currentNodes.get("__doc__"); // get doc node with children from current doc
324
- if (!guardDocWithChildren(currentNode)) {
325
- throw new Error("doc not found in nodesWithChildren");
326
- }
327
- let parent = chain.shift(); // get doc node descriptor from parent chain
328
- if (!guardDocParent(parent)) {
329
- throw new Error("doc parent not found in op chain");
330
- }
331
- let remainingChain = [];
332
- for (const [index, nextParent] of chain.entries()){
333
- const nextNodeWithChildren = currentNodes.get(nextParent.nodeId);
334
- // nextParent does not exist in the current document at all
335
- if (nextNodeWithChildren == null) {
336
- remainingChain = chain.slice(index);
337
- break;
338
- }
339
- // nextParent exists in the document, but in a different parent chain
340
- if (!currentNode.children.has(nextParent.nodeId)) {
341
- remainingChain = chain.slice(index);
342
- break;
343
- }
344
- currentNode = nextNodeWithChildren;
345
- parent = nextParent;
346
- }
347
- return {
348
- parent,
349
- node: currentNode.node,
350
- pos: currentNode.pos,
351
- remainingChain: [
352
- ...remainingChain
353
- ].reverse()
354
- };
355
- }
356
- // given a chain of parent node descriptors and a node
357
- // wrap the node in the parent chain
358
- function wrapNodeInParentChain(parentChain, node) {
359
- let child = node.copy(node.content);
360
- for (const parent of parentChain){
361
- const schema = node.type.schema;
362
- const nodeType = schema.nodes[parent.nodeType];
363
- if (!nodeType) {
364
- throw new Error(`node type ${parent.nodeType} not found in schema`);
365
- }
366
- const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
367
- child = nodeType.create(parent.nodeAttrs, child, marks);
368
- }
369
- return child;
370
- }
371
- // given a node, its position, and a parent descriptor of this node in some parent chain,
372
- // use the info from the descriptor to find the insertion position in the node
373
- // first try to find siblings, fallback to end of node
374
- function findInsertionPos(node, pos, parent) {
375
- let leftSibling = null;
376
- let rightSibling = null;
377
- node.descendants((child, localChildPos)=>{
378
- const childId = getNodeId(child);
379
- if (childId == null) return false;
380
- const globalChildPos = pos != null ? pos + 1 + localChildPos : localChildPos;
381
- if (parent.childSiblingIds[0] === childId) {
382
- leftSibling = {
383
- node: child,
384
- pos: globalChildPos
385
- };
386
- }
387
- if (parent.childSiblingIds[1] === childId) {
388
- rightSibling = {
389
- node: child,
390
- pos: globalChildPos
391
- };
392
- }
393
- // iterate only direct children
394
- return false;
395
- });
396
- if (rightSibling != null) {
397
- // insert before right sibling
398
- return rightSibling.pos;
399
- }
400
- if (leftSibling != null) {
401
- // insert after left sibling
402
- return leftSibling.pos + leftSibling.node.nodeSize;
403
- }
404
- // insert at end of node
405
- return pos != null ? pos + node.nodeSize - 1 : node.nodeSize - 1;
406
- }
407
- // delete a given node, and traverse upwards deleting parent nodes if they are now empty
408
- function deleteNodeWithParents(transform, node, pos) {
409
- let $mappedPos = transform.doc.resolve(pos);
410
- let deleteFrom = $mappedPos.pos;
411
- let deleteTo = $mappedPos.pos + node.nodeSize;
412
- console.log("deleteNodeWithParents", "initial delete range covers node", node.toString(), {
413
- deleteFrom,
414
- deleteTo,
415
- $mappedPos
416
- });
417
- while($mappedPos.depth > 0){
418
- $mappedPos = transform.doc.resolve($mappedPos.before());
419
- if ($mappedPos.nodeAfter?.childCount !== 1) break;
420
- deleteFrom = $mappedPos.pos;
421
- deleteTo = $mappedPos.pos + $mappedPos.nodeAfter.nodeSize;
422
- console.log("deleteNodeWithParents", "expanded delete range to cover node", $mappedPos.nodeAfter.toString(), {
423
- deleteFrom,
424
- deleteTo,
425
- $mappedPos
426
- });
427
- }
428
- console.log("deleteNodeWithParents", "final delete range covers node", $mappedPos.nodeAfter?.toString(), {
429
- deleteFrom,
430
- deleteTo,
431
- $mappedPos
432
- });
433
- transform.delete(deleteFrom, deleteTo);
434
- }