@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.11 → 0.3.3-wrap-unwrap.13
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/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +3 -2
- package/dist/features/wrapUnwrap/constants.d.ts +1 -1
- package/dist/features/wrapUnwrap/constants.js +1 -5
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +1 -1
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +6 -2
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +60 -20
- package/dist/features/wrapUnwrap/types.d.ts +4 -2
- package/dist/features/wrapUnwrap/types.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/testing/e2eTestSchema.d.ts +1 -1
- package/dist/testing/testBuilders.d.ts +1 -1
- package/dist/withSuggestChanges.d.ts +2 -0
- package/dist/withSuggestChanges.js +6 -4
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getNodeId } from "./getNodeId.js";
|
|
2
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
2
3
|
const TRACE_ENABLED = true;
|
|
3
4
|
function trace(...args) {
|
|
4
5
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -17,8 +18,8 @@ export function buildMaterializedPaths(doc) {
|
|
|
17
18
|
const rightSibling = children[index + 1];
|
|
18
19
|
const rightSiblingId = rightSibling ? getNodeId(rightSibling) : null;
|
|
19
20
|
const parent = {
|
|
20
|
-
nodeId:
|
|
21
|
-
nodeType:
|
|
21
|
+
nodeId: DOC_NODE_ID,
|
|
22
|
+
nodeType: DOC_NODE_ID,
|
|
22
23
|
nodeAttrs: {},
|
|
23
24
|
nodeMarks: [],
|
|
24
25
|
childSiblingIds: [
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
+
export const DOC_NODE_ID = "__doc__";
|
|
1
2
|
export const STRUCTURE_CHANGES_ADD_MARKS = "structure-changes-add-mark-type";
|
|
2
3
|
export const STRUCTURE_CHANGES_REVERT_MARKS = "structure-changes-revert-mark-type";
|
|
3
|
-
export const LIST_NODES = [
|
|
4
|
-
"orderedList",
|
|
5
|
-
"bulletList",
|
|
6
|
-
"listItem"
|
|
7
|
-
];
|
|
@@ -118,5 +118,5 @@ export function findInsertionPos(node, pos, parent, child) {
|
|
|
118
118
|
return leftSibling.pos + leftSibling.node.nodeSize;
|
|
119
119
|
}
|
|
120
120
|
// insert at end of node
|
|
121
|
-
return pos != null ? pos + node.nodeSize - 1 : node.
|
|
121
|
+
return pos != null ? pos + node.nodeSize - 1 : node.content.size;
|
|
122
122
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type Schema, type Node } from "prosemirror-model";
|
|
2
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
3
|
import { type SuggestionId } from "../../generateId.js";
|
|
4
|
+
import { type StructuralContextPath } from "./types.js";
|
|
4
5
|
import { Transform } from "prosemirror-transform";
|
|
5
6
|
export declare const structureChangesKey: PluginKey<any>;
|
|
6
|
-
export declare function structureChangesPlugin(generateId?: (schema: Schema, doc?: Node) => SuggestionId
|
|
7
|
-
|
|
7
|
+
export declare function structureChangesPlugin(generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
8
|
+
experimental_trackStructures?: StructuralContextPath[];
|
|
9
|
+
}): Plugin<any>;
|
|
10
|
+
export declare function suggestStructureChanges(docBefore: Node, docAfter: Node, structuralContextPaths: StructuralContextPath[], generateId?: (schema: Schema, doc?: Node) => SuggestionId): {
|
|
8
11
|
handled: boolean;
|
|
9
12
|
transform: Transform;
|
|
10
13
|
};
|
|
14
|
+
export declare function getRequiredStructuralContextPaths(structuralContextPaths: StructuralContextPath[] | undefined): StructuralContextPath[];
|
|
@@ -3,7 +3,7 @@ import { getSuggestionMarks } from "../../utils.js";
|
|
|
3
3
|
import { generateNextNumberId } from "../../generateId.js";
|
|
4
4
|
import { getNodeId } from "./getNodeId.js";
|
|
5
5
|
import { guardStructureMarkAttrs } from "./types.js";
|
|
6
|
-
import {
|
|
6
|
+
import { DOC_NODE_ID, STRUCTURE_CHANGES_ADD_MARKS } from "./constants.js";
|
|
7
7
|
import { Transform } from "prosemirror-transform";
|
|
8
8
|
import { isSuggestChangesEnabled, suggestChangesKey } from "../../plugin.js";
|
|
9
9
|
import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
|
|
@@ -15,12 +15,8 @@ function trace(...args) {
|
|
|
15
15
|
console.log("[structureChanges]", ...args);
|
|
16
16
|
}
|
|
17
17
|
export const structureChangesKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-structure-changes");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// children: [{ type: [LIST_ITEM_NODE] }],
|
|
21
|
-
// };
|
|
22
|
-
// const structures = [listStructure];
|
|
23
|
-
export function structureChangesPlugin(generateId) {
|
|
18
|
+
export function structureChangesPlugin(generateId, opts) {
|
|
19
|
+
const structuralContextPaths = getRequiredStructuralContextPaths(opts?.experimental_trackStructures);
|
|
24
20
|
return new Plugin({
|
|
25
21
|
key: structureChangesKey,
|
|
26
22
|
appendTransaction (transactions, _oldState, newState) {
|
|
@@ -92,7 +88,7 @@ export function structureChangesPlugin(generateId) {
|
|
|
92
88
|
trace("structureChangesPlugin", "appendTransaction", [
|
|
93
89
|
...transactions
|
|
94
90
|
]);
|
|
95
|
-
const { transform } = suggestStructureChanges(oldDoc, newDoc, generateId);
|
|
91
|
+
const { transform } = suggestStructureChanges(oldDoc, newDoc, structuralContextPaths, generateId);
|
|
96
92
|
const tr = newState.tr;
|
|
97
93
|
transform.steps.forEach((step)=>{
|
|
98
94
|
tr.step(step);
|
|
@@ -108,7 +104,7 @@ function isEnabled(tr, editorState) {
|
|
|
108
104
|
const isEnabled = isSuggestChangesEnabled(editorState) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {}));
|
|
109
105
|
return isEnabled;
|
|
110
106
|
}
|
|
111
|
-
export function suggestStructureChanges(docBefore, docAfter, generateId) {
|
|
107
|
+
export function suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId) {
|
|
112
108
|
const suggestionId = generateId ? generateId(docBefore.type.schema, docBefore) : generateNextNumberId(docBefore.type.schema, docBefore);
|
|
113
109
|
const pathsBefore = buildMaterializedPaths(docBefore);
|
|
114
110
|
const pathsAfter = buildMaterializedPaths(docAfter);
|
|
@@ -116,7 +112,7 @@ export function suggestStructureChanges(docBefore, docAfter, generateId) {
|
|
|
116
112
|
pathsBefore: Object.fromEntries(pathsBefore.entries()),
|
|
117
113
|
pathsAfter: Object.fromEntries(pathsAfter.entries())
|
|
118
114
|
});
|
|
119
|
-
const ops = getOps(pathsBefore, pathsAfter);
|
|
115
|
+
const ops = getOps(pathsBefore, pathsAfter, structuralContextPaths);
|
|
120
116
|
trace("suggestStructureChanges", "ops", {
|
|
121
117
|
ops: Object.fromEntries(ops.entries())
|
|
122
118
|
});
|
|
@@ -127,6 +123,12 @@ export function suggestStructureChanges(docBefore, docAfter, generateId) {
|
|
|
127
123
|
transform
|
|
128
124
|
};
|
|
129
125
|
}
|
|
126
|
+
export function getRequiredStructuralContextPaths(structuralContextPaths) {
|
|
127
|
+
if (!structuralContextPaths?.length) {
|
|
128
|
+
throw new Error("experimental_trackStructures must be provided when structure tracking is enabled");
|
|
129
|
+
}
|
|
130
|
+
return structuralContextPaths;
|
|
131
|
+
}
|
|
130
132
|
function addMarks(ops, tr, suggestionId) {
|
|
131
133
|
const perfAddMarks = performance.now();
|
|
132
134
|
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
@@ -136,7 +138,14 @@ function addMarks(ops, tr, suggestionId) {
|
|
|
136
138
|
if (nodeId == null) return true;
|
|
137
139
|
const op = ops.get(nodeId);
|
|
138
140
|
if (op == null) return true;
|
|
139
|
-
if (op.op === "move"
|
|
141
|
+
if (op.op === "move") {
|
|
142
|
+
if (hasStructureAddMark(node)) return true;
|
|
143
|
+
const inverseMoveMark = findInverseStructureMoveMark(node, op);
|
|
144
|
+
if (inverseMoveMark) {
|
|
145
|
+
tr.removeNodeMark(pos, inverseMoveMark);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
140
149
|
tr.addNodeMark(pos, structure.create({
|
|
141
150
|
id: suggestionId,
|
|
142
151
|
data: {
|
|
@@ -147,6 +156,16 @@ function addMarks(ops, tr, suggestionId) {
|
|
|
147
156
|
});
|
|
148
157
|
trace("perf", "addMarks", "took", Number((performance.now() - perfAddMarks).toFixed(2)), "ms");
|
|
149
158
|
}
|
|
159
|
+
function findInverseStructureMoveMark(node, op) {
|
|
160
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
161
|
+
return node.marks.find((mark)=>{
|
|
162
|
+
if (mark.type !== structure) return false;
|
|
163
|
+
if (!guardStructureMarkAttrs(mark.attrs)) return false;
|
|
164
|
+
const existingOp = mark.attrs.data.op;
|
|
165
|
+
if (existingOp.op !== "move") return false;
|
|
166
|
+
return sameParentChain(existingOp.from, op.to) && sameParentChain(existingOp.to, op.from);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
150
169
|
function hasStructureAddMark(node) {
|
|
151
170
|
const { structure } = getSuggestionMarks(node.type.schema);
|
|
152
171
|
return node.marks.some((mark)=>{
|
|
@@ -155,20 +174,22 @@ function hasStructureAddMark(node) {
|
|
|
155
174
|
return mark.attrs.data.op.op === "add";
|
|
156
175
|
});
|
|
157
176
|
}
|
|
158
|
-
function getOps(beforePaths, afterPaths) {
|
|
177
|
+
function getOps(beforePaths, afterPaths, structuralContextPaths) {
|
|
159
178
|
const ops = new Map();
|
|
179
|
+
const contextNodeTypes = getStructuralContextNodeTypes(structuralContextPaths);
|
|
160
180
|
// first take care of nodes that exist in both
|
|
161
181
|
for (const [id, beforePath] of beforePaths){
|
|
162
|
-
if (
|
|
182
|
+
if (contextNodeTypes.has(beforePath.nodeType)) continue;
|
|
163
183
|
const afterPath = afterPaths.get(id);
|
|
164
184
|
// node was removed - do nothing
|
|
165
185
|
if (afterPath == null) continue;
|
|
186
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
166
187
|
const sameChain = sameParentChain(beforePath.chain, afterPath.chain);
|
|
167
188
|
// node did not move anywhere - do nothing
|
|
168
189
|
if (sameChain) continue;
|
|
169
|
-
const
|
|
170
|
-
// node is outside
|
|
171
|
-
if (!
|
|
190
|
+
const hasStructuralContext = chainHasStructuralContext(beforePath.chain, structuralContextPaths) || chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
191
|
+
// node is outside configured structural contexts
|
|
192
|
+
if (!hasStructuralContext) continue;
|
|
172
193
|
const op = {
|
|
173
194
|
op: "move",
|
|
174
195
|
from: beforePath.chain,
|
|
@@ -179,12 +200,12 @@ function getOps(beforePaths, afterPaths) {
|
|
|
179
200
|
// now take care of nodes that exist only in afterPaths
|
|
180
201
|
// (we don't care about nodes that exist only in beforePaths - they were deleted)
|
|
181
202
|
for (const [id, afterPath] of afterPaths){
|
|
182
|
-
if (
|
|
203
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
183
204
|
// ignore nodes that also exist in beforePaths - they are already handled
|
|
184
205
|
if (beforePaths.has(id)) continue;
|
|
185
|
-
const
|
|
186
|
-
// node is outside
|
|
187
|
-
if (!
|
|
206
|
+
const hasStructuralContext = chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
207
|
+
// node is outside configured structural contexts
|
|
208
|
+
if (!hasStructuralContext) continue;
|
|
188
209
|
// node was added
|
|
189
210
|
const op = {
|
|
190
211
|
op: "add"
|
|
@@ -193,3 +214,22 @@ function getOps(beforePaths, afterPaths) {
|
|
|
193
214
|
}
|
|
194
215
|
return ops;
|
|
195
216
|
}
|
|
217
|
+
function getStructuralContextNodeTypes(structuralContextPaths) {
|
|
218
|
+
return new Set(structuralContextPaths.flat());
|
|
219
|
+
}
|
|
220
|
+
function chainHasStructuralContext(chain, structuralContextPaths) {
|
|
221
|
+
const topDownChain = [
|
|
222
|
+
...chain
|
|
223
|
+
].reverse().filter((parent)=>parent.nodeType !== DOC_NODE_ID).map((parent)=>parent.nodeType);
|
|
224
|
+
return structuralContextPaths.some((path)=>containsContiguousPath(topDownChain, path));
|
|
225
|
+
}
|
|
226
|
+
// does a chain like: __doc__->nodeTypeA->nodeTypeB->nodeTypeC->nodeTypeD
|
|
227
|
+
// contain a structural context path (a "sub chain") like nodeTypeB->nodeTypeC ?
|
|
228
|
+
function containsContiguousPath(chainNodeTypes, structuralContextPath) {
|
|
229
|
+
if (structuralContextPath.length > chainNodeTypes.length) return false;
|
|
230
|
+
for(let start = 0; start <= chainNodeTypes.length - structuralContextPath.length; start++){
|
|
231
|
+
const matches = structuralContextPath.every((nodeType, index)=>chainNodeTypes[start + index] === nodeType);
|
|
232
|
+
if (matches) return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Attrs } from "prosemirror-model";
|
|
2
2
|
import { type SuggestionId } from "../../generateId.js";
|
|
3
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
3
4
|
interface NodeParent {
|
|
4
5
|
nodeId: string;
|
|
5
6
|
nodeType: string;
|
|
@@ -14,9 +15,10 @@ export interface StructureMarkAttrs {
|
|
|
14
15
|
op: Op;
|
|
15
16
|
};
|
|
16
17
|
}
|
|
18
|
+
export type StructuralContextPath = readonly [string, ...string[]];
|
|
17
19
|
export interface DocParent extends Omit<NodeParent, "nodeId"> {
|
|
18
|
-
nodeId:
|
|
19
|
-
nodeType:
|
|
20
|
+
nodeId: typeof DOC_NODE_ID;
|
|
21
|
+
nodeType: typeof DOC_NODE_ID;
|
|
20
22
|
}
|
|
21
23
|
export type Parent = NodeParent | DocParent;
|
|
22
24
|
export interface MoveOp {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
1
2
|
export function guardDocParent(parent) {
|
|
2
|
-
return parent != null && parent.nodeId ===
|
|
3
|
+
return parent != null && parent.nodeId === DOC_NODE_ID && parent.nodeType === DOC_NODE_ID;
|
|
3
4
|
}
|
|
4
5
|
export function guardStructureMarkAttrs(attrs) {
|
|
5
6
|
if (!("data" in attrs)) return false;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled, } from "./p
|
|
|
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 type { Op as StructureOp, StructureMarkAttrs, } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export type { Op as StructureOp, StructureMarkAttrs, StructuralContextPath, } from "./features/wrapUnwrap/types.js";
|
|
8
8
|
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Schema } from "prosemirror-model";
|
|
2
|
-
export declare function createSchema(deletionMarksVisibility?: "hidden" | "visible"): Schema<"blockquote" | "text" | "
|
|
2
|
+
export declare function createSchema(deletionMarksVisibility?: "hidden" | "visible"): Schema<"blockquote" | "text" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "image" | "hard_break" | "orderedList" | "bulletList" | "listItem", "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
|
-
export declare const schema: Schema<"blockquote" | "text" | "
|
|
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
|
};
|
|
@@ -3,6 +3,7 @@ import { type EditorState, type Transaction } from "prosemirror-state";
|
|
|
3
3
|
import { type Transform } from "prosemirror-transform";
|
|
4
4
|
import { type EditorView } from "prosemirror-view";
|
|
5
5
|
import { type SuggestionId } from "./generateId.js";
|
|
6
|
+
import { type StructuralContextPath } from "./features/wrapUnwrap/types.js";
|
|
6
7
|
/**
|
|
7
8
|
* Given a standard transaction from ProseMirror, produce
|
|
8
9
|
* a new transaction that tracks the changes from the original,
|
|
@@ -27,5 +28,6 @@ export declare function transformToSuggestionTransaction(originalTransaction: Tr
|
|
|
27
28
|
*/
|
|
28
29
|
export declare function withSuggestChanges(dispatchTransaction?: EditorView["dispatch"], generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
29
30
|
experimental_trackStructureChanges?: boolean;
|
|
31
|
+
experimental_trackStructures?: StructuralContextPath[];
|
|
30
32
|
experimental_ensureUniqueNodeIds?: (transactions: Transaction[], oldDoc: Node, newDoc: Node) => Transform;
|
|
31
33
|
}): EditorView["dispatch"];
|
|
@@ -10,7 +10,7 @@ import { isSuggestChangesEnabled, suggestChangesKey } from "./plugin.js";
|
|
|
10
10
|
import { generateNextNumberId } from "./generateId.js";
|
|
11
11
|
import { getSuggestionMarks } from "./utils.js";
|
|
12
12
|
import { prependDeletionsWithZWSP } from "./prependDeletionsWithZWSP.js";
|
|
13
|
-
import { suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
13
|
+
import { getRequiredStructuralContextPaths, suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
14
14
|
const TRACE_ENABLED = true;
|
|
15
15
|
function trace(...args) {
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -121,13 +121,15 @@ function getStepHandler(step) {
|
|
|
121
121
|
if (isEnabled) {
|
|
122
122
|
let structureChangesResult = null;
|
|
123
123
|
const docBefore = transaction.docs[0];
|
|
124
|
-
|
|
124
|
+
const structuralContextPaths = opts?.experimental_trackStructureChanges ? getRequiredStructuralContextPaths(opts.experimental_trackStructures) : null;
|
|
125
|
+
const ensureUniqueNodeIds = opts?.experimental_ensureUniqueNodeIds;
|
|
126
|
+
if (transaction.docChanged && docBefore && structuralContextPaths && typeof ensureUniqueNodeIds === "function") {
|
|
125
127
|
trace("trying to track structure changes first...");
|
|
126
128
|
// after a transaction, some nodes may not yet have unique ids (they were just added, and the unique id plugin has not yet run)
|
|
127
129
|
// this hook allows to "post-process" the transaction and add the missing ids
|
|
128
130
|
// basically it allows to run the core logic of the unique ids plugin earlier
|
|
129
131
|
const perfUid = performance.now();
|
|
130
|
-
const uniqueNodeIdsTransform =
|
|
132
|
+
const uniqueNodeIdsTransform = ensureUniqueNodeIds([
|
|
131
133
|
transaction
|
|
132
134
|
], docBefore, transaction.doc);
|
|
133
135
|
trace("perf", "structure", "ensureUniqueNodsIds took", Number((performance.now() - perfUid).toFixed(2)), "ms");
|
|
@@ -137,7 +139,7 @@ function getStepHandler(step) {
|
|
|
137
139
|
// if handled, then ignore the main plugin
|
|
138
140
|
// otherwise use the main plugin
|
|
139
141
|
const perfStructure = performance.now();
|
|
140
|
-
structureChangesResult = suggestStructureChanges(docBefore, docAfter, generateId);
|
|
142
|
+
structureChangesResult = suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId);
|
|
141
143
|
trace("perf", "structure", "suggestStructureChanges took", Number((performance.now() - perfStructure).toFixed(2)), "ms");
|
|
142
144
|
trace("structure changes transform completed", structureChangesResult.transform);
|
|
143
145
|
if (structureChangesResult.handled) {
|