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
35 changed files with 479 additions and 305 deletions

View File

@@ -40,8 +40,18 @@ export interface AffineCommandOptions {
preconditionStrategy?: PreconditionStrategy | (() => boolean); preconditionStrategy?: PreconditionStrategy | (() => boolean);
// main text on the left.. // main text on the left..
// make text a function so that we can do i18n and interpolation when we need to // make text a function so that we can do i18n and interpolation when we need to
label?: string | (() => string) | ReactNode | (() => ReactNode); label:
icon: React.ReactNode; // todo: need a mapping from string -> React element/SVG | string
| (() => string)
| {
title: string;
subTitle?: string;
}
| (() => {
title: string;
subTitle?: string;
});
icon: ReactNode; // todo: need a mapping from string -> React element/SVG
category?: CommandCategory; category?: CommandCategory;
// we use https://github.com/jamiebuilds/tinykeys so that we can use the same keybinding definition // we use https://github.com/jamiebuilds/tinykeys so that we can use the same keybinding definition
// for both mac and windows // for both mac and windows
@@ -53,8 +63,11 @@ export interface AffineCommandOptions {
export interface AffineCommand { export interface AffineCommand {
readonly id: string; readonly id: string;
readonly preconditionStrategy: PreconditionStrategy | (() => boolean); readonly preconditionStrategy: PreconditionStrategy | (() => boolean);
readonly label?: ReactNode | string; readonly label: {
readonly icon?: React.ReactNode; // icon name title: string;
subTitle?: string;
};
readonly icon?: ReactNode; // icon name
readonly category: CommandCategory; readonly category: CommandCategory;
readonly keyBinding?: KeybindingOptions; readonly keyBinding?: KeybindingOptions;
run(): void | Promise<void>; run(): void | Promise<void>;
@@ -71,8 +84,15 @@ export function createAffineCommand(
options.preconditionStrategy ?? PreconditionStrategy.Always, options.preconditionStrategy ?? PreconditionStrategy.Always,
category: options.category ?? 'affine:general', category: options.category ?? 'affine:general',
get label() { get label() {
const label = options.label; let label = options.label;
return typeof label === 'function' ? label?.() : label; label = typeof label === 'function' ? label?.() : label;
label =
typeof label === 'string'
? {
title: label,
}
: label;
return label;
}, },
keyBinding: keyBinding:
typeof options.keyBinding === 'string' typeof options.keyBinding === 'string'

View File

@@ -1,5 +1,5 @@
import { CloseIcon } from '@blocksuite/icons'; import { CloseIcon } from '@blocksuite/icons';
import type React from 'react'; import type { ReactNode } from 'react';
import { import {
browserWarningStyle, browserWarningStyle,
@@ -14,7 +14,7 @@ export const BrowserWarning = ({
}: { }: {
show: boolean; show: boolean;
onClose: () => void; onClose: () => void;
message: React.ReactNode; message: ReactNode;
}) => { }) => {
if (!show) { if (!show) {
return null; return null;

View File

@@ -88,11 +88,7 @@ const CloudShareMenu = (props: ShareMenuProps) => {
modal: false, modal: false,
}} }}
> >
<Button <Button data-testid="cloud-share-menu-button" type="plain">
data-testid="cloud-share-menu-button"
type="plain"
onClick={() => console.log('gg')}
>
<div <div
style={{ style={{
color: isSharedPage color: isSharedPage

View File

@@ -1,4 +1,4 @@
import { atom, useAtom } from 'jotai'; import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
export type DateFormats = export type DateFormats =
@@ -75,7 +75,3 @@ export const appSettingAtom = atom<
set(appSettingBaseAtom, { ...prev, ...next }); set(appSettingBaseAtom, { ...prev, ...next });
} }
); );
export const useAppSetting = () => {
return useAtom(appSettingAtom);
};

View File

@@ -20,7 +20,7 @@ export function registerAffineCreationCommands({
registerAffineCommand({ registerAffineCommand({
id: 'affine:new-page', id: 'affine:new-page',
category: 'affine:creation', category: 'affine:creation',
label: t['com.affine.cmdk.affine.new-page'], label: t['com.affine.cmdk.affine.new-page'](),
icon: <PlusIcon />, icon: <PlusIcon />,
keyBinding: environment.isDesktop keyBinding: environment.isDesktop
? { ? {
@@ -39,7 +39,7 @@ export function registerAffineCreationCommands({
id: 'affine:new-edgeless-page', id: 'affine:new-edgeless-page',
category: 'affine:creation', category: 'affine:creation',
icon: <PlusIcon />, icon: <PlusIcon />,
label: t['com.affine.cmdk.affine.new-edgeless-page'], label: t['com.affine.cmdk.affine.new-edgeless-page'](),
run() { run() {
pageHelper.createEdgeless(); pageHelper.createEdgeless();
}, },
@@ -51,7 +51,7 @@ export function registerAffineCreationCommands({
id: 'affine:new-workspace', id: 'affine:new-workspace',
category: 'affine:creation', category: 'affine:creation',
icon: <PlusIcon />, icon: <PlusIcon />,
label: t['com.affine.cmdk.affine.new-workspace'], label: t['com.affine.cmdk.affine.new-workspace'](),
run() { run() {
store.set(openCreateWorkspaceModalAtom, 'new'); store.set(openCreateWorkspaceModalAtom, 'new');
}, },
@@ -62,7 +62,7 @@ export function registerAffineCreationCommands({
id: 'affine:import-workspace', id: 'affine:import-workspace',
category: 'affine:creation', category: 'affine:creation',
icon: <ImportIcon />, icon: <ImportIcon />,
label: t['com.affine.cmdk.affine.import-workspace'], label: t['com.affine.cmdk.affine.import-workspace'](),
preconditionStrategy: () => { preconditionStrategy: () => {
return environment.isDesktop; return environment.isDesktop;
}, },

View File

@@ -18,7 +18,7 @@ export function registerAffineHelpCommands({
id: 'affine:help-whats-new', id: 'affine:help-whats-new',
category: 'affine:help', category: 'affine:help',
icon: <NewIcon />, icon: <NewIcon />,
label: () => t['com.affine.cmdk.affine.whats-new'](), label: t['com.affine.cmdk.affine.whats-new'](),
run() { run() {
window.open(runtimeConfig.changelogUrl, '_blank'); window.open(runtimeConfig.changelogUrl, '_blank');
}, },
@@ -29,7 +29,7 @@ export function registerAffineHelpCommands({
id: 'affine:help-contact-us', id: 'affine:help-contact-us',
category: 'affine:help', category: 'affine:help',
icon: <ContactWithUsIcon />, icon: <ContactWithUsIcon />,
label: () => t['com.affine.cmdk.affine.contact-us'](), label: t['com.affine.cmdk.affine.contact-us'](),
run() { run() {
store.set(openSettingModalAtom, { store.set(openSettingModalAtom, {
open: true, open: true,
@@ -44,7 +44,7 @@ export function registerAffineHelpCommands({
id: 'affine:help-getting-started', id: 'affine:help-getting-started',
category: 'affine:help', category: 'affine:help',
icon: <UserGuideIcon />, icon: <UserGuideIcon />,
label: () => t['com.affine.cmdk.affine.getting-started'](), label: t['com.affine.cmdk.affine.getting-started'](),
preconditionStrategy: () => environment.isDesktop, preconditionStrategy: () => environment.isDesktop,
run() { run() {
store.set(openOnboardingModalAtom, true); store.set(openOnboardingModalAtom, true);

View File

@@ -17,14 +17,11 @@ export function registerAffineLayoutCommands({
id: 'affine:toggle-left-sidebar', id: 'affine:toggle-left-sidebar',
category: 'affine:layout', category: 'affine:layout',
icon: <SidebarIcon />, icon: <SidebarIcon />,
label: () => { label: () =>
const open = store.get(appSidebarOpenAtom); store.get(appSidebarOpenAtom)
return t[ ? t['com.affine.cmdk.affine.left-sidebar.collapse']()
open : t['com.affine.cmdk.affine.left-sidebar.expand'](),
? 'com.affine.cmdk.affine.left-sidebar.collapse'
: 'com.affine.cmdk.affine.left-sidebar.expand'
]();
},
keyBinding: { keyBinding: {
binding: '$mod+/', binding: '$mod+/',
}, },

View File

@@ -33,7 +33,7 @@ export function registerAffineNavigationCommands({
id: 'affine:goto-all-pages', id: 'affine:goto-all-pages',
category: 'affine:navigation', category: 'affine:navigation',
icon: <ArrowRightBigIcon />, icon: <ArrowRightBigIcon />,
label: () => t['com.affine.cmdk.affine.navigation.goto-all-pages'](), label: t['com.affine.cmdk.affine.navigation.goto-all-pages'](),
run() { run() {
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
setPageListMode('all'); setPageListMode('all');
@@ -49,7 +49,7 @@ export function registerAffineNavigationCommands({
preconditionStrategy: () => { preconditionStrategy: () => {
return pageListMode !== 'page'; return pageListMode !== 'page';
}, },
label: () => t['com.affine.cmdk.affine.navigation.goto-page-list'](), label: t['com.affine.cmdk.affine.navigation.goto-page-list'](),
run() { run() {
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
setPageListMode('page'); setPageListMode('page');
@@ -65,7 +65,7 @@ export function registerAffineNavigationCommands({
preconditionStrategy: () => { preconditionStrategy: () => {
return pageListMode !== 'edgeless'; return pageListMode !== 'edgeless';
}, },
label: () => t['com.affine.cmdk.affine.navigation.goto-edgeless-list'](), label: t['com.affine.cmdk.affine.navigation.goto-edgeless-list'](),
run() { run() {
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
setPageListMode('edgeless'); setPageListMode('edgeless');
@@ -78,7 +78,7 @@ export function registerAffineNavigationCommands({
id: 'affine:goto-workspace', id: 'affine:goto-workspace',
category: 'affine:navigation', category: 'affine:navigation',
icon: <ArrowRightBigIcon />, icon: <ArrowRightBigIcon />,
label: () => t['com.affine.cmdk.affine.navigation.goto-workspace'](), label: t['com.affine.cmdk.affine.navigation.goto-workspace'](),
run() { run() {
store.set(openWorkspaceListModalAtom, true); store.set(openWorkspaceListModalAtom, true);
}, },
@@ -90,7 +90,7 @@ export function registerAffineNavigationCommands({
id: 'affine:open-settings', id: 'affine:open-settings',
category: 'affine:navigation', category: 'affine:navigation',
icon: <ArrowRightBigIcon />, icon: <ArrowRightBigIcon />,
label: () => t['com.affine.cmdk.affine.navigation.open-settings'](), label: t['com.affine.cmdk.affine.navigation.open-settings'](),
run() { run() {
store.set(openSettingModalAtom, { store.set(openSettingModalAtom, {
activeTab: 'appearance', activeTab: 'appearance',
@@ -106,7 +106,7 @@ export function registerAffineNavigationCommands({
id: 'affine:goto-trash', id: 'affine:goto-trash',
category: 'affine:navigation', category: 'affine:navigation',
icon: <ArrowRightBigIcon />, icon: <ArrowRightBigIcon />,
label: () => t['com.affine.cmdk.affine.navigation.goto-trash'](), label: t['com.affine.cmdk.affine.navigation.goto-trash'](),
run() { run() {
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH); navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH);
setPageListMode('all'); setPageListMode('all');

View File

@@ -1,76 +1,16 @@
import { Trans } from '@affine/i18n';
import type { useAFFiNEI18N } from '@affine/i18n/hooks'; import type { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SettingsIcon } from '@blocksuite/icons'; import { SettingsIcon } from '@blocksuite/icons';
import { import {
PreconditionStrategy, PreconditionStrategy,
registerAffineCommand, registerAffineCommand,
} from '@toeverything/infra/command'; } from '@toeverything/infra/command';
import { type createStore, useAtomValue } from 'jotai'; import { type createStore } from 'jotai';
import type { useTheme } from 'next-themes'; import type { useTheme } from 'next-themes';
import { openQuickSearchModalAtom } from '../atoms'; import { openQuickSearchModalAtom } from '../atoms';
import { appSettingAtom } from '../atoms/settings'; import { appSettingAtom } from '../atoms/settings';
import type { useLanguageHelper } from '../hooks/affine/use-language-helper'; 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({ export function registerAffineSettingsCommands({
t, t,
store, store,
@@ -83,7 +23,7 @@ export function registerAffineSettingsCommands({
languageHelper: ReturnType<typeof useLanguageHelper>; languageHelper: ReturnType<typeof useLanguageHelper>;
}) { }) {
const unsubs: Array<() => void> = []; const unsubs: Array<() => void> = [];
const { onSelect, languagesList, currentLanguage } = languageHelper; const { onLanguageChange, languagesList, currentLanguage } = languageHelper;
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:show-quick-search', id: 'affine:show-quick-search',
@@ -92,6 +32,7 @@ export function registerAffineSettingsCommands({
keyBinding: { keyBinding: {
binding: '$mod+K', binding: '$mod+K',
}, },
label: '',
icon: <SettingsIcon />, icon: <SettingsIcon />,
run() { run() {
const quickSearchModalState = store.get(openQuickSearchModalAtom); const quickSearchModalState = store.get(openQuickSearchModalAtom);
@@ -104,14 +45,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-color-scheme-to-auto', id: 'affine:change-color-scheme-to-auto',
label: ( label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
<Trans 'com.affine.themeSettings.system'
i18nKey="com.affine.cmdk.affine.color-scheme.to" ]()}`,
values={{ colour: 'Auto' }}
>
Change Colour Scheme to <strong>colour</strong>
</Trans>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => theme.theme !== 'system', preconditionStrategy: () => theme.theme !== 'system',
@@ -123,14 +59,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-color-scheme-to-dark', id: 'affine:change-color-scheme-to-dark',
label: ( label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
<Trans 'com.affine.themeSettings.dark'
i18nKey="com.affine.cmdk.affine.color-scheme.to" ]()}`,
values={{ colour: 'Dark' }}
>
Change Colour Scheme to <strong>colour</strong>
</Trans>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => theme.theme !== 'dark', preconditionStrategy: () => theme.theme !== 'dark',
@@ -143,14 +74,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-color-scheme-to-light', id: 'affine:change-color-scheme-to-light',
label: ( label: `${t['com.affine.cmdk.affine.color-scheme.to']()} ${t[
<Trans 'com.affine.themeSettings.light'
i18nKey="com.affine.cmdk.affine.color-scheme.to" ]()}`,
values={{ colour: 'Light' }}
>
Change Colour Scheme to <strong>colour</strong>
</Trans>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => theme.theme !== 'light', preconditionStrategy: () => theme.theme !== 'light',
@@ -164,16 +90,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-font-style-to-sans', id: 'affine:change-font-style-to-sans',
label: ( label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
<Trans 'com.affine.appearanceSettings.fontStyle.sans'
i18nKey="com.affine.cmdk.affine.font-style.to" ]()}`,
values={{
fontFamily: t['com.affine.appearanceSettings.fontStyle.sans'](),
}}
>
Change Font Style to <strong>fontFamily</strong>
</Trans>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => preconditionStrategy: () =>
@@ -190,19 +109,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-font-style-to-serif', id: 'affine:change-font-style-to-serif',
label: ( label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
<Trans 'com.affine.appearanceSettings.fontStyle.serif'
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>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => preconditionStrategy: () =>
@@ -219,19 +128,9 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:change-font-style-to-mono', id: 'affine:change-font-style-to-mono',
label: ( label: `${t['com.affine.cmdk.affine.font-style.to']()} ${t[
<Trans 'com.affine.appearanceSettings.fontStyle.mono'
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>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => preconditionStrategy: () =>
@@ -250,22 +149,14 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `affine:change-display-language-to-${language.name}`, id: `affine:change-display-language-to-${language.name}`,
label: ( label: `${t['com.affine.cmdk.affine.display-language.to']()} ${
<Trans language.originalName
i18nKey="com.affine.cmdk.affine.display-language.to" }`,
values={{
language: language.originalName,
}}
>
Change Display Language to
<strong>language</strong>
</Trans>
),
category: 'affine:settings', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => currentLanguage?.tag !== language.tag, preconditionStrategy: () => currentLanguage?.tag !== language.tag,
run() { run() {
onSelect(language.tag); onLanguageChange(language.tag);
}, },
}) })
); );
@@ -275,7 +166,12 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `affine:change-client-border-style`, 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', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop, preconditionStrategy: () => environment.isDesktop,
@@ -291,7 +187,12 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `affine:change-full-width-layout`, 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', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
run() { run() {
@@ -306,7 +207,14 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `affine:change-noise-background-on-the-sidebar`, 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', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop, preconditionStrategy: () => environment.isDesktop,
@@ -322,7 +230,12 @@ export function registerAffineSettingsCommands({
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: `affine:change-translucent-ui-on-the-sidebar`, 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', category: 'affine:settings',
icon: <SettingsIcon />, icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop, preconditionStrategy: () => environment.isDesktop,

View File

@@ -18,7 +18,7 @@ export function registerAffineUpdatesCommands({
id: 'affine:restart-to-upgrade', id: 'affine:restart-to-upgrade',
category: 'affine:updates', category: 'affine:updates',
icon: <ResetIcon />, icon: <ResetIcon />,
label: () => t['com.affine.cmdk.affine.restart-to-upgrade'](), label: t['com.affine.cmdk.affine.restart-to-upgrade'](),
preconditionStrategy: () => !!store.get(updateReadyAtom), preconditionStrategy: () => !!store.get(updateReadyAtom),
run() { run() {
window.apis?.updater.quitAndInstall().catch(err => { window.apis?.updater.quitAndInstall().catch(err => {

View File

@@ -3,10 +3,10 @@ import {
type WorkspaceRootProps, type WorkspaceRootProps,
} from '@affine/component/workspace'; } from '@affine/component/workspace';
import { useAppSetting } from '../../atoms/settings'; import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
export const AppContainer = (props: WorkspaceRootProps) => { export const AppContainer = (props: WorkspaceRootProps) => {
const [appSettings] = useAppSetting(); const { appSettings } = useAppSettingHelper();
return ( return (
<AppContainerWithoutSettings <AppContainerWithoutSettings

View File

@@ -5,7 +5,8 @@ import { useLanguageHelper } from '../../../hooks/affine/use-language-helper';
// Fixme: keyboard focus should be supported by Menu component // Fixme: keyboard focus should be supported by Menu component
const LanguageMenuContent = memo(function LanguageMenuContent() { const LanguageMenuContent = memo(function LanguageMenuContent() {
const { currentLanguage, languagesList, onSelect } = useLanguageHelper(); const { currentLanguage, languagesList, onLanguageChange } =
useLanguageHelper();
return ( return (
<> <>
{languagesList.map(option => { {languagesList.map(option => {
@@ -14,7 +15,7 @@ const LanguageMenuContent = memo(function LanguageMenuContent() {
key={option.name} key={option.name}
selected={currentLanguage?.originalName === option.originalName} selected={currentLanguage?.originalName === option.originalName}
title={option.name} title={option.name}
onSelect={() => onSelect(option.tag)} onSelect={() => onLanguageChange(option.tag)}
> >
{option.originalName} {option.originalName}
</MenuItem> </MenuItem>

View File

@@ -66,12 +66,7 @@ const PublishPanelAffine = (props: PublishPanelAffineProps) => {
marginBottom: isPublic ? '12px' : '25px', marginBottom: isPublic ? '12px' : '25px',
}} }}
> >
<Switch <Switch checked={isPublic} />
checked={isPublic}
// onChange={useCallback(value => {
// console.log('onChange', value);
// }, [])}
/>
</SettingRow> </SettingRow>
{isPublic ? ( {isPublic ? (
<FlexWrapper justifyContent="space-between" marginBottom={25}> <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 { SettingWrapper } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons'; 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 { relatedLinks } from './config';
import { communityItem, communityWrapper, link } from './style.css'; import { communityItem, communityWrapper, link } from './style.css';
export const AboutAffine = () => { export const AboutAffine = () => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [appSettings, setAppSettings] = useAppSetting(); const { appSettings, updateSettings } = useAppSettingHelper();
const changeSwitch = useCallback(
(key: keyof AppSetting, checked: boolean) => {
setAppSettings({ [key]: checked });
},
[setAppSettings]
);
return ( return (
<> <>
<SettingHeader <SettingHeader
@@ -47,7 +40,7 @@ export const AboutAffine = () => {
> >
<Switch <Switch
checked={appSettings.autoCheckUpdate} checked={appSettings.autoCheckUpdate}
onChange={checked => changeSwitch('autoCheckUpdate', checked)} onChange={checked => updateSettings('autoCheckUpdate', checked)}
/> />
</SettingRow> </SettingRow>
<SettingRow <SettingRow
@@ -58,7 +51,7 @@ export const AboutAffine = () => {
> >
<Switch <Switch
checked={appSettings.autoCheckUpdate} checked={appSettings.autoCheckUpdate}
onChange={checked => changeSwitch('autoCheckUpdate', checked)} onChange={checked => updateSettings('autoCheckUpdate', checked)}
/> />
</SettingRow> </SettingRow>
<SettingRow <SettingRow

View File

@@ -5,8 +5,8 @@ import { useCallback } from 'react';
import { import {
dateFormatOptions, dateFormatOptions,
type DateFormats, type DateFormats,
useAppSetting,
} from '../../../../../atoms/settings'; } from '../../../../../atoms/settings';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
interface DateFormatMenuContentProps { interface DateFormatMenuContentProps {
currentOption: DateFormats; currentOption: DateFormats;
@@ -35,12 +35,12 @@ const DateFormatMenuContent = ({
}; };
export const DateFormatSetting = () => { export const DateFormatSetting = () => {
const [appearanceSettings, setAppSettings] = useAppSetting(); const { appSettings, updateSettings } = useAppSettingHelper();
const handleSelect = useCallback( const handleSelect = useCallback(
(option: DateFormats) => { (option: DateFormats) => {
setAppSettings({ dateFormat: option }); updateSettings('dateFormat', option);
}, },
[setAppSettings] [updateSettings]
); );
return ( return (
@@ -48,12 +48,12 @@ export const DateFormatSetting = () => {
items={ items={
<DateFormatMenuContent <DateFormatMenuContent
onSelect={handleSelect} onSelect={handleSelect}
currentOption={appearanceSettings.dateFormat} currentOption={appSettings.dateFormat}
/> />
} }
> >
<MenuTrigger data-testid="date-format-menu-trigger" block> <MenuTrigger data-testid="date-format-menu-trigger" block>
{dayjs(new Date()).format(appearanceSettings.dateFormat)} {dayjs(new Date()).format(appSettings.dateFormat)}
</MenuTrigger> </MenuTrigger>
</Menu> </Menu>
); );

View File

@@ -9,9 +9,9 @@ import { useCallback } from 'react';
import { import {
type AppSetting, type AppSetting,
fontStyleOptions, fontStyleOptions,
useAppSetting,
windowFrameStyleOptions, windowFrameStyleOptions,
} from '../../../../../atoms/settings'; } from '../../../../../atoms/settings';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
import { LanguageMenu } from '../../../language-menu'; import { LanguageMenu } from '../../../language-menu';
import { DateFormatSetting } from './date-format-setting'; import { DateFormatSetting } from './date-format-setting';
import { settingWrapper } from './style.css'; import { settingWrapper } from './style.css';
@@ -47,7 +47,7 @@ export const ThemeSettings = () => {
const FontFamilySettings = () => { const FontFamilySettings = () => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [appSettings, setAppSettings] = useAppSetting(); const { appSettings, updateSettings } = useAppSettingHelper();
return ( return (
<RadioButtonGroup <RadioButtonGroup
width={250} width={250}
@@ -55,9 +55,9 @@ const FontFamilySettings = () => {
value={appSettings.fontStyle} value={appSettings.fontStyle}
onValueChange={useCallback( onValueChange={useCallback(
(key: AppSetting['fontStyle']) => { (key: AppSetting['fontStyle']) => {
setAppSettings({ fontStyle: key }); updateSettings('fontStyle', key);
}, },
[setAppSettings] [updateSettings]
)} )}
> >
{fontStyleOptions.map(({ key, value }) => { {fontStyleOptions.map(({ key, value }) => {
@@ -95,14 +95,8 @@ const FontFamilySettings = () => {
export const AppearanceSettings = () => { export const AppearanceSettings = () => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [appSettings, setAppSettings] = useAppSetting(); const { appSettings, updateSettings } = useAppSettingHelper();
const changeSwitch = useCallback(
(key: keyof AppSetting, checked: boolean) => {
setAppSettings({ [key]: checked });
},
[setAppSettings]
);
return ( return (
<> <>
<SettingHeader <SettingHeader
@@ -139,7 +133,7 @@ export const AppearanceSettings = () => {
> >
<Switch <Switch
checked={appSettings.clientBorder} checked={appSettings.clientBorder}
onChange={checked => changeSwitch('clientBorder', checked)} onChange={checked => updateSettings('clientBorder', checked)}
/> />
</SettingRow> </SettingRow>
) : null} ) : null}
@@ -151,7 +145,7 @@ export const AppearanceSettings = () => {
<Switch <Switch
data-testid="full-width-layout-trigger" data-testid="full-width-layout-trigger"
checked={appSettings.fullWidthLayout} checked={appSettings.fullWidthLayout}
onChange={checked => changeSwitch('fullWidthLayout', checked)} onChange={checked => updateSettings('fullWidthLayout', checked)}
/> />
</SettingRow> </SettingRow>
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? ( {runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
@@ -164,7 +158,7 @@ export const AppearanceSettings = () => {
width={250} width={250}
defaultValue={appSettings.windowFrameStyle} defaultValue={appSettings.windowFrameStyle}
onValueChange={(value: AppSetting['windowFrameStyle']) => { onValueChange={(value: AppSetting['windowFrameStyle']) => {
setAppSettings({ windowFrameStyle: value }); updateSettings('windowFrameStyle', value);
}} }}
> >
{windowFrameStyleOptions.map(option => { {windowFrameStyleOptions.map(option => {
@@ -194,7 +188,7 @@ export const AppearanceSettings = () => {
> >
<Switch <Switch
checked={appSettings.startWeekOnMonday} checked={appSettings.startWeekOnMonday}
onChange={checked => changeSwitch('startWeekOnMonday', checked)} onChange={checked => updateSettings('startWeekOnMonday', checked)}
/> />
</SettingRow> </SettingRow>
</SettingWrapper> </SettingWrapper>
@@ -213,7 +207,7 @@ export const AppearanceSettings = () => {
<Switch <Switch
checked={appSettings.enableNoisyBackground} checked={appSettings.enableNoisyBackground}
onChange={checked => onChange={checked =>
changeSwitch('enableNoisyBackground', checked) updateSettings('enableNoisyBackground', checked)
} }
/> />
</SettingRow> </SettingRow>
@@ -227,7 +221,7 @@ export const AppearanceSettings = () => {
<Switch <Switch
checked={appSettings.enableBlurBackground} checked={appSettings.enableBlurBackground}
onChange={checked => onChange={checked =>
changeSwitch('enableBlurBackground', checked) updateSettings('enableBlurBackground', checked)
} }
/> />
</SettingRow> </SettingRow>

View File

@@ -29,7 +29,8 @@ import {
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { pageSettingFamily } from '../atoms'; 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 { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
import { Bookmark } from './bookmark'; import { Bookmark } from './bookmark';
import * as styles from './page-detail-editor.css'; import * as styles from './page-detail-editor.css';
@@ -68,7 +69,7 @@ const EditorWrapper = memo(function EditorWrapper({
const currentMode = pageSetting?.mode ?? 'page'; const currentMode = pageSetting?.mode ?? 'page';
const setBlockHub = useSetAtom(rootBlockHubAtom); const setBlockHub = useSetAtom(rootBlockHubAtom);
const [appSettings] = useAppSetting(); const { appSettings } = useAppSettingHelper();
assertExists(meta); assertExists(meta);
const value = useMemo(() => { const value = useMemo(() => {

View File

@@ -1,7 +1,6 @@
import { commandScore } from '@affine/cmdk'; import { commandScore } from '@affine/cmdk';
import { useCollectionManager } from '@affine/component/page-list'; import { useCollectionManager } from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter'; import type { Collection } from '@affine/env/filter';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons'; import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons';
import type { Page, PageMeta } from '@blocksuite/store'; import type { Page, PageMeta } from '@blocksuite/store';
@@ -150,19 +149,27 @@ export const pageToCommand = (
page: PageMeta, page: PageMeta,
store: ReturnType<typeof getCurrentStore>, store: ReturnType<typeof getCurrentStore>,
navigationHelper: ReturnType<typeof useNavigateHelper>, navigationHelper: ReturnType<typeof useNavigateHelper>,
t: ReturnType<typeof useAFFiNEI18N> t: ReturnType<typeof useAFFiNEI18N>,
label?: {
title: string;
subTitle?: string;
}
): CMDKCommand => { ): CMDKCommand => {
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode; const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
const currentWorkspaceId = store.get(currentWorkspaceIdAtom); const currentWorkspaceId = store.get(currentWorkspaceIdAtom);
const label = page.title || t['Untitled'](); const title = page.title || t['Untitled']();
const commandLabel = label || {
title: title,
};
return { return {
id: page.id, id: page.id,
label: label, label: commandLabel,
// hack: when comparing, the part between >>> and <<< will be ignored // hack: when comparing, the part between >>> and <<< will be ignored
// adding this patch so that CMDK will not complain about duplicated commands // adding this patch so that CMDK will not complain about duplicated commands
value: value:
label + valueWrapperStart + page.id + '.' + category + valueWrapperEnd, title + valueWrapperStart + page.id + '.' + category + valueWrapperEnd,
originalValue: label, originalValue: title,
category: category, category: category,
run: () => { run: () => {
if (!currentWorkspaceId) { if (!currentWorkspaceId) {
@@ -179,8 +186,6 @@ export const pageToCommand = (
const contentMatchedMagicString = '__$$content_matched$$__'; const contentMatchedMagicString = '__$$content_matched$$__';
export const usePageCommands = () => { export const usePageCommands = () => {
// todo: considering collections for searching pages
// const { savedCollections } = useCollectionManager(currentCollectionsAtom);
const recentPages = useRecentPages(); const recentPages = useRecentPages();
const pages = useWorkspacePages(); const pages = useWorkspacePages();
const store = getCurrentStore(); const store = getCurrentStore();
@@ -203,11 +208,11 @@ export const usePageCommands = () => {
workspace.blockSuiteWorkspace.search({ query }).values() workspace.blockSuiteWorkspace.search({ query }).values()
) as unknown as { space: string; content: string }[]; ) as unknown as { space: string; content: string }[];
const pageIds = searchResults.map(id => { const pageIds = searchResults.map(result => {
if (id.space.startsWith('space:')) { if (result.space.startsWith('space:')) {
return id.space.slice(6); return result.space.slice(6);
} else { } else {
return id.space; return result.space;
} }
}); });
@@ -215,12 +220,21 @@ export const usePageCommands = () => {
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode; const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
const category = const category =
pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages'; pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages';
const label = {
title: page.title,
subTitle:
searchResults.find(result => result.space === page.id)?.content ||
'',
};
const command = pageToCommand( const command = pageToCommand(
category, category,
page, page,
store, store,
navigationHelper, navigationHelper,
t t,
label
); );
if (pageIds.includes(page.id)) { if (pageIds.includes(page.id)) {
@@ -235,14 +249,7 @@ export const usePageCommands = () => {
if (results.every(command => command.originalValue !== query)) { if (results.every(command => command.originalValue !== query)) {
results.push({ results.push({
id: 'affine:pages:create-page', id: 'affine:pages:create-page',
label: ( label: `${t['com.affine.cmdk.affine.create-new-page-as']()} ${query}`,
<Trans
i18nKey="com.affine.cmdk.affine.create-new-page-as"
values={{ query }}
>
Create New Page as: <strong>query</strong>
</Trans>
),
value: 'affine::create-page' + query, // hack to make the page always showing in the search result value: 'affine::create-page' + query, // hack to make the page always showing in the search result
category: 'affine:creation', category: 'affine:creation',
run: async () => { run: async () => {
@@ -255,14 +262,9 @@ export const usePageCommands = () => {
results.push({ results.push({
id: 'affine:pages:create-edgeless', id: 'affine:pages:create-edgeless',
label: ( label: `${t[
<Trans 'com.affine.cmdk.affine.create-new-edgeless-as'
values={{ query }} ]()} ${query}`,
i18nKey="com.affine.cmdk.affine.create-new-edgeless-as"
>
Create New Edgeless as: <strong>query</strong>
</Trans>
),
value: 'affine::create-edgeless' + query, // hack to make the page always showing in the search result value: 'affine::create-edgeless' + query, // hack to make the page always showing in the search result
category: 'affine:creation', category: 'affine:creation',
run: async () => { 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', display: 'flex',
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
minWidth: 120,
flexDirection: 'row-reverse',
}); });
export const keybinding = style({ export const keybinding = style({
@@ -153,8 +155,8 @@ globalStyle(`${root} [cmdk-list]:hover::-webkit-scrollbar-thumb:hover`, {
globalStyle(`${root} [cmdk-item]`, { globalStyle(`${root} [cmdk-item]`, {
display: 'flex', display: 'flex',
height: 44, minHeight: 44,
padding: '0 12px', padding: '6px 12px',
alignItems: 'center', alignItems: 'center',
cursor: 'default', cursor: 'default',
borderRadius: 4, borderRadius: 4,

View File

@@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { PageMeta } from '@blocksuite/store'; import type { PageMeta } from '@blocksuite/store';
import type { CommandCategory } from '@toeverything/infra/command'; import type { CommandCategory } from '@toeverything/infra/command';
import clsx from 'clsx'; import clsx from 'clsx';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom } from 'jotai';
import { Suspense, useLayoutEffect, useMemo, useState } from 'react'; import { Suspense, useLayoutEffect, useMemo, useState } from 'react';
import { import {
@@ -13,8 +13,10 @@ import {
customCommandFilter, customCommandFilter,
useCMDKCommandGroups, useCMDKCommandGroups,
} from './data'; } from './data';
import { HighlightLabel } from './highlight';
import * as styles from './main.css'; import * as styles from './main.css';
import { CMDKModal, type CMDKModalProps } from './modal'; import { CMDKModal, type CMDKModalProps } from './modal';
import { NotFoundGroup } from './not-found';
import type { CMDKCommand } from './types'; import type { CMDKCommand } from './types';
type NoParametersKeys<T> = { type NoParametersKeys<T> = {
@@ -52,10 +54,16 @@ const QuickSearchGroup = ({
}) => { }) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const i18nkey = categoryToI18nKey[category]; const i18nkey = categoryToI18nKey[category];
const setQuery = useSetAtom(cmdkQueryAtom); const [query, setQuery] = useAtom(cmdkQueryAtom);
return ( return (
<Command.Group key={category} heading={t[i18nkey]()}> <Command.Group key={category} heading={t[i18nkey]()}>
{commands.map(command => { {commands.map(command => {
const label =
typeof command.label === 'string'
? {
title: command.label,
}
: command.label;
return ( return (
<Command.Item <Command.Item
key={command.id} key={command.id}
@@ -78,7 +86,7 @@ const QuickSearchGroup = ({
command.originalValue ? command.originalValue : undefined command.originalValue ? command.originalValue : undefined
} }
> >
{command.label} <HighlightLabel highlight={query} label={label} />
</div> </div>
{command.timestamp ? ( {command.timestamp ? (
<div className={styles.timestamp}> <div className={styles.timestamp}>
@@ -197,6 +205,7 @@ export const CMDKContainer = ({
<Command.List data-opening={opening ? true : undefined}> <Command.List data-opening={opening ? true : undefined}>
{children} {children}
</Command.List> </Command.List>
<NotFoundGroup />
</Command> </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 // we can use a single render function to render all different commands
export interface CMDKCommand { export interface CMDKCommand {
id: string; id: string;
label: string | React.ReactNode; label:
| string
| {
title: string;
subTitle?: string;
};
icon?: React.ReactNode; icon?: React.ReactNode;
category: CommandCategory; category: CommandCategory;
keyBinding?: string | { binding: string }; keyBinding?: string | { binding: string };

View File

@@ -10,7 +10,7 @@ import { currentPageIdAtom } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useCallback, useEffect, useRef, useState } from 'react'; 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 { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper'; import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
@@ -29,7 +29,7 @@ export const TrashButtonGroup = () => {
); );
assertExists(pageMeta); assertExists(pageMeta);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [appSettings] = useAppSetting(); const { appSettings } = useAppSettingHelper();
const { jumpToSubPath } = useNavigateHelper(); const { jumpToSubPath } = useNavigateHelper();
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const restoreRef = useRef(null); const restoreRef = useRef(null);

View File

@@ -26,7 +26,7 @@ import { forwardRef, useCallback, useEffect, useMemo } from 'react';
import { openWorkspaceListModalAtom } from '../../atoms'; import { openWorkspaceListModalAtom } from '../../atoms';
import { useHistoryAtom } from '../../atoms/history'; 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 { useGeneralShortcuts } from '../../hooks/affine/use-shortcuts';
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper'; import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands'; import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands';
@@ -100,7 +100,7 @@ export const RootAppSidebar = ({
onOpenSettingModal, onOpenSettingModal,
}: RootAppSidebarProps): ReactElement => { }: RootAppSidebarProps): ReactElement => {
const currentWorkspaceId = currentWorkspace.id; const currentWorkspaceId = currentWorkspace.id;
const [appSettings] = useAppSetting(); const { appSettings } = useAppSettingHelper();
const { backToAll } = useCollectionManager(currentCollectionsAtom); const { backToAll } = useCollectionManager(currentCollectionsAtom);
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace; const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
const t = useAFFiNEI18N(); 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) => { (event: string) => {
i18n.changeLanguage(event); i18n.changeLanguage(event);
}, },
@@ -27,8 +27,8 @@ export function useLanguageHelper() {
() => ({ () => ({
currentLanguage, currentLanguage,
languagesList, 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, languageHelper,
}) })
); );
unsubs.push(registerAffineLayoutCommands({ store, t })); unsubs.push(registerAffineLayoutCommands({ t, store }));
unsubs.push( unsubs.push(
registerAffineCreationCommands({ registerAffineCreationCommands({
store, store,

View File

@@ -38,7 +38,6 @@ import { Map as YMap } from 'yjs';
import { openQuickSearchModalAtom, openSettingModalAtom } from '../atoms'; import { openQuickSearchModalAtom, openSettingModalAtom } from '../atoms';
import { mainContainerAtom } from '../atoms/element'; import { mainContainerAtom } from '../atoms/element';
import { useAppSetting } from '../atoms/settings';
import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper'; import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper';
import { AppContainer } from '../components/affine/app-container'; import { AppContainer } from '../components/affine/app-container';
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
@@ -50,6 +49,7 @@ import {
DROPPABLE_SIDEBAR_TRASH, DROPPABLE_SIDEBAR_TRASH,
RootAppSidebar, RootAppSidebar,
} from '../components/root-app-sidebar'; } 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 { useBlockSuiteMetaHelper } from '../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../hooks/use-navigate-helper'; import { useNavigateHelper } from '../hooks/use-navigate-helper';
@@ -230,7 +230,7 @@ export const WorkspaceLayoutInner = ({
[moveToTrash, t] [moveToTrash, t]
); );
const [appSetting] = useAppSetting(); const { appSettings } = useAppSettingHelper();
const location = useLocation(); const location = useLocation();
const { pageId } = useParams(); const { pageId } = useParams();
const pageMeta = useBlockSuitePageMeta( const pageMeta = useBlockSuitePageMeta(
@@ -269,7 +269,7 @@ export const WorkspaceLayoutInner = ({
<Suspense fallback={<MainContainer ref={setMainContainer} />}> <Suspense fallback={<MainContainer ref={setMainContainer} />}>
<MainContainer <MainContainer
ref={setMainContainer} ref={setMainContainer}
padding={appSetting.clientBorder} padding={appSettings.clientBorder}
inTrashPage={inTrashPage} inTrashPage={inTrashPage}
> >
{incompatible ? <MigrationFallback /> : children} {incompatible ? <MigrationFallback /> : children}

View File

@@ -589,9 +589,9 @@
"com.affine.cmdk.affine.new-page": "New Page", "com.affine.cmdk.affine.new-page": "New Page",
"com.affine.cmdk.affine.new-edgeless-page": "New Edgeless", "com.affine.cmdk.affine.new-edgeless-page": "New Edgeless",
"com.affine.cmdk.affine.new-workspace": "New Workspace", "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-page-as": "Create New Page as:",
"com.affine.cmdk.affine.create-new-edgeless-as": "Create New Edgeless as: <1>{{query}}</1>", "com.affine.cmdk.affine.create-new-edgeless-as": "Create New Edgeless as:",
"com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to <1>{{colour}}</1>", "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.expand": "Expand Left Sidebar",
"com.affine.cmdk.affine.left-sidebar.collapse": "Collapse Left Sidebar", "com.affine.cmdk.affine.left-sidebar.collapse": "Collapse Left Sidebar",
"com.affine.cmdk.affine.navigation.goto-all-pages": "Go to All Pages", "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.add-to-favourites": "Add to Favourites",
"com.affine.cmdk.affine.editor.remove-from-favourites": "Remove from 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.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.font-style.to": "Change Font Style to",
"com.affine.cmdk.affine.display-language.to": "Change Display Language to <1>{{language}}</1>", "com.affine.cmdk.affine.display-language.to": "Change Display Language to",
"com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to <1>{{state}}</1>", "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 <1>{{state}}</1>", "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 <1>{{state}}</1>", "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 <1>{{state}}</1>", "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.whats-new": "What's New",
"com.affine.cmdk.affine.getting-started": "Getting Started", "com.affine.cmdk.affine.getting-started": "Getting Started",
"com.affine.cmdk.affine.contact-us": "Contact Us", "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.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.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.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 const actual = await page
.locator('[cmdk-item] [data-testid=cmdk-label]') .locator('[cmdk-item] [data-testid=cmdk-label]')
.allInnerTexts(); .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) { async function titleIsFocused(page: Page) {
@@ -100,9 +102,7 @@ test('Create a new page with keyword', async ({ page }) => {
await clickNewPageButton(page); await clickNewPageButton(page);
await openQuickSearchByShortcut(page); await openQuickSearchByShortcut(page);
await page.keyboard.insertText('test123456'); await page.keyboard.insertText('test123456');
const addNewPage = page.locator( const addNewPage = page.locator('[cmdk-item] >> text=Create New Page as:');
'[cmdk-item] >> text=Create New Page as: test123456'
);
await addNewPage.click(); await addNewPage.click();
await page.waitForTimeout(300); await page.waitForTimeout(300);
await assertTitle(page, 'test123456'); 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 // input title and create new page
await page.keyboard.insertText('test123456'); await page.keyboard.insertText('test123456');
await page.waitForTimeout(300); await page.waitForTimeout(300);
const addNewPage = page.locator( const addNewPage = page.locator('[cmdk-item] >> text=Create New Page as:');
'[cmdk-item] >> text=Create New Page as: test123456'
);
await addNewPage.click(); await addNewPage.click();
await page.waitForTimeout(300); await page.waitForTimeout(300);
@@ -218,36 +216,34 @@ test('assert the recent browse pages are on the recent list', async ({
// create first page // create first page
await clickNewPageButton(page); await clickNewPageButton(page);
await waitForEditorLoad(page);
{ {
const title = getBlockSuiteEditorTitle(page); const title = getBlockSuiteEditorTitle(page);
await title.pressSequentially('sgtokidoki', { await title.pressSequentially('sgtokidoki');
delay: 50, expect(await title.innerText()).toBe('sgtokidoki');
});
} }
await page.waitForTimeout(200);
// create second page // create second page
await openQuickSearchByShortcut(page); await openQuickSearchByShortcut(page);
const addNewPage = page.locator('[cmdk-item] >> text=New Page'); const addNewPage = page.locator('[cmdk-item] >> text=New Page');
await addNewPage.click(); await addNewPage.click();
await waitForEditorLoad(page);
{ {
const title = getBlockSuiteEditorTitle(page); const title = getBlockSuiteEditorTitle(page);
await title.pressSequentially('theliquidhorse', { await title.pressSequentially('theliquidhorse');
delay: 50, expect(await title.innerText()).toBe('theliquidhorse');
});
} }
await page.waitForTimeout(200); await page.waitForTimeout(200);
// create thrid page // create thrid page
await openQuickSearchByShortcut(page); await openQuickSearchByShortcut(page);
await addNewPage.click(); await addNewPage.click();
await waitForEditorLoad(page);
{ {
const title = getBlockSuiteEditorTitle(page); const title = getBlockSuiteEditorTitle(page);
await title.pressSequentially('battlekot', { await title.pressSequentially('battlekot');
delay: 50, expect(await title.innerText()).toBe('battlekot');
});
} }
await page.waitForTimeout(200);
await openQuickSearchByShortcut(page); 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'); const addNewPage = page.locator('[cmdk-item] >> text=New Page');
await addNewPage.click(); await addNewPage.click();
} }
await page.waitForTimeout(200); await waitForEditorLoad(page);
{ {
const title = getBlockSuiteEditorTitle(page); const title = getBlockSuiteEditorTitle(page);
await title.pressSequentially('affine is the best', { await title.pressSequentially('affine is the best');
delay: 50, expect(await title.innerText()).toBe('affine is the best');
});
} }
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await openQuickSearchByShortcut(page); 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 keyboardDownAndSelect(page, 'Restore from Trash');
await expect(restoreButton).not.toBeVisible(); 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, registerAffineSettingsCommands,
} from '@affine/core/commands'; } from '@affine/core/commands';
import { CMDKQuickSearchModal } from '@affine/core/components/pure/cmdk'; import { CMDKQuickSearchModal } from '@affine/core/components/pure/cmdk';
import { HighlightLabel } from '@affine/core/components/pure/cmdk/highlight';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
@@ -12,7 +13,7 @@ import type { Page } from '@blocksuite/store';
import type { Meta, StoryFn } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom'; import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
import { useStore } from 'jotai'; import { useStore } from 'jotai';
import { useEffect, useLayoutEffect } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import { withRouter } from 'storybook-addon-react-router-v6'; import { withRouter } from 'storybook-addon-react-router-v6';
export default { export default {
@@ -43,7 +44,7 @@ function useRegisterCommands() {
themes: ['auto', 'dark', 'light'], themes: ['auto', 'dark', 'light'],
}, },
languageHelper: { languageHelper: {
onSelect: () => {}, onLanguageChange: () => {},
languagesList: [ languagesList: [
{ tag: 'en', name: 'English', originalName: 'English' }, { tag: 'en', name: 'English', originalName: 'English' },
{ {
@@ -65,7 +66,10 @@ function useRegisterCommands() {
isPreferredEdgeless: () => false, isPreferredEdgeless: () => false,
}, },
}), }),
registerAffineLayoutCommands({ t, store }), registerAffineLayoutCommands({
t,
store,
}),
]; ];
return () => { return () => {
@@ -98,3 +102,16 @@ export const CMDKStoryWithCommands: StoryFn = () => {
}; };
CMDKStoryWithCommands.decorators = [withRouter]; 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} />
</>
);
};