From 264f0dd2be69e7dbda42a45f461543f4f063ce47 Mon Sep 17 00:00:00 2001 From: Flrande <1978616327@qq.com> Date: Fri, 7 Mar 2025 08:50:31 +0000 Subject: [PATCH] fix(editor): improve backspace ux for callout block (#10696) --- .../block-callout/src/callout-keymap.ts | 34 ++++++++++++++ .../affine/block-callout/src/callout-spec.ts | 2 + .../src/utils/merge-with-prev.ts | 13 +++++- .../e2e/blocksuite/callout/callout.spec.ts | 46 ++++++++++++++++++- tests/kit/src/utils/keyboard.ts | 6 +++ 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 blocksuite/affine/block-callout/src/callout-keymap.ts diff --git a/blocksuite/affine/block-callout/src/callout-keymap.ts b/blocksuite/affine/block-callout/src/callout-keymap.ts new file mode 100644 index 0000000000..138755d7d6 --- /dev/null +++ b/blocksuite/affine/block-callout/src/callout-keymap.ts @@ -0,0 +1,34 @@ +import { CalloutBlockModel } from '@blocksuite/affine-model'; +import { matchModels } from '@blocksuite/affine-shared/utils'; +import { + BlockSelection, + KeymapExtension, + TextSelection, +} from '@blocksuite/block-std'; + +export const CalloutKeymapExtension = KeymapExtension(std => { + return { + Backspace: ctx => { + const text = std.selection.find(TextSelection); + if (text && text.isCollapsed() && text.from.index === 0) { + const event = ctx.get('defaultState').event; + event.preventDefault(); + + const block = std.store.getBlock(text.from.blockId); + if (!block) return false; + const parent = std.store.getParent(block.model); + if (!parent) return false; + if (!matchModels(parent, [CalloutBlockModel])) return false; + + std.selection.setGroup('note', [ + std.selection.create(BlockSelection, { + blockId: parent.id, + }), + ]); + + return true; + } + return false; + }, + }; +}); diff --git a/blocksuite/affine/block-callout/src/callout-spec.ts b/blocksuite/affine/block-callout/src/callout-spec.ts index f4b047ac4d..db583ebcc7 100644 --- a/blocksuite/affine/block-callout/src/callout-spec.ts +++ b/blocksuite/affine/block-callout/src/callout-spec.ts @@ -3,10 +3,12 @@ import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; +import { CalloutKeymapExtension } from './callout-keymap'; import { calloutSlashMenuConfig } from './configs/slash-menu'; export const CalloutBlockSpec: ExtensionType[] = [ FlavourExtension('affine:callout'), BlockViewExtension('affine:callout', literal`affine-callout`), + CalloutKeymapExtension, SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig), ]; diff --git a/blocksuite/affine/block-paragraph/src/utils/merge-with-prev.ts b/blocksuite/affine/block-paragraph/src/utils/merge-with-prev.ts index b7e180da2a..7f6bc447e9 100644 --- a/blocksuite/affine/block-paragraph/src/utils/merge-with-prev.ts +++ b/blocksuite/affine/block-paragraph/src/utils/merge-with-prev.ts @@ -1,6 +1,7 @@ import { AttachmentBlockModel, BookmarkBlockModel, + CalloutBlockModel, CodeBlockModel, DatabaseBlockModel, DividerBlockModel, @@ -53,8 +54,18 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) { return handleNoPreviousSibling(editorHost, model); } + const modelIndex = parent.children.indexOf(model); + const prevSibling = doc.getPrev(model); + if (matchModels(prevSibling, [CalloutBlockModel])) { + editorHost.selection.setGroup('note', [ + editorHost.selection.create(BlockSelection, { + blockId: prevSibling.id, + }), + ]); + return true; + } + if (matchModels(prevBlock, [ParagraphBlockModel, ListBlockModel])) { - const modelIndex = parent.children.indexOf(model); if ( (modelIndex === -1 || modelIndex === parent.children.length - 1) && parent.role === 'content' diff --git a/tests/affine-local/e2e/blocksuite/callout/callout.spec.ts b/tests/affine-local/e2e/blocksuite/callout/callout.spec.ts index 3bed86deba..4332157c46 100644 --- a/tests/affine-local/e2e/blocksuite/callout/callout.spec.ts +++ b/tests/affine-local/e2e/blocksuite/callout/callout.spec.ts @@ -1,4 +1,10 @@ -import { undoByKeyboard } from '@affine-test/kit/utils/keyboard'; +import { + pressArrowDown, + pressArrowUp, + pressBackspace, + pressEnter, + undoByKeyboard, +} from '@affine-test/kit/utils/keyboard'; import { openHomePage } from '@affine-test/kit/utils/load-page'; import { type } from '@affine-test/kit/utils/page-logic'; import { expect, test } from '@playwright/test'; @@ -60,3 +66,41 @@ test('disable slash menu in callout block', async ({ page }) => { await type(page, '/'); await expect(slashMenu).toBeVisible(); }); + +test('press backspace after callout block', async ({ page }) => { + await pressEnter(page); + await pressArrowUp(page); + await type(page, '/callout\n'); + await pressArrowDown(page); + + const paragraph = page.locator('affine-paragraph'); + const callout = page.locator('affine-callout'); + expect(await paragraph.count()).toBe(3); + expect(await callout.count()).toBe(1); + + await pressBackspace(page); + expect(await paragraph.count()).toBe(3); + expect(await callout.count()).toBe(1); + + await pressBackspace(page); + await expect(paragraph).toHaveCount(2); + await expect(callout).toHaveCount(0); +}); + +test('press backspace in callout block', async ({ page }) => { + const paragraph = page.locator('affine-paragraph'); + const callout = page.locator('affine-callout'); + + await type(page, '/callout\n'); + + expect(await paragraph.count()).toBe(2); + expect(await callout.count()).toBe(1); + + await pressBackspace(page); + await expect(paragraph).toHaveCount(2); + await expect(callout).toHaveCount(1); + + await pressBackspace(page); + await expect(paragraph).toHaveCount(1); + await expect(callout).toHaveCount(0); +}); diff --git a/tests/kit/src/utils/keyboard.ts b/tests/kit/src/utils/keyboard.ts index c3436a38aa..0d70d59c83 100644 --- a/tests/kit/src/utils/keyboard.ts +++ b/tests/kit/src/utils/keyboard.ts @@ -38,6 +38,12 @@ export async function pressArrowUp(page: Page, count = 1) { } } +export async function pressArrowDown(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('ArrowDown', { delay: 20 }); + } +} + export async function pressTab(page: Page) { await page.keyboard.press('Tab', { delay: 50 }); }