1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-07-31 20:40:19 +02:00

Fix edge-cases when text leaf nodes are deleted (#5325)

This commit is contained in:
Claudéric Demers
2023-03-02 12:13:36 -05:00
committed by GitHub
parent 4205e0f002
commit af3f828b12
3 changed files with 76 additions and 30 deletions

View File

@@ -0,0 +1,5 @@
---
'slate-react': patch
---
Fix edge-cases in the Android input manager when text leaf nodes are deleted, such as when deleting text leaf nodes adjacent to inline void nodes.

View File

@@ -2,6 +2,7 @@ import { DebouncedFunc } from 'lodash'
import { Editor, Node, Path, Point, Range, Text, Transforms } from 'slate' import { Editor, Node, Path, Point, Range, Text, Transforms } from 'slate'
import { ReactEditor } from '../../plugin/react-editor' import { ReactEditor } from '../../plugin/react-editor'
import { import {
applyStringDiff,
mergeStringDiffs, mergeStringDiffs,
normalizePoint, normalizePoint,
normalizeRange, normalizeRange,
@@ -153,7 +154,7 @@ export function createAndroidInputManager({
EDITOR_TO_PENDING_DIFFS.get(editor) EDITOR_TO_PENDING_DIFFS.get(editor)
) )
let scheduleSelectionChange = !!EDITOR_TO_PENDING_DIFFS.get(editor)?.length let scheduleSelectionChange = hasPendingDiffs()
let diff: TextDiff | undefined let diff: TextDiff | undefined
while ((diff = EDITOR_TO_PENDING_DIFFS.get(editor)?.[0])) { while ((diff = EDITOR_TO_PENDING_DIFFS.get(editor)?.[0])) {
@@ -377,38 +378,75 @@ export function createAndroidInputManager({
return return
} }
if (Range.isExpanded(targetRange) && type.startsWith('delete')) { // By default, the input manager tries to store text diffs so that we can
const [start, end] = Range.edges(targetRange) // defer flushing them at a later point in time. We don't want to flush
const leaf = Node.leaf(editor, start.path) // for every input event as this can be expensive. However, there are some
// scenarios where we cannot safely store the text diff and must instead
// schedule an action to let Slate normalize the editor state.
let canStoreDiff = true
if (leaf.text.length === start.offset && end.offset === 0) { if (type.startsWith('delete')) {
const next = Editor.next(editor, { at: start.path, match: Text.isText }) if (Range.isExpanded(targetRange)) {
if (next && Path.equals(next[1], end.path)) {
targetRange = { anchor: end, focus: end }
}
}
}
if (Range.isExpanded(targetRange) && type.startsWith('delete')) {
if (Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
const [start, end] = Range.edges(targetRange) const [start, end] = Range.edges(targetRange)
const leaf = Node.leaf(editor, start.path)
const point = { path: targetRange.anchor.path, offset: start.offset } if (leaf.text.length === start.offset && end.offset === 0) {
const range = Editor.range(editor, point, point) const next = Editor.next(editor, {
handleUserSelect(range) at: start.path,
match: Text.isText,
return storeDiff(targetRange.anchor.path, { })
text: '', if (next && Path.equals(next[1], end.path)) {
end: end.offset, targetRange = { anchor: end, focus: end }
start: start.offset, }
}) }
} }
const direction = type.endsWith('Backward') ? 'backward' : 'forward' const direction = type.endsWith('Backward') ? 'backward' : 'forward'
return scheduleAction( const [start, end] = Range.edges(targetRange)
() => Editor.deleteFragment(editor, { direction }), const [leaf, path] = Editor.leaf(editor, start.path)
{ at: targetRange }
const diff = {
text: '',
start: start.offset,
end: end.offset,
}
const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor)
const relevantPendingDiffs = pendingDiffs?.find(change =>
Path.equals(change.path, path)
) )
const diffs = relevantPendingDiffs
? [relevantPendingDiffs.diff, diff]
: [diff]
const text = applyStringDiff(leaf.text, ...diffs)
if (text.length === 0) {
// Text leaf will be removed, so we need to schedule an
// action to remove it so that Slate can normalize instead
// of storing as a diff
canStoreDiff = false
}
if (Range.isExpanded(targetRange)) {
if (
canStoreDiff &&
Path.equals(targetRange.anchor.path, targetRange.focus.path)
) {
const point = { path: targetRange.anchor.path, offset: start.offset }
const range = Editor.range(editor, point, point)
handleUserSelect(range)
return storeDiff(targetRange.anchor.path, {
text: '',
end: end.offset,
start: start.offset,
})
}
return scheduleAction(
() => Editor.deleteFragment(editor, { direction }),
{ at: targetRange }
)
}
} }
switch (type) { switch (type) {
@@ -423,7 +461,7 @@ export function createAndroidInputManager({
case 'deleteContent': case 'deleteContent':
case 'deleteContentForward': { case 'deleteContentForward': {
const { anchor } = targetRange const { anchor } = targetRange
if (Range.isCollapsed(targetRange)) { if (canStoreDiff && Range.isCollapsed(targetRange)) {
const targetNode = Node.leaf(editor, anchor.path) const targetNode = Node.leaf(editor, anchor.path)
if (anchor.offset < targetNode.text.length) { if (anchor.offset < targetNode.text.length) {
@@ -451,6 +489,7 @@ export function createAndroidInputManager({
: !!nativeTargetRange?.collapsed : !!nativeTargetRange?.collapsed
if ( if (
canStoreDiff &&
nativeCollapsed && nativeCollapsed &&
Range.isCollapsed(targetRange) && Range.isCollapsed(targetRange) &&
anchor.offset > 0 anchor.offset > 0
@@ -610,8 +649,10 @@ export function createAndroidInputManager({
insertPositionHint = false insertPositionHint = false
} }
storeDiff(start.path, diff) if (canStoreDiff) {
return storeDiff(start.path, diff)
return
}
} }
return scheduleAction(() => Editor.insertText(editor, text), { return scheduleAction(() => Editor.insertText(editor, text), {

View File

@@ -52,7 +52,7 @@ export function verifyDiffState(editor: Editor, textDiff: TextDiff): boolean {
return Text.isText(nextNode) && nextNode.text.startsWith(diff.text) return Text.isText(nextNode) && nextNode.text.startsWith(diff.text)
} }
function applyStringDiff(text: string, ...diffs: StringDiff[]) { export function applyStringDiff(text: string, ...diffs: StringDiff[]) {
return diffs.reduce( return diffs.reduce(
(text, diff) => (text, diff) =>
text.slice(0, diff.start) + diff.text + text.slice(diff.end), text.slice(0, diff.start) + diff.text + text.slice(diff.end),