mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
feat: add page setting atom (#2725)
This commit is contained in:
+61
-26
@@ -2,9 +2,8 @@ import { DebugLogger } from '@affine/debug';
|
|||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
import { atomFamily, atomWithStorage } from 'jotai/utils';
|
||||||
|
|
||||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||||
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
|
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
|
||||||
@@ -96,34 +95,70 @@ export const workspaceRecentViewsAtom = atomWithStorage<WorkspaceRecentViews>(
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PreferredModeRecord = Record<Page['id'], 'page' | 'edgeless'>;
|
type PageMode = 'page' | 'edgeless';
|
||||||
/**
|
type PageLocalSetting = {
|
||||||
* @deprecated Use `useWorkspacePreferredMode` instead.
|
mode: PageMode;
|
||||||
*/
|
};
|
||||||
export const workspacePreferredModeAtom = atomWithStorage<PreferredModeRecord>(
|
|
||||||
'preferredMode',
|
type PartialPageLocalSettingWithPageId = Partial<PageLocalSetting> & {
|
||||||
{}
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageSettingsBaseAtom = atomWithStorage(
|
||||||
|
'pageSettings',
|
||||||
|
{} as Record<string, PageLocalSetting>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const workspaceRecentViresWriteAtom = atom<null, [string, View], View[]>(
|
// readonly atom by design
|
||||||
null,
|
export const pageSettingsAtom = atom(get => get(pageSettingsBaseAtom));
|
||||||
(get, set, id, value) => {
|
|
||||||
const record = get(workspaceRecentViewsAtom);
|
|
||||||
if (Array.isArray(record[id])) {
|
|
||||||
const idx = record[id].findIndex(view => view.id === value.id);
|
|
||||||
if (idx !== -1) {
|
|
||||||
record[id].splice(idx, 1);
|
|
||||||
}
|
|
||||||
record[id] = [value, ...record[id]];
|
|
||||||
} else {
|
|
||||||
record[id] = [value];
|
|
||||||
}
|
|
||||||
|
|
||||||
record[id] = record[id].slice(0, 3);
|
const recentPageSettingsBaseAtom = atomWithStorage<string[]>(
|
||||||
set(workspaceRecentViewsAtom, { ...record });
|
'recentPageSettings',
|
||||||
return record[id];
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const recentPageSettingsAtom = atom<PartialPageLocalSettingWithPageId[]>(
|
||||||
|
get => {
|
||||||
|
const recentPageIDs = get(recentPageSettingsBaseAtom);
|
||||||
|
const pageSettings = get(pageSettingsAtom);
|
||||||
|
return recentPageIDs.map(id => ({
|
||||||
|
...pageSettings[id],
|
||||||
|
id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||||
|
atom(
|
||||||
|
get => get(pageSettingsBaseAtom)[pageId],
|
||||||
|
(
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
patch:
|
||||||
|
| Partial<PageLocalSetting>
|
||||||
|
| ((prevSetting: PageLocalSetting | undefined) => void)
|
||||||
|
) => {
|
||||||
|
set(recentPageSettingsBaseAtom, ids => {
|
||||||
|
// pick 3 recent page ids
|
||||||
|
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
|
||||||
|
});
|
||||||
|
set(pageSettingsBaseAtom, settings => ({
|
||||||
|
...settings,
|
||||||
|
[pageId]: {
|
||||||
|
...settings[pageId],
|
||||||
|
...(typeof patch === 'function' ? patch(settings[pageId]) : patch),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setPageModeAtom = atom(
|
||||||
|
void 0,
|
||||||
|
(get, set, pageId: string, mode: PageMode) => {
|
||||||
|
set(pageSettingFamily(pageId), { mode });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PageModeOption = 'all' | 'page' | 'edgeless';
|
export type PageModeOption = 'all' | 'page' | 'edgeless';
|
||||||
export const pageModeSelectAtom = atom<PageModeOption>('all');
|
export const allPageModeSelectAtom = atom<PageModeOption>('all');
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { useAtom } from 'jotai';
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { pageModeSelectAtom } from '../../../atoms';
|
import { allPageModeSelectAtom } from '../../../atoms';
|
||||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
import { toast } from '../../../utils';
|
import { toast } from '../../../utils';
|
||||||
@@ -107,7 +107,7 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
|||||||
permanentlyDeletePage,
|
permanentlyDeletePage,
|
||||||
cancelPublicPage,
|
cancelPublicPage,
|
||||||
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||||
const [filterMode] = useAtom(pageModeSelectAtom);
|
const [filterMode] = useAtom(allPageModeSelectAtom);
|
||||||
const { createPage, createEdgeless, importFile, isPreferredEdgeless } =
|
const { createPage, createEdgeless, importFile, isPreferredEdgeless } =
|
||||||
usePageHelper(blockSuiteWorkspace);
|
usePageHelper(blockSuiteWorkspace);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useWorkspacePreferredMode } from '../../../hooks/use-recent-views';
|
import { pageSettingsAtom, setPageModeAtom } from '../../../atoms';
|
||||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
|
|
||||||
@@ -9,24 +11,25 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { openPage } = useRouterHelper(router);
|
const { openPage } = useRouterHelper(router);
|
||||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||||
const { getPreferredMode, setPreferredMode } = useWorkspacePreferredMode();
|
const pageSettings = useAtomValue(pageSettingsAtom);
|
||||||
const isPreferredEdgeless = (pageId: string) => {
|
const isPreferredEdgeless = useCallback(
|
||||||
return getPreferredMode(pageId) === 'edgeless';
|
(pageId: string) => pageSettings[pageId]?.mode === 'edgeless',
|
||||||
};
|
[pageSettings]
|
||||||
|
);
|
||||||
const createPageAndOpen = () => {
|
const setPageMode = useSetAtom(setPageModeAtom);
|
||||||
|
const createPageAndOpen = useCallback(() => {
|
||||||
const page = createPage();
|
const page = createPage();
|
||||||
return openPage(blockSuiteWorkspace.id, page.id);
|
return openPage(blockSuiteWorkspace.id, page.id);
|
||||||
};
|
}, [blockSuiteWorkspace.id, createPage, openPage]);
|
||||||
const createEdgelessAndOpen = () => {
|
const createEdgelessAndOpen = useCallback(() => {
|
||||||
const page = createPage();
|
const page = createPage();
|
||||||
setPreferredMode(page.id, 'edgeless');
|
setPageMode(page.id, 'edgeless');
|
||||||
return openPage(blockSuiteWorkspace.id, page.id);
|
return openPage(blockSuiteWorkspace.id, page.id);
|
||||||
};
|
}, [blockSuiteWorkspace.id, createPage, openPage, setPageMode]);
|
||||||
const importFileAndOpen = async () => {
|
const importFileAndOpen = useCallback(async () => {
|
||||||
const { showImportModal } = await import('@blocksuite/blocks');
|
const { showImportModal } = await import('@blocksuite/blocks');
|
||||||
showImportModal({ workspace: blockSuiteWorkspace });
|
showImportModal({ workspace: blockSuiteWorkspace });
|
||||||
};
|
}, [blockSuiteWorkspace]);
|
||||||
return {
|
return {
|
||||||
createPage: createPageAndOpen,
|
createPage: createPageAndOpen,
|
||||||
createEdgeless: createEdgelessAndOpen,
|
createEdgeless: createEdgelessAndOpen,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
import { pageSettingFamily } from '../../../../atoms';
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../../shared';
|
||||||
import { toast } from '../../../../utils';
|
import { toast } from '../../../../utils';
|
||||||
import { StyledEditorModeSwitch } from './style';
|
import { StyledEditorModeSwitch } from './style';
|
||||||
@@ -22,9 +22,8 @@ export const EditorModeSwitch = ({
|
|||||||
blockSuiteWorkspace,
|
blockSuiteWorkspace,
|
||||||
pageId,
|
pageId,
|
||||||
}: EditorModeSwitchProps) => {
|
}: EditorModeSwitchProps) => {
|
||||||
const currentMode =
|
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||||
useAtomValue(workspacePreferredModeAtom)[pageId] ?? 'page';
|
const currentMode = setting?.mode ?? 'page';
|
||||||
const setMode = useSetAtom(workspacePreferredModeAtom);
|
|
||||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
);
|
||||||
@@ -43,11 +42,11 @@ export const EditorModeSwitch = ({
|
|||||||
active={currentMode === 'page'}
|
active={currentMode === 'page'}
|
||||||
hide={trash && currentMode !== 'page'}
|
hide={trash && currentMode !== 'page'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMode(mode => {
|
setSetting(setting => {
|
||||||
if (mode[pageMeta.id] !== 'page') {
|
if (setting?.mode !== 'page') {
|
||||||
toast(t['com.affine.pageMode']());
|
toast(t['com.affine.pageMode']());
|
||||||
}
|
}
|
||||||
return { ...mode, [pageMeta.id]: 'page' };
|
return { ...setting, mode: 'page' };
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -56,11 +55,11 @@ export const EditorModeSwitch = ({
|
|||||||
active={currentMode === 'edgeless'}
|
active={currentMode === 'edgeless'}
|
||||||
hide={trash && currentMode !== 'edgeless'}
|
hide={trash && currentMode !== 'edgeless'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMode(mode => {
|
setSetting(setting => {
|
||||||
if (mode[pageMeta.id] !== 'edgeless') {
|
if (setting?.mode !== 'edgeless') {
|
||||||
toast(t['com.affine.edgelessMode']());
|
toast(t['com.affine.pageMode']());
|
||||||
}
|
}
|
||||||
return { ...mode, [pageMeta.id]: 'edgeless' };
|
return { ...setting, mode: 'edgeless' };
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+5
-6
@@ -18,7 +18,7 @@ import { useAtom } from 'jotai';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
import { pageSettingFamily } from '../../../../atoms';
|
||||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||||
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
|
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
|
||||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||||
@@ -64,8 +64,8 @@ const PageMenu = () => {
|
|||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
);
|
||||||
assertExists(pageMeta);
|
assertExists(pageMeta);
|
||||||
const [record, set] = useAtom(workspacePreferredModeAtom);
|
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||||
const mode = record[pageId] ?? 'page';
|
const mode = setting?.mode ?? 'page';
|
||||||
|
|
||||||
const favorite = pageMeta.favorite ?? false;
|
const favorite = pageMeta.favorite ?? false;
|
||||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||||
@@ -98,9 +98,8 @@ const PageMenu = () => {
|
|||||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||||
data-testid="editor-option-menu-edgeless"
|
data-testid="editor-option-menu-edgeless"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
set(record => ({
|
setSetting(setting => ({
|
||||||
...record,
|
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
|
||||||
[pageId]: mode === 'page' ? 'edgeless' : 'page',
|
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../atoms';
|
import { pageSettingFamily } from '../atoms';
|
||||||
import { contentLayoutAtom } from '../atoms/layout';
|
import { contentLayoutAtom } from '../atoms/layout';
|
||||||
import type { AffineOfficialWorkspace } from '../shared';
|
import type { AffineOfficialWorkspace } from '../shared';
|
||||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||||
@@ -63,11 +63,13 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
const meta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
const meta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
);
|
||||||
|
const pageSettingAtom = pageSettingFamily(pageId);
|
||||||
|
const pageSetting = useAtomValue(pageSettingAtom);
|
||||||
const currentMode =
|
const currentMode =
|
||||||
useAtomValue(workspacePreferredModeAtom)[pageId] ??
|
pageSetting?.mode ?? DEFAULT_HELLO_WORLD_PAGE_ID === pageId
|
||||||
DEFAULT_HELLO_WORLD_PAGE_ID === pageId
|
|
||||||
? 'edgeless'
|
? 'edgeless'
|
||||||
: 'page';
|
: 'page';
|
||||||
|
|
||||||
const setEditor = useSetAtom(rootCurrentEditorAtom);
|
const setEditor = useSetAtom(rootCurrentEditorAtom);
|
||||||
assertExists(meta);
|
assertExists(meta);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { assertExists } from '@blocksuite/store';
|
|||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import Image from 'next/legacy/image';
|
import Image from 'next/legacy/image';
|
||||||
import type { NextRouter } from 'next/router';
|
import type { NextRouter } from 'next/router';
|
||||||
import type { Dispatch, FC, SetStateAction } from 'react';
|
import type { Dispatch, FC, SetStateAction } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { useRecentlyViewed } from '../../../hooks/use-recent-views';
|
import { recentPageSettingsAtom } from '../../../atoms';
|
||||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
import { useSwitchToConfig } from './config';
|
import { useSwitchToConfig } from './config';
|
||||||
@@ -35,7 +36,7 @@ export const Results: FC<ResultsProps> = ({
|
|||||||
assertExists(blockSuiteWorkspace.id);
|
assertExists(blockSuiteWorkspace.id);
|
||||||
const List = useSwitchToConfig(blockSuiteWorkspace.id);
|
const List = useSwitchToConfig(blockSuiteWorkspace.id);
|
||||||
|
|
||||||
const recentlyViewed = useRecentlyViewed();
|
const recentPageSetting = useAtomValue(recentPageSettingsAtom);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const { jumpToPage } = useRouterHelper(router);
|
const { jumpToPage } = useRouterHelper(router);
|
||||||
const results = blockSuiteWorkspace.search(query);
|
const results = blockSuiteWorkspace.search(query);
|
||||||
@@ -45,7 +46,8 @@ export const Results: FC<ResultsProps> = ({
|
|||||||
const resultsPageMeta = pageList.filter(
|
const resultsPageMeta = pageList.filter(
|
||||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||||
);
|
);
|
||||||
const recentlyViewedItem = recentlyViewed.filter(recent => {
|
|
||||||
|
const recentlyViewedItem = recentPageSetting.filter(recent => {
|
||||||
const page = pageList.find(page => recent.id === page.id);
|
const page = pageList.find(page => recent.id === page.id);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useAtomValue } from 'jotai';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
import { pageSettingFamily } from '../../../../atoms';
|
||||||
import type { FavoriteListProps } from '../index';
|
import type { FavoriteListProps } from '../index';
|
||||||
import EmptyItem from './empty-item';
|
import EmptyItem from './empty-item';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
@@ -27,9 +27,9 @@ function FavoriteMenuItem({
|
|||||||
parentIds,
|
parentIds,
|
||||||
}: FavoriteMenuItemProps) {
|
}: FavoriteMenuItemProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const record = useAtomValue(workspacePreferredModeAtom);
|
const setting = useAtomValue(pageSettingFamily(pageId));
|
||||||
const active = router.query.pageId === pageId;
|
const active = router.query.pageId === pageId;
|
||||||
const icon = record[pageId] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
|
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
|
||||||
const references = useBlockSuitePageReferences(workspace, pageId);
|
const references = useBlockSuitePageReferences(workspace, pageId);
|
||||||
const referencesToShow = useMemo(() => {
|
const referencesToShow = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { useAtom } from 'jotai';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { openQuickSearchModalAtom, pageModeSelectAtom } from '../../../atoms';
|
import {
|
||||||
|
allPageModeSelectAtom,
|
||||||
|
openQuickSearchModalAtom,
|
||||||
|
} from '../../../atoms';
|
||||||
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
|
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
|
||||||
import { Header } from '../../blocksuite/workspace-header/header';
|
import { Header } from '../../blocksuite/workspace-header/header';
|
||||||
import * as styles from '../../blocksuite/workspace-header/styles.css';
|
import * as styles from '../../blocksuite/workspace-header/styles.css';
|
||||||
@@ -40,7 +43,7 @@ export const WorkspaceTitle: React.FC<WorkspaceTitleProps> = ({
|
|||||||
|
|
||||||
export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
|
export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [value, setMode] = useAtom(pageModeSelectAtom);
|
const [value, setMode] = useAtom(allPageModeSelectAtom);
|
||||||
const handleValueChange = (value: string) => {
|
const handleValueChange = (value: string) => {
|
||||||
if (value !== 'all' && value !== 'page' && value !== 'edgeless') {
|
if (value !== 'all' && value !== 'page' && value !== 'edgeless') {
|
||||||
throw new Error('Invalid value for page mode option');
|
throw new Error('Invalid value for page mode option');
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* @vitest-environment happy-dom
|
|
||||||
*/
|
|
||||||
import type { LocalWorkspace } from '@affine/env/workspace';
|
|
||||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
|
||||||
import {
|
|
||||||
rootCurrentWorkspaceIdAtom,
|
|
||||||
rootWorkspacesMetadataAtom,
|
|
||||||
} from '@affine/workspace/atom';
|
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
|
||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
import { assertExists } from '@blocksuite/store';
|
|
||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
import { createStore, Provider } from 'jotai';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import routerMock from 'next-router-mock';
|
|
||||||
import { createDynamicRouteParser } from 'next-router-mock/dynamic-routes';
|
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
|
||||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { LocalAdapter } from '../../adapters/local';
|
|
||||||
import { workspacesAtom } from '../../atoms';
|
|
||||||
import { rootCurrentWorkspaceAtom } from '../../atoms/root';
|
|
||||||
import { BlockSuiteWorkspace } from '../../shared';
|
|
||||||
import { useCurrentWorkspace } from '../current/use-current-workspace';
|
|
||||||
import {
|
|
||||||
useRecentlyViewed,
|
|
||||||
useSyncRecentViewsWithRouter,
|
|
||||||
} from '../use-recent-views';
|
|
||||||
|
|
||||||
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
beforeAll(() => {
|
|
||||||
routerMock.useParser(
|
|
||||||
createDynamicRouteParser([
|
|
||||||
`/workspace/[workspaceId/${WorkspaceSubPath.ALL}`,
|
|
||||||
`/workspace/[workspaceId/${WorkspaceSubPath.SETTING}`,
|
|
||||||
`/workspace/[workspaceId/${WorkspaceSubPath.TRASH}`,
|
|
||||||
'/workspace/[workspaceId]/[pageId]',
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getJotaiContext() {
|
|
||||||
const store = createStore();
|
|
||||||
const ProviderWrapper: FC<PropsWithChildren> = function ProviderWrapper({
|
|
||||||
children,
|
|
||||||
}) {
|
|
||||||
return <Provider store={store}>{children}</Provider>;
|
|
||||||
};
|
|
||||||
const workspaces = await store.get(workspacesAtom);
|
|
||||||
expect(workspaces.length).toBe(0);
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
ProviderWrapper,
|
|
||||||
initialWorkspaces: workspaces,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
blockSuiteWorkspace = new BlockSuiteWorkspace({ id: 'test' })
|
|
||||||
.register(AffineSchemas)
|
|
||||||
.register(__unstableSchemas);
|
|
||||||
const initPage = (page: Page) => {
|
|
||||||
expect(page).not.toBeNull();
|
|
||||||
assertExists(page);
|
|
||||||
const pageBlockId = page.addBlock('affine:page', {
|
|
||||||
title: new page.Text(''),
|
|
||||||
});
|
|
||||||
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
|
||||||
page.addBlock('affine:paragraph', {}, frameId);
|
|
||||||
};
|
|
||||||
initPage(
|
|
||||||
blockSuiteWorkspace.createPage({
|
|
||||||
id: 'page0',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
initPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
|
||||||
initPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('useRecentlyViewed', () => {
|
|
||||||
test('basic', async () => {
|
|
||||||
const { ProviderWrapper, store } = await getJotaiContext();
|
|
||||||
const workspaceId = blockSuiteWorkspace.id;
|
|
||||||
const pageId = 'page0';
|
|
||||||
store.set(rootWorkspacesMetadataAtom, [
|
|
||||||
{
|
|
||||||
id: workspaceId,
|
|
||||||
flavour: WorkspaceFlavour.LOCAL,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
LocalAdapter.CRUD.get = vi.fn().mockResolvedValue({
|
|
||||||
id: workspaceId,
|
|
||||||
flavour: WorkspaceFlavour.LOCAL,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
providers: [],
|
|
||||||
} satisfies LocalWorkspace);
|
|
||||||
store.set(rootCurrentWorkspaceIdAtom, blockSuiteWorkspace.id);
|
|
||||||
const workspace = await store.get(rootCurrentWorkspaceAtom);
|
|
||||||
expect(workspace?.id).toBe(blockSuiteWorkspace.id);
|
|
||||||
const currentHook = renderHook(() => useCurrentWorkspace(), {
|
|
||||||
wrapper: ProviderWrapper,
|
|
||||||
});
|
|
||||||
expect(currentHook.result.current[0]?.id).toEqual(workspaceId);
|
|
||||||
store.set(rootCurrentWorkspaceIdAtom, blockSuiteWorkspace.id);
|
|
||||||
await store.get(rootCurrentWorkspaceAtom);
|
|
||||||
const recentlyViewedHook = renderHook(() => useRecentlyViewed(), {
|
|
||||||
wrapper: ProviderWrapper,
|
|
||||||
});
|
|
||||||
expect(recentlyViewedHook.result.current).toEqual([]);
|
|
||||||
const routerHook = renderHook(() => useRouter(), {
|
|
||||||
wrapper: ProviderWrapper,
|
|
||||||
});
|
|
||||||
await routerHook.result.current.push({
|
|
||||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
|
||||||
query: {
|
|
||||||
workspaceId,
|
|
||||||
pageId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
routerHook.rerender();
|
|
||||||
const syncHook = renderHook(
|
|
||||||
router => useSyncRecentViewsWithRouter(router, blockSuiteWorkspace),
|
|
||||||
{
|
|
||||||
wrapper: ProviderWrapper,
|
|
||||||
initialProps: routerHook.result.current,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
syncHook.rerender(routerHook.result.current);
|
|
||||||
expect(recentlyViewedHook.result.current).toEqual([
|
|
||||||
{
|
|
||||||
id: 'page0',
|
|
||||||
mode: 'page',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../atoms';
|
import { pageSettingFamily } from '../../atoms';
|
||||||
|
|
||||||
const currentModeAtom = atom<'page' | 'edgeless'>(get => {
|
const currentModeAtom = atom<'page' | 'edgeless'>(get => {
|
||||||
const pageId = get(rootCurrentPageIdAtom);
|
const pageId = get(rootCurrentPageIdAtom);
|
||||||
const record = get(workspacePreferredModeAtom);
|
// fixme(himself65): pageId should not be null
|
||||||
if (pageId) return record[pageId] ?? 'page';
|
if (!pageId) {
|
||||||
else {
|
|
||||||
return 'page';
|
return 'page';
|
||||||
}
|
}
|
||||||
|
return get(pageSettingFamily(pageId))?.mode ?? 'page';
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useCurrentMode = () => {
|
export const useCurrentMode = () => {
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import type { Page, Workspace } from '@blocksuite/store';
|
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
|
||||||
import type { NextRouter } from 'next/router';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
workspacePreferredModeAtom,
|
|
||||||
workspaceRecentViewsAtom,
|
|
||||||
workspaceRecentViresWriteAtom,
|
|
||||||
} from '../atoms';
|
|
||||||
import { useCurrentWorkspace } from './current/use-current-workspace';
|
|
||||||
|
|
||||||
export const useWorkspacePreferredMode = () => {
|
|
||||||
const [record, setPreferred] = useAtom(workspacePreferredModeAtom);
|
|
||||||
return {
|
|
||||||
getPreferredMode: (pageId: Page['id']) => record[pageId] ?? 'page',
|
|
||||||
setPreferredMode: (pageId: Page['id'], mode: 'page' | 'edgeless') => {
|
|
||||||
setPreferred(record => ({
|
|
||||||
...record,
|
|
||||||
[pageId]: mode,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useRecentlyViewed() {
|
|
||||||
const [workspace] = useCurrentWorkspace();
|
|
||||||
const workspaceId = workspace?.id || null;
|
|
||||||
const recentlyViewed = useAtomValue(workspaceRecentViewsAtom);
|
|
||||||
|
|
||||||
if (!workspaceId) return [];
|
|
||||||
return recentlyViewed[workspaceId] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSyncRecentViewsWithRouter(
|
|
||||||
router: NextRouter,
|
|
||||||
blockSuiteWorkspace: Workspace
|
|
||||||
) {
|
|
||||||
const workspaceId = blockSuiteWorkspace.id;
|
|
||||||
const pageId = router.query.pageId;
|
|
||||||
const set = useSetAtom(workspaceRecentViresWriteAtom);
|
|
||||||
const meta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
|
||||||
meta => meta.id === pageId
|
|
||||||
);
|
|
||||||
const { getPreferredMode } = useWorkspacePreferredMode();
|
|
||||||
|
|
||||||
const currentMode =
|
|
||||||
typeof pageId === 'string' ? getPreferredMode(pageId) : 'page';
|
|
||||||
useEffect(() => {
|
|
||||||
if (!workspaceId) return;
|
|
||||||
if (pageId && meta) {
|
|
||||||
set(workspaceId, {
|
|
||||||
id: pageId as string,
|
|
||||||
/**
|
|
||||||
* @deprecated No necessary update `mode` at here, use `mode` from {@link useWorkspacePreferredMode} directly.
|
|
||||||
*/
|
|
||||||
mode: currentMode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [pageId, meta, workspaceId, set, currentMode]);
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ import { useCallback } from 'react';
|
|||||||
import { getUIAdapter } from '../../../adapters/workspace';
|
import { getUIAdapter } from '../../../adapters/workspace';
|
||||||
import { rootCurrentWorkspaceAtom } from '../../../atoms/root';
|
import { rootCurrentWorkspaceAtom } from '../../../atoms/root';
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
import { useSyncRecentViewsWithRouter } from '../../../hooks/use-recent-views';
|
|
||||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||||
import { WorkspaceLayout } from '../../../layouts/workspace-layout';
|
import { WorkspaceLayout } from '../../../layouts/workspace-layout';
|
||||||
import type { NextPageWithLayout } from '../../../shared';
|
import type { NextPageWithLayout } from '../../../shared';
|
||||||
@@ -25,7 +24,6 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
assertExists(currentWorkspace);
|
assertExists(currentWorkspace);
|
||||||
assertExists(currentPageId);
|
assertExists(currentPageId);
|
||||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||||
useSyncRecentViewsWithRouter(router, blockSuiteWorkspace);
|
|
||||||
|
|
||||||
const onLoad = useCallback(
|
const onLoad = useCallback(
|
||||||
(page: Page, editor: EditorContainer) => {
|
(page: Page, editor: EditorContainer) => {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
// FIXME: add this back
|
||||||
|
"noUncheckedIndexedAccess": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|||||||
Reference in New Issue
Block a user