Skip to content

Commit 08a6e50

Browse files
committed
fix: adjust search UI
1 parent e1c152f commit 08a6e50

File tree

9 files changed

+250
-84
lines changed

9 files changed

+250
-84
lines changed

src/@types/translations/en.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3044,9 +3044,14 @@
30443044
"memberCount_one": "{{count}} member",
30453045
"memberCount_many": "{{count}} members",
30463046
"memberCount_other": "{{count}} members",
3047-
"aiMatch": "AI match",
3048-
"titleMatch": "Title match",
3047+
"AIsearch": "AI search",
3048+
"titleOnly": "Title only",
30493049
"namespace": "Namespace",
30503050
"manageNamespaceDescription": "Manage your namespace and homepage",
3051-
"homepage": "Homepage"
3051+
"homepage": "Homepage",
3052+
"recentPages": "Recent pages",
3053+
"searchResults": "Search results",
3054+
"noSearchResults": "No search results",
3055+
"AISearchPlaceholder": "Search or ask a question..",
3056+
"searchLabel": "Search"
30523057
}

src/components/app/app.hooks.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,14 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
403403

404404
const views = uniqBy(res, 'view_id');
405405

406-
setRecentViews(views);
406+
setRecentViews(views.filter(item => {
407+
return !item.extra?.is_space && findView(outline || [], item.view_id);
408+
}));
407409
return views;
408410
} catch (e) {
409411
console.error('Recent views not found');
410412
}
411-
}, [currentWorkspaceId, service]);
413+
}, [currentWorkspaceId, service, outline]);
412414

413415
const loadTrash = useCallback(async (currentWorkspaceId: string) => {
414416

@@ -647,10 +649,12 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
647649
if (!service || !currentWorkspaceId) return;
648650
const isDatabase = [ViewLayout.Board, ViewLayout.Grid, ViewLayout.Calendar].includes(view.layout);
649651
const viewId = view.view_id;
652+
const children = view.children || [];
653+
const visibleViewIds = [view.view_id, ...(children.map((v) => v.view_id))];
650654

651655
await service.publishView(currentWorkspaceId, viewId, {
652656
publish_name: publishName,
653-
visible_database_view_ids: isDatabase ? view.children?.map((v) => v.view_id) : undefined,
657+
visible_database_view_ids: isDatabase ? visibleViewIds : undefined,
654658
});
655659
void loadOutline(currentWorkspaceId, false);
656660
}, [currentWorkspaceId, loadOutline, service]);

src/components/app/search/BestMatch.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import { useTranslation } from 'react-i18next';
1111
function BestMatch ({
1212
onClose,
1313
searchValue,
14-
setLoading,
1514
}: {
1615
onClose: () => void;
1716
searchValue: string;
18-
setLoading: (loading: boolean) => void;
1917
}) {
20-
const [views, setViews] = React.useState<View[]>([]);
18+
const [views, setViews] = React.useState<View[] | undefined>(undefined);
2119
const { t } = useTranslation();
2220
const outline = useAppOutline();
2321
const service = useService();
22+
const [loading, setLoading] = React.useState<boolean>(false);
23+
2424
const currentWorkspaceId = useCurrentWorkspaceId();
2525
const handleSearch = useCallback(async (searchTerm: string) => {
2626
if (!outline) return;
@@ -38,15 +38,18 @@ function BestMatch ({
3838
return findView(outline, id);
3939
});
4040

41-
setViews(views.filter(Boolean) as View[]);
41+
setViews(views.filter(item => {
42+
if (!item) return false;
43+
return !item.extra?.is_space;
44+
}) as View[]);
4245
// eslint-disable-next-line
4346
} catch (e: any) {
4447
notify.error(e.message);
4548
}
4649

4750
setLoading(false);
4851

49-
}, [currentWorkspaceId, outline, service, setLoading]);
52+
}, [currentWorkspaceId, outline, service]);
5053

