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

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.
@@ -1,9 +1,8 @@
1
1
  import { type Transform } from "prosemirror-transform";
2
2
  import { type MoveOp } from "../types.js";
3
3
  import { type Node } from "prosemirror-model";
4
- import { type NodeWithChildren, type Parent } from "../types.js";
4
+ import { type Parent } from "../types.js";
5
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
6
  export declare function getDeepestSurvivingParent(parentChain: Parent[], doc: Node): {
8
7
  parent: Parent;
9
8
  node: Node;
@@ -11,4 +10,7 @@ export declare function getDeepestSurvivingParent(parentChain: Parent[], doc: No
11
10
  remainingChain: Parent[];
12
11
  };
13
12
  export declare function wrapNodeInParentChain(parentChain: Parent[], node: Node): Node;
14
- export declare function findInsertionPos(node: Node, pos: number | null, parent: Parent): number;
13
+ export declare function findInsertionPos(node: Node, pos: number | null, parent: Parent, child: Node): number | {
14
+ from: number;
15
+ to: number;
16
+ };
@@ -1,6 +1,5 @@
1
1
  import { deleteNodeUpwards } from "./deleteNodeUpwards.js";
2
2
  import { getNodeId } from "../getNodeId.js";
3
- import { guardDocParent, guardDocWithChildren } from "../types.js";
4
3
  export function revertMoveOp(op, tr, node, pos) {
5
4
  console.log("revertMoveOp", "node", node.toString(), "was moved from", op.from, {
6
5
  op,
@@ -11,50 +10,15 @@ export function revertMoveOp(op, tr, node, pos) {
11
10
  parent
12
11
  });
13
12
  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);
13
+ const insertTo = findInsertionPos(parent.node, parent.pos, parent.parent, child);
14
+ if (typeof insertTo === "number") {
15
+ tr.insert(insertTo, child);
16
+ } else {
17
+ tr.replaceWith(insertTo.from, insertTo.to, child);
18
+ }
22
19
  const mappedPos = tr.mapping.map(pos);
23
20
  deleteNodeUpwards(tr, node, mappedPos);
24
21
  }
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
22
  // given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
59
23
  // return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
60
24
  // also return the remaining part of the chain
@@ -62,38 +26,41 @@ export function getDeepestSurvivingParent(parentChain, doc) {
62
26
  const chain = [
63
27
  ...parentChain
64
28
  ].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");
29
+ const root = chain.shift();
30
+ if (root == null) {
31
+ throw new Error("Parent chain is empty");
73
32
  }
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;
33
+ let result = {
34
+ parent: root,
35
+ node: doc,
36
+ pos: null
37
+ };
38
+ let remainingChain = [
39
+ ...chain
40
+ ];
41
+ // follow the chain up-down
42
+ // look for the node with the matching id in the children of the previously found node
43
+ for (const [index, item] of chain.entries()){
44
+ let found = false;
45
+ result.node.forEach((child, offset)=>{
46
+ if (found) return;
47
+ if (child.attrs["id"] !== item.nodeId) return;
48
+ found = true;
49
+ const pos = result.pos == null ? offset : result.pos + 1 + offset;
50
+ result = {
51
+ parent: item,
52
+ node: child,
53
+ pos
54
+ };
55
+ remainingChain = chain.slice(index + 1);
56
+ });
57
+ if (!found) break;
89
58
  }
90
59
  return {
91
- parent,
92
- node: currentNode.node,
93
- pos: currentNode.pos,
94
- remainingChain: [
95
- ...remainingChain
96
- ].reverse()
60
+ parent: result.parent,
61
+ node: result.node,
62
+ pos: result.pos,
63
+ remainingChain: remainingChain.reverse()
97
64
  };
98
65
  }
99
66
  // given a chain of parent node descriptors and a node
@@ -107,14 +74,17 @@ export function wrapNodeInParentChain(parentChain, node) {
107
74
  throw new Error(`node type ${parent.nodeType} not found in schema`);
108
75
  }
109
76
  const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
110
- child = nodeType.create(parent.nodeAttrs, child, marks);
77
+ const parentNode = nodeType.createAndFill(parent.nodeAttrs, child, marks);
78
+ if (parentNode == null) throw new Error(`Unable to create node ${nodeType.name} with child ${child.toString()}`);
79
+ child = parentNode;
80
+ child.check();
111
81
  }
