mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
fix(core): correctly toggle visibility of starter-bar based on doc.isEmpty (#10439)
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user