mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
feat(core): ignore empty journal for global useBlockSuitePageMeta hook (#5715)
[TOV-494](https://linear.app/affine-design/issue/TOV-494/空-journal-的隐藏处理)
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { Schema, Workspace } from '@blocksuite/store';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { useBlockSuitePageMeta } from '../use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '../use-block-suite-workspace-helper';
|
||||
|
||||
let blockSuiteWorkspace: Workspace;
|
||||
|
||||
const schema = new Schema();
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
|
||||
beforeEach(async () => {
|
||||
blockSuiteWorkspace = new Workspace({
|
||||
id: 'test',
|
||||
schema,
|
||||
});
|
||||
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
|
||||
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
||||
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
||||
});
|
||||
|
||||
describe('useBlockSuiteWorkspaceHelper', () => {
|
||||
test('should create page', () => {
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
|
||||
const helperHook = renderHook(() =>
|
||||
useBlockSuiteWorkspaceHelper(blockSuiteWorkspace)
|
||||
);
|
||||
const pageMetaHook = renderHook(() =>
|
||||
useBlockSuitePageMeta(blockSuiteWorkspace)
|
||||
);
|
||||
expect(pageMetaHook.result.current.length).toBe(3);
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
|
||||
const page = helperHook.result.current.createPage('page4');
|
||||
expect(page.id).toBe('page4');
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(4);
|
||||
pageMetaHook.rerender();
|
||||
expect(pageMetaHook.result.current.length).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { configureTestingEnvironment } from '@affine/core/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { initEmptyPage, ServiceProviderContext } from '@toeverything/infra';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { useBlockSuitePageMeta } from '../use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '../use-block-suite-workspace-helper';
|
||||
|
||||
const configureTestingWorkspace = async () => {
|
||||
const { workspace } = await configureTestingEnvironment();
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
|
||||
// await initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page-0' }));
|
||||
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
||||
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
||||
|
||||
return workspace;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
|
||||
});
|
||||
|
||||
const getWrapper = (workspace: Workspace) =>
|
||||
function Provider({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<ServiceProviderContext.Provider value={workspace.services}>
|
||||
{children}
|
||||
</ServiceProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useBlockSuiteWorkspaceHelper', () => {
|
||||
test('should create page', async () => {
|
||||
const workspace = await configureTestingWorkspace();
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const Wrapper = getWrapper(workspace);
|
||||
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
|
||||
const helperHook = renderHook(
|
||||
() => useBlockSuiteWorkspaceHelper(blockSuiteWorkspace),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
const pageMetaHook = renderHook(
|
||||
() => useBlockSuitePageMeta(blockSuiteWorkspace),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(pageMetaHook.result.current.length).toBe(3);
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
|
||||
const page = helperHook.result.current.createPage('page4');
|
||||
expect(page.id).toBe('page4');
|
||||
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(4);
|
||||
pageMetaHook.rerender();
|
||||
expect(pageMetaHook.result.current.length).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { use } from 'foxact/use';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { WorkspacePropertiesAdapter } from '../modules/workspace/properties';
|
||||
import { useBlockSuitePageMeta } from './use-block-suite-page-meta';
|
||||
import { useAllBlockSuitePageMeta } from './use-all-block-suite-page-meta';
|
||||
|
||||
function getProxy<T extends object>(obj: T) {
|
||||
return new Proxy(obj, {});
|
||||
@@ -14,7 +14,7 @@ const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
|
||||
use(adapter.workspace.blockSuiteWorkspace.doc.whenSynced);
|
||||
const [proxy, setProxy] = useState(adapter);
|
||||
// fixme: this is a hack to force re-render when default meta changed
|
||||
useBlockSuitePageMeta(adapter.workspace.blockSuiteWorkspace);
|
||||
useAllBlockSuitePageMeta(adapter.workspace.blockSuiteWorkspace);
|
||||
useEffect(() => {
|
||||
// todo: track which properties are used and then filter by property path change
|
||||
// using Y.YEvent.path
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import type { Atom } from 'jotai';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
|
||||
const weakMap = new WeakMap<Workspace, Atom<PageMeta[]>>();
|
||||
|
||||
// this hook is extracted from './use-block-suite-page-meta.ts' to avoid circular dependency
|
||||
export function useAllBlockSuitePageMeta(
|
||||
blockSuiteWorkspace: Workspace
|
||||
): PageMeta[] {
|
||||
if (!weakMap.has(blockSuiteWorkspace)) {
|
||||
const baseAtom = atom<PageMeta[]>(blockSuiteWorkspace.meta.pageMetas);
|
||||
weakMap.set(blockSuiteWorkspace, baseAtom);
|
||||
baseAtom.onMount = set => {
|
||||
set(blockSuiteWorkspace.meta.pageMetas);
|
||||
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
|
||||
set(blockSuiteWorkspace.meta.pageMetas);
|
||||
});
|
||||
return () => {
|
||||
dispose.dispose();
|
||||
};
|
||||
};
|
||||
}
|
||||
return useAtomValue(weakMap.get(blockSuiteWorkspace) as Atom<PageMeta[]>);
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
import type { PageBlockModel } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import type { Atom } from 'jotai';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const weakMap = new WeakMap<Workspace, Atom<PageMeta[]>>();
|
||||
import { useAllBlockSuitePageMeta } from './use-all-block-suite-page-meta';
|
||||
import { useJournalHelper } from './use-journal';
|
||||
|
||||
export function useBlockSuitePageMeta(
|
||||
blockSuiteWorkspace: Workspace
|
||||
): PageMeta[] {
|
||||
if (!weakMap.has(blockSuiteWorkspace)) {
|
||||
const baseAtom = atom<PageMeta[]>(blockSuiteWorkspace.meta.pageMetas);
|
||||
weakMap.set(blockSuiteWorkspace, baseAtom);
|
||||
baseAtom.onMount = set => {
|
||||
set(blockSuiteWorkspace.meta.pageMetas);
|
||||
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
|
||||
set(blockSuiteWorkspace.meta.pageMetas);
|
||||
});
|
||||
return () => {
|
||||
dispose.dispose();
|
||||
};
|
||||
};
|
||||
}
|
||||
return useAtomValue(weakMap.get(blockSuiteWorkspace) as Atom<PageMeta[]>);
|
||||
/**
|
||||
* Get pageMetas excluding journal pages without updatedDate
|
||||
* If you want to get all pageMetas, use `useAllBlockSuitePageMeta` instead
|
||||
* @returns
|
||||
*/
|
||||
export function useBlockSuitePageMeta(blocksuiteWorkspace: Workspace) {
|
||||
const pageMetas = useAllBlockSuitePageMeta(blocksuiteWorkspace);
|
||||
const { isPageJournal } = useJournalHelper(blocksuiteWorkspace);
|
||||
return useMemo(
|
||||
() =>
|
||||
pageMetas.filter(
|
||||
pageMeta => !isPageJournal(pageMeta.id) || !!pageMeta.updatedDate
|
||||
),
|
||||
[isPageJournal, pageMetas]
|
||||
);
|
||||
}
|
||||
|
||||
export function usePageMetaHelper(blockSuiteWorkspace: Workspace) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@affine/component';
|
||||
import { MoveToTrash } from '@affine/core/components/page-list';
|
||||
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspacePageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
|
||||
import {
|
||||
useJournalHelper,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
useJournalRouteHelper,
|
||||
} from '@affine/core/hooks/use-journal';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import type { BlockSuiteWorkspace } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
@@ -21,7 +23,7 @@ import {
|
||||
PageIcon,
|
||||
TodayIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -42,21 +44,28 @@ const CountDisplay = ({
|
||||
return <span {...attrs}>{count > max ? `${max}+` : count}</span>;
|
||||
};
|
||||
interface PageItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
page: Page;
|
||||
pageMeta: PageMeta;
|
||||
workspace: BlockSuiteWorkspace;
|
||||
right?: ReactNode;
|
||||
}
|
||||
const PageItem = ({ page, right, className, ...attrs }: PageItemProps) => {
|
||||
const { isJournal } = useJournalInfoHelper(page.workspace, page.id);
|
||||
const title = useBlockSuiteWorkspacePageTitle(page.workspace, page.id);
|
||||
const PageItem = ({
|
||||
pageMeta,
|
||||
workspace,
|
||||
right,
|
||||
className,
|
||||
...attrs
|
||||
}: PageItemProps) => {
|
||||
const { isJournal } = useJournalInfoHelper(workspace, pageMeta.id);
|
||||
const title = useBlockSuiteWorkspacePageTitle(workspace, pageMeta.id);
|
||||
|
||||
const Icon = isJournal
|
||||
? TodayIcon
|
||||
: page.meta.mode === 'edgeless'
|
||||
: pageMeta.mode === 'edgeless'
|
||||
? EdgelessIcon
|
||||
: PageIcon;
|
||||
return (
|
||||
<div
|
||||
aria-label={page.meta.title}
|
||||
aria-label={pageMeta.title}
|
||||
className={clsx(className, styles.pageItem)}
|
||||
{...attrs}
|
||||
>
|
||||
@@ -147,15 +156,12 @@ const EditorJournalPanel = (props: EditorExtensionProps) => {
|
||||
};
|
||||
|
||||
const sortPagesByDate = (
|
||||
pages: Page[],
|
||||
pages: PageMeta[],
|
||||
field: 'updatedDate' | 'createDate',
|
||||
order: 'asc' | 'desc' = 'desc'
|
||||
) => {
|
||||
return [...pages].sort((a, b) => {
|
||||
return (
|
||||
(order === 'asc' ? 1 : -1) *
|
||||
dayjs(b.meta[field]).diff(dayjs(a.meta[field]))
|
||||
);
|
||||
return (order === 'asc' ? 1 : -1) * dayjs(b[field]).diff(dayjs(a[field]));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -174,21 +180,21 @@ const JournalDailyCountBlock = ({ workspace, date }: JournalBlockProps) => {
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const t = useAFFiNEI18N();
|
||||
const [activeItem, setActiveItem] = useState<NavItemName>('createdToday');
|
||||
const pageMetas = useBlockSuitePageMeta(workspace);
|
||||
|
||||
const navigateHelper = useNavigateHelper();
|
||||
|
||||
const getTodaysPages = useCallback(
|
||||
(field: 'createDate' | 'updatedDate') => {
|
||||
const pages: Page[] = [];
|
||||
Array.from(workspace.pages.values()).forEach(page => {
|
||||
if (page.meta.trash) return;
|
||||
if (page.meta[field] && dayjs(page.meta[field]).isSame(date, 'day')) {
|
||||
pages.push(page);
|
||||
}
|
||||
});
|
||||
return sortPagesByDate(pages, field);
|
||||
return sortPagesByDate(
|
||||
pageMetas.filter(pageMeta => {
|
||||
if (pageMeta.trash) return false;
|
||||
return pageMeta[field] && dayjs(pageMeta[field]).isSame(date, 'day');
|
||||
}),
|
||||
field
|
||||
);
|
||||
},
|
||||
[date, workspace.pages]
|
||||
[date, pageMetas]
|
||||
);
|
||||
|
||||
const createdToday = useMemo(
|
||||
@@ -257,14 +263,15 @@ const JournalDailyCountBlock = ({ workspace, date }: JournalBlockProps) => {
|
||||
<Scrollable.Scrollbar />
|
||||
<Scrollable.Viewport>
|
||||
<div className={styles.dailyCountContent} ref={nodeRef}>
|
||||
{renderList.map((page, index) => (
|
||||
{renderList.map((pageMeta, index) => (
|
||||
<PageItem
|
||||
onClick={() =>
|
||||
navigateHelper.openPage(workspace.id, page.id)
|
||||
navigateHelper.openPage(workspace.id, pageMeta.id)
|
||||
}
|
||||
tabIndex={name === activeItem ? 0 : -1}
|
||||
key={index}
|
||||
page={page}
|
||||
pageMeta={pageMeta}
|
||||
workspace={workspace}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -315,7 +322,8 @@ const ConflictList = ({
|
||||
<PageItem
|
||||
aria-label={page.meta.title}
|
||||
aria-selected={isCurrent}
|
||||
page={page}
|
||||
pageMeta={page.meta}
|
||||
workspace={workspace}
|
||||
key={page.id}
|
||||
right={
|
||||
<Menu
|
||||
|
||||
Reference in New Issue
Block a user