112
82
  return child;
113
83
  }
114
84
  // given a node, its position, and a parent descriptor of this node in some parent chain,
115
85
  // use the info from the descriptor to find the insertion position in the node
116
86
  // first try to find siblings, fallback to end of node
117
- export function findInsertionPos(node, pos, parent) {
87
+ export function findInsertionPos(node, pos, parent, child) {
118
88
  let leftSibling = null;
119
89
  let rightSibling = null;
120
90
  node.descendants((child, localChildPos)=>{
@@ -137,6 +107,16 @@ export function findInsertionPos(node, pos, parent) {
137
107
  return false;
138
108
  });
139
109
  if (rightSibling != null) {
110
+ // special case: we need to insert as the first child, but the existing first child is an empty node of the same type
111
+ // in this case, we need to replace the existing first child with the new node
112
+ const firstChild = node.children[0];
113
+ if (parent.childSiblingIds[0] == null && firstChild?.type === child.type && firstChild.textContent === "") {
114
+ const from = pos != null ? pos + 1 : 0;
115
+ return {
116
+ from,
117
+ to: from + firstChild.nodeSize
118
+ };
119
+ }
140
120
  // insert before right sibling
141
121
  return rightSibling.pos;
142
122
  }
@@ -43,11 +43,11 @@ function revertStructureSuggestionWithPrerequisites(tr, suggestionId) {
43
43
  function revertOneStructureSuggestion(tr, suggestionId) {
44
44
  console.group("revertStructureSuggestion", "reverting structure suggestion", suggestionId);
45
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(), {
46
+ while(structureMark !== null){
47
+ console.groupCollapsed("revertStructureSuggestion", "reverting structure mark", structureMark.mark.attrs["id"], "at pos", structureMark.pos, "at node", structureMark.node.toString(), {
48
48
  structureMark
49
49
  });
50
- revertStructureMark(tr, structureMark.mark, structureMark.$pos.pos);
50
+ revertStructureMark(tr, structureMark.mark, structureMark.pos);
51
51
  console.groupEnd();
52
52
  structureMark = findNextStructureMark(tr.doc, suggestionId);
53
53
  }
@@ -154,7 +154,6 @@ function buildOrderedSuggestionIds(node, suggestionId, materializedPaths) {
154
154
  return Array.from(suggestionIds).reverse();
155
155
  }
156
156
  function findNextStructureMark(node, suggestionId) {
157
- console.log("findNextStructureMark", suggestionId);
158
157
  const { structure } = getSuggestionMarks(node.type.schema);
159
158
  const structureMarks = [];
160
159
  node.descendants((descendant, pos)=>{
@@ -163,18 +162,11 @@ function findNextStructureMark(node, suggestionId) {
163
162
  structureMarks.push({
164
163
  mark,
165
164
  node: descendant,
165
+ pos,
166
166
  $pos: node.resolve(pos)
167
167
  });
168
168
  return true;
169
169
  });
170
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];
171
+ return structureMarks[0] ?? null;
180
172
  }
@@ -1,13 +1,4 @@
1
- import { type Attrs, type Node } from "prosemirror-model";
2
- interface NonDocNodeWithChildren {
3
- node: Node;
4
- pos: number;
5
- children: Set<string>;
6
- }
7
- export interface DocWithChildren extends Omit<NonDocNodeWithChildren, "pos"> {
8
- pos: null;
9
- }
10
- export type NodeWithChildren = NonDocNodeWithChildren | DocWithChildren;
1
+ import { type Attrs } from "prosemirror-model";
11
2
  interface NodeParent {
12
3
  nodeId: string;
13
4
  nodeType: string;
@@ -41,5 +32,4 @@ export type MaterializedPaths = Map<string, {
41
32
  }>;
42
33
  export declare function guardDocParent(parent: Parent | undefined): parent is DocParent;
43
34
  export declare function guardStructureMarkAttrs(attrs: Attrs): attrs is StructureMarkAttrs;
44
- export declare function guardDocWithChildren(nodeWithChildren: NodeWithChildren | undefined): nodeWithChildren is DocWithChildren;
45
35
  export {};
@@ -8,6 +8,3 @@ export function guardStructureMarkAttrs(attrs) {
8
8
  if (!("op" in data)) return false;
9
9
  return true;
10
10
  }
11
- export function guardDocWithChildren(nodeWithChildren) {
12
- return nodeWithChildren != null && nodeWithChildren.pos === null;
13
- }
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export { withSuggestChanges, transformToSuggestionTransaction, } from "./withSug
5
5
  export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled, } from "./ensureSelectionPlugin.js";
6
6
  export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
7
7
  export type { Op as StructureOp, StructureMarkAttrs, } from "./features/wrapUnwrap/types.js";
8
+ export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled } from "./pl
4
4
  export { withSuggestChanges, transformToSuggestionTransaction } from "./withSuggestChanges.js";
5
5
  export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled } from "./ensureSelectionPlugin.js";
6
6
  export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
7
+ export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
@@ -0,0 +1,2 @@
1
+ import { type NodeType } from "prosemirror-model";
2
+ export declare function listInputRules(bulletListNodeType: NodeType, orderedListNodeType: NodeType): import("prosemirror-inputrules").InputRule[];
@@ -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,4 @@
1
+ import { InputRule } from "prosemirror-inputrules";
2
+ import { type Node } from "prosemirror-model";
3
+ import { type Attrs, type NodeType } from "prosemirror-model";
4
+ export declare function wrappingInputRule(regexp: RegExp, nodeType: NodeType, getAttrs?: Attrs | null | ((matches: RegExpMatchArray) => Attrs | null), joinPredicate?: (match: RegExpMatchArray, node: Node) => boolean): InputRule;
@@ -0,0 +1,28 @@
1
+ import { InputRule } from "prosemirror-inputrules";
2
+ import { canJoin, findWrapping } from "prosemirror-transform";
3
+ import { getSuggestionMarks } from "./utils.js";
4
+ import { ZWSP } from "./constants.js";
5
+ /// return a boolean to indicate whether a join should happen.
6
+ export function wrappingInputRule(regexp, nodeType, getAttrs = null, joinPredicate) {
7
+ return new InputRule(regexp, (state, match, start, end)=>{
8
+ const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
9
+ const tr = state.tr;
10
+ // check and try to preserve zwsp
11
+ const { insertion } = getSuggestionMarks(state.doc.type.schema);
12
+ let $start = tr.doc.resolve(start);
13
+ if (insertion.isInSet($start.nodeAfter?.marks ?? []) && match[0].startsWith(ZWSP)) {
14
+ // preserve a single ZWSP at the start
15
+ tr.delete(start + 1, end);
16
+ } else {
17
+ tr.delete(start, end);
18
+ }
19
+ // the rest of the rule unchanged
20
+ $start = tr.doc.resolve(start);
21
+ const range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs);
22
+ if (!wrapping) return null;
23
+ tr.wrap(range, wrapping);
24
+ const before = tr.doc.resolve(start - 1).nodeBefore;
25
+ if (before && before.type == nodeType && canJoin(tr.doc, start - 1) && (!joinPredicate || joinPredicate(match, before))) tr.join(start - 1);
26
+ return tr;
27
+ });
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-marker/prosemirror-suggest-changes",
3
- "version": "0.3.3-wrap-unwrap.8",
3
+ "version": "0.3.3-wrap-unwrap.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "dist/index.js",