feat(core): add setting commands (#4568)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
JimmFly 2023-10-11 11:31:04 +08:00 committed by GitHub
parent b1eb926b7b
commit 1f6a105e5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 524 additions and 60 deletions

View File

@ -63,7 +63,7 @@ const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', {
type SetStateAction<Value> = Value | ((prev: Value) => Value); type SetStateAction<Value> = Value | ((prev: Value) => Value);
const appSettingAtom = atom< export const appSettingAtom = atom<
AppSetting, AppSetting,
[SetStateAction<Partial<AppSetting>>], [SetStateAction<Partial<AppSetting>>],
void void

View File

@ -0,0 +1,58 @@
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
import { registerAffineCommand } from '@toeverything/infra/command';
import type { createStore } from 'jotai';
import { openOnboardingModalAtom, openSettingModalAtom } from '../atoms';
export function registerAffineHelpCommands({
t,
store,
}: {
t: ReturnType<typeof useAFFiNEI18N>;
store: ReturnType<typeof createStore>;
}) {
const unsubs: Array<() => void> = [];
unsubs.push(
registerAffineCommand({
id: 'affine:help-whats-new',
category: 'affine:help',
icon: <NewIcon />,
label: () => t['com.affine.cmdk.affine.whats-new'](),
run() {
window.open(runtimeConfig.changelogUrl, '_blank');
},
})
);
unsubs.push(
registerAffineCommand({
id: 'affine:help-contact-us',
category: 'affine:help',
icon: <ContactWithUsIcon />,
label: () => t['com.affine.cmdk.affine.contact-us'](),
run() {
store.set(openSettingModalAtom, {
open: true,
activeTab: 'about',
workspaceId: null,
});
},
})
);
unsubs.push(
registerAffineCommand({
id: 'affine:help-getting-started',
category: 'affine:help',
icon: <UserGuideIcon />,
label: () => t['com.affine.cmdk.affine.getting-started'](),
preconditionStrategy: () => environment.isDesktop,
run() {
store.set(openOnboardingModalAtom, true);
},
})
);
return () => {
unsubs.forEach(unsub => unsub());
};
}

View File

@ -5,20 +5,85 @@ import {
PreconditionStrategy, PreconditionStrategy,
registerAffineCommand, registerAffineCommand,
} from '@toeverything/infra/command'; } from '@toeverything/infra/command';
import type { createStore } from 'jotai'; import { type createStore, useAtomValue } 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 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,
store, store,
theme, theme,
languageHelper,
}: { }: {
t: ReturnType<typeof useAFFiNEI18N>; t: ReturnType<typeof useAFFiNEI18N>;
store: ReturnType<typeof createStore>; store: ReturnType<typeof createStore>;
theme: ReturnType<typeof useTheme>; theme: ReturnType<typeof useTheme>;
languageHelper: ReturnType<typeof useLanguageHelper>;
}) { }) {
const unsubs: Array<() => void> = []; const unsubs: Array<() => void> = [];
const { onSelect, languagesList, currentLanguage } = languageHelper;
unsubs.push( unsubs.push(
registerAffineCommand({ registerAffineCommand({
id: 'affine:show-quick-search', id: 'affine:show-quick-search',
@ -29,7 +94,8 @@ export function registerAffineSettingsCommands({
}, },
icon: <SettingsIcon />, icon: <SettingsIcon />,
run() { run() {
store.set(openQuickSearchModalAtom, true); const quickSearchModalState = store.get(openQuickSearchModalAtom);
store.set(openQuickSearchModalAtom, !quickSearchModalState);
}, },
}) })
); );
@ -94,6 +160,181 @@ export function registerAffineSettingsCommands({
}) })
); );
//Font styles
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>
),
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () =>
store.get(appSettingAtom).fontStyle !== 'Sans',
run() {
store.set(appSettingAtom, prev => ({
...prev,
fontStyle: 'Sans',
}));
},
})
);
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>
),
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () =>
store.get(appSettingAtom).fontStyle !== 'Serif',
run() {
store.set(appSettingAtom, prev => ({
...prev,
fontStyle: 'Serif',
}));
},
})
);
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>
),
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () =>
store.get(appSettingAtom).fontStyle !== 'Mono',
run() {
store.set(appSettingAtom, prev => ({
...prev,
fontStyle: 'Mono',
}));
},
})
);
//Display Language
languagesList.forEach(language => {
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>
),
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () => currentLanguage?.tag !== language.tag,
run() {
onSelect(language.tag);
},
})
);
});
//Layout Style
unsubs.push(
registerAffineCommand({
id: `affine:change-client-border-style`,
label: <ClientBorderStyleLabel />,
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop,
run() {
store.set(appSettingAtom, prev => ({
...prev,
clientBorder: !prev.clientBorder,
}));
},
})
);
unsubs.push(
registerAffineCommand({
id: `affine:change-full-width-layout`,
label: <FullWidthLayoutLabel />,
category: 'affine:settings',
icon: <SettingsIcon />,
run() {
store.set(appSettingAtom, prev => ({
...prev,
fullWidthLayout: !prev.fullWidthLayout,
}));
},
})
);
unsubs.push(
registerAffineCommand({
id: `affine:change-noise-background-on-the-sidebar`,
label: <NoisyBackgroundLabel />,
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop,
run() {
store.set(appSettingAtom, prev => ({
...prev,
enableNoisyBackground: !prev.enableNoisyBackground,
}));
},
})
);
unsubs.push(
registerAffineCommand({
id: `affine:change-translucent-ui-on-the-sidebar`,
label: <BlurBackgroundLabel />,
category: 'affine:settings',
icon: <SettingsIcon />,
preconditionStrategy: () => environment.isDesktop,
run() {
store.set(appSettingAtom, prev => ({
...prev,
enableBlurBackground: !prev.enableBlurBackground,
}));
},
})
);
return () => { return () => {
unsubs.forEach(unsub => unsub()); unsubs.forEach(unsub => unsub());
}; };