5154
const debounceSearch = useMemo(() => {
5255
return debounce(handleSearch, 300);
@@ -58,7 +61,8 @@ function BestMatch ({
5861

5962
return <ViewList
6063
views={views}
61-
title={t('commandPalette.bestMatches')}
64+
loading={loading}
65+
title={t('searchResults')}
6266
onClose={onClose}
6367
/>;
6468
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { View } from '@/application/types';
2+
import { findAncestors } from '@/components/_shared/outline/utils';
3+
import { RichTooltip } from '@/components/_shared/popover';
4+
import PageIcon from '@/components/_shared/view-icon/PageIcon';
5+
import { useAppHandlers, useAppOutline } from '@/components/app/app.hooks';
6+
import { IconButton, Paper, Tooltip } from '@mui/material';
7+
import React, { useCallback, useMemo } from 'react';
8+
import { useTranslation } from 'react-i18next';
9+
import { ReactComponent as MoreIcon } from '@/assets/more.svg';
10+
import { ReactComponent as PrivateIcon } from '@/assets/lock.svg';
11+
12+
function ListItem ({
13+
selectedView,
14+
view,
15+
onClick,
16+
onClose,
17+
}: {
18+
selectedView: string;
19+
view: View;
20+
onClick: () => void;
21+
onClose: () => void;
22+
}) {
23+
const { t } = useTranslation();
24+
const outline = useAppOutline();
25+
const [open, setOpen] = React.useState<boolean>(false);
26+
const toView = useAppHandlers().toView;
27+
28+
const ancestors = useMemo(() => {
29+
if (!outline) return [];
30+
return findAncestors(outline, view.view_id)?.slice(0, -1) || [];
31+
}, [outline, view.view_id]);
32+
33+
const renderBreadcrumb = useCallback((view: View) => {
34+
const isPrivate = view.is_private && view.extra?.is_space;
35+
36+
return <Tooltip
37+
disableInteractive={true}
38+
title={view.name}
39+
>
40+
<div
41+
style={{
42+
cursor: view.extra?.is_space ? 'default' : 'pointer',
43+
}}
44+
onClick={e => {
45+
e.stopPropagation();
46+
if (view.extra?.is_space) return;
47+
void toView(view.view_id);
48+
onClose();
49+
}}
50+
className={`text-text-caption max-w-[250px] overflow-hidden ${view.extra?.is_space ? '' : 'hover:underline'} flex items-center gap-2`}
51+
>
52+
<span className={'truncate'}>{view.name || t('menuAppHeader.defaultNewPageName')}</span>
53+
{isPrivate &&
54+
<div className={'h-4 w-4 text-base min-w-4 text-text-title opacity-80'}>
55+
<PrivateIcon />
56+
</div>
57+
}
58+
</div>
59+
</Tooltip>;
60+
}, [onClose, t, toView]);
61+
62+
const breadcrumbs = useMemo(() => {
63+
if (!ancestors) return null;
64+
if (ancestors.length <= 3) {
65+
return ancestors.map((ancestor, index) => {
66+
return <div
67+
key={ancestor.view_id}
68+
className={'flex items-center gap-2'}
69+
>
70+
{renderBreadcrumb(ancestor)}
71+
{index !== ancestors.length - 1 && <span>{'/'}</span>}
72+
</div>;
73+
});
74+
}
75+
76+
const first = renderBreadcrumb(ancestors[0]);
77+
const last = renderBreadcrumb(ancestors[ancestors.length - 1]);
78+
79+
return <>
80+
{first}
81+
<div className={'flex items-center gap-2'}>
82+
<span>{'/'}</span>
83+
<RichTooltip
84+
open={open}
85+
placement="bottom"
86+
onClose={() => setOpen(false)}
87+
content={
88+
<Paper className={'p-1'}>
89+
{ancestors.slice(1, -1).map((ancestor) => {
90+
return <div
91+
key={ancestor.view_id}
92+
className={'flex items-center w-full gap-2 p-1.5'}
93+
>
94+
{renderBreadcrumb(ancestor)}
95+
</div>;
96+
})}
97+
</Paper>
98+
}
99+
>
100+
<IconButton
101+
onClick={(e) => {
102+
e.stopPropagation();
103+
setOpen((prev) => !prev);
104+
}}
105+
size={'small'}
106+
>
107+
<MoreIcon />
108+
</IconButton>
109+
</RichTooltip>
110+
<span>{'/'}</span>
111+
{last}
112+
</div>
113+
</>;
114+
115+
}, [ancestors, open, renderBreadcrumb]);
116+
117+
return (
118+
<div
119+
data-item-id={view.view_id}
120+
style={{
121+
backgroundColor: selectedView === view.view_id ? 'var(--fill-list-active)' : undefined,
122+
}}
123+
onClick={onClick}
124+
className={'flex border-t border-line-default w-full p-4 cursor-pointer hover:bg-fill-list-active gap-2'}
125+
>
126+
<div className={'w-7 h-7 border flex items-center justify-center rounded border-line-border'}>
127+
<PageIcon
128+
view={view}
129+
className={'w-4 h-4 flex items-center justify-center'}
130+
/>
131+
</div>
132+
<div className={'flex flex-col py-[3px] gap-2 w-full'}>
133+
<div className={'text-base font-medium flex-1 truncate'}>
134+
{view.name.trim() || t('menuAppHeader.defaultNewPageName')}
135+
</div>
136+
{ancestors?.length ?
137+
<div className={'text-sm text-text-caption overflow-hidden w-full gap-2 flex items-center'}>
138+
{breadcrumbs}
139+
</div> : null}
140+
141+
</div>
142+
143+
</div>
144+
);
145+
}
146+
147+
export default ListItem;

src/components/app/search/RecentViews.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
1-
import { useAppRecent } from '@/components/app/app.hooks';
1+
import { View } from '@/application/types';
22
import ViewList from '@/components/app/search/ViewList';
3-
import React, { useEffect } from 'react';
3+
import React from 'react';
44
import { useTranslation } from 'react-i18next';
55

66
function RecentViews ({
77
onClose,
8-
setLoading,
8+
loading,
9+
recentViews,
910
}: {
1011
onClose: () => void;
11-
setLoading: (loading: boolean) => void;
12+
loading: boolean;
13+
recentViews?: View[];
1214
}) {
13-
const {
14-
recentViews,
15-
loadRecentViews,
16-
} = useAppRecent();
17-
const { t } = useTranslation();
1815

19-
useEffect(() => {
20-
void (async () => {
21-
setLoading(true);
22-
await loadRecentViews?.();
23-
setLoading(false);
24-
})();
25-
}, [loadRecentViews, setLoading]);
16+
const { t } = useTranslation();
2617

2718
return (
2819
<ViewList
20+
loading={loading}
2921
views={recentViews}
30-
title={t('commandPalette.recentHistory')}
22+
title={t('recentPages')}
3123
onClose={onClose}
3224
/>
3325
);

0 commit comments

Comments
 (0)