JimmFly
2023-11-29 02:35:12 +00:00
parent 469a18f794
commit c78eb96507
7 changed files with 119 additions and 62 deletions

View File

@@ -20,23 +20,15 @@ import {
MenuItem, MenuItem,
MenuSeparator, MenuSeparator,
} from '@toeverything/components/menu'; } from '@toeverything/components/menu';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { import { useAtomValue } from 'jotai';
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { setPageModeAtom } from '../../../atoms';
import { currentModeAtom } from '../../../atoms/mode'; import { currentModeAtom } from '../../../atoms/mode';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useExportPage } from '../../../hooks/affine/use-export-page'; import { useExportPage } from '../../../hooks/affine/use-export-page';
import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper'; import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { toast } from '../../../utils'; import { toast } from '../../../utils';
import { PageHistoryModal } from '../../affine/page-history-modal/history-modal'; import { PageHistoryModal } from '../../affine/page-history-modal/history-modal';
import { HeaderDropDownButton } from '../../pure/header-drop-down-button'; import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
@@ -50,7 +42,6 @@ type PageMenuProps = {
export const PageMenu = ({ rename, pageId }: PageMenuProps) => { export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const ref = useRef(null); const ref = useRef(null);
const { openPage } = useNavigateHelper();
// fixme(himself65): remove these hooks ASAP // fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace(); const [workspace] = useCurrentWorkspace();
@@ -64,11 +55,9 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const currentMode = useAtomValue(currentModeAtom); const currentMode = useAtomValue(currentModeAtom);
const favorite = pageMeta.favorite ?? false; const favorite = pageMeta.favorite ?? false;
const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace); const { togglePageMode, toggleFavorite, duplicate } =
const { togglePageMode, toggleFavorite } =
useBlockSuiteMetaHelper(blockSuiteWorkspace); useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace); const { importFile } = usePageHelper(blockSuiteWorkspace);
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace); const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
const [historyModalOpen, setHistoryModalOpen] = useState(false); const [historyModalOpen, setHistoryModalOpen] = useState(false);
@@ -107,34 +96,11 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
}; };
const exportHandler = useExportPage(currentPage); const exportHandler = useExportPage(currentPage);
const setPageMode = useSetAtom(setPageModeAtom);
const duplicate = useAsyncCallback(async () => { const handleDuplicate = useCallback(() => {
const currentPageMeta = currentPage.meta; duplicate(pageId);
const newPage = createPage(); }, [duplicate, pageId]);
await newPage.waitForLoaded();
const update = encodeStateAsUpdate(currentPage.spaceDoc);
applyUpdate(newPage.spaceDoc, update);
setPageMeta(newPage.id, {
tags: currentPageMeta.tags,
favorite: currentPageMeta.favorite,
});
setPageMode(newPage.id, currentMode);
setPageTitle(newPage.id, `${currentPageMeta.title}(1)`);
openPage(blockSuiteWorkspace.id, newPage.id);
}, [
blockSuiteWorkspace.id,
createPage,
currentMode,
currentPage.meta,
currentPage.spaceDoc,
openPage,
setPageMeta,
setPageMode,
setPageTitle,
]);
const EditMenu = ( const EditMenu = (
<> <>
<MenuItem <MenuItem
@@ -199,7 +165,7 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
</MenuIcon> </MenuIcon>
} }
data-testid="editor-option-menu-duplicate" data-testid="editor-option-menu-duplicate"
onSelect={duplicate} onSelect={handleDuplicate}
style={menuItemStyle} style={menuItemStyle}
> >
{t['com.affine.header.option.duplicate']()} {t['com.affine.header.option.duplicate']()}

View File