View File

@ -0,0 +1,35 @@
import { updateReadyAtom } from '@affine/component/app-sidebar/app-updater-button';
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ResetIcon } from '@blocksuite/icons';
import { registerAffineCommand } from '@toeverything/infra/command';
import type { createStore } from 'jotai';
export function registerAffineUpdatesCommands({
t,
store,
}: {
t: ReturnType<typeof useAFFiNEI18N>;
store: ReturnType<typeof createStore>;
}) {
const unsubs: Array<() => void> = [];
unsubs.push(
registerAffineCommand({
id: 'affine:restart-to-upgrade',
category: 'affine:updates',
icon: <ResetIcon />,
label: () => t['com.affine.cmdk.affine.restart-to-upgrade'](),
preconditionStrategy: () => !!store.get(updateReadyAtom),
run() {
window.apis?.updater.quitAndInstall().catch(err => {
// TODO: add error toast here
console.error(err);
});
},
})
);
return () => {
unsubs.forEach(unsub => unsub());
};
}

View File

@ -1,3 +1,6 @@
export * from './affine-creation'; export * from './affine-creation';
export * from './affine-help';
export * from './affine-layout'; export * from './affine-layout';
export * from './affine-navigation';
export * from './affine-settings'; export * from './affine-settings';
export * from './affine-updates';

View File

