fix(core): correctly toggle visibility of starter-bar based on doc.isEmpty (#10439)

This commit is contained in:
CatsJuice
2025-02-26 07:49:50 +00:00
parent 866b096304
commit e1fd8f5d80
4 changed files with 64 additions and 10 deletions

View File

@@ -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 type { Boxed } from '@blocksuite/store';
import { BlockModel, nanoid } from '@blocksuite/store'; import { BlockModel, nanoid } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { import {
@@ -98,6 +104,8 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
oldValues: Record<string, unknown>; oldValues: Record<string, unknown>;
}>(); }>();
private readonly _isEmpty$ = signal(false);
get elementModels() { get elementModels() {
const models: GfxPrimitiveElementModel[] = []; const models: GfxPrimitiveElementModel[] = [];
this._elementModels.forEach(model => models.push(model.model)); this._elementModels.forEach(model => models.push(model.model));
@@ -113,7 +121,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
} }
override isEmpty(): boolean { override isEmpty(): boolean {
return this._elementModels.size === 0 && this.children.length === 0; return this._isEmpty$.value;
} }
constructor() { constructor() {
@@ -370,6 +378,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
if (isGfxGroupCompatibleModel(payload.model)) { if (isGfxGroupCompatibleModel(payload.model)) {
this._groupLikeModels.set(payload.id, payload.model); this._groupLikeModels.set(payload.id, payload.model);
} }
break; break;
case 'delete': case 'delete':
if (isGfxGroupCompatibleModel(payload.model)) { if (isGfxGroupCompatibleModel(payload.model)) {
@@ -382,6 +391,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
group.removeChild(payload.model as GfxModel); group.removeChild(payload.model as GfxModel);
} }
} }
break; break;
} }
}); });
@@ -445,6 +455,25 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
}); });
} }
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( protected _extendElement(
ctorMap: Record< ctorMap: Record<
string, string,
@@ -460,6 +489,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
protected _init() { protected _init() {
this._initElementModels(); this._initElementModels();
this._watchGroupRelationChange(); this._watchGroupRelationChange();
this._watchChildrenChange();
} }
getConstructor(type: string) { getConstructor(type: string) {

View File

@@ -1,7 +1,7 @@
import { Container, type ServiceProvider } from '@blocksuite/global/di'; import { Container, type ServiceProvider } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { type Disposable, Slot } from '@blocksuite/global/utils'; 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 type { ExtensionType } from '../../extension/extension.js';
import { StoreSelectionExtension } from '../../extension/index.js'; import { StoreSelectionExtension } from '../../extension/index.js';
@@ -55,6 +55,10 @@ export class Store {
private readonly _readonly = signal(false); private readonly _readonly = signal(false);
private readonly _isEmpty = computed(() => {
return this.root?.isEmpty() ?? true;
});
private readonly _schema: Schema; private readonly _schema: Schema;
readonly slots: Doc['slots'] & { readonly slots: Doc['slots'] & {
@@ -215,7 +219,11 @@ export class Store {
} }
get isEmpty() { get isEmpty() {
return this.root?.isEmpty() ?? true; return this._isEmpty.peek();
}
get isEmpty$() {
return this._isEmpty;
} }
get loaded() { get loaded() {

View File

@@ -115,7 +115,7 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
} }
return ( return (
<div className={styles.root}> <div className={styles.root} data-testid="starter-bar">
{t['com.affine.page-starter-bar.start']()} {t['com.affine.page-starter-bar.start']()}
<ul className={styles.badges}> <ul className={styles.badges}>
{enableAI ? ( {enableAI ? (
@@ -166,12 +166,9 @@ export const StarterBar = ({ doc }: { doc: Store }) => {
); );
useEffect(() => { useEffect(() => {
const disposable = doc.slots.blockUpdated.on(() => { return doc.isEmpty$.subscribe(value => {
setIsEmpty(doc.isEmpty); setIsEmpty(value);
}); });
return () => {
disposable.dispose();
};
}, [doc]); }, [doc]);
if (!isEmpty || isTemplate) return null; if (!isEmpty || isTemplate) return null;

View File

@@ -251,3 +251,22 @@ test('create template doc from sidebar template entrance', async ({ page }) => {
page.getByTestId(`template-doc-item-${templateDocId}`) page.getByTestId(`template-doc-item-${templateDocId}`)
).toBeVisible(); ).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();
});