From 6959a2dab3cdc3b1cd0d2816b695d8fc235ccdca Mon Sep 17 00:00:00 2001 From: doouding Date: Wed, 14 May 2025 13:56:24 +0000 Subject: [PATCH] fix: peekable in edgeless mode (#12271) Fixes [BS-3374](https://linear.app/affine-design/issue/BS-3374/) ## Summary by CodeRabbit - **New Features** - Improved control over when the peek view is shown in "edgeless" editor mode, ensuring it only activates when interacting directly with the relevant component. - **Bug Fixes** - Prevented unintended peek view activation in "edgeless" mode when clicking outside the associated component. - **Tests** - Added end-to-end test verifying the peek view does not open when content is covered by a canvas element. - **Chores** - Added utility function to streamline creating synced pages in edgeless mode during tests. --- .../affine/components/src/peek/peekable.ts | 39 +++++++++++- tests/affine-local/e2e/peek-view.spec.ts | 59 +++++++++++++++++++ tests/kit/src/utils/page-logic.ts | 14 +++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/blocksuite/affine/components/src/peek/peekable.ts b/blocksuite/affine/components/src/peek/peekable.ts index d9a9a0c5b4..f4db6a7650 100644 --- a/blocksuite/affine/components/src/peek/peekable.ts +++ b/blocksuite/affine/components/src/peek/peekable.ts @@ -1,5 +1,8 @@ +import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { isInsideEdgelessEditor } from '@blocksuite/affine-shared/utils'; import type { Constructor } from '@blocksuite/global/utils'; +import { GfxControllerIdentifier } from '@blocksuite/std/gfx'; +import type { BlockModel } from '@blocksuite/store'; import type { LitElement, TemplateResult } from 'lit'; import { PeekableController } from './controller.js'; @@ -47,6 +50,34 @@ export const Peekable = const derivedClass = class extends Class { [symbol] = new PeekableController(this as unknown as T, options.enableOn); + /** + * In edgeless mode, we need to check if the click target is not covered by + * other elements. If it is, we should not show the peek view. + */ + private _peekableInEdgeless(e: MouseEvent) { + const docModeService = this.std.getOptional(DocModeProvider); + + if ( + !('model' in this) || + !docModeService || + docModeService.getEditorMode() !== 'edgeless' + ) { + return true; + } + + const model = this['model'] as BlockModel; + const gfx = this.std.get(GfxControllerIdentifier); + const hitTarget = gfx.getElementByPoint( + ...gfx.viewport.toModelCoordFromClientCoord([e.clientX, e.clientY]) + ); + + if (hitTarget && hitTarget !== model) { + return false; + } + + return true; + } + override connectedCallback() { super.connectedCallback(); @@ -56,7 +87,7 @@ export const Peekable = if (actions.includes('double-click')) { this.disposables.addFromEvent(target, 'dblclick', e => { - if (this[symbol].peekable) { + if (this[symbol].peekable && this._peekableInEdgeless(e)) { e.stopPropagation(); this[symbol].peek().catch(console.error); } @@ -68,7 +99,11 @@ export const Peekable = !isInsideEdgelessEditor(this.std.host) ) { this.disposables.addFromEvent(target, 'click', e => { - if (e.shiftKey && this[symbol].peekable) { + if ( + e.shiftKey && + this[symbol].peekable && + this._peekableInEdgeless(e) + ) { e.stopPropagation(); e.stopImmediatePropagation(); this[symbol].peek().catch(console.error); diff --git a/tests/affine-local/e2e/peek-view.spec.ts b/tests/affine-local/e2e/peek-view.spec.ts index e39657eca8..2c856ca4d6 100644 --- a/tests/affine-local/e2e/peek-view.spec.ts +++ b/tests/affine-local/e2e/peek-view.spec.ts @@ -1,5 +1,6 @@ import { test } from '@affine-test/kit/playwright'; import { + clickEdgelessModeButton, getSelectedXYWH, getViewportBound, } from '@affine-test/kit/utils/editor'; @@ -8,6 +9,7 @@ import { openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, createLinkedPage, + createSyncedPageInEdgeless, type, waitForEmptyEditor, } from '@affine-test/kit/utils/page-logic'; @@ -104,6 +106,63 @@ test('can open peek view via db+click link card', async ({ page }) => { ).toBeVisible(); }); +test('should not open peek view when content is covered by canvas element', async ({ + page, +}) => { + await page.keyboard.press('Enter'); + await clickEdgelessModeButton(page); + + await createSyncedPageInEdgeless(page, 'Test Page'); + + const syncedDocBlock = await page.locator( + 'affine-embed-edgeless-synced-doc-block' + ); + const syncedDocRect = (await syncedDocBlock.boundingBox())!; + + await expect(syncedDocBlock).toBeDefined(); + + const syncedDocCenter = { + x: syncedDocRect.x + syncedDocRect.width / 2, + y: syncedDocRect.y + syncedDocRect.height / 2, + }; + + // click to make sure peek view is working + await page.mouse.move(syncedDocCenter.x, syncedDocCenter.y, { + steps: 10, + }); + await page.mouse.dblclick(syncedDocCenter.x, syncedDocCenter.y); + await page.waitForTimeout(100); + await expect(page.getByTestId('peek-view-modal')).toBeVisible(); + + // close peek view + await page + .locator('[data-testid="peek-view-control"][data-action-name="close"]') + .click(); + await expect(page.getByTestId('peek-view-modal')).not.toBeVisible(); + + // create a shape covering the synced doc block + await page.locator('edgeless-toolbar-button.edgeless-shape-button').click(); + await page.mouse.move(syncedDocRect.x, syncedDocRect.y); + await page.mouse.down(); + await page.mouse.move( + syncedDocRect.x + syncedDocRect.width, + syncedDocRect.y + syncedDocRect.height, + { + steps: 10, + } + ); + await page.mouse.up(); + await page.waitForTimeout(100); + + // click the same position again + await page.mouse.move(syncedDocCenter.x, syncedDocCenter.y, { + steps: 10, + }); + await page.mouse.dblclick(syncedDocCenter.x, syncedDocCenter.y); + // should not open peek view because the synced doc block is covered by shape + await expect(page.getByTestId('peek-view-modal')).not.toBeVisible(); +}); + test('can open peek view for embedded frames', async ({ page }) => { const frameInViewport = async () => { const peekView = page.locator('[data-testid="peek-view-modal"]'); diff --git a/tests/kit/src/utils/page-logic.ts b/tests/kit/src/utils/page-logic.ts index c22934fdc2..94c9807a81 100644 --- a/tests/kit/src/utils/page-logic.ts +++ b/tests/kit/src/utils/page-logic.ts @@ -75,6 +75,20 @@ export const createLinkedPage = async (page: Page, pageName?: string) => { .click(); }; +export const createSyncedPageInEdgeless = async ( + page: Page, + pageName?: string +) => { + await page.keyboard.type('@', { delay: 50 }); + const cmdkPopover = page.locator('[data-testid="cmdk-quick-search"]'); + await expect(cmdkPopover).toBeVisible(); + await type(page, pageName || 'Untitled'); + + await cmdkPopover + .locator('[cmdk-item][data-value="creation:create-page"]') + .click(); +}; + export const createTodayPage = async (page: Page) => { // fixme: workaround for @ popover not showing up when editor is not ready await page.waitForTimeout(500);