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