Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/slate-diff/handle-change-one-node.ts
1698 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { isEqual } from "lodash";
7
import { copy_without } from "@cocalc/util/misc";
8
import { Node, Operation } from "slate";
9
import { slateDiff } from "./diff";
10
11
// Replace node at path by nextNode using the first
12
// strategy that works.
13
export function handleChangeOneNode(
14
node: Node,
15
nextNode: Node,
16
path: number[]
17
): Operation[] {
18
for (const strategy of STRATEGIES) {
19
const ops = strategy(node, nextNode, path);
20
if (ops != null) {
21
return ops;
22
}
23
}
24
throw Error("BUG");
25
}
26
27
// We try each of the Handler functions listed below until one of them
28
// matches. When one does, that is used to compute the operations. At
29
// least one will, since the last one is a fallback that works for any
30
// input.
31
32
type Handler = (
33
node: Node,
34
nextNode: Node,
35
path: number[]
36
) => Operation[] | undefined;
37
38
const STRATEGIES: Handler[] = [];
39
40
/*
41
Common special case -- only the children change:
42
43
If we have two blocks and only the children change,
44
we recursively call our top level diff algorithm on
45
those children. */
46
STRATEGIES.push((node, nextNode, path) => {
47
if (
48
node["children"] != null &&
49
nextNode["children"] != null &&
50
isEqual(
51
copy_without(node, ["children"]),
52
copy_without(nextNode, ["children"])
53
)
54
) {
55
return slateDiff(node["children"], nextNode["children"], path);
56
}
57
});
58
59
/* Common special case -- only the value property changes:
60
61
A common special case is that one (or more) properties changes, e.g.,
62
when editing a fenced code block, checkbox, etc., the value
63
property changes but nothing else does. Using set_node we can
64
deal with anything changing except children/text.
65
*/
66
STRATEGIES.push((node, nextNode, path) => {
67
const properties: any = {};
68
const newProperties: any = {};
69
for (const key in node) {
70
if (!isEqual(node[key], nextNode[key])) {
71
if (key == "children" || key == "text") return; // can't do via set_node
72
properties[key] = node[key];
73
newProperties[key] = nextNode[key];
74
}
75
}
76
for (const key in nextNode) {
77
if (node[key] == undefined) {
78
if (key == "children" || key == "text") return; // can't do via set_node
79
newProperties[key] = nextNode[key];
80
}
81
}
82
// set_node can change everything except the children and text fields.
83
return [
84
{
85
type: "set_node",
86
path,
87
properties,
88
newProperties,
89
},
90
];
91
});
92
93
// TODO: we could combine the above two, where children changes *and* any
94
// property changes (except text).
95
// I can't think of any case where that actually happens though.
96
97
/*
98
Generic fallback strategy if nothing else works:
99
100
Just remove and set, since that's the only thing to do generically.
101
We want to avoid this as much as possible, since it is not efficient
102
and breaks the cursor selection! This will always work though.
103
*/
104
// IMPORTANT: this must be added last!
105
STRATEGIES.push((node, nextNode, path) => {
106
return [
107
{
108
type: "remove_node",
109
path,
110
node,
111
},
112
{
113
type: "insert_node",
114
path,
115
node: nextNode,
116
},
117
];
118
});
119
120