@ -1,24 +1,18 @@
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu'; import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
import type { ReactElement } from 'react'; import { memo, type ReactElement } from 'react';
import { useCallback, useMemo } from 'react';
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 = ({ const LanguageMenuContent = memo(function LanguageMenuContent() {
currentLanguage, const { currentLanguage, languagesList, onSelect } = useLanguageHelper();
onSelect,
}: {
currentLanguage?: string;
onSelect: (value: string) => void;
}) => {
return ( return (
<> <>
{LOCALES.map(option => { {languagesList.map(option => {
return ( return (
<MenuItem <MenuItem
key={option.name} key={option.name}
selected={currentLanguage === option.originalName} selected={currentLanguage?.originalName === option.originalName}
title={option.name} title={option.name}
onSelect={() => onSelect(option.tag)} onSelect={() => onSelect(option.tag)}
> >
@ -28,30 +22,13 @@ const LanguageMenuContent = ({
})} })}
</> </>
); );
}; });
export const LanguageMenu = () => { export const LanguageMenu = () => {
const i18n = useI18N(); const { currentLanguage } = useLanguageHelper();
const currentLanguage = useMemo(
() => LOCALES.find(item => item.tag === i18n.language),
[i18n.language]
);
const onSelect = useCallback(
(event: string) => {
return i18n.changeLanguage(event);
},
[i18n]
);
return ( return (
<Menu <Menu
items={ items={(<LanguageMenuContent />) as ReactElement}
(
<LanguageMenuContent
currentLanguage={currentLanguage?.originalName}
onSelect={onSelect}
/>
) as ReactElement
}
contentOptions={{ contentOptions={{
style: { style: {
background: 'var(--affine-white)', background: 'var(--affine-white)',

View File

@ -24,7 +24,7 @@ export const ThemeSettings = () => {
<RadioButtonGroup <RadioButtonGroup
width={250} width={250}
className={settingWrapper} className={settingWrapper}
defaultValue={theme} value={theme}
onValueChange={useCallback( onValueChange={useCallback(
(value: string) => { (value: string) => {
setTheme(value); setTheme(value);
@ -52,7 +52,7 @@ const FontFamilySettings = () => {
<RadioButtonGroup <RadioButtonGroup
width={250} width={250}
className={settingWrapper} className={settingWrapper}
defaultValue={appSettings.fontStyle} value={appSettings.fontStyle}
onValueChange={useCallback( onValueChange={useCallback(
(key: AppSetting['fontStyle']) => { (key: AppSetting['fontStyle']) => {
setAppSettings({ fontStyle: key }); setAppSettings({ fontStyle: key });

View File

@ -19,6 +19,32 @@ export const searchInput = style({
'::placeholder': { '::placeholder': {
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
}, },
selectors: {
'&.inEditor': {
paddingTop: '12px',
paddingBottom: '18px',
},
},
});
export const pageTitleWrapper = style({
display: 'flex',
alignItems: 'center',
padding: '18px 24px 0 24px',
width: '100%',
});
export const pageTitle = style({
padding: '2px 6px',
borderRadius: 4,
fontSize: 'var(--affine-font-xs)',
lineHeight: '20px',
color: 'var(--affine-text-secondary-color)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100%',
backgroundColor: 'var(--affine-background-secondary-color)',
}); });
export const panelContainer = style({ export const panelContainer = style({
@ -41,6 +67,9 @@ export const itemLabel = style({
lineHeight: '1.5', lineHeight: '1.5',
color: 'var(--affine-text-primary-color)', color: 'var(--affine-text-primary-color)',
flex: 1, flex: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}); });
export const timestamp = style({ export const timestamp = style({

View File

@ -1,6 +1,7 @@
import { Command } from '@affine/cmdk'; import { Command } from '@affine/cmdk';
import { formatDate } from '@affine/component/page-list'; import { formatDate } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
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, useSetAtom } from 'jotai';
@ -124,14 +125,17 @@ export const CMDKContainer = ({
onQueryChange, onQueryChange,
query, query,
children, children,
pageMeta,
...rest ...rest
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
className?: string; className?: string;
query: string; query: string;
pageMeta?: PageMeta;
onQueryChange: (query: string) => void; onQueryChange: (query: string) => void;
}>) => { }>) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [value, setValue] = useAtom(cmdkValueAtom); const [value, setValue] = useAtom(cmdkValueAtom);
const isInEditor = pageMeta !== undefined;
return ( return (
<Command <Command
{...rest} {...rest}
@ -153,20 +157,32 @@ export const CMDKContainer = ({
}} }}
> >
{/* todo: add page context here */} {/* todo: add page context here */}
{isInEditor ? (
<div className={styles.pageTitleWrapper}>
<span className={styles.pageTitle}>
{pageMeta.title ? pageMeta.title : t['Untitled']()}
</span>
</div>
) : null}
<Command.Input <Command.Input
placeholder={t['com.affine.cmdk.placeholder']()} placeholder={t['com.affine.cmdk.placeholder']()}
autoFocus autoFocus
{...rest} {...rest}
value={query} value={query}
onValueChange={onQueryChange} onValueChange={onQueryChange}
className={clsx(className, styles.searchInput)} className={clsx(className, styles.searchInput, {
inEditor: isInEditor,
})}
/> />
<Command.List>{children}</Command.List> <Command.List>{children}</Command.List>
</Command> </Command>
); );
}; };
export const CMDKQuickSearchModal = (props: CMDKModalProps) => { export const CMDKQuickSearchModal = ({
pageMeta,
...props
}: CMDKModalProps & { pageMeta?: PageMeta }) => {
const [query, setQuery] = useAtom(cmdkQueryAtom); const [query, setQuery] = useAtom(cmdkQueryAtom);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.open) { if (props.open) {
@ -179,6 +195,7 @@ export const CMDKQuickSearchModal = (props: CMDKModalProps) => {
className={styles.root} className={styles.root}
query={query} query={query}
onQueryChange={setQuery} onQueryChange={setQuery}
pageMeta={pageMeta}
> >
<Suspense fallback={<Command.Loading />}> <Suspense fallback={<Command.Loading />}>
<QuickSearchCommands onOpenChange={props.onOpenChange} /> <QuickSearchCommands onOpenChange={props.onOpenChange} />

View File

@ -0,0 +1,34 @@
import { LOCALES, useI18N } from '@affine/i18n';
import { useCallback, useMemo } from 'react';
export function useLanguageHelper() {
const i18n = useI18N();
const currentLanguage = useMemo(
() => LOCALES.find(item => item.tag === i18n.language),
[i18n.language]
);
const languagesList = useMemo(
() =>
LOCALES.map(item => ({
tag: item.tag,
originalName: item.originalName,
name: item.name,
})),
[]
);
const onSelect = useCallback(
(event: string) => {
i18n.changeLanguage(event);
},
[i18n]
);
return useMemo(
() => ({
currentLanguage,
languagesList,
onSelect,
}),
[currentLanguage, languagesList, onSelect]
);
}

View File

@ -52,17 +52,20 @@ export function useNavigateHelper() {
}, },
[navigate] [navigate]
); );
const isPublicWorkspace = useMemo(() => {
return location.pathname.indexOf('/public-workspace') === 0;
}, [location.pathname]);
const openPage = useCallback( const openPage = useCallback(
(workspaceId: string, pageId: string) => { (workspaceId: string, pageId: string) => {
const isPublicWorkspace =
location.pathname.indexOf('/public-workspace') === 0;
if (isPublicWorkspace) { if (isPublicWorkspace) {
return jumpToPublicWorkspacePage(workspaceId, pageId); return jumpToPublicWorkspacePage(workspaceId, pageId);
} else { } else {
return jumpToPage(workspaceId, pageId); return jumpToPage(workspaceId, pageId);
} }
}, },
[jumpToPage, jumpToPublicWorkspacePage, location.pathname] [jumpToPage, jumpToPublicWorkspacePage, isPublicWorkspace]
); );
const jumpToIndex = useCallback( const jumpToIndex = useCallback(

View File

@ -6,11 +6,14 @@ import { useEffect } from 'react';
import { allPageModeSelectAtom } from '../atoms'; import { allPageModeSelectAtom } from '../atoms';
import { import {
registerAffineCreationCommands, registerAffineCreationCommands,
registerAffineHelpCommands,
registerAffineLayoutCommands, registerAffineLayoutCommands,
registerAffineNavigationCommands,
registerAffineSettingsCommands, registerAffineSettingsCommands,
registerAffineUpdatesCommands,
} from '../commands'; } from '../commands';
import { registerAffineNavigationCommands } from '../commands/affine-navigation';
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
import { useLanguageHelper } from './affine/use-language-helper';
import { useCurrentWorkspace } from './current/use-current-workspace'; import { useCurrentWorkspace } from './current/use-current-workspace';
import { useNavigateHelper } from './use-navigate-helper'; import { useNavigateHelper } from './use-navigate-helper';
@ -19,11 +22,18 @@ export function useRegisterWorkspaceCommands() {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const theme = useTheme(); const theme = useTheme();
const [currentWorkspace] = useCurrentWorkspace(); const [currentWorkspace] = useCurrentWorkspace();
const languageHelper = useLanguageHelper();
const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace); const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
const navigationHelper = useNavigateHelper(); const navigationHelper = useNavigateHelper();
const [pageListMode, setPageListMode] = useAtom(allPageModeSelectAtom); const [pageListMode, setPageListMode] = useAtom(allPageModeSelectAtom);
useEffect(() => { useEffect(() => {
const unsubs: Array<() => void> = []; const unsubs: Array<() => void> = [];
unsubs.push(
registerAffineUpdatesCommands({
store,
t,
})
);
unsubs.push( unsubs.push(
registerAffineNavigationCommands({ registerAffineNavigationCommands({
store, store,
@ -34,7 +44,14 @@ export function useRegisterWorkspaceCommands() {
setPageListMode, setPageListMode,
}) })
); );
unsubs.push(registerAffineSettingsCommands({ store, t, theme })); unsubs.push(
registerAffineSettingsCommands({
store,
t,
theme,
languageHelper,
})
);
unsubs.push(registerAffineLayoutCommands({ store, t })); unsubs.push(registerAffineLayoutCommands({ store, t }));
unsubs.push( unsubs.push(
registerAffineCreationCommands({ registerAffineCreationCommands({
@ -43,6 +60,12 @@ export function useRegisterWorkspaceCommands() {
t, t,
}) })
); );
unsubs.push(
registerAffineHelpCommands({
store,
t,
})
);
return () => { return () => {
unsubs.forEach(unsub => unsub()); unsubs.forEach(unsub => unsub());
@ -56,5 +79,6 @@ export function useRegisterWorkspaceCommands() {
navigationHelper, navigationHelper,
pageListMode, pageListMode,
setPageListMode, setPageListMode,
languageHelper,
]); ]);
} }

View File

@ -69,11 +69,16 @@ const CMDKQuickSearchModal = lazy(() =>
); );
export const QuickSearch = () => { export const QuickSearch = () => {
const [currentWorkspace] = useCurrentWorkspace();
const [openQuickSearchModal, setOpenQuickSearchModalAtom] = useAtom( const [openQuickSearchModal, setOpenQuickSearchModalAtom] = useAtom(
openQuickSearchModalAtom openQuickSearchModalAtom
); );
const [currentWorkspace] = useCurrentWorkspace();
const { pageId } = useParams();
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace; const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(
currentWorkspace?.blockSuiteWorkspace
).find(meta => meta.id === pageId);
if (!blockSuiteWorkspace) { if (!blockSuiteWorkspace) {
return null; return null;
@ -83,6 +88,7 @@ export const QuickSearch = () => {
<CMDKQuickSearchModal <CMDKQuickSearchModal
open={openQuickSearchModal} open={openQuickSearchModal}
onOpenChange={setOpenQuickSearchModalAtom} onOpenChange={setOpenQuickSearchModalAtom}
pageMeta={pageMeta}
/> />
); );
}; };

View File

@ -42,6 +42,18 @@ function useRegisterCommands() {
theme: 'auto', theme: 'auto',
themes: ['auto', 'dark', 'light'], themes: ['auto', 'dark', 'light'],
}, },
languageHelper: {
onSelect: () => {},
languagesList: [
{ tag: 'en', name: 'English', originalName: 'English' },
{
tag: 'zh-Hans',
name: 'Simplified Chinese',
originalName: '简体中文',
},
],
currentLanguage: undefined,
},
}), }),
registerAffineCreationCommands({ registerAffineCreationCommands({
t, t,

View File

@ -259,3 +259,5 @@ export function AppUpdaterButton({
/> />
); );
} }
export * from './index.jotai';

View File

@ -1,6 +1,11 @@
// components/switch.tsx // components/switch.tsx
import clsx from 'clsx'; import clsx from 'clsx';
import { type HTMLAttributes, type ReactNode, useState } from 'react'; import {
type HTMLAttributes,
type ReactNode,
useCallback,
useState,
} from 'react';
import * as styles from './index.css'; import * as styles from './index.css';
@ -10,15 +15,23 @@ type SwitchProps = Omit<HTMLAttributes<HTMLLabelElement>, 'onChange'> & {
children?: ReactNode; children?: ReactNode;
}; };
export const Switch = (props: SwitchProps) => { export const Switch = ({
const { checked, onChange, children, ...otherProps } = props; checked: checkedProp = false,
const [isChecked, setIsChecked] = useState(checked); onChange: onChangeProp,
children,
...otherProps
}: SwitchProps) => {
const [checkedState, setCheckedState] = useState(checkedProp);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const checked = onChangeProp ? checkedProp : checkedState;
const newChecked = event.target.checked; const onChange = useCallback(
setIsChecked(newChecked); (event: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(newChecked); const newChecked = event.target.checked;
}; if (onChangeProp) onChangeProp(newChecked);
else setCheckedState(newChecked);
},
[onChangeProp]
);
return ( return (
<label className={clsx(styles.labelStyle)} {...otherProps}> <label className={clsx(styles.labelStyle)} {...otherProps}>
@ -26,13 +39,13 @@ export const Switch = (props: SwitchProps) => {
<input <input
className={clsx(styles.inputStyle)} className={clsx(styles.inputStyle)}
type="checkbox" type="checkbox"
value={isChecked ? 'on' : 'off'} value={checked ? 'on' : 'off'}
checked={isChecked} checked={checked}
onChange={handleChange} onChange={onChange}
/> />
<span <span
className={clsx(styles.switchStyle, { className={clsx(styles.switchStyle, {
[styles.switchCheckedStyle]: isChecked, [styles.switchCheckedStyle]: checked,
})} })}
/> />
</label> </label>

View File

@ -618,5 +618,15 @@
"com.affine.cmdk.affine.import-workspace": "Import Workspace", "com.affine.cmdk.affine.import-workspace": "Import Workspace",
"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.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.whats-new": "What's New",
"com.affine.cmdk.affine.getting-started": "Getting Started",
"com.affine.cmdk.affine.contact-us": "Contact Us",
"com.affine.cmdk.affine.restart-to-upgrade": "Restart to Upgrade"
} }