mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 20:16:26 +08:00
feat(core): add search result highlighting (#4667)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -3,10 +3,10 @@ import {
|
||||
type WorkspaceRootProps,
|
||||
} from '@affine/component/workspace';
|
||||
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
|
||||
export const AppContainer = (props: WorkspaceRootProps) => {
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
return (
|
||||
<AppContainerWithoutSettings
|
||||
|
||||
@@ -5,7 +5,8 @@ import { useLanguageHelper } from '../../../hooks/affine/use-language-helper';
|
||||
|
||||
// Fixme: keyboard focus should be supported by Menu component
|
||||
const LanguageMenuContent = memo(function LanguageMenuContent() {
|
||||
const { currentLanguage, languagesList, onSelect } = useLanguageHelper();
|
||||
const { currentLanguage, languagesList, onLanguageChange } =
|
||||
useLanguageHelper();
|
||||
return (
|
||||
<>
|
||||
{languagesList.map(option => {
|
||||
@@ -14,7 +15,7 @@ const LanguageMenuContent = memo(function LanguageMenuContent() {
|
||||
key={option.name}
|
||||
selected={currentLanguage?.originalName === option.originalName}
|
||||
title={option.name}
|
||||
onSelect={() => onSelect(option.tag)}
|
||||
onSelect={() => onLanguageChange(option.tag)}
|
||||
>
|
||||
{option.originalName}
|
||||
</MenuItem>
|
||||
|
||||
+1
-6
@@ -66,12 +66,7 @@ const PublishPanelAffine = (props: PublishPanelAffineProps) => {
|
||||
marginBottom: isPublic ? '12px' : '25px',
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
checked={isPublic}
|
||||
// onChange={useCallback(value => {
|
||||
// console.log('onChange', value);
|
||||
// }, [])}
|
||||
/>
|
||||
<Switch checked={isPublic} />
|
||||
</SettingRow>
|
||||
{isPublic ? (
|
||||
<FlexWrapper justifyContent="space-between" marginBottom={25}>
|
||||
|
||||
+4
-11
@@ -4,21 +4,14 @@ import { SettingRow } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { type AppSetting, useAppSetting } from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
import { relatedLinks } from './config';
|
||||
import { communityItem, communityWrapper, link } from './style.css';
|
||||
|
||||
export const AboutAffine = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const changeSwitch = useCallback(
|
||||
(key: keyof AppSetting, checked: boolean) => {
|
||||
setAppSettings({ [key]: checked });
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
@@ -47,7 +40,7 @@ export const AboutAffine = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.autoCheckUpdate}
|
||||
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||
onChange={checked => updateSettings('autoCheckUpdate', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -58,7 +51,7 @@ export const AboutAffine = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.autoCheckUpdate}
|
||||
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||
onChange={checked => updateSettings('autoCheckUpdate', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
|
||||
+6
-6
@@ -5,8 +5,8 @@ import { useCallback } from 'react';
|
||||
import {
|
||||
dateFormatOptions,
|
||||
type DateFormats,
|
||||
useAppSetting,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
|
||||
interface DateFormatMenuContentProps {
|
||||
currentOption: DateFormats;
|
||||
@@ -35,12 +35,12 @@ const DateFormatMenuContent = ({
|
||||
};
|
||||
|
||||
export const DateFormatSetting = () => {
|
||||
const [appearanceSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
const handleSelect = useCallback(
|
||||
(option: DateFormats) => {
|
||||
setAppSettings({ dateFormat: option });
|
||||
updateSettings('dateFormat', option);
|
||||
},
|
||||
[setAppSettings]
|
||||
[updateSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -48,12 +48,12 @@ export const DateFormatSetting = () => {
|
||||
items={
|
||||
<DateFormatMenuContent
|
||||
onSelect={handleSelect}
|
||||
currentOption={appearanceSettings.dateFormat}
|
||||
currentOption={appSettings.dateFormat}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MenuTrigger data-testid="date-format-menu-trigger" block>
|
||||
{dayjs(new Date()).format(appearanceSettings.dateFormat)}
|
||||
{dayjs(new Date()).format(appSettings.dateFormat)}
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
+11
-17
@@ -9,9 +9,9 @@ import { useCallback } from 'react';
|
||||
import {
|
||||
type AppSetting,
|
||||
fontStyleOptions,
|
||||
useAppSetting,
|
||||
windowFrameStyleOptions,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
import { LanguageMenu } from '../../../language-menu';
|
||||
import { DateFormatSetting } from './date-format-setting';
|
||||
import { settingWrapper } from './style.css';
|
||||
@@ -47,7 +47,7 @@ export const ThemeSettings = () => {
|
||||
|
||||
const FontFamilySettings = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
return (
|
||||
<RadioButtonGroup
|
||||
width={250}
|
||||
@@ -55,9 +55,9 @@ const FontFamilySettings = () => {
|
||||
value={appSettings.fontStyle}
|
||||
onValueChange={useCallback(
|
||||
(key: AppSetting['fontStyle']) => {
|
||||
setAppSettings({ fontStyle: key });
|
||||
updateSettings('fontStyle', key);
|
||||
},
|
||||
[setAppSettings]
|
||||
[updateSettings]
|
||||
)}
|
||||
>
|
||||
{fontStyleOptions.map(({ key, value }) => {
|
||||
@@ -95,14 +95,8 @@ const FontFamilySettings = () => {
|
||||
export const AppearanceSettings = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [appSettings, setAppSettings] = useAppSetting();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
|
||||
const changeSwitch = useCallback(
|
||||
(key: keyof AppSetting, checked: boolean) => {
|
||||
setAppSettings({ [key]: checked });
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
@@ -139,7 +133,7 @@ export const AppearanceSettings = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.clientBorder}
|
||||
onChange={checked => changeSwitch('clientBorder', checked)}
|
||||
onChange={checked => updateSettings('clientBorder', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
) : null}
|
||||
@@ -151,7 +145,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
data-testid="full-width-layout-trigger"
|
||||
checked={appSettings.fullWidthLayout}
|
||||
onChange={checked => changeSwitch('fullWidthLayout', checked)}
|
||||
onChange={checked => updateSettings('fullWidthLayout', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
|
||||
@@ -164,7 +158,7 @@ export const AppearanceSettings = () => {
|
||||
width={250}
|
||||
defaultValue={appSettings.windowFrameStyle}
|
||||
onValueChange={(value: AppSetting['windowFrameStyle']) => {
|
||||
setAppSettings({ windowFrameStyle: value });
|
||||
updateSettings('windowFrameStyle', value);
|
||||
}}
|
||||
>
|
||||
{windowFrameStyleOptions.map(option => {
|
||||
@@ -194,7 +188,7 @@ export const AppearanceSettings = () => {
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.startWeekOnMonday}
|
||||
onChange={checked => changeSwitch('startWeekOnMonday', checked)}
|
||||
onChange={checked => updateSettings('startWeekOnMonday', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
@@ -213,7 +207,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
checked={appSettings.enableNoisyBackground}
|
||||
onChange={checked =>
|
||||
changeSwitch('enableNoisyBackground', checked)
|
||||
updateSettings('enableNoisyBackground', checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
@@ -227,7 +221,7 @@ export const AppearanceSettings = () => {
|
||||
<Switch
|
||||
checked={appSettings.enableBlurBackground}
|
||||
onChange={checked =>
|
||||
changeSwitch('enableBlurBackground', checked)
|
||||
updateSettings('enableBlurBackground', checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import { pageSettingFamily } from '../atoms';
|
||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||
import { fontStyleOptions } from '../atoms/settings';
|
||||
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
|
||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||
import { Bookmark } from './bookmark';
|
||||
import * as styles from './page-detail-editor.css';
|
||||
@@ -68,7 +69,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
const currentMode = pageSetting?.mode ?? 'page';
|
||||
|
||||
const setBlockHub = useSetAtom(rootBlockHubAtom);
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
assertExists(meta);
|
||||
const value = useMemo(() => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { commandScore } from '@affine/cmdk';
|
||||
import { useCollectionManager } from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
@@ -150,19 +149,27 @@ export const pageToCommand = (
|
||||
page: PageMeta,
|
||||
store: ReturnType<typeof getCurrentStore>,
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>,
|
||||
t: ReturnType<typeof useAFFiNEI18N>
|
||||
t: ReturnType<typeof useAFFiNEI18N>,
|
||||
label?: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
}
|
||||
): CMDKCommand => {
|
||||
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
|
||||
const currentWorkspaceId = store.get(currentWorkspaceIdAtom);
|
||||
const label = page.title || t['Untitled']();
|
||||
const title = page.title || t['Untitled']();
|
||||
const commandLabel = label || {
|
||||
title: title,
|
||||
};
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
label: label,
|
||||
label: commandLabel,
|
||||
// hack: when comparing, the part between >>> and <<< will be ignored
|
||||
// adding this patch so that CMDK will not complain about duplicated commands
|
||||
value:
|
||||
label + valueWrapperStart + page.id + '.' + category + valueWrapperEnd,
|
||||
originalValue: label,
|
||||
title + valueWrapperStart + page.id + '.' + category + valueWrapperEnd,
|
||||
originalValue: title,
|
||||
category: category,
|
||||
run: () => {
|
||||
if (!currentWorkspaceId) {
|
||||
@@ -179,8 +186,6 @@ export const pageToCommand = (
|
||||
const contentMatchedMagicString = '__$$content_matched$$__';
|
||||
|
||||
export const usePageCommands = () => {
|
||||
// todo: considering collections for searching pages
|
||||
// const { savedCollections } = useCollectionManager(currentCollectionsAtom);
|
||||
const recentPages = useRecentPages();
|
||||
const pages = useWorkspacePages();
|
||||
const store = getCurrentStore();
|
||||
@@ -203,11 +208,11 @@ export const usePageCommands = () => {
|
||||
workspace.blockSuiteWorkspace.search({ query }).values()
|
||||
) as unknown as { space: string; content: string }[];
|
||||
|
||||
const pageIds = searchResults.map(id => {
|
||||
if (id.space.startsWith('space:')) {
|
||||
return id.space.slice(6);
|
||||
const pageIds = searchResults.map(result => {
|
||||
if (result.space.startsWith('space:')) {
|
||||
return result.space.slice(6);
|
||||
} else {
|
||||
return id.space;
|
||||
return result.space;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -215,12 +220,21 @@ export const usePageCommands = () => {
|
||||
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
|
||||
const category =
|
||||
pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages';
|
||||
|
||||
const label = {
|
||||
title: page.title,
|
||||
subTitle:
|
||||
searchResults.find(result => result.space === page.id)?.content ||
|
||||
'',
|
||||
};
|
||||
|
||||
const command = pageToCommand(
|
||||
category,
|
||||
page,
|
||||
store,
|
||||
navigationHelper,
|
||||
t
|
||||
t,
|
||||
label
|
||||
);
|
||||
|
||||
if (pageIds.includes(page.id)) {
|
||||
@@ -235,14 +249,7 @@ export const usePageCommands = () => {
|
||||
if (results.every(command => command.originalValue !== query)) {
|
||||
results.push({
|
||||
id: 'affine:pages:create-page',
|
||||
label: (
|
||||
<Trans
|
||||
i18nKey="com.affine.cmdk.affine.create-new-page-as"
|
||||
values={{ query }}
|
||||
>
|
||||
Create New Page as: <strong>query</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t['com.affine.cmdk.affine.create-new-page-as']()} ${query}`,
|
||||
value: 'affine::create-page' + query, // hack to make the page always showing in the search result
|
||||
category: 'affine:creation',
|
||||
run: async () => {
|
||||
@@ -255,14 +262,9 @@ export const usePageCommands = () => {
|
||||
|
||||
results.push({
|
||||
id: 'affine:pages:create-edgeless',
|
||||
label: (
|
||||
<Trans
|
||||
values={{ query }}
|
||||
i18nKey="com.affine.cmdk.affine.create-new-edgeless-as"
|
||||
>
|
||||
Create New Edgeless as: <strong>query</strong>
|
||||
</Trans>
|
||||
),
|
||||
label: `${t[
|
||||
'com.affine.cmdk.affine.create-new-edgeless-as'
|
||||
]()} ${query}`,
|
||||
value: 'affine::create-edgeless' + query, // hack to make the page always showing in the search result
|
||||
category: 'affine:creation',
|
||||
run: async () => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const highlightContainer = style({
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
});
|
||||
|
||||
export const highlightText = style({
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
});
|
||||
|
||||
export const highlightKeyword = style({
|
||||
color: 'var(--affine-primary-color)',
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'visible',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
export const labelTitle = style({
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '24px',
|
||||
fontWeight: 400,
|
||||
textAlign: 'justify',
|
||||
});
|
||||
|
||||
export const labelContent = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 400,
|
||||
textAlign: 'justify',
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { escapeRegExp } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
|
||||
import {
|
||||
highlightContainer,
|
||||
highlightKeyword,
|
||||
highlightText,
|
||||
labelContent,
|
||||
labelTitle,
|
||||
} from './highlight.css';
|
||||
|
||||
type SearchResultLabel = {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
|
||||
type HighlightProps = {
|
||||
text: string;
|
||||
highlight: string;
|
||||
};
|
||||
|
||||
type HighlightLabelProps = {
|
||||
label: SearchResultLabel;
|
||||
highlight: string;
|
||||
};
|
||||
|
||||
export const Highlight = memo(function Highlight({
|
||||
text = '',
|
||||
highlight = '',
|
||||
}: HighlightProps) {
|
||||
//Regex is used to ignore case
|
||||
const regex = highlight.trim()
|
||||
? new RegExp(`(${escapeRegExp(highlight)})`, 'ig')
|
||||
: null;
|
||||
|
||||
if (!regex) {
|
||||
return <span>{text}</span>;
|
||||
}
|
||||
const parts = text.split(regex);
|
||||
|
||||
return (
|
||||
<div className={highlightContainer}>
|
||||
{parts.map((part, i) => {
|
||||
if (regex.test(part)) {
|
||||
return (
|
||||
<span key={i} className={highlightKeyword}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span key={i} className={highlightText}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const HighlightLabel = memo(function HighlightLabel({
|
||||
label,
|
||||
highlight,
|
||||
}: HighlightLabelProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className={labelTitle}>
|
||||
<Highlight text={label.title} highlight={highlight} />
|
||||
</div>
|
||||
{label.subTitle ? (
|
||||
<div className={labelContent}>
|
||||
<Highlight text={label.subTitle} highlight={highlight} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -76,6 +76,8 @@ export const timestamp = style({
|
||||
display: 'flex',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
minWidth: 120,
|
||||
flexDirection: 'row-reverse',
|
||||
});
|
||||
|
||||
export const keybinding = style({
|
||||
@@ -153,8 +155,8 @@ globalStyle(`${root} [cmdk-list]:hover::-webkit-scrollbar-thumb:hover`, {
|
||||
|
||||
globalStyle(`${root} [cmdk-item]`, {
|
||||
display: 'flex',
|
||||
height: 44,
|
||||
padding: '0 12px',
|
||||
minHeight: 44,
|
||||
padding: '6px 12px',
|
||||
alignItems: 'center',
|
||||
cursor: 'default',
|
||||
borderRadius: 4,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import type { CommandCategory } from '@toeverything/infra/command';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useAtom } from 'jotai';
|
||||
import { Suspense, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -13,8 +13,10 @@ import {
|
||||
customCommandFilter,
|
||||
useCMDKCommandGroups,
|
||||
} from './data';
|
||||
import { HighlightLabel } from './highlight';
|
||||
import * as styles from './main.css';
|
||||
import { CMDKModal, type CMDKModalProps } from './modal';
|
||||
import { NotFoundGroup } from './not-found';
|
||||
import type { CMDKCommand } from './types';
|
||||
|
||||
type NoParametersKeys<T> = {
|
||||
@@ -52,10 +54,16 @@ const QuickSearchGroup = ({
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const i18nkey = categoryToI18nKey[category];
|
||||
const setQuery = useSetAtom(cmdkQueryAtom);
|
||||
const [query, setQuery] = useAtom(cmdkQueryAtom);
|
||||
return (
|
||||
<Command.Group key={category} heading={t[i18nkey]()}>
|
||||
{commands.map(command => {
|
||||
const label =
|
||||
typeof command.label === 'string'
|
||||
? {
|
||||
title: command.label,
|
||||
}
|
||||
: command.label;
|
||||
return (
|
||||
<Command.Item
|
||||
key={command.id}
|
||||
@@ -78,7 +86,7 @@ const QuickSearchGroup = ({
|
||||
command.originalValue ? command.originalValue : undefined
|
||||
}
|
||||
>
|
||||
{command.label}
|
||||
<HighlightLabel highlight={query} label={label} />
|
||||
</div>
|
||||
{command.timestamp ? (
|
||||
<div className={styles.timestamp}>
|
||||
@@ -197,6 +205,7 @@ export const CMDKContainer = ({
|
||||
<Command.List data-opening={opening ? true : undefined}>
|
||||
{children}
|
||||
</Command.List>
|
||||
<NotFoundGroup />
|
||||
</Command>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const notFoundContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0 8px',
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
export const notFoundItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
padding: '0 12px',
|
||||
gap: 16,
|
||||
});
|
||||
|
||||
export const notFoundIcon = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 20,
|
||||
color: 'var(--affine-icon-secondary)',
|
||||
padding: '12px 0',
|
||||
});
|
||||
|
||||
export const notFoundTitle = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
fontWeight: '600',
|
||||
lineHeight: '20px',
|
||||
textAlign: 'justify',
|
||||
padding: '8px',
|
||||
});
|
||||
|
||||
export const notFoundText = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: '22px',
|
||||
fontWeight: '400',
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useCommandState } from '@affine/cmdk';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useAtomValue } from 'jotai';
|
||||
|
||||
import { cmdkQueryAtom } from './data';
|
||||
import * as styles from './not-found.css';
|
||||
|
||||
export const NotFoundGroup = () => {
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
// hack: we know that the filtered count is 2 when there is no result (create page & edgeless)
|
||||
const renderNoResult =
|
||||
useCommandState(state => state.filtered.count === 2) || false;
|
||||
|
||||
if (!renderNoResult) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={styles.notFoundContainer}>
|
||||
<div
|
||||
className={styles.notFoundTitle}
|
||||
data-testid="cmdk-search-not-found"
|
||||
>{`Search for "${query}"`}</div>
|
||||
<div className={styles.notFoundItem}>
|
||||
<div className={styles.notFoundIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<div className={styles.notFoundText}>No results found</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,12 @@ export interface CommandContext {
|
||||
// we can use a single render function to render all different commands
|
||||
export interface CMDKCommand {
|
||||
id: string;
|
||||
label: string | React.ReactNode;
|
||||
label:
|
||||
| string
|
||||
| {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
icon?: React.ReactNode;
|
||||
category: CommandCategory;
|
||||
keyBinding?: string | { binding: string };
|
||||
|
||||
@@ -10,7 +10,7 @@ import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useAppSetting } from '../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../hooks/affine/use-app-setting-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
@@ -29,7 +29,7 @@ export const TrashButtonGroup = () => {
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const t = useAFFiNEI18N();
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const restoreRef = useRef(null);
|
||||
|
||||
@@ -26,7 +26,7 @@ import { forwardRef, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { openWorkspaceListModalAtom } from '../../atoms';
|
||||
import { useHistoryAtom } from '../../atoms/history';
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
import { useGeneralShortcuts } from '../../hooks/affine/use-shortcuts';
|
||||
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands';
|
||||
@@ -100,7 +100,7 @@ export const RootAppSidebar = ({
|
||||
onOpenSettingModal,
|
||||
}: RootAppSidebarProps): ReactElement => {
|
||||
const currentWorkspaceId = currentWorkspace.id;
|
||||
const [appSettings] = useAppSetting();
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { backToAll } = useCollectionManager(currentCollectionsAtom);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
Reference in New Issue
Block a user