Skip to content

Commit f1bb1fc

Browse files
committed
feat(mobile): search page ui (#8012)
feat(mobile): search page ui fix(core): quick search tags performance issue
1 parent 5e8683c commit f1bb1fc

File tree

26 files changed

+731
-50
lines changed

26 files changed

+731
-50
lines changed

packages/frontend/component/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './hooks';
12
export * from './lit-react';
23
export * from './styles';
34
export * from './ui/avatar';
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Entity, LiveData } from '@toeverything/infra';
2+
import Fuse from 'fuse.js';
3+
4+
import type { TagService } from '../../tag';
5+
import type { QuickSearchSession } from '../providers/quick-search-provider';
6+
import type { QuickSearchGroup } from '../types/group';
7+
import type { QuickSearchItem } from '../types/item';
8+
import { highlighter } from '../utils/highlighter';
9+
import { QuickSearchTagIcon } from '../views/tag-icon';
10+
11+
const group: QuickSearchGroup = {
12+
id: 'tags',
13+
label: {
14+
key: 'com.affine.cmdk.affine.category.affine.tags',
15+
},
16+
score: 10,
17+
};
18+
19+
export class TagsQuickSearchSession
20+
extends Entity
21+
implements QuickSearchSession<'tags', { tagId: string }>
22+
{
23+
constructor(private readonly tagService: TagService) {
24+
super();
25+
}
26+
27+
query$ = new LiveData('');
28+
29+
items$: LiveData<QuickSearchItem<'tags', { tagId: string }>[]> =
30+
LiveData.computed(get => {
31+
const query = get(this.query$);
32+
33+
// has performance issues with `tagList.tagMetas$`
34+
const tags = get(this.tagService.tagList.tags$).map(tag => ({
35+
id: tag.id,
36+
title: get(tag.value$),
37+
color: get(tag.color$),
38+
}));
39+
40+
const fuse = new Fuse(tags, {
41+
keys: ['title'],
42+
includeMatches: true,
43+
includeScore: true,
44+
ignoreLocation: true,
45+
threshold: 0.0,
46+
});
47+
48+
const result = fuse.search(query);
49+
50+
return result.map<QuickSearchItem<'tags', { tagId: string }>>(
51+
({ item, matches, score = 1 }) => {
52+
const normalizedRange = ([start, end]: [number, number]) =>
53+
[
54+
start,
55+
end +
56+
1 /* in fuse, the `end` is different from the `substring` */,
57+
] as [number, number];
58+
const titleMatches = matches
59+
?.filter(match => match.key === 'title')
60+
.flatMap(match => match.indices.map(normalizedRange));
61+
62+
const Icon = () => QuickSearchTagIcon({ color: item.color });
63+
64+
return {
65+
id: 'tag:' + item.id,
66+
source: 'tags',
67+
label: {
68+
title: (highlighter(
69+
item.title,
70+
'<b>',
71+
'</b>',
72+
titleMatches ?? []
73+
) ??
74+
item.title) || {
75+
key: 'Untitled',
76+
},
77+
},
78+
group,
79+
score: 1 - score,
80+
icon: Icon,
81+
matches: titleMatches,
82+
payload: { tagId: item.id },
83+
};
84+
}
85+
);
86+
});
87+
88+
query(query: string) {
89+
this.query$.next(query);
90+
}
91+
}

