Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/elements/set-element.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 { useRef } from "react";
7
import { Editor, Element, Transforms } from "slate";
8
import { rangeAll } from "../slate-util";
9
10
export function setElement(
11
editor: Editor,
12
element: Element,
13
obj: object
14
): Element | undefined {
15
// Usually when setElement is called, the element we are searching for is right
16
// near the selection, so this first search finds it.
17
for (const [, path] of Editor.nodes(editor, {
18
match: (node) => node === element,
19
})) {
20
Transforms.setNodes(editor, obj, { at: path });
21
// We only want the first one (there are no others, but setNode doesn't have a short circuit option).
22
// Also, we return the transformed node, so have to find it:
23
return Editor.node(editor, path)[0] as Element;
24
}
25
26
// Searching at the selection failed, so we try searching the entire document instead.
27
// This has to work.
28
for (const [, path] of Editor.nodes(editor, {
29
match: (node) => node === element,
30
at: rangeAll(editor),
31
})) {
32
Transforms.setNodes(editor, obj, { at: path });
33
return Editor.node(editor, path)[0] as Element;
34
}
35
36
// This situation should never ever happen anymore (see big comment below):
37
console.warn(
38
"WARNING: setElement unable to find element in document",
39
element,
40
obj
41
);
42
}
43
44
export function useSetElement(editor: Editor, element: Element): (obj) => void {
45
// This is a trick to get around the fact that
46
// the onChange callback below can't directly reference
47
// the element, since it gets the version of element
48
// from when that closure was created.
49
const elementRef = useRef<Element>(element);
50
elementRef.current = element;
51
return (obj) => {
52
const newElement = setElement(editor, elementRef.current, obj);
53
if (newElement !== undefined) {
54
// Here's why we do this: if we call the function returned by useSetElement twice in the same
55
// render loop, then the above "elementRef.current = element;" doesn't have a chance
56
// to happen (it happens at most once per render loop), and the second call to setElement
57
// then fails. Data loss ensues. A way to cause this is when editing code in codemirror,
58
// then hitting return and getting an indented line (e.g. "def f(): #" then hit return);
59
// CodeMirror triggers two onChange events with the same content, and the second one causes
60
// the warning about "setElement unable to find element in document". I'm sure onChange could
61
// fire in other NOT harmless ways in CodeMirror as well triggering this, and that I've seen
62
// it, with the result being that something you just typed is undone.
63
// That's why we imediately set the elementRef here:
64
elementRef.current = newElement;
65
}
66
};
67
}
68
69