1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-09 16:56:36 +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,22 +378,59 @@ 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
// defer flushing them at a later point in time. We don't want to flush
// 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 (type.startsWith('delete')) {
if (Range.isExpanded(targetRange)) {
const [start, end] = Range.edges(targetRange) const [start, end] = Range.edges(targetRange)
const leaf = Node.leaf(editor, start.path) const leaf = Node.leaf(editor, start.path)
if (leaf.text.length === start.offset && end.offset === 0) { if (leaf.text.length === start.offset && end.offset === 0) {
const next = Editor.next(editor, { at: start.path, match: Text.isText }) const next = Editor.next(editor, {
at: start.path,
match: Text.isText,
})
if (next && Path.equals(next[1], end.path)) { if (next && Path.equals(next[1], end.path)) {
targetRange = { anchor: end, focus: end } targetRange = { anchor: end, focus: end }
} }
} }
} }
if (Range.isExpanded(targetRange) && type.startsWith('delete')) { const direction = type.endsWith('Backward') ? 'backward' : 'forward'
if (Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
const [start, end] = Range.edges(targetRange) const [start, end] = Range.edges(targetRange)
const [leaf, path] = Editor.leaf(editor, start.path)
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 point = { path: targetRange.anchor.path, offset: start.offset }
const range = Editor.range(editor, point, point) const range = Editor.range(editor, point, point)
handleUserSelect(range) handleUserSelect(range)
@@ -404,12 +442,12 @@ export function createAndroidInputManager({
}) })
} }
const direction = type.endsWith('Backward') ? 'backward' : 'forward'
return scheduleAction( return scheduleAction(
() => Editor.deleteFragment(editor, { direction }), () => Editor.deleteFragment(editor, { direction }),
{ at: targetRange } { at: targetRange }
) )
} }
}
switch (type) { switch (type) {
case 'deleteByComposition': case 'deleteByComposition':
@@ -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,9 +649,11 @@ export function createAndroidInputManager({
insertPositionHint = false insertPositionHint = false
} }
if (canStoreDiff) {
storeDiff(start.path, diff) storeDiff(start.path, diff)
return return
} }
}
return scheduleAction(() => Editor.insertText(editor, text), { return scheduleAction(() => Editor.insertText(editor, text), {
at: targetRange, at: targetRange,

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),