JimmFly 2023-11-29 02:35:12 +00:00
parent 469a18f794
commit c78eb96507
No known key found for this signature in database
GPG Key ID: 14A6F56854E1BED7
7 changed files with 119 additions and 62 deletions

View File

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

View File

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

View File

@ -12,6 +12,7 @@ export const searchInput = style({
color: 'var(--affine-text-primary-color)',
fontSize: 'var(--affine-font-h-5)',
padding: '21px 24px',
marginBottom: '8px',
width: '100%',
borderBottom: '1px solid var(--affine-border-color)',
flexShrink: 0,
@ -114,13 +115,6 @@ globalStyle(`${root} [cmdk-group][hidden]`, {
display: 'none',
});
globalStyle(
`${root} [cmdk-group]:not([hidden]):first-of-type [cmdk-group-heading]`,
{
paddingTop: 16,
}
);
globalStyle(`${root} [cmdk-list]`, {
maxHeight: 400,
minHeight: 120,
@ -187,3 +181,11 @@ globalStyle(
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 { useCommandState } from '@affine/cmdk';
import { formatDate } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { PageMeta } from '@blocksuite/store';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import type { CommandCategory } from '@toeverything/infra/command';
import clsx from 'clsx';
import { useAtom } from 'jotai';
import { useAtom, useAtomValue } from 'jotai';
import { Suspense, useLayoutEffect, useMemo, useState } from 'react';
import {
@ -71,7 +72,7 @@ const QuickSearchGroup = ({
);
return (
<Command.Group key={category} heading={t[i18nkey]()}>
<Command.Group key={category} heading={query ? '' : t[i18nkey]()}>
{commands.map(command => {
const label =
typeof command.label === 'string'
@ -129,18 +130,43 @@ const QuickSearchCommands = ({
}: {
onOpenChange?: (open: boolean) => void;
}) => {
const t = useAFFiNEI18N();
const groups = useCMDKCommandGroups();
return groups.map(([category, commands]) => {
return (
<QuickSearchGroup
key={category}
onOpenChange={onOpenChange}
category={category}
commands={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 (
<QuickSearchGroup
key={category}
onOpenChange={onOpenChange}
category={category}
commands={commands}
/>
);
})}
</>
);
};
export const CMDKContainer = ({

View File

@ -1,25 +1,31 @@
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import {
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 } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { setPageModeAtom } from '../../atoms';
import { currentModeAtom } from '../../atoms/mode';
import type { BlockSuiteWorkspace } from '../../shared';
import { getWorkspaceSetting } from '../../utils/workspace-setting';
import { useNavigateHelper } from '../use-navigate-helper';
import { useReferenceLinkHelper } from './use-reference-link-helper';
export function useBlockSuiteMetaHelper(
blockSuiteWorkspace: BlockSuiteWorkspace
) {
const { setPageMeta, getPageMeta, setPageReadonly } =
const { setPageMeta, getPageMeta, setPageReadonly, setPageTitle } =
usePageMetaHelper(blockSuiteWorkspace);
const { addReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace);
const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
const setPageMode = useSetAtom(setPageModeAtom);
const currentMode = useAtomValue(currentModeAtom);
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const { openPage } = useNavigateHelper();
const switchToPageMode = useCallback(
(pageId: string) => {
@ -140,6 +146,40 @@ export function useBlockSuiteMetaHelper(
[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 {
switchToPageMode,
switchToEdgelessMode,
@ -155,5 +195,7 @@ export function useBlockSuiteMetaHelper(
removeToTrash,
restoreFromTrash,
permanentlyDeletePage,
duplicate,
};
}

View File

@ -41,7 +41,7 @@ export function useRegisterBlocksuiteEditorCommands(
}));
}, [pageId, setPageHistoryModalState]);
const { togglePageMode, toggleFavorite, restoreFromTrash } =
const { togglePageMode, toggleFavorite, restoreFromTrash, duplicate } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const exportHandler = useExportPage(currentPage);
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(
registerAffineCommand({
id: `editor:${mode}-export-to-pdf`,
@ -234,5 +247,6 @@ export function useRegisterBlocksuiteEditorCommands(
trash,
isCloudWorkspace,
openHistoryModal,
duplicate,
]);
}

View File

@ -486,6 +486,7 @@
"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.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.color-mode.to": "Change Colour Mode to",
"com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to",