packages/frontend/core/src/modules/quicksearch/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {
99
import { CollectionService } from '../collection';
1010
import { DocsSearchService } from '../docs-search';
1111
import { WorkspacePropertiesAdapter } from '../properties';
12+
import { TagService } from '../tag';
1213
import { WorkbenchService } from '../workbench';
1314
import { QuickSearch } from './entities/quick-search';
1415
import { CollectionsQuickSearchSession } from './impls/collections';
1516
import { CommandsQuickSearchSession } from './impls/commands';
1617
import { CreationQuickSearchSession } from './impls/creation';
1718
import { DocsQuickSearchSession } from './impls/docs';
1819
import { RecentDocsQuickSearchSession } from './impls/recent-docs';
20+
import { TagsQuickSearchSession } from './impls/tags';
1921
import { CMDKQuickSearchService } from './services/cmdk';
2022
import { DocDisplayMetaService } from './services/doc-display-meta';
2123
import { QuickSearchService } from './services/quick-search';
@@ -28,8 +30,10 @@ export { CommandsQuickSearchSession } from './impls/commands';
2830
export { CreationQuickSearchSession } from './impls/creation';
2931
export { DocsQuickSearchSession } from './impls/docs';
3032
export { RecentDocsQuickSearchSession } from './impls/recent-docs';
33+
export { TagsQuickSearchSession } from './impls/tags';
3134
export type { QuickSearchItem } from './types/item';
3235
export { QuickSearchContainer } from './views/container';
36+
export { QuickSearchTagIcon } from './views/tag-icon';
3337

3438
export function configureQuickSearchModule(framework: Framework) {
3539
framework
@@ -51,6 +55,7 @@ export function configureQuickSearchModule(framework: Framework) {
5155
])
5256
.entity(CreationQuickSearchSession)
5357
.entity(CollectionsQuickSearchSession, [CollectionService])
58+
.entity(TagsQuickSearchSession, [TagService])
5459
.entity(RecentDocsQuickSearchSession, [
5560
RecentDocsService,
5661
DocDisplayMetaService,

packages/frontend/core/src/modules/quicksearch/services/cmdk.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CommandsQuickSearchSession } from '../impls/commands';
88
import { CreationQuickSearchSession } from '../impls/creation';
99
import { DocsQuickSearchSession } from '../impls/docs';
1010
import { RecentDocsQuickSearchSession } from '../impls/recent-docs';
11+
import { TagsQuickSearchSession } from '../impls/tags';
1112
import type { QuickSearchService } from './quick-search';
1213

1314
export class CMDKQuickSearchService extends Service {
@@ -30,6 +31,7 @@ export class CMDKQuickSearchService extends Service {
3031
this.framework.createEntity(CommandsQuickSearchSession),
3132
this.framework.createEntity(CreationQuickSearchSession),
3233
this.framework.createEntity(DocsQuickSearchSession),
34+
this.framework.createEntity(TagsQuickSearchSession),
3335
],
3436
result => {
3537
if (!result) {
@@ -60,6 +62,8 @@ export class CMDKQuickSearchService extends Service {
6062
this.workbenchService.workbench.openCollection(
6163
result.payload.collectionId
6264
);
65+
} else if (result.source === 'tags') {
66+
this.workbenchService.workbench.openTag(result.payload.tagId);
6367
} else if (result.source === 'creation') {
6468
if (result.id === 'creation:create-page') {
6569
const newDoc = this.docsService.createDoc({
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const QuickSearchTagIcon = ({ color }: { color: string }) => {
2+
return (
3+
<svg
4+
width="1em"
5+
height="1em"
6+
viewBox="0 0 24 24"
7+
xmlns="http://www.w3.org/2000/svg"
8+
>
9+
<circle cx="12" cy="12" fill={color} r="5" />
10+
</svg>
11+
);
12+
};

packages/frontend/i18n/src/resources/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@
544544
"com.affine.cloudTempDisable.description": "We are upgrading the AFFiNE Cloud service and it is temporarily unavailable on the client side. If you wish to stay updated on the progress and be notified on availability, you can fill out the <1>AFFiNE Cloud Signup</1>.",
545545
"com.affine.cloudTempDisable.title": "AFFiNE Cloud is upgrading now.",
546546
"com.affine.cmdk.affine.category.affine.collections": "Collections",
547+
"com.affine.cmdk.affine.category.affine.tags": "Tags",
547548
"com.affine.cmdk.affine.category.affine.creation": "Create",
548549
"com.affine.cmdk.affine.category.affine.edgeless": "Edgeless",
549550
"com.affine.cmdk.affine.category.affine.general": "General",
@@ -1557,5 +1558,6 @@
15571558
"com.affine.import-template.dialog.errorLoad": "Failed to load template, please try again.",
15581559
"com.affine.import-template.dialog.createDocToWorkspace": "Create doc to \"{{workspace}}\"",
15591560
"com.affine.import-template.dialog.createDocToNewWorkspace": "Create into a New Workspace",
1560-
"com.affine.import-template.dialog.createDocWithTemplate": "Create doc with \"{{templateName}}\" template"
1561+
"com.affine.import-template.dialog.createDocWithTemplate": "Create doc with \"{{templateName}}\" template",
1562+
"com.affine.mobile.search.empty": "No results found"
15611563
}

packages/frontend/mobile/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717
"@toeverything/theme": "^1.0.7",
1818
"clsx": "^2.1.1",
1919
"core-js": "^3.36.1",
20+
"figma-squircle": "^0.3.1",
2021
"intl-segmenter-polyfill-rs": "^0.1.7",
22+
"lodash-es": "^4.17.21",
2123
"react": "^18.2.0",
2224
"react-dom": "^18.2.0",
2325
"react-router-dom": "^6.26.1"
2426
},
2527
"devDependencies": {
2628
"@affine/cli": "workspace:*",
29+
"@types/lodash-es": "^4.17.12",
2730
"@types/react": "^18.2.75",
2831
"@types/react-dom": "^18.2.24",
2932
"@vanilla-extract/css": "^1.15.5",

packages/frontend/mobile/src/app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { Suspense } from 'react';
2727
import { RouterProvider } from 'react-router-dom';
2828

29+
import { configureMobileModules } from './modules';
2930
import { router } from './router';
3031

3132
if (!environment.isBrowser && environment.isDebug) {
@@ -58,6 +59,7 @@ configureBrowserWorkbenchModule(framework);
5859
configureLocalStorageStateStorageImpls(framework);
5960
configureBrowserWorkspaceFlavours(framework);
6061
configureIndexedDBWorkspaceEngineStorageProvider(framework);
62+
configureMobileModules(framework);
6163
const frameworkProvider = framework.provider();
6264

6365
// setup application lifecycle events, and emit application start event

packages/frontend/mobile/src/components/doc-card/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import {
99
import type { DocMeta } from '@blocksuite/store';
1010
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
1111
import clsx from 'clsx';
12-
import { forwardRef, useCallback } from 'react';
12+
import { forwardRef, type ReactNode, useCallback } from 'react';
1313

1414
import * as styles from './styles.css';
1515
import { DocCardTags } from './tag';
1616

1717
export interface DocCardProps extends Omit<WorkbenchLinkProps, 'to'> {
18-
meta: DocMeta;
18+
meta: {
19+
id: DocMeta['id'];
20+
title?: ReactNode;
21+
} & { [key: string]: any };
1922
showTags?: boolean;
2023
}
2124

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './app-tabs';
22
export * from './doc-card';
33
export * from './page-header';
4-
export * from './search-button';
4+
export * from './search-input';
5+
export * from './search-result';
56
export * from './workspace-selector';

0 commit comments

Comments
 (0)