From 77d9f60ab5e497aadf2d0c9564b1e79525984734 Mon Sep 17 00:00:00 2001 From: Jim Fisher Date: Sun, 24 Oct 2021 15:31:00 +0100 Subject: [PATCH] Fix crash when a void node deletes itself on click (#4616) * Fix crash when a void node deletes itself on click Fixes https://github.com/ianstormtaylor/slate/issues/4240 * Add 'image delete' feature to example My immediate motivation is to demonstrate the bug that this fixes. But this is also a very common editor feature, and I think it's valuable to show how to achieve it. * add changeset * fix:eslint * revert changes to mentions.tsx --- .changeset/curly-ghosts-look.md | 5 +++ .../slate-react/src/components/editable.tsx | 32 ++++++++++------ site/examples/images.tsx | 38 ++++++++++++++++++- 3 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 .changeset/curly-ghosts-look.md diff --git a/.changeset/curly-ghosts-look.md b/.changeset/curly-ghosts-look.md new file mode 100644 index 000000000..a2adaced4 --- /dev/null +++ b/.changeset/curly-ghosts-look.md @@ -0,0 +1,5 @@ +--- +'slate-react': patch +--- + +Fixed crash on self-deleting void node diff --git a/packages/slate-react/src/components/editable.tsx b/packages/slate-react/src/components/editable.tsx index 2df1ec4bb..03ebe1849 100644 --- a/packages/slate-react/src/components/editable.tsx +++ b/packages/slate-react/src/components/editable.tsx @@ -706,19 +706,29 @@ export const Editable = (props: EditableProps) => { ) { const node = ReactEditor.toSlateNode(editor, event.target) const path = ReactEditor.findPath(editor, node) - const start = Editor.start(editor, path) - const end = Editor.end(editor, path) - const startVoid = Editor.void(editor, { at: start }) - const endVoid = Editor.void(editor, { at: end }) + // At this time, the Slate document may be arbitrarily different, + // because onClick handlers can change the document before we get here. + // Therefore we must check that this path actually exists, + // and that it still refers to the same node. + if (Editor.hasPath(editor, path)) { + const lookupNode = Node.get(editor, path) + if (lookupNode === node) { + const start = Editor.start(editor, path) + const end = Editor.end(editor, path) - if ( - startVoid && - endVoid && - Path.equals(startVoid[1], endVoid[1]) - ) { - const range = Editor.range(editor, start) - Transforms.select(editor, range) + const startVoid = Editor.void(editor, { at: start }) + const endVoid = Editor.void(editor, { at: end }) + + if ( + startVoid && + endVoid && + Path.equals(startVoid[1], endVoid[1]) + ) { + const range = Editor.range(editor, start) + Transforms.select(editor, range) + } + } } } }, diff --git a/site/examples/images.tsx b/site/examples/images.tsx index 30fb3964a..f406c734f 100644 --- a/site/examples/images.tsx +++ b/site/examples/images.tsx @@ -9,6 +9,7 @@ import { useSelected, useFocused, withReact, + ReactEditor, } from 'slate-react' import { withHistory } from 'slate-history' import { css } from 'emotion' @@ -89,12 +90,20 @@ const Element = props => { } const Image = ({ attributes, children, element }) => { + const editor = useSlateStatic() + const path = ReactEditor.findPath(editor, element) + const selected = useSelected() const focused = useFocused() return (
{children} -
+
{ box-shadow: ${selected && focused ? '0 0 0 3px #B4D5FF' : 'none'}; `} /> +
) @@ -159,6 +181,20 @@ const initialValue: Descendant[] = [ }, ], }, + { + type: 'paragraph', + children: [ + { + text: + 'You can delete images with the cross in the top left. Try deleting this sheep:', + }, + ], + }, + { + type: 'image', + url: 'https://source.unsplash.com/zOwZKwZOZq8', + children: [{ text: '' }], + }, ] export default ImagesExample