From fd25cd875b5640e076532c269931c751a6e98ba8 Mon Sep 17 00:00:00 2001 From: pengx17 Date: Mon, 10 Feb 2025 15:51:55 +0000 Subject: [PATCH] feat: focus the create page item when query returns no result in at menu (#10060) fix AF-2191 --- .../root-block/widgets/linked-doc/config.ts | 17 ++++++++++ .../widgets/linked-doc/linked-doc-popover.ts | 32 +++++++++++++++++++ .../modules/at-menu-config/services/index.ts | 23 ++++++++++++- tests/affine-local/e2e/links.spec.ts | 15 +++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/config.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/config.ts index 1c957a5ca8..3840d48d9e 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/config.ts @@ -45,6 +45,23 @@ export interface LinkedWidgetConfig { abortSignal: AbortSignal ) => Promise | LinkedMenuGroup[]; + /** + * Auto focused item + * + * Will be called when the menu is + * - opened + * - query changed + * - menu group or its items changed + * + * If the return value is not null, no action will be taken. + */ + autoFocusedItem?: ( + menus: LinkedMenuGroup[], + query: string, + editorHost: EditorHost, + inlineEditor: AffineInlineEditor + ) => LinkedMenuItem | null; + mobile: { useScreenHeight?: boolean; /** diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts index 9fc9329eca..fd55f90dfa 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts @@ -17,6 +17,7 @@ import { throttle, WithDisposable, } from '@blocksuite/global/utils'; +import { effect } from '@preact/signals-core'; import { css, html, LitElement, nothing } from 'lit'; import { property, query, queryAll, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; @@ -47,6 +48,8 @@ export class LinkedDocPopover extends SignalWatcher( private readonly _expanded = new Map(); + private _menusItemsEffectCleanup: () => void = () => {}; + private readonly _updateLinkedDocGroup = async () => { const query = this._query; if (this._updateLinkedDocGroupAbortController) { @@ -65,6 +68,30 @@ export class LinkedDocPopover extends SignalWatcher( this.context.inlineEditor, this._updateLinkedDocGroupAbortController.signal ); + + this._menusItemsEffectCleanup(); + + // need to rebind the effect because this._linkedDocGroup has changed. + this._menusItemsEffectCleanup = effect(() => { + this._updateAutoFocusedItem(); + }); + }; + + private readonly _updateAutoFocusedItem = () => { + if (!this._query) { + return; + } + const autoFocusedItem = this.context.config.autoFocusedItem?.( + this._linkedDocGroup, + this._query, + this.context.std.host, + this.context.inlineEditor + ); + if (autoFocusedItem) { + this._activatedItemIndex = this._flattenActionList.findIndex( + item => item.key === autoFocusedItem.key + ); + } }; private _updateLinkedDocGroupAbortController: AbortController | null = null; @@ -217,6 +244,11 @@ export class LinkedDocPopover extends SignalWatcher( }); } + override disconnectedCallback() { + super.disconnectedCallback(); + this._menusItemsEffectCleanup(); + } + override render() { const MAX_HEIGHT = 380; const style = this._position diff --git a/packages/frontend/core/src/modules/at-menu-config/services/index.ts b/packages/frontend/core/src/modules/at-menu-config/services/index.ts index 44da1bb8de..cc8e4086b4 100644 --- a/packages/frontend/core/src/modules/at-menu-config/services/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/services/index.ts @@ -16,7 +16,7 @@ import { NewXxxEdgelessIcon, NewXxxPageIcon, } from '@blocksuite/icons/lit'; -import { computed } from '@preact/signals-core'; +import { computed, Signal } from '@preact/signals-core'; import { Service } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { html } from 'lit'; @@ -28,6 +28,10 @@ import type { DocSearchMenuService } from '../../doc-search-menu/services'; import type { EditorSettingService } from '../../editor-setting'; import { type JournalService, suggestJournalDate } from '../../journal'; +function resolveSignal(data: T | Signal): T { + return data instanceof Signal ? data.value : data; +} + export class AtMenuConfigService extends Service { constructor( private readonly journalService: JournalService, @@ -46,6 +50,7 @@ export class AtMenuConfigService extends Service { return { getMenus: this.getMenusFn(), mobile: this.getMobileConfig(), + autoFocusedItem: this.autoFocusedItem, }; } @@ -56,6 +61,22 @@ export class AtMenuConfigService extends Service { }); } + private readonly autoFocusedItem = ( + menus: LinkedMenuGroup[], + query: string + ): LinkedMenuItem | null => { + if (query.trim().length === 0) { + return null; + } + // if the second group (linkToDocGroup) is EMPTY, + // if the query is NOT empty && the second group (linkToDocGroup) is EMPTY, + // we will focus on the first item of the third group (create), which is the "New Doc" item. + if (resolveSignal(menus[1].items).length === 0) { + return resolveSignal(menus[2].items)[0]; + } + return null; + }; + private newDocMenuGroup( query: string, close: () => void, diff --git a/tests/affine-local/e2e/links.spec.ts b/tests/affine-local/e2e/links.spec.ts index 4a1e5a0483..15d67c0c8b 100644 --- a/tests/affine-local/e2e/links.spec.ts +++ b/tests/affine-local/e2e/links.spec.ts @@ -473,6 +473,21 @@ test('@ popover with click "select a specific date" should show a date picker', ).toBeVisible(); }); +test('@ popover can auto focus on the "New Doc" item when query returns no items', async ({ + page, +}) => { + await page.keyboard.press('Enter'); + await waitForEmptyEditor(page); + await page.keyboard.press('@'); + await page.keyboard.type('nawowenni'); + await expect(page.locator('.linked-doc-popover')).toBeVisible(); + const newDocMenuItem = page + .locator('.linked-doc-popover') + .locator('[data-id="create-page"]'); + await expect(newDocMenuItem).toBeVisible(); + await expect(newDocMenuItem).toHaveAttribute('hover', 'true'); +}); + test('linked doc should show markdown preview in the backlink section', async ({ page, }) => {