mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor: move command registry to frontend/core (#7135)
move command registeration related logic out of infra module.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ImportIcon, PlusIcon } from '@blocksuite/icons';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { openCreateWorkspaceModalAtom } from '../atoms';
|
||||
import type { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineCreationCommands({
|
||||
store,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ContactWithUsIcon, NewIcon } from '@blocksuite/icons';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { openSettingModalAtom } from '../atoms';
|
||||
import { popupWindow } from '../utils';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineHelpCommands({
|
||||
t,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { SidebarIcon } from '@blocksuite/icons';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { appSidebarOpenAtom } from '../components/app-sidebar';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineLayoutCommands({
|
||||
t,
|
||||
|
||||
@@ -2,12 +2,12 @@ import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightBigIcon } from '@blocksuite/icons';
|
||||
import type { DocCollection } from '@blocksuite/store';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { openSettingModalAtom, openWorkspaceListModalAtom } from '../atoms';
|
||||
import type { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { mixpanel } from '../utils/mixpanel';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineNavigationCommands({
|
||||
t,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import {
|
||||
appSettingAtom,
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@toeverything/infra';
|
||||
import { appSettingAtom } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
import type { useTheme } from 'next-themes';
|
||||
|
||||
import { openQuickSearchModalAtom } from '../atoms';
|
||||
import type { useLanguageHelper } from '../hooks/affine/use-language-helper';
|
||||
import { mixpanel } from '../utils';
|
||||
import { PreconditionStrategy, registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineSettingsCommands({
|
||||
t,
|
||||
|
||||
@@ -2,9 +2,10 @@ import { updateReadyAtom } from '@affine/core/hooks/use-app-updater';
|
||||
import { apis } from '@affine/electron-api';
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ResetIcon } from '@blocksuite/icons';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineUpdatesCommands({
|
||||
t,
|
||||
store,
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './affine-layout';
|
||||
export * from './affine-navigation';
|
||||
export * from './affine-settings';
|
||||
export * from './affine-updates';
|
||||
export * from './registry';
|
||||
|
||||
5
packages/frontend/core/src/commands/registry/README.md
Normal file
5
packages/frontend/core/src/commands/registry/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# AFFiNE Command Abstractions
|
||||
|
||||
This package contains the command abstractions for the AFFiNE framework to be used for CMD-K.
|
||||
|
||||
The implementation is highly inspired by the [VSCode Command Abstractions](https://github.com/microsoft/vscode)
|
||||
103
packages/frontend/core/src/commands/registry/command.ts
Normal file
103
packages/frontend/core/src/commands/registry/command.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
// TODO: need better way for composing different precondition strategies
|
||||
export enum PreconditionStrategy {
|
||||
Always,
|
||||
InPaperOrEdgeless,
|
||||
InPaper,
|
||||
InEdgeless,
|
||||
InEdgelessPresentationMode,
|
||||
NoSearchResult,
|
||||
Never,
|
||||
}
|
||||
|
||||
export type CommandCategory =
|
||||
| 'editor:insert-object'
|
||||
| 'editor:page'
|
||||
| 'editor:edgeless'
|
||||
| 'affine:recent'
|
||||
| 'affine:pages'
|
||||
| 'affine:edgeless'
|
||||
| 'affine:collections'
|
||||
| 'affine:navigation'
|
||||
| 'affine:creation'
|
||||
| 'affine:settings'
|
||||
| 'affine:layout'
|
||||
| 'affine:updates'
|
||||
| 'affine:help'
|
||||
| 'affine:general'
|
||||
| 'affine:results';
|
||||
|
||||
export interface KeybindingOptions {
|
||||
binding: string;
|
||||
// some keybindings are already registered in blocksuite
|
||||
// we can skip the registration of these keybindings __FOR NOW__
|
||||
skipRegister?: boolean;
|
||||
}
|
||||
|
||||
export interface AffineCommandOptions {
|
||||
id: string;
|
||||
// a set of predefined precondition strategies, but also allow user to customize their own
|
||||
preconditionStrategy?: PreconditionStrategy | (() => boolean);
|
||||
// main text on the left..
|
||||
// make text a function so that we can do i18n and interpolation when we need to
|
||||
label:
|
||||
| string
|
||||
| (() => string)
|
||||
| {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
}
|
||||
| (() => {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
});
|
||||
icon: ReactNode; // todo: need a mapping from string -> React element/SVG
|
||||
category?: CommandCategory;
|
||||
// we use https://github.com/jamiebuilds/tinykeys so that we can use the same keybinding definition
|
||||
// for both mac and windows
|
||||
// todo: render keybinding in command palette
|
||||
keyBinding?: KeybindingOptions | string;
|
||||
run: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface AffineCommand {
|
||||
readonly id: string;
|
||||
readonly preconditionStrategy: PreconditionStrategy | (() => boolean);
|
||||
readonly label: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
readonly icon?: ReactNode; // icon name
|
||||
readonly category: CommandCategory;
|
||||
readonly keyBinding?: KeybindingOptions;
|
||||
run(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export function createAffineCommand(
|
||||
options: AffineCommandOptions
|
||||
): AffineCommand {
|
||||
return {
|
||||
id: options.id,
|
||||
run: options.run,
|
||||
icon: options.icon,
|
||||
preconditionStrategy:
|
||||
options.preconditionStrategy ?? PreconditionStrategy.Always,
|
||||
category: options.category ?? 'affine:general',
|
||||
get label() {
|
||||
let label = options.label;
|
||||
label = typeof label === 'function' ? label?.() : label;
|
||||
label =
|
||||
typeof label === 'string'
|
||||
? {
|
||||
title: label,
|
||||
}
|
||||
: label;
|
||||
return label;
|
||||
},
|
||||
keyBinding:
|
||||
typeof options.keyBinding === 'string'
|
||||
? { binding: options.keyBinding }
|
||||
: options.keyBinding,
|
||||
};
|
||||
}
|
||||
2
packages/frontend/core/src/commands/registry/index.ts
Normal file
2
packages/frontend/core/src/commands/registry/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './command';
|
||||
export * from './registry';
|
||||
64
packages/frontend/core/src/commands/registry/registry.ts
Normal file
64
packages/frontend/core/src/commands/registry/registry.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
// @ts-expect-error upstream type is wrong
|
||||
import { tinykeys } from 'tinykeys';
|
||||
|
||||
import type { AffineCommand, AffineCommandOptions } from './command';
|
||||
import { createAffineCommand } from './command';
|
||||
|
||||
const commandLogger = new DebugLogger('command:registry');
|
||||
|
||||
export const AffineCommandRegistry = new (class {
|
||||
readonly commands: Map<string, AffineCommand> = new Map();
|
||||
|
||||
register(options: AffineCommandOptions) {
|
||||
if (this.commands.has(options.id)) {
|
||||
commandLogger.warn(`Command ${options.id} already registered.`);
|
||||
return () => {};
|
||||
}
|
||||
const command = createAffineCommand(options);
|
||||
this.commands.set(command.id, command);
|
||||
|
||||
let unsubKb: (() => void) | undefined;
|
||||
|
||||
if (
|
||||
command.keyBinding &&
|
||||
!command.keyBinding.skipRegister &&
|
||||
typeof window !== 'undefined'
|
||||
) {
|
||||
const { binding: keybinding } = command.keyBinding;
|
||||
unsubKb = tinykeys(window, {
|
||||
[keybinding]: async (e: Event) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await command.run();
|
||||
} catch (e) {
|
||||
console.error(`Failed to invoke keybinding [${keybinding}]`, e);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
commandLogger.debug(`Registered command ${command.id}`);
|
||||
return () => {
|
||||
unsubKb?.();
|
||||
this.commands.delete(command.id);
|
||||
commandLogger.debug(`Unregistered command ${command.id}`);
|
||||
};
|
||||
}
|
||||
|
||||
get(id: string): AffineCommand | undefined {
|
||||
if (!this.commands.has(id)) {
|
||||
commandLogger.warn(`Command ${id} not registered.`);
|
||||
return undefined;
|
||||
}
|
||||
return this.commands.get(id);
|
||||
}
|
||||
|
||||
getAll(): AffineCommand[] {
|
||||
return Array.from(this.commands.values());
|
||||
}
|
||||
})();
|
||||
|
||||
export function registerAffineCommand(options: AffineCommandOptions) {
|
||||
return AffineCommandRegistry.register(options);
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
type AffineCommand,
|
||||
AffineCommandRegistry,
|
||||
type CommandCategory,
|
||||
PreconditionStrategy,
|
||||
} from '@affine/core/commands';
|
||||
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useGetDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
|
||||
import { useJournalHelper } from '@affine/core/hooks/use-journal';
|
||||
@@ -12,17 +18,10 @@ import {
|
||||
TodayIcon,
|
||||
ViewLayersIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type {
|
||||
AffineCommand,
|
||||
CommandCategory,
|
||||
DocRecord,
|
||||
Workspace,
|
||||
} from '@toeverything/infra';
|
||||
import type { DocRecord, Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
AffineCommandRegistry,
|
||||
DocsService,
|
||||
GlobalContextService,
|
||||
PreconditionStrategy,
|
||||
useLiveData,
|
||||
useService,
|
||||
WorkspaceService,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommandCategory } from '@toeverything/infra';
|
||||
import type { CommandCategory } from '@affine/core/commands';
|
||||
import { groupBy } from 'lodash-es';
|
||||
|
||||
import { commandScore } from './command-score';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Loading } from '@affine/component/ui/loading';
|
||||
import type { CommandCategory } from '@affine/core/commands';
|
||||
import { formatDate } from '@affine/core/components/page-list';
|
||||
import { useDocEngineStatus } from '@affine/core/hooks/affine/use-doc-engine-status';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { DocMeta } from '@blocksuite/store';
|
||||
import type { CommandCategory } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { Command } from 'cmdk';
|
||||
import { useDebouncedValue } from 'foxact/use-debounced-value';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CommandCategory, DocMode } from '@toeverything/infra';
|
||||
import type { CommandCategory } from '@affine/core/commands';
|
||||
import type { DocMode } from '@toeverything/infra';
|
||||
|
||||
export interface CommandContext {
|
||||
docMode: DocMode | undefined;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { toast } from '@affine/component';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@affine/core/commands';
|
||||
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||
import { mixpanel } from '@affine/core/utils';
|
||||
@@ -8,8 +12,6 @@ import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
DocService,
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
useLiveData,
|
||||
useService,
|
||||
WorkspaceService,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { registerAffineCommand } from '@affine/core/commands';
|
||||
import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url';
|
||||
import { registerAffineCommand } from '@toeverything/infra';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useRegisterCopyLinkCommands({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { registerAffineCommand } from '@affine/core/commands';
|
||||
import { FindInPageService } from '@affine/core/modules/find-in-page/services/find-in-page';
|
||||
import { registerAffineCommand, useService } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export function useRegisterFindInPageCommands() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
} from '@affine/core/commands';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { NavigatorService } from '../services/navigator';
|
||||
|
||||
Reference in New Issue
Block a user