mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-10 05:23:40 +03:00
feat(core): add search result highlighting (#4667)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
parent
14bee1811c
commit
5226d6c568
@ -40,8 +40,18 @@ export interface AffineCommandOptions {
|
||||
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) | ReactNode | (() => ReactNode);
|
||||
icon: React.ReactNode; // todo: need a mapping from string -> React element/SVG
|
||||
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
|
||||
@ -53,8 +63,11 @@ export interface AffineCommandOptions {
|
||||
export interface AffineCommand {
|
||||
readonly id: string;
|
||||
readonly preconditionStrategy: PreconditionStrategy | (() => boolean);
|
||||
readonly label?: ReactNode | string;
|
||||
readonly icon?: React.ReactNode; // icon name
|
||||
readonly label: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
readonly icon?: ReactNode; // icon name
|
||||
readonly category: CommandCategory;
|
||||
readonly keyBinding?: KeybindingOptions;
|
||||
run(): void | Promise<void>;
|
||||
@ -71,8 +84,15 @@ export function createAffineCommand(
|
||||
options.preconditionStrategy ?? PreconditionStrategy.Always,
|
||||
category: options.category ?? 'affine:general',
|
||||
get label() {
|
||||
const label = options.label;
|
||||
return typeof label === 'function' ? label?.() : 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'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
browserWarningStyle,
|
||||
@ -14,7 +14,7 @@ export const BrowserWarning = ({
|
||||
}: {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
message: React.ReactNode;
|
||||
message: ReactNode;
|
||||
}) => {
|
||||
if (!show) {
|
||||
return null;
|
||||
|
@ -88,11 +88,7 @@ const CloudShareMenu = (props: ShareMenuProps) => {
|
||||
modal: false,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="cloud-share-menu-button"
|
||||
type="plain"
|
||||
onClick={() => console.log('gg')}
|
||||
>
|
||||
<Button data-testid="cloud-share-menu-button" type="plain">
|
||||
<div
|
||||
style={{
|
||||
color: isSharedPage
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export type DateFormats =
|
||||
@ -75,7 +75,3 @@ export const appSettingAtom = atom<
|
||||
set(appSettingBaseAtom, { ...prev, ...next });
|
||||
}
|
||||
);
|
||||
|
||||
export const useAppSetting = () => {
|
||||
return useAtom(appSettingAtom);
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ export function registerAffineCreationCommands({
|
||||
registerAffineCommand({
|
||||
id: 'affine:new-page',
|
||||
category: 'affine:creation',
|
||||
label: t['com.affine.cmdk.affine.new-page'],
|
||||
label: t['com.affine.cmdk.affine.new-page'](),
|
||||
icon: <PlusIcon />,
|
||||
keyBinding: environment.isDesktop
|
||||
? {
|
||||
@ -39,7 +39,7 @@ export function registerAffineCreationCommands({
|
||||
id: 'affine:new-edgeless-page',
|
||||
category: 'affine:creation',
|
||||
icon: <PlusIcon />,
|
||||
label: t['com.affine.cmdk.affine.new-edgeless-page'],
|
||||
label: t['com.affine.cmdk.affine.new-edgeless-page'](),
|
||||
run() {
|
||||
pageHelper.createEdgeless();
|
||||
},
|
||||
@ -51,7 +51,7 @@ export function registerAffineCreationCommands({
|
||||
id: 'affine:new-workspace',
|
||||
category: 'affine:creation',
|
||||
icon: <PlusIcon />,
|
||||
label: t['com.affine.cmdk.affine.new-workspace'],
|
||||
label: t['com.affine.cmdk.affine.new-workspace'](),
|
||||
run() {
|
||||
store.set(openCreateWorkspaceModalAtom, 'new');
|
||||
},
|
||||
@ -62,7 +62,7 @@ export function registerAffineCreationCommands({
|
||||
id: 'affine:import-workspace',
|
||||
category: 'affine:creation',
|
||||
icon: <ImportIcon />,
|
||||
label: t['com.affine.cmdk.affine.import-workspace'],
|
||||
label: t['com.affine.cmdk.affine.import-workspace'](),
|
||||
preconditionStrategy: () => {
|
||||
return environment.isDesktop;
|
||||
},
|
||||
|
@ -18,7 +18,7 @@ export function registerAffineHelpCommands({
|
||||
id: 'affine:help-whats-new',
|
||||
category: 'affine:help',
|
||||
icon: <NewIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.whats-new'](),
|
||||
label: t['com.affine.cmdk.affine.whats-new'](),
|
||||
run() {
|
||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||
},
|
||||
@ -29,7 +29,7 @@ export function registerAffineHelpCommands({
|
||||
id: 'affine:help-contact-us',
|
||||
category: 'affine:help',
|
||||
icon: <ContactWithUsIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.contact-us'](),
|
||||
label: t['com.affine.cmdk.affine.contact-us'](),
|
||||
run() {
|
||||
store.set(openSettingModalAtom, {
|
||||
open: true,
|
||||
@ -44,7 +44,7 @@ export function registerAffineHelpCommands({
|
||||
id: 'affine:help-getting-started',
|
||||
category: 'affine:help',
|
||||
icon: <UserGuideIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.getting-started'](),
|
||||
label: t['com.affine.cmdk.affine.getting-started'](),
|
||||
preconditionStrategy: () => environment.isDesktop,
|
||||
run() {
|
||||
store.set(openOnboardingModalAtom, true);
|
||||
|
@ -17,14 +17,11 @@ export function registerAffineLayoutCommands({
|
||||
id: 'affine:toggle-left-sidebar',
|
||||
category: 'affine:layout',
|
||||
icon: <SidebarIcon />,
|
||||
label: () => {
|
||||
const open = store.get(appSidebarOpenAtom);
|
||||
return t[
|
||||
open
|
||||
? 'com.affine.cmdk.affine.left-sidebar.collapse'
|
||||
: 'com.affine.cmdk.affine.left-sidebar.expand'
|
||||
]();
|
||||
},
|
||||
label: () =>
|
||||
store.get(appSidebarOpenAtom)
|
||||
? t['com.affine.cmdk.affine.left-sidebar.collapse']()
|
||||
: t['com.affine.cmdk.affine.left-sidebar.expand'](),
|
||||
|
||||
keyBinding: {
|
||||
binding: '$mod+/',
|
||||
},
|
||||
|
@ -33,7 +33,7 @@ export function registerAffineNavigationCommands({
|
||||
id: 'affine:goto-all-pages',
|
||||
category: 'affine:navigation',
|
||||
icon: <ArrowRightBigIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.navigation.goto-all-pages'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.goto-all-pages'](),
|
||||
run() {
|
||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
setPageListMode('all');
|
||||
@ -49,7 +49,7 @@ export function registerAffineNavigationCommands({
|
||||
preconditionStrategy: () => {
|
||||
return pageListMode !== 'page';
|
||||
},
|
||||
label: () => t['com.affine.cmdk.affine.navigation.goto-page-list'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.goto-page-list'](),
|
||||
run() {
|
||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
setPageListMode('page');
|
||||
@ -65,7 +65,7 @@ export function registerAffineNavigationCommands({
|
||||
preconditionStrategy: () => {
|
||||
return pageListMode !== 'edgeless';
|
||||
},
|
||||
label: () => t['com.affine.cmdk.affine.navigation.goto-edgeless-list'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.goto-edgeless-list'](),
|
||||
run() {
|
||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
setPageListMode('edgeless');
|
||||
@ -78,7 +78,7 @@ export function registerAffineNavigationCommands({
|
||||
id: 'affine:goto-workspace',
|
||||
category: 'affine:navigation',
|
||||
icon: <ArrowRightBigIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.navigation.goto-workspace'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.goto-workspace'](),
|
||||
run() {
|
||||
store.set(openWorkspaceListModalAtom, true);
|
||||
},
|
||||
@ -90,7 +90,7 @@ export function registerAffineNavigationCommands({
|
||||
id: 'affine:open-settings',
|
||||
category: 'affine:navigation',
|
||||
icon: <ArrowRightBigIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.navigation.open-settings'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.open-settings'](),
|
||||
run() {
|
||||
store.set(openSettingModalAtom, {
|
||||
activeTab: 'appearance',
|
||||
@ -106,7 +106,7 @@ export function registerAffineNavigationCommands({
|
||||
id: 'affine:goto-trash',
|
||||
category: 'affine:navigation',
|
||||
icon: <ArrowRightBigIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.navigation.goto-trash'](),
|
||||
label: t['com.affine.cmdk.affine.navigation.goto-trash'](),
|
||||
run() {
|
||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH);
|
||||
setPageListMode('all');
|
||||
|
@ -1,76 +1,16 @@
|
||||
import { Trans } from '@affine/i18n';
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@toeverything/infra/command';
|
||||
import { type createStore, useAtomValue } from 'jotai';
|
||||
import { type createStore } from 'jotai';
|
||||
import type { useTheme } from 'next-themes';
|
||||
|
||||
import { openQuickSearchModalAtom } from '../atoms';
|
||||
import { appSettingAtom } from '../atoms/settings';
|
||||
import type { useLanguageHelper } from '../hooks/affine/use-language-helper';
|
||||
|
||||
// todo - find a better way to abstract the following translations components
|
||||
const ClientBorderStyleLabel = () => {
|
||||
const { clientBorder } = useAtomValue(appSettingAtom);
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.client-border-style.to"
|
||||
values={{
|
||||
state: clientBorder ? 'OFF' : 'ON',
|
||||
}}
|
||||
>
|
||||
Change Client Border Style to
|
||||
<strong>state</strong>
|
||||
</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
const FullWidthLayoutLabel = () => {
|
||||
const { fullWidthLayout } = useAtomValue(appSettingAtom);
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.full-width-layout.to"
|
||||
values={{
|
||||
state: fullWidthLayout ? 'OFF' : 'ON',
|
||||
}}
|
||||
>
|
||||
Change Full Width Layout to
|
||||
<strong>state</strong>
|
||||
</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
const NoisyBackgroundLabel = () => {
|
||||
const { enableNoisyBackground } = useAtomValue(appSettingAtom);
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.noise-background-on-the-sidebar.to"
|
||||
values={{
|
||||
state: enableNoisyBackground ? 'OFF' : 'ON',
|
||||
}}
|
||||
>
|
||||
Change Noise Background On The Sidebar to <strong>state</strong>
|
||||
</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
const BlurBackgroundLabel = () => {
|
||||
const { enableBlurBackground } = useAtomValue(appSettingAtom);
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.translucent-ui-on-the-sidebar.to"
|
||||
values={{
|
||||
state: enableBlurBackground ? 'OFF' : 'ON',
|
||||
}}
|
||||
>
|
||||
Change Translucent UI On The Sidebar to <strong>state</strong>
|
||||
</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
export function registerAffineSettingsCommands({
|
||||
t,
|
||||
store,
|
||||
@ -83,7 +23,7 @@ export function registerAffineSettingsCommands({
|
||||
languageHelper: ReturnType<typeof useLanguageHelper>;
|
||||
}) {
|
||||
const unsubs: Array<() => void> = [];
|
||||
const { onSelect, languagesList, currentLanguage } = languageHelper;
|
||||
const { onLanguageChange, languagesList, currentLanguage } = languageHelper;
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:show-quick-search',
|
||||
@ -92,6 +32,7 @@ export function registerAffineSettingsCommands({
|
||||
keyBinding: {
|
||||
binding: '$mod+K',
|
||||
},
|
||||
label: '',
|
||||
icon: <SettingsIcon />,
|
||||
run() {
|
||||
const quickSearchModalState = store.get(openQuickSearchModalAtom);
|
||||
@ -104,14 +45,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-color-scheme-to-auto',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.color-scheme.to"
|
||||
values={{ colour: 'Auto' }}
|
||||
>
|
||||
Change Colour Scheme to <strong>colour</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
|
||||
'com.affine.themeSettings.system'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => theme.theme !== 'system',
|
||||
@ -123,14 +59,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-color-scheme-to-dark',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.color-scheme.to"
|
||||
values={{ colour: 'Dark' }}
|
||||
>
|
||||
Change Colour Scheme to <strong>colour</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
|
||||
'com.affine.themeSettings.dark'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => theme.theme !== 'dark',
|
||||
@ -143,14 +74,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-color-scheme-to-light',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.color-scheme.to"
|
||||
values={{ colour: 'Light' }}
|
||||
>
|
||||
Change Colour Scheme to <strong>colour</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
|
||||
'com.affine.themeSettings.light'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => theme.theme !== 'light',
|
||||
@ -164,16 +90,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-font-style-to-sans',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.font-style.to"
|
||||
values={{
|
||||
fontFamily: t['com.affine.appearanceSettings.fontStyle.sans'](),
|
||||
}}
|
||||
>
|
||||
Change Font Style to <strong>fontFamily</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
|
||||
'com.affine.appearanceSettings.fontStyle.sans'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () =>
|
||||
@ -190,19 +109,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-font-style-to-serif',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.font-style.to"
|
||||
values={{
|
||||
fontFamily: t['com.affine.appearanceSettings.fontStyle.serif'](),
|
||||
}}
|
||||
>
|
||||
Change Font Style to
|
||||
<strong style={{ fontFamily: 'var(--affine-font-serif-family)' }}>
|
||||
fontFamily
|
||||
</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
|
||||
'com.affine.appearanceSettings.fontStyle.serif'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () =>
|
||||
@ -219,19 +128,9 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: 'affine:change-font-style-to-mono',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.font-style.to"
|
||||
values={{
|
||||
fontFamily: t['com.affine.appearanceSettings.fontStyle.mono'](),
|
||||
}}
|
||||
>
|
||||
Change Font Style to
|
||||
<strong style={{ fontFamily: 'var(--affine-font-mono-family)' }}>
|
||||
fontFamily
|
||||
</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
|
||||
'com.affine.appearanceSettings.fontStyle.mono'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () =>
|
||||
@ -250,22 +149,14 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `affine:change-display-language-to-${language.name}`,
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.display-language.to"
|
||||
values={{
|
||||
language: language.originalName,
|
||||
}}
|
||||
>
|
||||
Change Display Language to
|
||||
<strong>language</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.display-language.to']()} ${
|
||||
language.originalName
|
||||
}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => currentLanguage?.tag !== language.tag,
|
||||
run() {
|
||||
onSelect(language.tag);
|
||||
onLanguageChange(language.tag);
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -275,7 +166,12 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `affine:change-client-border-style`,
|
||||
label: <ClientBorderStyleLabel />,
|
||||
label: () => `${t['com.affine.cmdk.affine.client-border-style.to']()} ${t[
|
||||
store.get(appSettingAtom).clientBorder
|
||||
? 'com.affine.cmdk.affine.switch-state.off'
|
||||
: 'com.affine.cmdk.affine.switch-state.on'
|
||||
]()}
|
||||
`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => environment.isDesktop,
|
||||
@ -291,7 +187,12 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `affine:change-full-width-layout`,
|
||||
label: <FullWidthLayoutLabel />,
|
||||
label: () =>
|
||||
`${t['com.affine.cmdk.affine.full-width-layout.to']()} ${t[
|
||||
store.get(appSettingAtom).fullWidthLayout
|
||||
? 'com.affine.cmdk.affine.switch-state.off'
|
||||
: 'com.affine.cmdk.affine.switch-state.on'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
run() {
|
||||
@ -306,7 +207,14 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `affine:change-noise-background-on-the-sidebar`,
|
||||
label: <NoisyBackgroundLabel />,
|
||||
label: () =>
|
||||
`${t[
|
||||
'com.affine.cmdk.affine.noise-background-on-the-sidebar.to'
|
||||
]()} ${t[
|
||||
store.get(appSettingAtom).enableNoisyBackground
|
||||
? 'com.affine.cmdk.affine.switch-state.off'
|
||||
: 'com.affine.cmdk.affine.switch-state.on'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => environment.isDesktop,
|
||||
@ -322,7 +230,12 @@ export function registerAffineSettingsCommands({
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `affine:change-translucent-ui-on-the-sidebar`,
|
||||
label: <BlurBackgroundLabel />,
|
||||
label: () =>
|
||||
`${t['com.affine.cmdk.affine.translucent-ui-on-the-sidebar.to']()} ${t[
|
||||
store.get(appSettingAtom).enableBlurBackground
|
||||
? 'com.affine.cmdk.affine.switch-state.off'
|
||||
: 'com.affine.cmdk.affine.switch-state.on'
|
||||
]()}`,
|
||||
category: 'affine:settings',
|
||||
icon: <SettingsIcon />,
|
||||
preconditionStrategy: () => environment.isDesktop,
|
||||
|
@ -18,7 +18,7 @@ export function registerAffineUpdatesCommands({
|
||||
id: 'affine:restart-to-upgrade',
|
||||
category: 'affine:updates',
|
||||
icon: <ResetIcon />,
|
||||
label: () => t['com.affine.cmdk.affine.restart-to-upgrade'](),
|
||||
label: t['com.affine.cmdk.affine.restart-to-upgrade'](),
|
||||
preconditionStrategy: () => !!store.get(updateReadyAtom),
|
||||
run() {
|
||||
window.apis?.updater.quitAndInstall().catch(err => {
|
||||
|
@ -3,10 +3,10 @@ import {
|
||||
type WorkspaceRootProps,
|
||||
} from '@affine/component/workspace';
|
||||
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
|
||||
export const AppContainer = (props: WorkspaceRootProps) => {
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
return (
|
||||
<AppContainerWithoutSettings
|
||||
|
@ -5,7 +5,8 @@ import { useLanguageHelper } from '../../../hooks/affine/use-language-helper';
|
||||
|
||||
// Fixme: keyboard focus should be supported by Menu component
|
||||
const LanguageMenuContent = memo(function LanguageMenuContent() {
|
||||
const { currentLanguage, languagesList, onSelect } = useLanguageHelper();
|
||||
const { currentLanguage, languagesList, onLanguageChange } =
|
||||
useLanguageHelper();
|
||||
return (
|
||||
<>
|
||||
{languagesList.map(option => {
|
||||
@ -14,7 +15,7 @@ const LanguageMenuContent = memo(function LanguageMenuContent() {
|
||||
key={option.name}
|
||||
selected={currentLanguage?.originalName === option.originalName}
|
||||
title={option.name}
|
||||
onSelect={() => onSelect(option.tag)}
|
||||
onSelect={() => onLanguageChange(option.tag)}
|
||||
>
|
||||
{option.originalName}
|
||||
</MenuItem>
|
||||
|
@ -66,12 +66,7 @@ const PublishPanelAffine = (props: PublishPanelAffineProps) => {
|
||||
marginBottom: isPublic ? '12px' : '25px',
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
checked={isPublic}
|
||||
// onChange={useCallback(value => {
|
||||
// console.log('onChange', value);
|
||||
// }, [])}
|
||||
/>
|
||||
<Switch checked={isPublic} />
|
||||
</SettingRow>
|
||||
{isPublic ? (
|
||||
<FlexWrapper justifyContent="space-between" marginBottom={25}>
|
||||
|
@ -4,21 +4,14 @@ import { SettingRow } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { type AppSetting, useAppSetting } from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
import { relatedLinks } from './config';
|
||||
import { communityItem, communityWrapper, link } from './style.css';
|
||||
|
||||
export const AboutAffine = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const changeSwitch = useCallback(
|
||||
(key: keyof AppSetting, checked: boolean) => {
|
||||
setAppSettings({ [key]: checked });
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
@ -47,7 +40,7 @@ export const AboutAffine = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.autoCheckUpdate}
|
||||
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||
onChange={checked => updateSettings('autoCheckUpdate', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@ -58,7 +51,7 @@ export const AboutAffine = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.autoCheckUpdate}
|
||||
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||
onChange={checked => updateSettings('autoCheckUpdate', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
|
@ -5,8 +5,8 @@ import { useCallback } from 'react';
|
||||
import {
|
||||
dateFormatOptions,
|
||||
type DateFormats,
|
||||
useAppSetting,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
|
||||
interface DateFormatMenuContentProps {
|
||||
currentOption: DateFormats;
|
||||
@ -35,12 +35,12 @@ const DateFormatMenuContent = ({
|
||||
};
|
||||
|
||||
export const DateFormatSetting = () => {
|
||||
const [appearanceSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
const handleSelect = useCallback(
|
||||
(option: DateFormats) => {
|
||||
setAppSettings({ dateFormat: option });
|
||||
updateSettings('dateFormat', option);
|
||||
},
|
||||
[setAppSettings]
|
||||
[updateSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -48,12 +48,12 @@ export const DateFormatSetting = () => {
|
||||
items={
|
||||
<DateFormatMenuContent
|
||||
onSelect={handleSelect}
|
||||
currentOption={appearanceSettings.dateFormat}
|
||||
currentOption={appSettings.dateFormat}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MenuTrigger data-testid="date-format-menu-trigger" block>
|
||||
{dayjs(new Date()).format(appearanceSettings.dateFormat)}
|
||||
{dayjs(new Date()).format(appSettings.dateFormat)}
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -9,9 +9,9 @@ import { useCallback } from 'react';
|
||||
import {
|
||||
type AppSetting,
|
||||
fontStyleOptions,
|
||||
useAppSetting,
|
||||
windowFrameStyleOptions,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
import { LanguageMenu } from '../../../language-menu';
|
||||
import { DateFormatSetting } from './date-format-setting';
|
||||
import { settingWrapper } from './style.css';
|
||||
@ -47,7 +47,7 @@ export const ThemeSettings = () => {
|
||||
|
||||
const FontFamilySettings = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
return (
|
||||
<RadioButtonGroup
|
||||
width={250}
|
||||
@ -55,9 +55,9 @@ const FontFamilySettings = () => {
|
||||
value={appSettings.fontStyle}
|
||||
onValueChange={useCallback(
|
||||
(key: AppSetting['fontStyle']) => {
|
||||
setAppSettings({ fontStyle: key });
|
||||
updateSettings('fontStyle', key);
|
||||
},
|
||||
[setAppSettings]
|
||||
[updateSettings]
|
||||
)}
|
||||
>
|
||||
{fontStyleOptions.map(({ key, value }) => {
|
||||
@ -95,14 +95,8 @@ const FontFamilySettings = () => {
|
||||
export const AppearanceSettings = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
|
||||
const changeSwitch = useCallback(
|
||||
(key: keyof AppSetting, checked: boolean) => {
|
||||
setAppSettings({ [key]: checked });
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
@ -139,7 +133,7 @@ export const AppearanceSettings = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.clientBorder}
|
||||
onChange={checked => changeSwitch('clientBorder', checked)}
|
||||
onChange={checked => updateSettings('clientBorder', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
) : null}
|
||||
@ -151,7 +145,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
data-testid="full-width-layout-trigger"
|
||||
checked={appSettings.fullWidthLayout}
|
||||
onChange={checked => changeSwitch('fullWidthLayout', checked)}
|
||||
onChange={checked => updateSettings('fullWidthLayout', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
|
||||
@ -164,7 +158,7 @@ export const AppearanceSettings = () => {
|
||||
width={250}
|
||||
defaultValue={appSettings.windowFrameStyle}
|
||||
onValueChange={(value: AppSetting['windowFrameStyle']) => {
|
||||
setAppSettings({ windowFrameStyle: value });
|
||||
updateSettings('windowFrameStyle', value);
|
||||
}}
|
||||
>
|
||||
{windowFrameStyleOptions.map(option => {
|
||||
@ -194,7 +188,7 @@ export const AppearanceSettings = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.startWeekOnMonday}
|
||||
onChange={checked => changeSwitch('startWeekOnMonday', checked)}
|
||||
onChange={checked => updateSettings('startWeekOnMonday', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
@ -213,7 +207,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
checked={appSettings.enableNoisyBackground}
|
||||
onChange={checked =>
|
||||
changeSwitch('enableNoisyBackground', checked)
|
||||
updateSettings('enableNoisyBackground', checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
@ -227,7 +221,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
checked={appSettings.enableBlurBackground}
|
||||
onChange={checked =>
|
||||
changeSwitch('enableBlurBackground', checked)
|
||||
updateSettings('enableBlurBackground', checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
@ -29,7 +29,8 @@ import {
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import { pageSettingFamily } from '../atoms';
|
||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||
import { fontStyleOptions } from '../atoms/settings';
|
||||
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
|
||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||
import { Bookmark } from './bookmark';
|
||||
import * as styles from './page-detail-editor.css';
|
||||
@ -68,7 +69,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
const currentMode = pageSetting?.mode ?? 'page';
|
||||
|
||||
const setBlockHub = useSetAtom(rootBlockHubAtom);
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
assertExists(meta);
|
||||
const value = useMemo(() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
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, ViewLayersIcon } from '@blocksuite/icons';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
@ -150,19 +149,27 @@ export const pageToCommand = (
|
||||
page: PageMeta,
|
||||
store: ReturnType<typeof getCurrentStore>,
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>,
|
||||
t: ReturnType<typeof useAFFiNEI18N>
|
||||
t: ReturnType<typeof useAFFiNEI18N>,
|
||||
label?: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
}
|
||||
): CMDKCommand => {
|
||||
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
|
||||
const currentWorkspaceId = store.get(currentWorkspaceIdAtom);
|
||||
const label = page.title || t['Untitled']();
|
||||
const title = page.title || t['Untitled']();
|
||||
const commandLabel = label || {
|
||||
title: title,
|
||||
};
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
label: label,
|
||||
label: commandLabel,
|
||||
// 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 + page.id + '.' + category + valueWrapperEnd,
|
||||
originalValue: label,
|
||||
title + valueWrapperStart + page.id + '.' + category + valueWrapperEnd,
|
||||
originalValue: title,
|
||||
category: category,
|
||||
run: () => {
|
||||
if (!currentWorkspaceId) {
|
||||
@ -179,8 +186,6 @@ export const pageToCommand = (
|
||||
const contentMatchedMagicString = '__$$content_matched$$__';
|
||||
|
||||
export const usePageCommands = () => {
|
||||
// todo: considering collections for searching pages
|
||||
// const { savedCollections } = useCollectionManager(currentCollectionsAtom);
|
||||
const recentPages = useRecentPages();
|
||||
const pages = useWorkspacePages();
|
||||
const store = getCurrentStore();
|
||||
@ -203,11 +208,11 @@ export const usePageCommands = () => {
|
||||
workspace.blockSuiteWorkspace.search({ query }).values()
|
||||
) as unknown as { space: string; content: string }[];
|
||||
|
||||
const pageIds = searchResults.map(id => {
|
||||
if (id.space.startsWith('space:')) {
|
||||
return id.space.slice(6);
|
||||
const pageIds = searchResults.map(result => {
|
||||
if (result.space.startsWith('space:')) {
|
||||
return result.space.slice(6);
|
||||
} else {
|
||||
return id.space;
|
||||
return result.space;
|
||||
}
|
||||
});
|
||||
|
||||
@ -215,12 +220,21 @@ export const usePageCommands = () => {
|
||||
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
|
||||
const category =
|
||||
pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages';
|
||||
|
||||
const label = {
|
||||
title: page.title,
|
||||
subTitle:
|
||||
searchResults.find(result => result.space === page.id)?.content ||
|
||||
'',
|
||||
};
|
||||
|
||||
const command = pageToCommand(
|
||||
category,
|
||||
page,
|
||||
store,
|
||||
navigationHelper,
|
||||
t
|
||||
t,
|
||||
label
|
||||
);
|
||||
|
||||
if (pageIds.includes(page.id)) {
|
||||
@ -235,14 +249,7 @@ export const usePageCommands = () => {
|
||||
if (results.every(command => command.originalValue !== query)) {
|
||||
results.push({
|
||||
id: 'affine:pages:create-page',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.create-new-page-as"
|
||||
values={{ query }}
|
||||
>
|
||||
Create New Page as: <strong>query</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.create-new-page-as']()} ${query}`,
|
||||
value: 'affine::create-page' + query, // hack to make the page always showing in the search result
|
||||
category: 'affine:creation',
|
||||
run: async () => {
|
||||
@ -255,14 +262,9 @@ export const usePageCommands = () => {
|
||||
|
||||
results.push({
|
||||
id: 'affine:pages:create-edgeless',
|
||||
label: (
|
||||
<Trans
|
||||
values={{ query }}
|
||||
i18nKey="com.affine.cmdk.affine.create-new-edgeless-as"
|
||||
>
|
||||
Create New Edgeless as: <strong>query</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t[
|
||||
'com.affine.cmdk.affine.create-new-edgeless-as'
|
||||
]()} ${query}`,
|
||||
value: 'affine::create-edgeless' + query, // hack to make the page always showing in the search result
|
||||
category: 'affine:creation',
|
||||
run: async () => {
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const highlightContainer = style({
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
});
|
||||
|
||||
export const highlightText = style({
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
});
|
||||
|
||||
export const highlightKeyword = style({
|
||||
color: 'var(--affine-primary-color)',
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'visible',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
export const labelTitle = style({
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '24px',
|
||||
fontWeight: 400,
|
||||
textAlign: 'justify',
|
||||
});
|
||||
|
||||
export const labelContent = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 400,
|
||||
textAlign: 'justify',
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
import { escapeRegExp } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
|
||||
import {
|
||||
highlightContainer,
|
||||
highlightKeyword,
|
||||
highlightText,
|
||||
labelContent,
|
||||
labelTitle,
|
||||
} from './highlight.css';
|
||||
|
||||
type SearchResultLabel = {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
|
||||
type HighlightProps = {
|
||||
text: string;
|
||||
highlight: string;
|
||||
};
|
||||
|
||||
type HighlightLabelProps = {
|
||||
label: SearchResultLabel;
|
||||
highlight: string;
|
||||
};
|
||||
|
||||
export const Highlight = memo(function Highlight({
|
||||
text = '',
|
||||
highlight = '',
|
||||
}: HighlightProps) {
|
||||
//Regex is used to ignore case
|
||||
const regex = highlight.trim()
|
||||
? new RegExp(`(${escapeRegExp(highlight)})`, 'ig')
|
||||
: null;
|
||||
|
||||
if (!regex) {
|
||||
return <span>{text}</span>;
|
||||
}
|
||||
const parts = text.split(regex);
|
||||
|
||||
return (
|
||||
<div className={highlightContainer}>
|
||||
{parts.map((part, i) => {
|
||||
if (regex.test(part)) {
|
||||
return (
|
||||
<span key={i} className={highlightKeyword}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span key={i} className={highlightText}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const HighlightLabel = memo(function HighlightLabel({
|
||||
label,
|
||||
highlight,
|
||||
}: HighlightLabelProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className={labelTitle}>
|
||||
<Highlight text={label.title} highlight={highlight} />
|
||||
</div>
|
||||
{label.subTitle ? (
|
||||
<div className={labelContent}>
|
||||
<Highlight text={label.subTitle} highlight={highlight} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -76,6 +76,8 @@ export const timestamp = style({
|
||||
display: 'flex',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
minWidth: 120,
|
||||
flexDirection: 'row-reverse',
|
||||
});
|
||||
|
||||
export const keybinding = style({
|
||||
@ -153,8 +155,8 @@ globalStyle(`${root} [cmdk-list]:hover::-webkit-scrollbar-thumb:hover`, {
|
||||
|
||||
globalStyle(`${root} [cmdk-item]`, {
|
||||
display: 'flex',
|
||||
height: 44,
|
||||
padding: '0 12px',
|
||||
minHeight: 44,
|
||||
padding: '6px 12px',
|
||||
alignItems: 'center',
|
||||
cursor: 'default',
|
||||
borderRadius: 4,
|
||||
|
@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import type { CommandCategory } from '@toeverything/infra/command';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useAtom } from 'jotai';
|
||||
import { Suspense, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
@ -13,8 +13,10 @@ import {
|
||||
customCommandFilter,
|
||||
useCMDKCommandGroups,
|
||||
} from './data';
|
||||
import { HighlightLabel } from './highlight';
|
||||
import * as styles from './main.css';
|
||||
import { CMDKModal, type CMDKModalProps } from './modal';
|
||||
import { NotFoundGroup } from './not-found';
|
||||
import type { CMDKCommand } from './types';
|
||||
|
||||
type NoParametersKeys<T> = {
|
||||
@ -52,10 +54,16 @@ const QuickSearchGroup = ({
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const i18nkey = categoryToI18nKey[category];
|
||||
const setQuery = useSetAtom(cmdkQueryAtom);
|
||||
const [query, setQuery] = useAtom(cmdkQueryAtom);
|
||||
return (
|
||||
<Command.Group key={category} heading={t[i18nkey]()}>
|
||||
{commands.map(command => {
|
||||
const label =
|
||||
typeof command.label === 'string'
|
||||
? {
|
||||
title: command.label,
|
||||
}
|
||||
: command.label;
|
||||
return (
|
||||
<Command.Item
|
||||
key={command.id}
|
||||
@ -78,7 +86,7 @@ const QuickSearchGroup = ({
|
||||
command.originalValue ? command.originalValue : undefined
|
||||
}
|
||||
>
|
||||
{command.label}
|
||||
<HighlightLabel highlight={query} label={label} />
|
||||
</div>
|
||||
{command.timestamp ? (
|
||||
<div className={styles.timestamp}>
|
||||
@ -197,6 +205,7 @@ export const CMDKContainer = ({
|
||||
<Command.List data-opening={opening ? true : undefined}>
|
||||
{children}
|
||||
</Command.List>
|
||||
<NotFoundGroup />
|
||||
</Command>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const notFoundContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0 8px',
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
export const notFoundItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
padding: '0 12px',
|
||||
gap: 16,
|
||||
});
|
||||
|
||||
export const notFoundIcon = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 20,
|
||||
color: 'var(--affine-icon-secondary)',
|
||||
padding: '12px 0',
|
||||
});
|
||||
|
||||
export const notFoundTitle = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
fontWeight: '600',
|
||||
lineHeight: '20px',
|
||||
textAlign: 'justify',
|
||||
padding: '8px',
|
||||
});
|
||||
|
||||
export const notFoundText = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: '22px',
|
||||
fontWeight: '400',
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
import { useCommandState } from '@affine/cmdk';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useAtomValue } from 'jotai';
|
||||
|
||||
import { cmdkQueryAtom } from './data';
|
||||
import * as styles from './not-found.css';
|
||||
|
||||
export const NotFoundGroup = () => {
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
// hack: we know that the filtered count is 2 when there is no result (create page & edgeless)
|
||||
const renderNoResult =
|
||||
useCommandState(state => state.filtered.count === 2) || false;
|
||||
|
||||
if (!renderNoResult) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={styles.notFoundContainer}>
|
||||
<div
|
||||
className={styles.notFoundTitle}
|
||||
data-testid="cmdk-search-not-found"
|
||||
>{`Search for "${query}"`}</div>
|
||||
<div className={styles.notFoundItem}>
|
||||
<div className={styles.notFoundIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<div className={styles.notFoundText}>No results found</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -11,7 +11,12 @@ export interface CommandContext {
|
||||
// we can use a single render function to render all different commands
|
||||
export interface CMDKCommand {
|
||||
id: string;
|
||||
label: string | React.ReactNode;
|
||||
label:
|
||||
| string
|
||||
| {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
icon?: React.ReactNode;
|
||||
category: CommandCategory;
|
||||
keyBinding?: string | { binding: string };
|
||||
|
@ -10,7 +10,7 @@ import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useAppSetting } from '../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../hooks/affine/use-app-setting-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
@ -29,7 +29,7 @@ export const TrashButtonGroup = () => {
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const restoreRef = useRef(null);
|
||||
|
@ -26,7 +26,7 @@ import { forwardRef, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { openWorkspaceListModalAtom } from '../../atoms';
|
||||
import { useHistoryAtom } from '../../atoms/history';
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
import { useGeneralShortcuts } from '../../hooks/affine/use-shortcuts';
|
||||
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands';
|
||||
@ -100,7 +100,7 @@ export const RootAppSidebar = ({
|
||||
onOpenSettingModal,
|
||||
}: RootAppSidebarProps): ReactElement => {
|
||||
const currentWorkspaceId = currentWorkspace.id;
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { backToAll } = useCollectionManager(currentCollectionsAtom);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { type AppSetting, appSettingAtom } from '../../atoms/settings';
|
||||
|
||||
export function useAppSettingHelper() {
|
||||
const [appSettings, setAppSettings] = useAtom(appSettingAtom);
|
||||
|
||||
const updateSettings = useCallback(
|
||||
<K extends keyof AppSetting>(key: K, value: AppSetting[K]) => {
|
||||
setAppSettings(prevSettings => ({ ...prevSettings, [key]: value }));
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
appSettings,
|
||||
updateSettings,
|
||||
}),
|
||||
[appSettings, updateSettings]
|
||||
);
|
||||
}
|
@ -16,7 +16,7 @@ export function useLanguageHelper() {
|
||||
})),
|
||||
[]
|
||||
);
|
||||
const onSelect = useCallback(
|
||||
const onLanguageChange = useCallback(
|
||||
(event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
},
|
||||
@ -27,8 +27,8 @@ export function useLanguageHelper() {
|
||||
() => ({
|
||||
currentLanguage,
|
||||
languagesList,
|
||||
onSelect,
|
||||
onLanguageChange,
|
||||
}),
|
||||
[currentLanguage, languagesList, onSelect]
|
||||
[currentLanguage, languagesList, onLanguageChange]
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
export function useSwitchSidebarStatus() {
|
||||
const [isOpened, setOpened] = useAtom(appSidebarOpenAtom);
|
||||
|
||||
const onOpenChange = useCallback(() => {
|
||||
setOpened(open => !open);
|
||||
}, [setOpened]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
onOpenChange,
|
||||
isOpened,
|
||||
}),
|
||||
[isOpened, onOpenChange]
|
||||
);
|
||||
}
|
@ -52,7 +52,7 @@ export function useRegisterWorkspaceCommands() {
|
||||
languageHelper,
|
||||
})
|
||||
);
|
||||
unsubs.push(registerAffineLayoutCommands({ store, t }));
|
||||
unsubs.push(registerAffineLayoutCommands({ t, store }));
|
||||
unsubs.push(
|
||||
registerAffineCreationCommands({
|
||||
store,
|
||||
|
@ -38,7 +38,6 @@ import { Map as YMap } from 'yjs';
|
||||
|
||||
import { openQuickSearchModalAtom, openSettingModalAtom } from '../atoms';
|
||||
import { mainContainerAtom } from '../atoms/element';
|
||||
import { useAppSetting } from '../atoms/settings';
|
||||
import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper';
|
||||
import { AppContainer } from '../components/affine/app-container';
|
||||
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
|
||||
@ -50,6 +49,7 @@ import {
|
||||
DROPPABLE_SIDEBAR_TRASH,
|
||||
RootAppSidebar,
|
||||
} from '../components/root-app-sidebar';
|
||||
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
@ -230,7 +230,7 @@ export const WorkspaceLayoutInner = ({
|
||||
[moveToTrash, t]
|
||||
);
|
||||
|
||||
const [appSetting] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const location = useLocation();
|
||||
const { pageId } = useParams();
|
||||
const pageMeta = useBlockSuitePageMeta(
|
||||
@ -269,7 +269,7 @@ export const WorkspaceLayoutInner = ({
|
||||
<Suspense fallback={<MainContainer ref={setMainContainer} />}>
|
||||
<MainContainer
|
||||
ref={setMainContainer}
|
||||
padding={appSetting.clientBorder}
|
||||
padding={appSettings.clientBorder}
|
||||
inTrashPage={inTrashPage}
|
||||
>
|
||||
{incompatible ? <MigrationFallback /> : children}
|
||||
|
@ -589,9 +589,9 @@
|
||||
"com.affine.cmdk.affine.new-page": "New Page",
|
||||
"com.affine.cmdk.affine.new-edgeless-page": "New Edgeless",
|
||||
"com.affine.cmdk.affine.new-workspace": "New Workspace",
|
||||
"com.affine.cmdk.affine.create-new-page-as": "Create New Page as: <1>{{query}}</1>",
|
||||
"com.affine.cmdk.affine.create-new-edgeless-as": "Create New Edgeless as: <1>{{query}}</1>",
|
||||
"com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to <1>{{colour}}</1>",
|
||||
"com.affine.cmdk.affine.create-new-page-as": "Create New Page as:",
|
||||
"com.affine.cmdk.affine.create-new-edgeless-as": "Create New Edgeless as:",
|
||||
"com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to",
|
||||
"com.affine.cmdk.affine.left-sidebar.expand": "Expand Left Sidebar",
|
||||
"com.affine.cmdk.affine.left-sidebar.collapse": "Collapse Left Sidebar",
|
||||
"com.affine.cmdk.affine.navigation.goto-all-pages": "Go to All Pages",
|
||||
@ -619,12 +619,12 @@
|
||||
"com.affine.cmdk.affine.editor.add-to-favourites": "Add to Favourites",
|
||||
"com.affine.cmdk.affine.editor.remove-from-favourites": "Remove from Favourites",
|
||||
"com.affine.cmdk.affine.editor.restore-from-trash": "Restore from Trash",
|
||||
"com.affine.cmdk.affine.font-style.to": "Change Font Style to <1>{{fontFamily}}</1>",
|
||||
"com.affine.cmdk.affine.display-language.to": "Change Display Language to <1>{{language}}</1>",
|
||||
"com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to <1>{{state}}</1>",
|
||||
"com.affine.cmdk.affine.full-width-layout.to": "Change Full Width Layout to <1>{{state}}</1>",
|
||||
"com.affine.cmdk.affine.noise-background-on-the-sidebar.to": "Change Noise Background On The Sidebar to <1>{{state}}</1>",
|
||||
"com.affine.cmdk.affine.translucent-ui-on-the-sidebar.to": "Change Translucent UI On The Sidebar to <1>{{state}}</1>",
|
||||
"com.affine.cmdk.affine.font-style.to": "Change Font Style to",
|
||||
"com.affine.cmdk.affine.display-language.to": "Change Display Language to",
|
||||
"com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to",
|
||||
"com.affine.cmdk.affine.full-width-layout.to": "Change Full Width Layout to",
|
||||
"com.affine.cmdk.affine.noise-background-on-the-sidebar.to": "Change Noise Background On The Sidebar to",
|
||||
"com.affine.cmdk.affine.translucent-ui-on-the-sidebar.to": "Change Translucent UI On The Sidebar to",
|
||||
"com.affine.cmdk.affine.whats-new": "What's New",
|
||||
"com.affine.cmdk.affine.getting-started": "Getting Started",
|
||||
"com.affine.cmdk.affine.contact-us": "Contact Us",
|
||||
@ -637,5 +637,7 @@
|
||||
"com.affine.auth.sign-out.confirm-modal.title": "Sign out?",
|
||||
"com.affine.auth.sign-out.confirm-modal.description": "After signing out, the Cloud Workspaces associated with this account will be removed from the current device, and signing in again will add them back.",
|
||||
"com.affine.auth.sign-out.confirm-modal.cancel": "Cancel",
|
||||
"com.affine.auth.sign-out.confirm-modal.confirm": "Sign Out"
|
||||
"com.affine.auth.sign-out.confirm-modal.confirm": "Sign Out",
|
||||
"com.affine.cmdk.affine.switch-state.on": "ON",
|
||||
"com.affine.cmdk.affine.switch-state.off": "OFF"
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ async function assertResultList(page: Page, texts: string[]) {
|
||||
const actual = await page
|
||||
.locator('[cmdk-item] [data-testid=cmdk-label]')
|
||||
.allInnerTexts();
|
||||
expect(actual).toEqual(texts);
|
||||
const actualSplit = actual[0].split('\n');
|
||||
expect(actualSplit[0]).toEqual(texts[0]);
|
||||
expect(actualSplit[1]).toEqual(texts[0]);
|
||||
}
|
||||
|
||||
async function titleIsFocused(page: Page) {
|
||||
@ -100,9 +102,7 @@ test('Create a new page with keyword', async ({ page }) => {
|
||||
await clickNewPageButton(page);
|
||||
await openQuickSearchByShortcut(page);
|
||||
await page.keyboard.insertText('test123456');
|
||||
const addNewPage = page.locator(
|
||||
'[cmdk-item] >> text=Create New Page as: test123456'
|
||||
);
|
||||
const addNewPage = page.locator('[cmdk-item] >> text=Create New Page as:');
|
||||
await addNewPage.click();
|
||||
await page.waitForTimeout(300);
|
||||
await assertTitle(page, 'test123456');
|
||||
@ -126,9 +126,7 @@ test('Create a new page and search this page', async ({ page }) => {
|
||||
// input title and create new page
|
||||
await page.keyboard.insertText('test123456');
|
||||
await page.waitForTimeout(300);
|
||||
const addNewPage = page.locator(
|
||||
'[cmdk-item] >> text=Create New Page as: test123456'
|
||||
);
|
||||
const addNewPage = page.locator('[cmdk-item] >> text=Create New Page as:');
|
||||
await addNewPage.click();
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
@ -218,36 +216,34 @@ test('assert the recent browse pages are on the recent list', async ({
|
||||
|
||||
// create first page
|
||||
await clickNewPageButton(page);
|
||||
await waitForEditorLoad(page);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.pressSequentially('sgtokidoki', {
|
||||
delay: 50,
|
||||
});
|
||||
await title.pressSequentially('sgtokidoki');
|
||||
expect(await title.innerText()).toBe('sgtokidoki');
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// create second page
|
||||
await openQuickSearchByShortcut(page);
|
||||
const addNewPage = page.locator('[cmdk-item] >> text=New Page');
|
||||
await addNewPage.click();
|
||||
await waitForEditorLoad(page);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.pressSequentially('theliquidhorse', {
|
||||
delay: 50,
|
||||
});
|
||||
await title.pressSequentially('theliquidhorse');
|
||||
expect(await title.innerText()).toBe('theliquidhorse');
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// create thrid page
|
||||
await openQuickSearchByShortcut(page);
|
||||
await addNewPage.click();
|
||||
await waitForEditorLoad(page);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.pressSequentially('battlekot', {
|
||||
delay: 50,
|
||||
});
|
||||
await title.pressSequentially('battlekot');
|
||||
expect(await title.innerText()).toBe('battlekot');
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await openQuickSearchByShortcut(page);
|
||||
{
|
||||
@ -268,12 +264,11 @@ test('assert the recent browse pages are on the recent list', async ({
|
||||
const addNewPage = page.locator('[cmdk-item] >> text=New Page');
|
||||
await addNewPage.click();
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
await waitForEditorLoad(page);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.pressSequentially('affine is the best', {
|
||||
delay: 50,
|
||||
});
|
||||
await title.pressSequentially('affine is the best');
|
||||
expect(await title.innerText()).toBe('affine is the best');
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
await openQuickSearchByShortcut(page);
|
||||
@ -332,3 +327,15 @@ test('can use cmdk to delete page and restore it', async ({ page }) => {
|
||||
await keyboardDownAndSelect(page, 'Restore from Trash');
|
||||
await expect(restoreButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('show not found item', async ({ page }) => {
|
||||
await openHomePage(page);
|
||||
await waitForEditorLoad(page);
|
||||
await clickNewPageButton(page);
|
||||
await openQuickSearchByShortcut(page);
|
||||
// input title and create new page
|
||||
await page.keyboard.insertText('test123456');
|
||||
const notFoundItem = page.getByTestId('cmdk-search-not-found');
|
||||
await expect(notFoundItem).toBeVisible();
|
||||
await expect(notFoundItem).toHaveText('Search for "test123456"');
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
registerAffineSettingsCommands,
|
||||
} from '@affine/core/commands';
|
||||
import { CMDKQuickSearchModal } from '@affine/core/components/pure/cmdk';
|
||||
import { HighlightLabel } from '@affine/core/components/pure/cmdk/highlight';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
@ -12,7 +13,7 @@ import type { Page } from '@blocksuite/store';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
||||
import { useStore } from 'jotai';
|
||||
import { useEffect, useLayoutEffect } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { withRouter } from 'storybook-addon-react-router-v6';
|
||||
|
||||
export default {
|
||||
@ -43,7 +44,7 @@ function useRegisterCommands() {
|
||||
themes: ['auto', 'dark', 'light'],
|
||||
},
|
||||
languageHelper: {
|
||||
onSelect: () => {},
|
||||
onLanguageChange: () => {},
|
||||
languagesList: [
|
||||
{ tag: 'en', name: 'English', originalName: 'English' },
|
||||
{
|
||||
@ -65,7 +66,10 @@ function useRegisterCommands() {
|
||||
isPreferredEdgeless: () => false,
|
||||
},
|
||||
}),
|
||||
registerAffineLayoutCommands({ t, store }),
|
||||
registerAffineLayoutCommands({
|
||||
t,
|
||||
store,
|
||||
}),
|
||||
];
|
||||
|
||||
return () => {
|
||||
@ -98,3 +102,16 @@ export const CMDKStoryWithCommands: StoryFn = () => {
|
||||
};
|
||||
|
||||
CMDKStoryWithCommands.decorators = [withRouter];
|
||||
|
||||
export const HighlightStory: StoryFn = () => {
|
||||
const [query, setQuery] = useState('');
|
||||
const label = {
|
||||
title: 'title',
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<input value={query} onChange={e => setQuery(e.target.value)} />
|
||||
<HighlightLabel label={label} highlight={query} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user