diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index 50be767c2c..e22d019c0b 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -16,7 +16,7 @@ import type { ActivePeekView } from '@affine/core/modules/peek-view/entities/pee import { CreationQuickSearchSession, DocsQuickSearchSession, - type QuickSearchItem, + LinksQuickSearchSession, QuickSearchService, RecentDocsQuickSearchSession, } from '@affine/core/modules/quicksearch'; @@ -32,6 +32,8 @@ import type { AffineReference, DocMode, DocModeProvider, + QuickSearchResult, + ReferenceParams, RootService, } from '@blocksuite/blocks'; import { @@ -46,7 +48,6 @@ import { QuickSearchProvider, ReferenceNodeConfigExtension, } from '@blocksuite/blocks'; -import { LinkIcon } from '@blocksuite/icons/rc'; import { AIChatBlockSchema } from '@blocksuite/presets'; import type { BlockSnapshot } from '@blocksuite/store'; import { @@ -282,10 +283,7 @@ export function patchDocModeService( export function patchQuickSearchService(framework: FrameworkProvider) { const QuickSearch = QuickSearchExtension({ async searchDoc(options) { - let searchResult: - | { docId: string; isNewDoc?: boolean } - | { userInput: string } - | null = null; + let searchResult: QuickSearchResult = null; if (options.skipSelection) { const query = options.userInput; if (!query) { @@ -319,43 +317,45 @@ export function patchQuickSearchService(framework: FrameworkProvider) { framework.get(QuickSearchService).quickSearch.show( [ framework.get(RecentDocsQuickSearchSession), - framework.get(DocsQuickSearchSession), framework.get(CreationQuickSearchSession), - (query: string) => { - if ( - (query.startsWith('http://') || - query.startsWith('https://')) && - resolveLinkToDoc(query) === null - ) { - return [ - { - id: 'link', - source: 'link', - icon: LinkIcon, - label: { - key: 'com.affine.cmdk.affine.insert-link', - }, - payload: { url: query }, - } as QuickSearchItem<'link', { url: string }>, - ]; - } - return []; - }, + framework.get(DocsQuickSearchSession), + framework.get(LinksQuickSearchSession), ], result => { if (result === null) { resolve(null); return; } - if (result.source === 'docs' || result.source === 'recent-doc') { + + if (result.source === 'docs') { resolve({ docId: result.payload.docId, }); - } else if (result.source === 'link') { + return; + } + + if (result.source === 'recent-doc') { resolve({ - userInput: result.payload.url, + docId: result.payload.docId, }); - } else if (result.source === 'creation') { + return; + } + + if (result.source === 'link') { + if (result.payload.external) { + const userInput = result.payload.external.url; + resolve({ userInput }); + return; + } + + if (result.payload.internal) { + const { docId, params } = result.payload.internal; + resolve({ docId, params }); + } + return; + } + + if (result.source === 'creation') { const docsService = framework.get(DocsService); const mode = result.id === 'creation:create-edgeless' @@ -365,10 +365,12 @@ export function patchQuickSearchService(framework: FrameworkProvider) { primaryMode: mode, title: result.payload.title, }); + resolve({ docId: newDoc.id, isNewDoc: true, }); + return; } }, { @@ -413,17 +415,27 @@ export function patchQuickSearchService(framework: FrameworkProvider) { const linkedDoc = std.collection.getDoc(result.docId); if (!linkedDoc) return; - host.doc.addSiblingBlocks(model, [ - { - flavour: 'affine:embed-linked-doc', - pageId: linkedDoc.id, - }, - ]); + const props: { + flavour: string; + pageId: string; + params?: ReferenceParams; + } = { + flavour: 'affine:embed-linked-doc', + pageId: linkedDoc.id, + }; + + if (!result.isNewDoc && result.params) { + props.params = result.params; + } + + host.doc.addSiblingBlocks(model, [props]); + if (result.isNewDoc) { track.doc.editor.slashMenu.createDoc({ control: 'linkDoc' }); track.doc.editor.slashMenu.linkDoc({ control: 'createDoc' }); + } else { + track.doc.editor.slashMenu.linkDoc({ control: 'linkDoc' }); } - track.doc.editor.slashMenu.linkDoc({ control: 'linkDoc' }); } else if ('userInput' in result) { const embedOptions = std .get(EmbedOptionProvider) diff --git a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts index 6bc60d8eb4..6e8b0105b9 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts @@ -10,7 +10,6 @@ import { truncate } from 'lodash-es'; import { EMPTY, map, mergeMap, of, switchMap } from 'rxjs'; import type { DocsSearchService } from '../../docs-search'; -import { resolveLinkToDoc } from '../../navigation'; import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { DocDisplayMetaService } from '../services/doc-display-meta'; import type { QuickSearchItem } from '../types/item'; @@ -55,29 +54,7 @@ export class DocsQuickSearchSession if (!query) { out = of([] as QuickSearchItem<'docs', DocsPayload>[]); } else { - const resolvedDoc = resolveLinkToDoc(query); - const resolvedDocId = resolvedDoc?.docId; - const resolvedBlockId = resolvedDoc?.blockIds?.[0]; - out = this.docsSearchService.search$(query).pipe( - map(docs => { - if ( - resolvedDocId && - !docs.some(doc => doc.docId === resolvedDocId) - ) { - return [ - { - docId: resolvedDocId, - score: 100, - blockId: resolvedBlockId, - blockContent: '', - }, - ...docs, - ]; - } - - return docs; - }), map(docs => docs .map(doc => { diff --git a/packages/frontend/core/src/modules/quicksearch/impls/links.ts b/packages/frontend/core/src/modules/quicksearch/impls/links.ts new file mode 100644 index 0000000000..f2d853d1ea --- /dev/null +++ b/packages/frontend/core/src/modules/quicksearch/impls/links.ts @@ -0,0 +1,107 @@ +import type { ReferenceParams } from '@blocksuite/blocks'; +import { BlockLinkIcon, LinkIcon } from '@blocksuite/icons/rc'; +import type { DocsService } from '@toeverything/infra'; +import { Entity, LiveData } from '@toeverything/infra'; +import { isEmpty, pick, truncate } from 'lodash-es'; + +import { resolveLinkToDoc } from '../../navigation'; +import type { QuickSearchSession } from '../providers/quick-search-provider'; +import type { DocDisplayMetaService } from '../services/doc-display-meta'; +import type { QuickSearchItem } from '../types/item'; + +type LinkPayload = { + internal?: { + docId: string; + title?: string; + blockId?: string; + blockContent?: string; + params?: ReferenceParams; + }; + external?: { + url: string; + }; +}; + +export class LinksQuickSearchSession + extends Entity + implements QuickSearchSession<'link', LinkPayload> +{ + constructor( + private readonly docsService: DocsService, + private readonly docDisplayMetaService: DocDisplayMetaService + ) { + super(); + } + + query$ = new LiveData(''); + + items$ = LiveData.computed(get => { + const query = get(this.query$); + if (!query) return []; + + const isLink = query.startsWith('http://') || query.startsWith('https://'); + if (!isLink) return []; + + const resolvedDoc = resolveLinkToDoc(query); + if (!resolvedDoc) { + return [ + { + id: 'link', + source: 'link', + icon: LinkIcon, + label: { + key: 'com.affine.cmdk.affine.insert-link', + }, + payload: { external: { url: query } }, + } as QuickSearchItem<'link', LinkPayload>, + ]; + } + + const docId = resolvedDoc.docId; + const doc = this.docsService.list.doc$(docId).value; + if (!doc || get(doc.trash$)) return []; + + const params = pick(resolvedDoc, ['mode', 'blockIds', 'elementIds']); + const { title, icon, updatedDate } = + this.docDisplayMetaService.getDocDisplayMeta(doc); + const blockId = params?.blockIds?.[0]; + const linkToNode = Boolean(blockId); + const score = 100; + const internal = { + docId, + score, + blockId, + blockContent: '', + }; + + if (linkToNode && !isEmpty(params)) { + Object.assign(internal, { params }); + } + + return [ + { + id: ['doc', doc.id, linkToNode ? blockId : ''].join(':'), + source: 'link', + group: { + id: 'docs', + label: { + key: 'com.affine.quicksearch.group.searchfor', + options: { query: truncate(query) }, + }, + score: 5, + }, + label: { + title: title, + }, + score, + icon: linkToNode ? BlockLinkIcon : icon, + timestamp: updatedDate, + payload: { internal }, + } as QuickSearchItem<'link', LinkPayload>, + ]; + }); + + query(query: string) { + this.query$.next(query); + } +} diff --git a/packages/frontend/core/src/modules/quicksearch/index.ts b/packages/frontend/core/src/modules/quicksearch/index.ts index 0801037003..528dcd6cc1 100644 --- a/packages/frontend/core/src/modules/quicksearch/index.ts +++ b/packages/frontend/core/src/modules/quicksearch/index.ts @@ -16,6 +16,7 @@ import { CollectionsQuickSearchSession } from './impls/collections'; import { CommandsQuickSearchSession } from './impls/commands'; import { CreationQuickSearchSession } from './impls/creation'; import { DocsQuickSearchSession } from './impls/docs'; +import { LinksQuickSearchSession } from './impls/links'; import { RecentDocsQuickSearchSession } from './impls/recent-docs'; import { TagsQuickSearchSession } from './impls/tags'; import { CMDKQuickSearchService } from './services/cmdk'; @@ -29,6 +30,7 @@ export { CollectionsQuickSearchSession } from './impls/collections'; export { CommandsQuickSearchSession } from './impls/commands'; export { CreationQuickSearchSession } from './impls/creation'; export { DocsQuickSearchSession } from './impls/docs'; +export { LinksQuickSearchSession } from './impls/links'; export { RecentDocsQuickSearchSession } from './impls/recent-docs'; export { TagsQuickSearchSession } from './impls/tags'; export type { QuickSearchItem } from './types/item'; @@ -53,6 +55,7 @@ export function configureQuickSearchModule(framework: Framework) { DocsService, DocDisplayMetaService, ]) + .entity(LinksQuickSearchSession, [DocsService, DocDisplayMetaService]) .entity(CreationQuickSearchSession) .entity(CollectionsQuickSearchSession, [CollectionService]) .entity(TagsQuickSearchSession, [TagService]) diff --git a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts index 31e9c16fbb..226c5ea2e1 100644 --- a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts +++ b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts @@ -7,6 +7,7 @@ import { CollectionsQuickSearchSession } from '../impls/collections'; import { CommandsQuickSearchSession } from '../impls/commands'; import { CreationQuickSearchSession } from '../impls/creation'; import { DocsQuickSearchSession } from '../impls/docs'; +import { LinksQuickSearchSession } from '../impls/links'; import { RecentDocsQuickSearchSession } from '../impls/recent-docs'; import { TagsQuickSearchSession } from '../impls/tags'; import type { QuickSearchService } from './quick-search'; @@ -31,20 +32,33 @@ export class CMDKQuickSearchService extends Service { this.framework.createEntity(CommandsQuickSearchSession), this.framework.createEntity(CreationQuickSearchSession), this.framework.createEntity(DocsQuickSearchSession), + this.framework.createEntity(LinksQuickSearchSession), this.framework.createEntity(TagsQuickSearchSession), ], result => { if (!result) { return; } + if (result.source === 'commands') { result.payload.run()?.catch(err => { console.error(err); }); - } else if ( - result.source === 'recent-doc' || - result.source === 'docs' - ) { + return; + } + + if (result.source === 'link') { + if (result.payload.internal) { + const { docId, params } = result.payload.internal; + this.workbenchService.workbench.openDoc({ + docId, + ...params, + }); + } + return; + } + + if (result.source === 'recent-doc' || result.source === 'docs') { const doc: { docId: string; blockId?: string; @@ -62,13 +76,22 @@ export class CMDKQuickSearchService extends Service { } this.workbenchService.workbench.openDoc(options); - } else if (result.source === 'collections') { + return; + } + + if (result.source === 'collections') { this.workbenchService.workbench.openCollection( result.payload.collectionId ); - } else if (result.source === 'tags') { + return; + } + + if (result.source === 'tags') { this.workbenchService.workbench.openTag(result.payload.tagId); - } else if (result.source === 'creation') { + return; + } + + if (result.source === 'creation') { if (result.id === 'creation:create-page') { const newDoc = this.docsService.createDoc({ primaryMode: 'page', @@ -82,6 +105,7 @@ export class CMDKQuickSearchService extends Service { }); this.workbenchService.workbench.openDoc(newDoc.id); } + return; } }, {