@@ -14,11 +14,14 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
const { openPage, jumpToSubPath } = useNavigateHelper(); const { openPage, jumpToSubPath } = useNavigateHelper();
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const pageSettings = useAtomValue(pageSettingsAtom); const pageSettings = useAtomValue(pageSettingsAtom);
const isPreferredEdgeless = useCallback( const isPreferredEdgeless = useCallback(
(pageId: string) => pageSettings[pageId]?.mode === 'edgeless', (pageId: string) => pageSettings[pageId]?.mode === 'edgeless',
[pageSettings] [pageSettings]
); );
const setPageMode = useSetAtom(setPageModeAtom); const setPageMode = useSetAtom(setPageModeAtom);
const createPageAndOpen = useCallback( const createPageAndOpen = useCallback(
(mode?: 'page' | 'edgeless') => { (mode?: 'page' | 'edgeless') => {
const page = createPage(); const page = createPage();
@@ -31,9 +34,11 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
}, },
[blockSuiteWorkspace.id, createPage, openPage, setPageMode] [blockSuiteWorkspace.id, createPage, openPage, setPageMode]
); );
const createEdgelessAndOpen = useCallback(() => { const createEdgelessAndOpen = useCallback(() => {
return createPageAndOpen('edgeless'); return createPageAndOpen('edgeless');
}, [createPageAndOpen]); }, [createPageAndOpen]);
const importFileAndOpen = useAsyncCallback(async () => { const importFileAndOpen = useAsyncCallback(async () => {
const { showImportModal } = await import('@blocksuite/blocks'); const { showImportModal } = await import('@blocksuite/blocks');
const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => { const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => {
@@ -55,6 +60,7 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
}; };
showImportModal({ workspace: blockSuiteWorkspace, onSuccess }); showImportModal({ workspace: blockSuiteWorkspace, onSuccess });
}, [blockSuiteWorkspace, openPage, jumpToSubPath]); }, [blockSuiteWorkspace, openPage, jumpToSubPath]);
return useMemo(() => { return useMemo(() => {
return { return {
createPage: createPageAndOpen, createPage: createPageAndOpen,

View File

@@ -12,6 +12,7 @@ export const searchInput = style({
color: 'var(--affine-text-primary-color)', color: 'var(--affine-text-primary-color)',
fontSize: 'var(--affine-font-h-5)', fontSize: 'var(--affine-font-h-5)',
padding: '21px 24px', padding: '21px 24px',
marginBottom: '8px',
width: '100%', width: '100%',
borderBottom: '1px solid var(--affine-border-color)', borderBottom: '1px solid var(--affine-border-color)',
flexShrink: 0, flexShrink: 0,
@@ -114,13 +115,6 @@ globalStyle(`${root} [cmdk-group][hidden]`, {
display: 'none', display: 'none',
}); });
globalStyle(
`${root} [cmdk-group]:not([hidden]):first-of-type [cmdk-group-heading]`,
{
paddingTop: 16,
}
);
globalStyle(`${root} [cmdk-list]`, { globalStyle(`${root} [cmdk-list]`, {
maxHeight: 400, maxHeight: 400,
minHeight: 120, minHeight: 120,
@@ -187,3 +181,11 @@ globalStyle(
color: 'var(--affine-error-color)', color: 'var(--affine-error-color)',
} }
); );
export const resultGroupHeader = style({
padding: '8px',
color: 'var(--affine-text-secondary-color)',
fontSize: 'var(--affine-font-xs)',
fontWeight: 600,
lineHeight: '1.67',
});

View File

@@ -1,11 +1,12 @@
import { Command } from '@affine/cmdk'; import { Command } from '@affine/cmdk';
import { useCommandState } from '@affine/cmdk';
import { formatDate } from '@affine/component/page-list'; import { formatDate } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { PageMeta } from '@blocksuite/store'; import type { PageMeta } from '@blocksuite/store';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import type { CommandCategory } from '@toeverything/infra/command'; import type { CommandCategory } from '@toeverything/infra/command';
import clsx from 'clsx'; import clsx from 'clsx';
import { useAtom } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { Suspense, useLayoutEffect, useMemo, useState } from 'react'; import { Suspense, useLayoutEffect, useMemo, useState } from 'react';
import { import {
@@ -71,7 +72,7 @@ const QuickSearchGroup = ({
); );
return ( return (
<Command.Group key={category} heading={t[i18nkey]()}> <Command.Group key={category} heading={query ? '' : t[i18nkey]()}>
{commands.map(command => { {commands.map(command => {
const label = const label =
typeof command.label === 'string' typeof command.label === 'string'
@@ -129,9 +130,32 @@ const QuickSearchCommands = ({
}: { }: {
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
}) => { }) => {
const t = useAFFiNEI18N();
const groups = useCMDKCommandGroups(); const groups = useCMDKCommandGroups();
return groups.map(([category, commands]) => { const query = useAtomValue(cmdkQueryAtom);
const resultCount = useCommandState(state => state.filtered.count);
const resultGroupHeader = useMemo(() => {
if (query) {
return (
<div className={styles.resultGroupHeader}>
{
// hack: use resultCount to determine if it is creation or results
// because the creation(as 2 results) is always shown at the top when there is no result
resultCount === 2
? t['com.affine.cmdk.affine.category.affine.creation']()
: t['com.affine.cmdk.affine.category.results']()
}
</div>
);
}
return null;
}, [query, resultCount, t]);
return (
<>
{resultGroupHeader}
{groups.map(([category, commands]) => {
return ( return (
<QuickSearchGroup <QuickSearchGroup
key={category} key={category}
@@ -140,7 +164,9 @@ const QuickSearchCommands = ({
commands={commands} commands={commands}
/> />
); );
}); })}
</>
);
}; };
export const CMDKContainer = ({ export const CMDKContainer = ({

View File

@@ -1,25 +1,31 @@
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { import {
useBlockSuitePageMeta, useBlockSuitePageMeta,
usePageMetaHelper, usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta'; } from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { useAtomValue, useSetAtom } from 'jotai'; import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { setPageModeAtom } from '../../atoms'; import { setPageModeAtom } from '../../atoms';
import { currentModeAtom } from '../../atoms/mode'; import { currentModeAtom } from '../../atoms/mode';
import type { BlockSuiteWorkspace } from '../../shared'; import type { BlockSuiteWorkspace } from '../../shared';
import { getWorkspaceSetting } from '../../utils/workspace-setting'; import { getWorkspaceSetting } from '../../utils/workspace-setting';
import { useNavigateHelper } from '../use-navigate-helper';
import { useReferenceLinkHelper } from './use-reference-link-helper'; import { useReferenceLinkHelper } from './use-reference-link-helper';
export function useBlockSuiteMetaHelper( export function useBlockSuiteMetaHelper(
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
) { ) {
const { setPageMeta, getPageMeta, setPageReadonly } = const { setPageMeta, getPageMeta, setPageReadonly, setPageTitle } =
usePageMetaHelper(blockSuiteWorkspace); usePageMetaHelper(blockSuiteWorkspace);
const { addReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace); const { addReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace);
const metas = useBlockSuitePageMeta(blockSuiteWorkspace); const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
const setPageMode = useSetAtom(setPageModeAtom); const setPageMode = useSetAtom(setPageModeAtom);
const currentMode = useAtomValue(currentModeAtom); const currentMode = useAtomValue(currentModeAtom);
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const { openPage } = useNavigateHelper();
const switchToPageMode = useCallback( const switchToPageMode = useCallback(
(pageId: string) => { (pageId: string) => {
@@ -140,6 +146,40 @@ export function useBlockSuiteMetaHelper(
[setPageMeta] [setPageMeta]
); );
const duplicate = useAsyncCallback(
async (pageId: string) => {
const currentPageMeta = getPageMeta(pageId);
const newPage = createPage();
const currentPage = blockSuiteWorkspace.getPage(pageId);
await newPage.waitForLoaded();
if (!currentPageMeta || !currentPage) {
return;
}
const update = encodeStateAsUpdate(currentPage.spaceDoc);
applyUpdate(newPage.spaceDoc, update);
setPageMeta(newPage.id, {
tags: currentPageMeta.tags,
favorite: currentPageMeta.favorite,
});
setPageMode(newPage.id, currentMode);
setPageTitle(newPage.id, `${currentPageMeta.title}(1)`);
openPage(blockSuiteWorkspace.id, newPage.id);
},
[
blockSuiteWorkspace,
createPage,
currentMode,
getPageMeta,
openPage,
setPageMeta,
setPageMode,
setPageTitle,
]
);
return { return {
switchToPageMode, switchToPageMode,
switchToEdgelessMode, switchToEdgelessMode,
@@ -155,5 +195,7 @@ export function useBlockSuiteMetaHelper(
removeToTrash, removeToTrash,
restoreFromTrash, restoreFromTrash,
permanentlyDeletePage, permanentlyDeletePage,
duplicate,
}; };
} }

View File

@@ -41,7 +41,7 @@ export function useRegisterBlocksuiteEditorCommands(
})); }));
}, [pageId, setPageHistoryModalState]); }, [pageId, setPageHistoryModalState]);
const { togglePageMode, toggleFavorite, restoreFromTrash } = const { togglePageMode, toggleFavorite, restoreFromTrash, duplicate } =
useBlockSuiteMetaHelper(blockSuiteWorkspace); useBlockSuiteMetaHelper(blockSuiteWorkspace);
const exportHandler = useExportPage(currentPage); const exportHandler = useExportPage(currentPage);
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace); const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
@@ -125,6 +125,19 @@ export function useRegisterBlocksuiteEditorCommands(
}) })
); );
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-duplicate`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.header.option.duplicate'](),
run() {
duplicate(pageId);
},
})
);
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `editor:${mode}-export-to-pdf`, id: `editor:${mode}-export-to-pdf`,
@@ -234,5 +247,6 @@ export function useRegisterBlocksuiteEditorCommands(
trash, trash,
isCloudWorkspace, isCloudWorkspace,
openHistoryModal, openHistoryModal,
duplicate,
]); ]);
} }

View File

@@ -486,6 +486,7 @@
"com.affine.cmdk.affine.category.editor.edgeless": "Edgeless Commands", "com.affine.cmdk.affine.category.editor.edgeless": "Edgeless Commands",
"com.affine.cmdk.affine.category.editor.insert-object": "Insert Object", "com.affine.cmdk.affine.category.editor.insert-object": "Insert Object",
"com.affine.cmdk.affine.category.editor.page": "Page Commands", "com.affine.cmdk.affine.category.editor.page": "Page Commands",
"com.affine.cmdk.affine.category.results": "Results",
"com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to", "com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to",
"com.affine.cmdk.affine.color-mode.to": "Change Colour Mode to", "com.affine.cmdk.affine.color-mode.to": "Change Colour Mode to",
"com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to", "com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to",