Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/slate-react/components/text.tsx
1698 views
1
import React from "react";
2
import { useRef } from "react";
3
import { Range, Element, Text as SlateText } from "slate";
4
5
import Leaf from "./leaf";
6
import { ReactEditor, useSlateStatic } from "..";
7
import { RenderLeafProps } from "./editable";
8
import { useIsomorphicLayoutEffect } from "../hooks/use-isomorphic-layout-effect";
9
import {
10
KEY_TO_ELEMENT,
11
NODE_TO_ELEMENT,
12
ELEMENT_TO_NODE,
13
} from "../utils/weak-maps";
14
import { isEqual } from "lodash";
15
16
/**
17
* Text.
18
*/
19
20
const Text = (props: {
21
decorations: Range[];
22
isLast: boolean;
23
parent: Element;
24
renderLeaf?: React.FC<RenderLeafProps>;
25
text: SlateText;
26
}) => {
27
const { decorations, isLast, parent, renderLeaf, text } = props;
28
const editor = useSlateStatic();
29
const ref = useRef<HTMLSpanElement>(null);
30
const leaves = SlateText.decorations(text, decorations);
31
const children: React.JSX.Element[] = [];
32
const key = ReactEditor.findKey(editor, text);
33
34
for (let i = 0; i < leaves.length; i++) {
35
const leaf = leaves[i];
36
// We need to use a key specifically for each leaf,
37
// otherwise when doing incremental search it doesn't
38
// properly update (which makes perfect sense).
39
const leaf_key = ReactEditor.findKey(editor, leaf);
40
41
children.push(
42
<Leaf
43
isLast={isLast && i === leaves.length - 1}
44
key={leaf_key.id}
45
leaf={leaf}
46
text={text}
47
parent={parent}
48
renderLeaf={renderLeaf}
49
/>,
50
);
51
}
52
53
// Update element-related weak maps with the DOM element ref.
54
useIsomorphicLayoutEffect(() => {
55
try {
56
// I've seen "TypeError: Invalid value used as weak map key"
57
// happen sometimes in a way I can't reproduce in the line
58
// 'NODE_TO_ELEMENT.set(text, ref.current);' below. Better
59
// to show a warning than crash the browser.
60
// TODO: i don't know what the implications of this are, if any.
61
if (ref.current) {
62
KEY_TO_ELEMENT.set(key, ref.current);
63
NODE_TO_ELEMENT.set(text, ref.current);
64
ELEMENT_TO_NODE.set(ref.current, text);
65
} else {
66
KEY_TO_ELEMENT.delete(key);
67
NODE_TO_ELEMENT.delete(text);
68
}
69
} catch (err) {
70
console.warn(err);
71
}
72
73
// It's also CRITICAL to update the selection after changing the text,
74
// at least when using windowing.
75
// See comment in selection-sync.ts about this.
76
editor.updateDOMSelection?.();
77
});
78
79
return (
80
<span data-slate-node="text" ref={ref}>
81
{children}
82
</span>
83
);
84
};
85
86
/**
87
* Check if a list of ranges is equal to another.
88
*
89
* PERF: this requires the two lists to also have the ranges inside them in the
90
* same order, but this is an okay constraint for us since decorations are
91
* kept in order, and the odd case where they aren't is okay to re-render for.
92
*/
93
94
const isRangeListEqual = (list?: Range[], another?: Range[]): boolean => {
95
if (list == null || another == null) {
96
return list === another;
97
}
98
if (list.length !== another.length) {
99
return false;
100
}
101
102
for (let i = 0; i < list.length; i++) {
103
const range = list[i];
104
const other = another[i];
105
106
if (!isEqual(range, other)) {
107
return false;
108
}
109
}
110
111
return true;
112
};
113
114
const MemoizedText = React.memo(Text, (prev, next) => {
115
// I think including parent is wrong here. E.g.,
116
// parent is not included in the analogous function
117
// in element.tsx. See my comment here:
118
// https://github.com/ianstormtaylor/slate/issues/4056#issuecomment-768059323
119
const is_equal =
120
// next.parent === prev.parent &&
121
next.renderLeaf === prev.renderLeaf &&
122
next.isLast === prev.isLast &&
123
next.text === prev.text &&
124
isRangeListEqual(next.decorations, prev.decorations);
125
/*
126
console.log("Text is_equal", is_equal, [
127
next.renderLeaf === prev.renderLeaf,
128
next.isLast === prev.isLast,
129
next.text === prev.text,
130
isEqual(next.decorations, prev.decorations),
131
]);*/
132
return is_equal;
133
});
134
135
export default MemoizedText;
136
137