feat(core): add search result highlighting (#4667)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
JimmFly 2023-10-24 13:54:37 +08:00 committed by GitHub
parent 14bee1811c
commit 5226d6c568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 479 additions and 305 deletions

View File

@ -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'

View File

@ -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;

View File

@ -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

View File

@ -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);
};

View File

@ -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;
},

View File

@ -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);

View File

@ -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+/',
},

View File

@ -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');

View File

@ -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,

View File

@ -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 => {

View File

@ -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

View File

@ -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>

View File

@ -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}>

View File

@ -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

View File

@ -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>
);

View File

@ -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>

View File

@ -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(() => {

View File

@ -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 () => {

View File

@ -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',
});

View File

@ -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>
);
});

View File

@ -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,

View File

@ -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>
);
};

View File

@ -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',
});

View File

@ -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>
);
};

View File

@ -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 };

View File

@ -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);

View File

@ -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();

View File

@ -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]
);
}

View File

@ -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]
);
}

View File

@ -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]
);
}

View File

@ -52,7 +52,7 @@ export function useRegisterWorkspaceCommands() {
languageHelper,
})
);
unsubs.push(registerAffineLayoutCommands({ store, t }));
unsubs.push(registerAffineLayoutCommands({ t, store }));
unsubs.push(
registerAffineCreationCommands({
store,

View File

@ -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}

View File

@ -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"
}

View File

@ -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"');
});

View File

@ -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} />
</>
);
};