mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-14 03:03:58 +02:00
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
This commit is contained in:
5
.changeset/curly-ghosts-look.md
Normal file
5
.changeset/curly-ghosts-look.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate-react': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed crash on self-deleting void node
|
@@ -706,19 +706,29 @@ export const Editable = (props: EditableProps) => {
|
|||||||
) {
|
) {
|
||||||
const node = ReactEditor.toSlateNode(editor, event.target)
|
const node = ReactEditor.toSlateNode(editor, event.target)
|
||||||
const path = ReactEditor.findPath(editor, node)
|
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 })
|
// At this time, the Slate document may be arbitrarily different,
|
||||||
const endVoid = Editor.void(editor, { at: end })
|
// 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 (
|
const startVoid = Editor.void(editor, { at: start })
|
||||||
startVoid &&
|
const endVoid = Editor.void(editor, { at: end })
|
||||||
endVoid &&
|
|
||||||
Path.equals(startVoid[1], endVoid[1])
|
if (
|
||||||
) {
|
startVoid &&
|
||||||
const range = Editor.range(editor, start)
|
endVoid &&
|
||||||
Transforms.select(editor, range)
|
Path.equals(startVoid[1], endVoid[1])
|
||||||
|
) {
|
||||||
|
const range = Editor.range(editor, start)
|
||||||
|
Transforms.select(editor, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
useSelected,
|
useSelected,
|
||||||
useFocused,
|
useFocused,
|
||||||
withReact,
|
withReact,
|
||||||
|
ReactEditor,
|
||||||
} from 'slate-react'
|
} from 'slate-react'
|
||||||
import { withHistory } from 'slate-history'
|
import { withHistory } from 'slate-history'
|
||||||
import { css } from 'emotion'
|
import { css } from 'emotion'
|
||||||
@@ -89,12 +90,20 @@ const Element = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Image = ({ attributes, children, element }) => {
|
const Image = ({ attributes, children, element }) => {
|
||||||
|
const editor = useSlateStatic()
|
||||||
|
const path = ReactEditor.findPath(editor, element)
|
||||||
|
|
||||||
const selected = useSelected()
|
const selected = useSelected()
|
||||||
const focused = useFocused()
|
const focused = useFocused()
|
||||||
return (
|
return (
|
||||||
<div {...attributes}>
|
<div {...attributes}>
|
||||||
{children}
|
{children}
|
||||||
<div contentEditable={false}>
|
<div
|
||||||
|
contentEditable={false}
|
||||||
|
className={css`
|
||||||
|
position: relative;
|
||||||
|
`}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={element.url}
|
src={element.url}
|
||||||
className={css`
|
className={css`
|
||||||
@@ -104,6 +113,19 @@ const Image = ({ attributes, children, element }) => {
|
|||||||
box-shadow: ${selected && focused ? '0 0 0 3px #B4D5FF' : 'none'};
|
box-shadow: ${selected && focused ? '0 0 0 3px #B4D5FF' : 'none'};
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
active
|
||||||
|
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||||
|
className={css`
|
||||||
|
display: ${selected && focused ? 'inline' : 'none'};
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
background-color: white;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Icon>delete</Icon>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -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
|
export default ImagesExample
|
||||||
|
Reference in New Issue
Block a user