Path: blob/master/src/packages/frontend/editors/slate/elements/set-element.ts
1698 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { useRef } from "react";6import { Editor, Element, Transforms } from "slate";7import { rangeAll } from "../slate-util";89export function setElement(10editor: Editor,11element: Element,12obj: object13): Element | undefined {14// Usually when setElement is called, the element we are searching for is right15// near the selection, so this first search finds it.16for (const [, path] of Editor.nodes(editor, {17match: (node) => node === element,18})) {19Transforms.setNodes(editor, obj, { at: path });20// We only want the first one (there are no others, but setNode doesn't have a short circuit option).21// Also, we return the transformed node, so have to find it:22return Editor.node(editor, path)[0] as Element;23}2425// Searching at the selection failed, so we try searching the entire document instead.26// This has to work.27for (const [, path] of Editor.nodes(editor, {28match: (node) => node === element,29at: rangeAll(editor),30})) {31Transforms.setNodes(editor, obj, { at: path });32return Editor.node(editor, path)[0] as Element;33}3435// This situation should never ever happen anymore (see big comment below):36console.warn(37"WARNING: setElement unable to find element in document",38element,39obj40);41}4243export function useSetElement(editor: Editor, element: Element): (obj) => void {44// This is a trick to get around the fact that45// the onChange callback below can't directly reference46// the element, since it gets the version of element47// from when that closure was created.48const elementRef = useRef<Element>(element);49elementRef.current = element;50return (obj) => {51const newElement = setElement(editor, elementRef.current, obj);52if (newElement !== undefined) {53// Here's why we do this: if we call the function returned by useSetElement twice in the same54// render loop, then the above "elementRef.current = element;" doesn't have a chance55// to happen (it happens at most once per render loop), and the second call to setElement56// then fails. Data loss ensues. A way to cause this is when editing code in codemirror,57// then hitting return and getting an indented line (e.g. "def f(): #" then hit return);58// CodeMirror triggers two onChange events with the same content, and the second one causes59// the warning about "setElement unable to find element in document". I'm sure onChange could60// fire in other NOT harmless ways in CodeMirror as well triggering this, and that I've seen61// it, with the result being that something you just typed is undone.62// That's why we imediately set the elementRef here:63elementRef.current = newElement;64}65};66}676869