From 95ef04f3e0e90f0572d554446a2e03258c55a915 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 30 Dec 2025 12:46:43 +0800 Subject: [PATCH] fix: cloud workspace search prefer & highlights --- .../docs-search/services/docs-search.ts | 39 +++++++++++++++--- .../src/modules/quicksearch/impls/docs.ts | 41 +++++++++++++++++-- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts index 90cc08116b..5f8427117a 100644 --- a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts +++ b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts @@ -1,5 +1,5 @@ import { toDocSearchParams } from '@affine/core/modules/navigation'; -import type { IndexerSyncState } from '@affine/nbstore'; +import type { IndexerPreferOptions, IndexerSyncState } from '@affine/nbstore'; import type { ReferenceParams } from '@blocksuite/affine/model'; import { fromPromise, LiveData, Service } from '@toeverything/infra'; import { isEmpty, omit } from 'lodash-es'; @@ -26,6 +26,24 @@ export class DocsSearchService extends Service { errorMessage: null, } as IndexerSyncState); + private normalizeHighlight(value?: string | null) { + if (!value) { + return value ?? ''; + } + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + return parsed.join(' '); + } + if (typeof parsed === 'string') { + return parsed; + } + } catch { + // ignore parse errors, return raw value + } + return value; + } + searchTitle$(query: string) { return this.indexer .search$( @@ -49,7 +67,10 @@ export class DocsSearchService extends Service { ); } - search$(query: string): Observable< + search$( + query: string, + prefer: IndexerPreferOptions = 'remote' + ): Observable< { docId: string; title: string; @@ -112,7 +133,7 @@ export class DocsSearchService extends Service { }, ], }, - prefer: 'remote', + prefer, } ) .pipe( @@ -123,10 +144,14 @@ export class DocsSearchService extends Service { const firstMatchFlavour = bucket.hits.nodes[0]?.fields.flavour; if (firstMatchFlavour === 'affine:page') { // is title match - const blockContent = bucket.hits.nodes[1]?.highlights.content[0]; // try to get block content + const blockContent = this.normalizeHighlight( + bucket.hits.nodes[1]?.highlights.content[0] + ); // try to get block content result.push({ docId: bucket.key, - title: bucket.hits.nodes[0].highlights.content[0], + title: this.normalizeHighlight( + bucket.hits.nodes[0].highlights.content[0] + ), score: bucket.score, blockContent, }); @@ -144,7 +169,9 @@ export class DocsSearchService extends Service { ? matchedBlockId : matchedBlockId[0], score: bucket.score, - blockContent: bucket.hits.nodes[0]?.highlights.content[0], + blockContent: this.normalizeHighlight( + bucket.hits.nodes[0]?.highlights.content[0] + ), }); } } diff --git a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts index d8a722fd55..8a851bf44a 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts @@ -107,8 +107,43 @@ export class DocsQuickSearchSession if (!query) { out = of([] as QuickSearchItem<'docs', DocsPayload>[]); } else { - out = this.docsSearchService.search$(query).pipe( - map(docs => + const preferRemote = + !this.searchLocally && this.isSupportServerIndexer(); + const preferMode = + this.searchLocally || !this.isSupportServerIndexer() + ? 'local' + : 'remote'; + const search$ = preferRemote + ? this.docsSearchService.search$(query, 'remote').pipe( + switchMap(docs => { + if (docs.length > 0) { + return of({ docs, useLocalLabel: false }); + } + return this.docsSearchService.search$(query, 'local').pipe( + map(localDocs => ({ + docs: localDocs, + useLocalLabel: true, + })) + ); + }), + catchError(() => + this.docsSearchService.search$(query, 'local').pipe( + map(localDocs => ({ + docs: localDocs, + useLocalLabel: true, + })) + ) + ) + ) + : this.docsSearchService.search$(query, preferMode).pipe( + map(docs => ({ + docs, + useLocalLabel: preferMode === 'local', + })) + ); + + out = search$.pipe( + map(({ docs, useLocalLabel }) => docs .map(doc => { const docRecord = this.docsService.list.doc$(doc.docId).value; @@ -126,7 +161,7 @@ export class DocsQuickSearchSession group: { id: 'docs', label: { - i18nKey: this.searchLocally + i18nKey: useLocalLabel ? 'com.affine.quicksearch.group.searchfor-locally' : 'com.affine.quicksearch.group.searchfor', options: { query: truncate(query) },