From 3e7e81516b1b5e0a45b7dc84a0f38df2303a34df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Hamburger=20Gr=C3=B8ngaard?= Date: Thu, 31 Oct 2024 09:49:54 +0100 Subject: [PATCH] fix(editor): handle edge case with deleting empty text blocks next to block objects --- .../src/editor/behavior/behavior.core.ts | 97 ++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/editor/behavior/behavior.core.ts b/packages/editor/src/editor/behavior/behavior.core.ts index 9a4de733..4829c752 100644 --- a/packages/editor/src/editor/behavior/behavior.core.ts +++ b/packages/editor/src/editor/behavior/behavior.core.ts @@ -1,5 +1,13 @@ +import {isPortableTextTextBlock} from '@sanity/types' import {defineBehavior} from './behavior.types' -import {getFocusBlockObject} from './behavior.utils' +import { + getFocusBlockObject, + getFocusTextBlock, + getNextBlock, + getPreviousBlock, + isEmptyTextBlock, + selectionIsCollapsed, +} from './behavior.utils' const softReturn = defineBehavior({ on: 'insert soft break', @@ -16,4 +24,89 @@ const breakingVoidBlock = defineBehavior({ actions: [() => [{type: 'insert text block', decorators: []}]], }) -export const coreBehaviors = [softReturn, breakingVoidBlock] +const deletingEmptyTextBlockAfterBlockObject = defineBehavior({ + on: 'delete backward', + guard: ({context}) => { + const focusTextBlock = getFocusTextBlock(context) + const selectionCollapsed = selectionIsCollapsed(context) + const previousBlock = getPreviousBlock(context) + + if (!focusTextBlock || !selectionCollapsed || !previousBlock) { + return false + } + + if ( + isEmptyTextBlock(focusTextBlock.node) && + !isPortableTextTextBlock(previousBlock.node) + ) { + return {focusTextBlock, previousBlock} + } + + return false + }, + actions: [ + (_, {focusTextBlock, previousBlock}) => [ + { + type: 'delete', + selection: { + anchor: {path: focusTextBlock.path, offset: 0}, + focus: {path: focusTextBlock.path, offset: 0}, + }, + }, + { + type: 'select', + selection: { + anchor: {path: previousBlock.path, offset: 0}, + focus: {path: previousBlock.path, offset: 0}, + }, + }, + ], + ], +}) + +const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({ + on: 'delete forward', + guard: ({context}) => { + const focusTextBlock = getFocusTextBlock(context) + const selectionCollapsed = selectionIsCollapsed(context) + const nextBlock = getNextBlock(context) + + if (!focusTextBlock || !selectionCollapsed || !nextBlock) { + return false + } + + if ( + isEmptyTextBlock(focusTextBlock.node) && + !isPortableTextTextBlock(nextBlock.node) + ) { + return {focusTextBlock, nextBlock} + } + + return false + }, + actions: [ + (_, {focusTextBlock, nextBlock}) => [ + { + type: 'delete', + selection: { + anchor: {path: focusTextBlock.path, offset: 0}, + focus: {path: focusTextBlock.path, offset: 0}, + }, + }, + { + type: 'select', + selection: { + anchor: {path: nextBlock.path, offset: 0}, + focus: {path: nextBlock.path, offset: 0}, + }, + }, + ], + ], +}) + +export const coreBehaviors = [ + softReturn, + breakingVoidBlock, + deletingEmptyTextBlockAfterBlockObject, + deletingEmptyTextBlockBeforeBlockObject, +]