From e1fd8f5d805750b4dbd4fe844f785bea2251df3a Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Wed, 26 Feb 2025 07:49:50 +0000 Subject: [PATCH] fix(core): correctly toggle visibility of starter-bar based on doc.isEmpty (#10439) --- .../src/gfx/model/surface/surface-model.ts | 34 +++++++++++++++++-- .../framework/store/src/model/store/store.ts | 12 +++++-- .../block-suite-editor/starter-bar.tsx | 9 ++--- tests/affine-local/e2e/template.spec.ts | 19 +++++++++++ 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/blocksuite/framework/block-std/src/gfx/model/surface/surface-model.ts b/blocksuite/framework/block-std/src/gfx/model/surface/surface-model.ts index 9ef7207954..1c385ce47a 100644 --- a/blocksuite/framework/block-std/src/gfx/model/surface/surface-model.ts +++ b/blocksuite/framework/block-std/src/gfx/model/surface/surface-model.ts @@ -1,6 +1,12 @@ -import { assertType, type Constructor, Slot } from '@blocksuite/global/utils'; +import { + assertType, + type Constructor, + DisposableGroup, + Slot, +} from '@blocksuite/global/utils'; import type { Boxed } from '@blocksuite/store'; import { BlockModel, nanoid } from '@blocksuite/store'; +import { signal } from '@preact/signals-core'; import * as Y from 'yjs'; import { @@ -98,6 +104,8 @@ export class SurfaceBlockModel extends BlockModel { oldValues: Record; }>(); + private readonly _isEmpty$ = signal(false); + get elementModels() { const models: GfxPrimitiveElementModel[] = []; this._elementModels.forEach(model => models.push(model.model)); @@ -113,7 +121,7 @@ export class SurfaceBlockModel extends BlockModel { } override isEmpty(): boolean { - return this._elementModels.size === 0 && this.children.length === 0; + return this._isEmpty$.value; } constructor() { @@ -370,6 +378,7 @@ export class SurfaceBlockModel extends BlockModel { if (isGfxGroupCompatibleModel(payload.model)) { this._groupLikeModels.set(payload.id, payload.model); } + break; case 'delete': if (isGfxGroupCompatibleModel(payload.model)) { @@ -382,6 +391,7 @@ export class SurfaceBlockModel extends BlockModel { group.removeChild(payload.model as GfxModel); } } + break; } }); @@ -445,6 +455,25 @@ export class SurfaceBlockModel extends BlockModel { }); } + private _watchChildrenChange() { + const updateIsEmpty = () => { + this._isEmpty$.value = + this._elementModels.size === 0 && this.children.length === 0; + }; + + const disposables = new DisposableGroup(); + disposables.add(this.elementAdded.on(updateIsEmpty)); + disposables.add(this.elementRemoved.on(updateIsEmpty)); + this.doc.slots.blockUpdated.on(payload => { + if (['add', 'delete'].includes(payload.type)) { + updateIsEmpty(); + } + }); + this.deleted.on(() => { + disposables.dispose(); + }); + } + protected _extendElement( ctorMap: Record< string, @@ -460,6 +489,7 @@ export class SurfaceBlockModel extends BlockModel { protected _init() { this._initElementModels(); this._watchGroupRelationChange(); + this._watchChildrenChange(); } getConstructor(type: string) { diff --git a/blocksuite/framework/store/src/model/store/store.ts b/blocksuite/framework/store/src/model/store/store.ts index e76db3c8a5..6fe832e7e6 100644 --- a/blocksuite/framework/store/src/model/store/store.ts +++ b/blocksuite/framework/store/src/model/store/store.ts @@ -1,7 +1,7 @@ import { Container, type ServiceProvider } from '@blocksuite/global/di'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { type Disposable, Slot } from '@blocksuite/global/utils'; -import { signal } from '@preact/signals-core'; +import { computed, signal } from '@preact/signals-core'; import type { ExtensionType } from '../../extension/extension.js'; import { StoreSelectionExtension } from '../../extension/index.js'; @@ -55,6 +55,10 @@ export class Store { private readonly _readonly = signal(false); + private readonly _isEmpty = computed(() => { + return this.root?.isEmpty() ?? true; + }); + private readonly _schema: Schema; readonly slots: Doc['slots'] & { @@ -215,7 +219,11 @@ export class Store { } get isEmpty() { - return this.root?.isEmpty() ?? true; + return this._isEmpty.peek(); + } + + get isEmpty$() { + return this._isEmpty; } get loaded() { diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/starter-bar.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/starter-bar.tsx index 260b6332cb..6ca5d8f524 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/starter-bar.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/starter-bar.tsx @@ -115,7 +115,7 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => { } return ( -
+
{t['com.affine.page-starter-bar.start']()}
    {enableAI ? ( @@ -166,12 +166,9 @@ export const StarterBar = ({ doc }: { doc: Store }) => { ); useEffect(() => { - const disposable = doc.slots.blockUpdated.on(() => { - setIsEmpty(doc.isEmpty); + return doc.isEmpty$.subscribe(value => { + setIsEmpty(value); }); - return () => { - disposable.dispose(); - }; }, [doc]); if (!isEmpty || isTemplate) return null; diff --git a/tests/affine-local/e2e/template.spec.ts b/tests/affine-local/e2e/template.spec.ts index eb3f9c47ed..a149ff69fa 100644 --- a/tests/affine-local/e2e/template.spec.ts +++ b/tests/affine-local/e2e/template.spec.ts @@ -251,3 +251,22 @@ test('create template doc from sidebar template entrance', async ({ page }) => { page.getByTestId(`template-doc-item-${templateDocId}`) ).toBeVisible(); }); + +test('should show starter-bar when doc is empty', async ({ page }) => { + await openHomePage(page); + await page.getByTestId('sidebar-new-page-button').click(); + await page.keyboard.press('ArrowDown'); + const starterBar = page.getByTestId('starter-bar'); + await expect(starterBar).toBeVisible(); + + await page.keyboard.type('1', { delay: 100 }); + await expect(starterBar).not.toBeVisible(); + await page.keyboard.type('1', { delay: 100 }); + await expect(starterBar).not.toBeVisible(); + await page.keyboard.press('Backspace'); + await expect(starterBar).not.toBeVisible(); + await page.keyboard.press('Backspace'); + await expect(starterBar).toBeVisible(); + await page.keyboard.type('1', { delay: 100 }); + await expect(starterBar).not.toBeVisible(); +});