diff --git a/apps/core/src/atoms/index.ts b/apps/core/src/atoms/index.ts index 8ae902a9e7..69ce42f732 100644 --- a/apps/core/src/atoms/index.ts +++ b/apps/core/src/atoms/index.ts @@ -100,3 +100,5 @@ export const setPageModeAtom = atom( export type PageModeOption = 'all' | 'page' | 'edgeless'; export const allPageModeSelectAtom = atom('all'); + +export const openWorkspaceListModalAtom = atom(false); diff --git a/apps/core/src/commands/affine-creation.tsx b/apps/core/src/commands/affine-creation.tsx index 870df41ab3..7773ee7886 100644 --- a/apps/core/src/commands/affine-creation.tsx +++ b/apps/core/src/commands/affine-creation.tsx @@ -1,5 +1,5 @@ import type { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { PlusIcon } from '@blocksuite/icons'; +import { ImportIcon, PlusIcon } from '@blocksuite/icons'; import { registerAffineCommand } from '@toeverything/infra/command'; import type { createStore } from 'jotai'; @@ -57,6 +57,20 @@ export function registerAffineCreationCommands({ }, }) ); + unsubs.push( + registerAffineCommand({ + id: 'affine:import-workspace', + category: 'affine:creation', + icon: , + label: t['com.affine.cmdk.affine.import-workspace'], + preconditionStrategy: () => { + return environment.isDesktop; + }, + run() { + store.set(openCreateWorkspaceModalAtom, 'add'); + }, + }) + ); return () => { unsubs.forEach(unsub => unsub()); diff --git a/apps/core/src/commands/affine-navigation.tsx b/apps/core/src/commands/affine-navigation.tsx index 0a10fe13fe..bf907857bc 100644 --- a/apps/core/src/commands/affine-navigation.tsx +++ b/apps/core/src/commands/affine-navigation.tsx @@ -4,7 +4,11 @@ import type { Workspace } from '@blocksuite/store'; import { registerAffineCommand } from '@toeverything/infra/command'; import type { createStore } from 'jotai'; -import { openSettingModalAtom } from '../atoms'; +import { + openSettingModalAtom, + openWorkspaceListModalAtom, + type PageModeOption, +} from '../atoms'; import type { useNavigateHelper } from '../hooks/use-navigate-helper'; import { WorkspaceSubPath } from '../shared'; @@ -13,10 +17,14 @@ export function registerAffineNavigationCommands({ store, workspace, navigationHelper, + pageMode, + setPageMode, }: { t: ReturnType; store: ReturnType; navigationHelper: ReturnType; + pageMode: PageModeOption; + setPageMode: React.Dispatch>; workspace: Workspace; }) { const unsubs: Array<() => void> = []; @@ -28,6 +36,51 @@ export function registerAffineNavigationCommands({ label: () => t['com.affine.cmdk.affine.navigation.goto-all-pages'](), run() { navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); + setPageMode('all'); + }, + }) + ); + + unsubs.push( + registerAffineCommand({ + id: 'affine:goto-page-list', + category: 'affine:navigation', + icon: , + preconditionStrategy: () => { + return pageMode !== 'page'; + }, + label: () => t['com.affine.cmdk.affine.navigation.goto-page-list'](), + run() { + navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); + setPageMode('page'); + }, + }) + ); + + unsubs.push( + registerAffineCommand({ + id: 'affine:goto-edgeless-list', + category: 'affine:navigation', + icon: , + preconditionStrategy: () => { + return pageMode !== 'edgeless'; + }, + label: () => t['com.affine.cmdk.affine.navigation.goto-edgeless-list'](), + run() { + navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); + setPageMode('edgeless'); + }, + }) + ); + + unsubs.push( + registerAffineCommand({ + id: 'affine:goto-workspace', + category: 'affine:navigation', + icon: , + label: () => t['com.affine.cmdk.affine.navigation.goto-workspace'](), + run() { + store.set(openWorkspaceListModalAtom, true); }, }) ); @@ -56,6 +109,7 @@ export function registerAffineNavigationCommands({ label: () => t['com.affine.cmdk.affine.navigation.goto-trash'](), run() { navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH); + setPageMode('all'); }, }) ); diff --git a/apps/core/src/components/pure/cmdk/data.tsx b/apps/core/src/components/pure/cmdk/data.tsx index a9004b1b29..e9fad1b094 100644 --- a/apps/core/src/components/pure/cmdk/data.tsx +++ b/apps/core/src/components/pure/cmdk/data.tsx @@ -1,7 +1,9 @@ import { commandScore } from '@affine/cmdk'; +import { useCollectionManager } from '@affine/component/page-list'; +import type { Collection } from '@affine/env/filter'; import { Trans } from '@affine/i18n'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { EdgelessIcon, PageIcon } from '@blocksuite/icons'; +import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons'; import type { Page, PageMeta } from '@blocksuite/store'; import { useBlockSuitePageMeta, @@ -33,6 +35,8 @@ import { } from '../../../atoms'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useNavigateHelper } from '../../../hooks/use-navigate-helper'; +import { WorkspaceSubPath } from '../../../shared'; +import { currentCollectionsAtom } from '../../../utils/user-setting'; import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils'; import type { CMDKCommand, CommandContext } from './types'; @@ -203,8 +207,11 @@ export const usePageCommands = () => { }); results = pages.map(page => { + const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode; + const category = + pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages'; const command = pageToCommand( - 'affine:pages', + category, page, store, navigationHelper, @@ -280,15 +287,86 @@ export const usePageCommands = () => { ]); }; +export const collectionToCommand = ( + collection: Collection, + store: ReturnType, + navigationHelper: ReturnType, + selectCollection: ReturnType['selectCollection'], + t: ReturnType +): CMDKCommand => { + const currentWorkspaceId = store.get(currentWorkspaceIdAtom); + const label = collection.name || t['Untitled'](); + const category = 'affine:collections'; + return { + id: collection.id, + label: label, + // hack: when comparing, the part between >>> and <<< will be ignored + // adding this patch so that CMDK will not complain about duplicated commands + value: + label + + valueWrapperStart + + collection.id + + '.' + + category + + valueWrapperEnd, + originalValue: label, + category: category, + run: () => { + if (!currentWorkspaceId) { + console.error('current workspace not found'); + return; + } + navigationHelper.jumpToSubPath(currentWorkspaceId, WorkspaceSubPath.ALL); + selectCollection(collection.id); + }, + icon: , + }; +}; + +export const useCollectionsCommands = () => { + // todo: considering collections for searching pages + const { savedCollections, selectCollection } = useCollectionManager( + currentCollectionsAtom + ); + const store = getCurrentStore(); + const query = useAtomValue(cmdkQueryAtom); + const navigationHelper = useNavigateHelper(); + const t = useAFFiNEI18N(); + + return useMemo(() => { + let results: CMDKCommand[] = []; + if (query.trim() === '') { + return results; + } else { + results = savedCollections.map(collection => { + const command = collectionToCommand( + collection, + store, + navigationHelper, + selectCollection, + t + ); + return command; + }); + return results; + } + }, [query, savedCollections, store, navigationHelper, selectCollection, t]); +}; + export const useCMDKCommandGroups = () => { const pageCommands = usePageCommands(); + const collectionCommands = useCollectionsCommands(); const affineCommands = useAtomValue(filteredAffineCommands); return useMemo(() => { - const commands = [...pageCommands, ...affineCommands]; + const commands = [ + ...pageCommands, + ...collectionCommands, + ...affineCommands, + ]; const groups = groupBy(commands, command => command.category); return Object.entries(groups) as [CommandCategory, CMDKCommand[]][]; - }, [affineCommands, pageCommands]); + }, [affineCommands, collectionCommands, pageCommands]); }; export const customCommandFilter = (value: string, search: string) => { diff --git a/apps/core/src/components/pure/cmdk/main.tsx b/apps/core/src/components/pure/cmdk/main.tsx index 35fb5ad70f..98bc4624aa 100644 --- a/apps/core/src/components/pure/cmdk/main.tsx +++ b/apps/core/src/components/pure/cmdk/main.tsx @@ -28,6 +28,8 @@ const categoryToI18nKey: Record = { 'affine:general': 'com.affine.cmdk.affine.category.affine.general', 'affine:layout': 'com.affine.cmdk.affine.category.affine.layout', 'affine:pages': 'com.affine.cmdk.affine.category.affine.pages', + 'affine:edgeless': 'com.affine.cmdk.affine.category.affine.edgeless', + 'affine:collections': 'com.affine.cmdk.affine.category.affine.collections', 'affine:settings': 'com.affine.cmdk.affine.category.affine.settings', 'affine:updates': 'com.affine.cmdk.affine.category.affine.updates', 'affine:help': 'com.affine.cmdk.affine.category.affine.help', diff --git a/apps/core/src/components/pure/workspace-mode-filter-tab/index.tsx b/apps/core/src/components/pure/workspace-mode-filter-tab/index.tsx index 5a133cade7..c546fc270f 100644 --- a/apps/core/src/components/pure/workspace-mode-filter-tab/index.tsx +++ b/apps/core/src/components/pure/workspace-mode-filter-tab/index.tsx @@ -17,7 +17,7 @@ export const WorkspaceModeFilterTab = () => { return ( diff --git a/apps/core/src/components/root-app-sidebar/index.tsx b/apps/core/src/components/root-app-sidebar/index.tsx index 0c929210be..b1f7f70675 100644 --- a/apps/core/src/components/root-app-sidebar/index.tsx +++ b/apps/core/src/components/root-app-sidebar/index.tsx @@ -20,10 +20,11 @@ import { import type { Page } from '@blocksuite/store'; import { useDroppable } from '@dnd-kit/core'; import { Menu } from '@toeverything/components/menu'; -import { useAtomValue } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import type { HTMLAttributes, ReactElement } from 'react'; -import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react'; +import { forwardRef, useCallback, useEffect, useMemo } from 'react'; +import { openWorkspaceListModalAtom } from '../../atoms'; import { useHistoryAtom } from '../../atoms/history'; import { useAppSetting } from '../../atoms/settings'; import type { AllWorkspace } from '../../shared'; @@ -100,7 +101,9 @@ export const RootAppSidebar = ({ const { backToAll } = useCollectionManager(currentCollectionsAtom); const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace; const t = useAFFiNEI18N(); - const [openUserWorkspaceList, setOpenUserWorkspaceList] = useState(false); + const [openUserWorkspaceList, setOpenUserWorkspaceList] = useAtom( + openWorkspaceListModalAtom + ); const onClickNewPage = useCallback(async () => { const page = createPage(); await page.waitForLoaded(); @@ -142,7 +145,7 @@ export const RootAppSidebar = ({ }); const closeUserWorkspaceList = useCallback(() => { setOpenUserWorkspaceList(false); - }, []); + }, [setOpenUserWorkspaceList]); return ( <> @@ -178,7 +181,7 @@ export const RootAppSidebar = ({ currentWorkspace={currentWorkspace} onClick={useCallback(() => { setOpenUserWorkspaceList(true); - }, [])} + }, [setOpenUserWorkspaceList])} /> { const unsubs: Array<() => void> = []; unsubs.push( @@ -28,6 +30,8 @@ export function useRegisterWorkspaceCommands() { t, workspace: currentWorkspace.blockSuiteWorkspace, navigationHelper, + pageMode, + setPageMode, }) ); unsubs.push(registerAffineSettingsCommands({ store, t, theme })); @@ -50,5 +54,7 @@ export function useRegisterWorkspaceCommands() { theme, currentWorkspace.blockSuiteWorkspace, navigationHelper, + pageMode, + setPageMode, ]); } diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index d9d0f5ea6b..91b4afcb5e 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -609,5 +609,11 @@ "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.edgeless": "Edgeless Commands", - "com.affine.cmdk.affine.editor.edgeless.presentation-start": "Start Presentation" + "com.affine.cmdk.affine.editor.edgeless.presentation-start": "Start Presentation", + "com.affine.cmdk.affine.navigation.goto-page-list": "Go to Page List", + "com.affine.cmdk.affine.navigation.goto-edgeless-list": "Go to Edgeless List", + "com.affine.cmdk.affine.navigation.goto-workspace": "Go to Workspace", + "com.affine.cmdk.affine.category.affine.edgeless": "Edgeless", + "com.affine.cmdk.affine.category.affine.collections": "Collections", + "com.affine.cmdk.affine.import-workspace": "Import Workspace" } diff --git a/packages/infra/src/command/command.ts b/packages/infra/src/command/command.ts index 85952f0e38..f57f8ad518 100644 --- a/packages/infra/src/command/command.ts +++ b/packages/infra/src/command/command.ts @@ -17,6 +17,8 @@ export type CommandCategory = | 'editor:edgeless' | 'affine:recent' | 'affine:pages' + | 'affine:edgeless' + | 'affine:collections' | 'affine:navigation' | 'affine:creation' | 'affine:settings'