feat(core): support tag search for ai chat (#10965)
Close [BS-2785](https://linear.app/affine-design/issue/BS-2785). Close [BS-2786](https://linear.app/affine-design/issue/BS-2786). 
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
import type { SearchDocMenuAction } from '@affine/core/modules/doc-search-menu/services';
|
import type {
|
||||||
|
SearchDocMenuAction,
|
||||||
|
SearchTagMenuAction,
|
||||||
|
} from '@affine/core/modules/search-menu/services';
|
||||||
import type { LinkedMenuGroup } from '@blocksuite/affine/blocks/root';
|
import type { LinkedMenuGroup } from '@blocksuite/affine/blocks/root';
|
||||||
import type { Store } from '@blocksuite/affine/store';
|
import type { Store } from '@blocksuite/affine/store';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
@@ -29,10 +32,15 @@ export interface DocDisplayConfig {
|
|||||||
getDoc: (docId: string) => Store | null;
|
getDoc: (docId: string) => Store | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocSearchMenuConfig {
|
export interface SearchMenuConfig {
|
||||||
getDocMenuGroup: (
|
getDocMenuGroup: (
|
||||||
query: string,
|
query: string,
|
||||||
action: SearchDocMenuAction,
|
action: SearchDocMenuAction,
|
||||||
abortSignal: AbortSignal
|
abortSignal: AbortSignal
|
||||||
) => LinkedMenuGroup;
|
) => LinkedMenuGroup;
|
||||||
|
getTagMenuGroup: (
|
||||||
|
query: string,
|
||||||
|
action: SearchTagMenuAction,
|
||||||
|
abortSignal: AbortSignal
|
||||||
|
) => LinkedMenuGroup;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { property, query, state } from 'lit/decorators.js';
|
|||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
import { AIProvider } from '../provider';
|
import { AIProvider } from '../provider';
|
||||||
import type { DocDisplayConfig, DocSearchMenuConfig } from './chat-config';
|
import type { DocDisplayConfig, SearchMenuConfig } from './chat-config';
|
||||||
import type {
|
import type {
|
||||||
ChatChip,
|
ChatChip,
|
||||||
ChatContextValue,
|
ChatContextValue,
|
||||||
@@ -75,7 +75,7 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docSearchMenuConfig!: DocSearchMenuConfig;
|
accessor searchMenuConfig!: SearchMenuConfig;
|
||||||
|
|
||||||
@query('.add-button')
|
@query('.add-button')
|
||||||
accessor addButton!: HTMLDivElement;
|
accessor addButton!: HTMLDivElement;
|
||||||
@@ -158,7 +158,7 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
template: html`
|
template: html`
|
||||||
<chat-panel-add-popover
|
<chat-panel-add-popover
|
||||||
.addChip=${this._addChip}
|
.addChip=${this._addChip}
|
||||||
.docSearchMenuConfig=${this.docSearchMenuConfig}
|
.searchMenuConfig=${this.searchMenuConfig}
|
||||||
.abortController=${this._abortController}
|
.abortController=${this._abortController}
|
||||||
></chat-panel-add-popover>
|
></chat-panel-add-popover>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
|
import type { TagMeta } from '@affine/core/components/page-list';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||||
@@ -13,7 +14,7 @@ import type { DocMeta } from '@blocksuite/store';
|
|||||||
import { css, html, type TemplateResult } from 'lit';
|
import { css, html, type TemplateResult } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import type { DocSearchMenuConfig } from '../chat-config';
|
import type { SearchMenuConfig } from '../chat-config';
|
||||||
import type { ChatChip } from '../chat-context';
|
import type { ChatChip } from '../chat-context';
|
||||||
|
|
||||||
enum AddPopoverMode {
|
enum AddPopoverMode {
|
||||||
@@ -107,11 +108,21 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private accessor _query = '';
|
private accessor _query = '';
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor _docGroup: MenuGroup = {
|
private accessor _searchGroup: MenuGroup = {
|
||||||
name: 'No Result',
|
name: 'No Result',
|
||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _toggleMode = (mode: AddPopoverMode) => {
|
||||||
|
this._mode = mode;
|
||||||
|
this._activatedIndex = 0;
|
||||||
|
this._query = '';
|
||||||
|
this._updateSearchGroup();
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.searchInput.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private readonly tcGroup: MenuGroup = {
|
private readonly tcGroup: MenuGroup = {
|
||||||
name: 'Tag & Collection',
|
name: 'Tag & Collection',
|
||||||
items: [
|
items: [
|
||||||
@@ -120,7 +131,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
name: 'Tags',
|
name: 'Tags',
|
||||||
icon: TagsIcon(),
|
icon: TagsIcon(),
|
||||||
action: () => {
|
action: () => {
|
||||||
this._mode = AddPopoverMode.Tags;
|
this._toggleMode(AddPopoverMode.Tags);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,7 +139,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
name: 'Collections',
|
name: 'Collections',
|
||||||
icon: CollectionsIcon(),
|
icon: CollectionsIcon(),
|
||||||
action: () => {
|
action: () => {
|
||||||
this._mode = AddPopoverMode.Collections;
|
this._toggleMode(AddPopoverMode.Collections);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -163,14 +174,14 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private get _menuGroup() {
|
private get _menuGroup() {
|
||||||
switch (this._mode) {
|
switch (this._mode) {
|
||||||
case AddPopoverMode.Tags:
|
case AddPopoverMode.Tags:
|
||||||
return [];
|
return [this._searchGroup];
|
||||||
case AddPopoverMode.Collections:
|
case AddPopoverMode.Collections:
|
||||||
return [];
|
return [this._searchGroup];
|
||||||
default:
|
default:
|
||||||
if (this._query) {
|
if (this._query) {
|
||||||
return [this._docGroup, this.uploadGroup];
|
return [this._searchGroup, this.uploadGroup];
|
||||||
}
|
}
|
||||||
return [this._docGroup, this.tcGroup, this.uploadGroup];
|
return [this._searchGroup, this.tcGroup, this.uploadGroup];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +198,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private accessor _mode: AddPopoverMode = AddPopoverMode.Default;
|
private accessor _mode: AddPopoverMode = AddPopoverMode.Default;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docSearchMenuConfig!: DocSearchMenuConfig;
|
accessor searchMenuConfig!: SearchMenuConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor addChip!: (chip: ChatChip) => void;
|
accessor addChip!: (chip: ChatChip) => void;
|
||||||
@@ -200,7 +211,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._updateDocGroup();
|
this._updateSearchGroup();
|
||||||
this.addEventListener('keydown', this._handleKeyDown);
|
this.addEventListener('keydown', this._handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,15 +302,25 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private _onInput(event: Event) {
|
private _onInput(event: Event) {
|
||||||
this._query = (event.target as HTMLInputElement).value;
|
this._query = (event.target as HTMLInputElement).value;
|
||||||
this._activatedIndex = 0;
|
this._activatedIndex = 0;
|
||||||
this._updateDocGroup();
|
this._updateSearchGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateDocGroup() {
|
private _updateSearchGroup() {
|
||||||
this._docGroup = this.docSearchMenuConfig.getDocMenuGroup(
|
switch (this._mode) {
|
||||||
this._query,
|
case AddPopoverMode.Tags:
|
||||||
this._addDocChip,
|
this._searchGroup = this.searchMenuConfig.getTagMenuGroup(
|
||||||
this.abortController.signal
|
this._query,
|
||||||
);
|
this._addTagChip,
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._searchGroup = this.searchMenuConfig.getDocMenuGroup(
|
||||||
|
this._query,
|
||||||
|
this._addDocChip,
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _addDocChip = (meta: DocMeta) => {
|
private readonly _addDocChip = (meta: DocMeta) => {
|
||||||
@@ -310,6 +331,10 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
this.abortController.abort();
|
this.abortController.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _addTagChip = (_tag: TagMeta) => {
|
||||||
|
this.abortController.abort();
|
||||||
|
};
|
||||||
|
|
||||||
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.isComposing) return;
|
if (event.isComposing) return;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import type {
|
|||||||
AINetworkSearchConfig,
|
AINetworkSearchConfig,
|
||||||
AppSidebarConfig,
|
AppSidebarConfig,
|
||||||
DocDisplayConfig,
|
DocDisplayConfig,
|
||||||
DocSearchMenuConfig,
|
SearchMenuConfig,
|
||||||
} from './chat-config';
|
} from './chat-config';
|
||||||
import type {
|
import type {
|
||||||
ChatChip,
|
ChatChip,
|
||||||
@@ -269,7 +269,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
accessor appSidebarConfig!: AppSidebarConfig;
|
accessor appSidebarConfig!: AppSidebarConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docSearchMenuConfig!: DocSearchMenuConfig;
|
accessor searchMenuConfig!: SearchMenuConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
@@ -574,7 +574,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.pollContextDocsAndFiles=${this._pollContextDocsAndFiles}
|
.pollContextDocsAndFiles=${this._pollContextDocsAndFiles}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.docSearchMenuConfig=${this.docSearchMenuConfig}
|
.searchMenuConfig=${this.searchMenuConfig}
|
||||||
></chat-panel-chips>
|
></chat-panel-chips>
|
||||||
<chat-panel-input
|
<chat-panel-input
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-
|
|||||||
import { enableFootnoteConfigExtension } from '@affine/core/blocksuite/extensions';
|
import { enableFootnoteConfigExtension } from '@affine/core/blocksuite/extensions';
|
||||||
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
|
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
|
||||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||||
import { DocSearchMenuService } from '@affine/core/modules/doc-search-menu/services';
|
import { SearchMenuService } from '@affine/core/modules/search-menu/services';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
|
||||||
@@ -57,7 +57,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
const searchService = framework.get(AINetworkSearchService);
|
const searchService = framework.get(AINetworkSearchService);
|
||||||
const docDisplayMetaService = framework.get(DocDisplayMetaService);
|
const docDisplayMetaService = framework.get(DocDisplayMetaService);
|
||||||
const workspaceService = framework.get(WorkspaceService);
|
const workspaceService = framework.get(WorkspaceService);
|
||||||
const docSearchMenuService = framework.get(DocSearchMenuService);
|
const searchMenuService = framework.get(SearchMenuService);
|
||||||
const workbench = framework.get(WorkbenchService).workbench;
|
const workbench = framework.get(WorkbenchService).workbench;
|
||||||
chatPanelRef.current.appSidebarConfig = {
|
chatPanelRef.current.appSidebarConfig = {
|
||||||
getWidth: () => {
|
getWidth: () => {
|
||||||
@@ -87,13 +87,12 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
return doc;
|
return doc;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
chatPanelRef.current.docSearchMenuConfig = {
|
chatPanelRef.current.searchMenuConfig = {
|
||||||
getDocMenuGroup: (query, action, abortSignal) => {
|
getDocMenuGroup: (query, action, abortSignal) => {
|
||||||
return docSearchMenuService.getDocMenuGroup(
|
return searchMenuService.getDocMenuGroup(query, action, abortSignal);
|
||||||
query,
|
},
|
||||||
action,
|
getTagMenuGroup: (query, action, abortSignal) => {
|
||||||
abortSignal
|
return searchMenuService.getTagMenuGroup(query, action, abortSignal);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const previewSpecBuilder = enableFootnoteConfigExtension(
|
const previewSpecBuilder = enableFootnoteConfigExtension(
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { type Framework } from '@toeverything/infra';
|
|||||||
import { WorkspaceDialogService } from '../dialogs';
|
import { WorkspaceDialogService } from '../dialogs';
|
||||||
import { DocsService } from '../doc';
|
import { DocsService } from '../doc';
|
||||||
import { DocDisplayMetaService } from '../doc-display-meta';
|
import { DocDisplayMetaService } from '../doc-display-meta';
|
||||||
import { DocSearchMenuService } from '../doc-search-menu/services';
|
|
||||||
import { EditorSettingService } from '../editor-setting';
|
import { EditorSettingService } from '../editor-setting';
|
||||||
import { JournalService } from '../journal';
|
import { JournalService } from '../journal';
|
||||||
|
import { SearchMenuService } from '../search-menu/services';
|
||||||
import { WorkspaceScope } from '../workspace';
|
import { WorkspaceScope } from '../workspace';
|
||||||
import { AtMenuConfigService } from './services';
|
import { AtMenuConfigService } from './services';
|
||||||
|
|
||||||
@@ -18,6 +18,6 @@ export function configAtMenuConfigModule(framework: Framework) {
|
|||||||
WorkspaceDialogService,
|
WorkspaceDialogService,
|
||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
DocsService,
|
DocsService,
|
||||||
DocSearchMenuService,
|
SearchMenuService,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import { html } from 'lit';
|
|||||||
import type { WorkspaceDialogService } from '../../dialogs';
|
import type { WorkspaceDialogService } from '../../dialogs';
|
||||||
import type { DocsService } from '../../doc';
|
import type { DocsService } from '../../doc';
|
||||||
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
||||||
import type { DocSearchMenuService } from '../../doc-search-menu/services';
|
|
||||||
import type { EditorSettingService } from '../../editor-setting';
|
import type { EditorSettingService } from '../../editor-setting';
|
||||||
import { type JournalService, suggestJournalDate } from '../../journal';
|
import { type JournalService, suggestJournalDate } from '../../journal';
|
||||||
|
import type { SearchMenuService } from '../../search-menu/services';
|
||||||
|
|
||||||
function resolveSignal<T>(data: T | Signal<T>): T {
|
function resolveSignal<T>(data: T | Signal<T>): T {
|
||||||
return data instanceof Signal ? data.value : data;
|
return data instanceof Signal ? data.value : data;
|
||||||
@@ -45,7 +45,7 @@ export class AtMenuConfigService extends Service {
|
|||||||
private readonly dialogService: WorkspaceDialogService,
|
private readonly dialogService: WorkspaceDialogService,
|
||||||
private readonly editorSettingService: EditorSettingService,
|
private readonly editorSettingService: EditorSettingService,
|
||||||
private readonly docsService: DocsService,
|
private readonly docsService: DocsService,
|
||||||
private readonly docsSearchMenuService: DocSearchMenuService
|
private readonly searchMenuService: SearchMenuService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -292,7 +292,7 @@ export class AtMenuConfigService extends Service {
|
|||||||
track.doc.editor.atMenu.linkDoc();
|
track.doc.editor.atMenu.linkDoc();
|
||||||
this.insertDoc(inlineEditor, meta.id);
|
this.insertDoc(inlineEditor, meta.id);
|
||||||
};
|
};
|
||||||
const result = this.docsSearchMenuService.getDocMenuGroup(
|
const result = this.searchMenuService.getDocMenuGroup(
|
||||||
query,
|
query,
|
||||||
action,
|
action,
|
||||||
abortSignal
|
abortSignal
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { configureDocModule } from './doc';
|
|||||||
import { configureDocDisplayMetaModule } from './doc-display-meta';
|
import { configureDocDisplayMetaModule } from './doc-display-meta';
|
||||||
import { configureDocInfoModule } from './doc-info';
|
import { configureDocInfoModule } from './doc-info';
|
||||||
import { configureDocLinksModule } from './doc-link';
|
import { configureDocLinksModule } from './doc-link';
|
||||||
import { configDocSearchMenuModule } from './doc-search-menu';
|
|
||||||
import { configureDocsSearchModule } from './docs-search';
|
import { configureDocsSearchModule } from './docs-search';
|
||||||
import { configureEditorModule } from './editor';
|
import { configureEditorModule } from './editor';
|
||||||
import { configureEditorSettingModule } from './editor-setting';
|
import { configureEditorSettingModule } from './editor-setting';
|
||||||
@@ -39,6 +38,7 @@ import { configurePDFModule } from './pdf';
|
|||||||
import { configurePeekViewModule } from './peek-view';
|
import { configurePeekViewModule } from './peek-view';
|
||||||
import { configurePermissionsModule } from './permissions';
|
import { configurePermissionsModule } from './permissions';
|
||||||
import { configureQuickSearchModule } from './quicksearch';
|
import { configureQuickSearchModule } from './quicksearch';
|
||||||
|
import { configSearchMenuModule } from './search-menu';
|
||||||
import { configureShareDocsModule } from './share-doc';
|
import { configureShareDocsModule } from './share-doc';
|
||||||
import { configureShareSettingModule } from './share-setting';
|
import { configureShareSettingModule } from './share-setting';
|
||||||
import {
|
import {
|
||||||
@@ -96,7 +96,7 @@ export function configureCommonModules(framework: Framework) {
|
|||||||
configureDocInfoModule(framework);
|
configureDocInfoModule(framework);
|
||||||
configureOpenInApp(framework);
|
configureOpenInApp(framework);
|
||||||
configAtMenuConfigModule(framework);
|
configAtMenuConfigModule(framework);
|
||||||
configDocSearchMenuModule(framework);
|
configSearchMenuModule(framework);
|
||||||
configureDndModule(framework);
|
configureDndModule(framework);
|
||||||
configureCommonGlobalStorageImpls(framework);
|
configureCommonGlobalStorageImpls(framework);
|
||||||
configureAINetworkSearchModule(framework);
|
configureAINetworkSearchModule(framework);
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import { type Framework } from '@toeverything/infra';
|
|||||||
import { DocDisplayMetaService } from '../doc-display-meta';
|
import { DocDisplayMetaService } from '../doc-display-meta';
|
||||||
import { DocsSearchService } from '../docs-search';
|
import { DocsSearchService } from '../docs-search';
|
||||||
import { RecentDocsService } from '../quicksearch';
|
import { RecentDocsService } from '../quicksearch';
|
||||||
|
import { TagService } from '../tag';
|
||||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||||
import { DocSearchMenuService } from './services';
|
import { SearchMenuService } from './services';
|
||||||
|
|
||||||
export function configDocSearchMenuModule(framework: Framework) {
|
export function configSearchMenuModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(DocSearchMenuService, [
|
.service(SearchMenuService, [
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
DocDisplayMetaService,
|
DocDisplayMetaService,
|
||||||
RecentDocsService,
|
RecentDocsService,
|
||||||
DocsSearchService,
|
DocsSearchService,
|
||||||
|
TagService,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { TagMeta } from '@affine/core/components/page-list';
|
||||||
import { fuzzyMatch } from '@affine/core/utils/fuzzy-match';
|
import { fuzzyMatch } from '@affine/core/utils/fuzzy-match';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
import type {
|
import type {
|
||||||
@@ -9,13 +10,16 @@ import type { DocMeta } from '@blocksuite/affine/store';
|
|||||||
import { computed } from '@preact/signals-core';
|
import { computed } from '@preact/signals-core';
|
||||||
import { Service } from '@toeverything/infra';
|
import { Service } from '@toeverything/infra';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||||
import { map, takeWhile } from 'rxjs';
|
import { map, takeWhile } from 'rxjs';
|
||||||
|
|
||||||
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
||||||
import type { DocsSearchService } from '../../docs-search';
|
import type { DocsSearchService } from '../../docs-search';
|
||||||
import type { RecentDocsService } from '../../quicksearch';
|
import { type RecentDocsService } from '../../quicksearch';
|
||||||
|
import { highlighter } from '../../quicksearch/utils/highlighter';
|
||||||
|
import type { TagService } from '../../tag';
|
||||||
import type { WorkspaceService } from '../../workspace';
|
import type { WorkspaceService } from '../../workspace';
|
||||||
|
|
||||||
const MAX_DOCS = 3;
|
const MAX_DOCS = 3;
|
||||||
@@ -26,12 +30,15 @@ type DocMetaWithHighlights = DocMeta & {
|
|||||||
|
|
||||||
export type SearchDocMenuAction = (meta: DocMeta) => Promise<void> | void;
|
export type SearchDocMenuAction = (meta: DocMeta) => Promise<void> | void;
|
||||||
|
|
||||||
export class DocSearchMenuService extends Service {
|
export type SearchTagMenuAction = (tagId: TagMeta) => Promise<void> | void;
|
||||||
|
|
||||||
|
export class SearchMenuService extends Service {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceService: WorkspaceService,
|
private readonly workspaceService: WorkspaceService,
|
||||||
private readonly docDisplayMetaService: DocDisplayMetaService,
|
private readonly docDisplayMetaService: DocDisplayMetaService,
|
||||||
private readonly recentDocsService: RecentDocsService,
|
private readonly recentDocsService: RecentDocsService,
|
||||||
private readonly docsSearch: DocsSearchService
|
private readonly docsSearch: DocsSearchService,
|
||||||
|
private readonly tagService: TagService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -210,4 +217,81 @@ export class DocSearchMenuService extends Service {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTagMenuGroup(
|
||||||
|
query: string,
|
||||||
|
action: SearchTagMenuAction,
|
||||||
|
_abortSignal: AbortSignal
|
||||||
|
): LinkedMenuGroup {
|
||||||
|
const tags: TagMeta[] = this.tagService.tagList.tagMetas$.value;
|
||||||
|
|
||||||
|
if (query.trim().length === 0) {
|
||||||
|
return {
|
||||||
|
name: I18n.t('com.affine.editor.at-menu.tags', {
|
||||||
|
query,
|
||||||
|
}),
|
||||||
|
items: tags.map(tag => this.toTagMenuItem(tag, action)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fuse = new Fuse(tags, {
|
||||||
|
keys: ['title'],
|
||||||
|
includeMatches: true,
|
||||||
|
includeScore: true,
|
||||||
|
ignoreLocation: true,
|
||||||
|
threshold: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = fuse.search(query);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: I18n.t('com.affine.editor.at-menu.link-to-doc', {
|
||||||
|
query,
|
||||||
|
}),
|
||||||
|
items: result.map(item => {
|
||||||
|
const normalizedRange = ([start, end]: [number, number]) =>
|
||||||
|
[
|
||||||
|
start,
|
||||||
|
end + 1 /* in fuse, the `end` is different from the `substring` */,
|
||||||
|
] as [number, number];
|
||||||
|
const titleMatches = item.matches
|
||||||
|
?.filter(match => match.key === 'title')
|
||||||
|
.flatMap(match => match.indices.map(normalizedRange));
|
||||||
|
const hTitle = highlighter(
|
||||||
|
item.item.title,
|
||||||
|
`<span style="color: ${cssVarV2('text/emphasis')}">`,
|
||||||
|
'</span>',
|
||||||
|
titleMatches ?? []
|
||||||
|
);
|
||||||
|
return this.toTagMenuItem(
|
||||||
|
{
|
||||||
|
...item.item,
|
||||||
|
title: hTitle ?? item.item.title,
|
||||||
|
},
|
||||||
|
action
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toTagMenuItem(
|
||||||
|
tag: TagMeta,
|
||||||
|
action: SearchTagMenuAction
|
||||||
|
): LinkedMenuItem {
|
||||||
|
const tagIcon = html`
|
||||||
|
<div style="display: flex; align-items: center; justify-content: center;">
|
||||||
|
<div
|
||||||
|
style="border-radius: 50%; height: 8px; width: 8px; margin: 4px; background-color: ${tag.color};"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return {
|
||||||
|
key: tag.id,
|
||||||
|
name: html`${unsafeHTML(tag.title)}`,
|
||||||
|
icon: tagIcon,
|
||||||
|
action: async () => {
|
||||||
|
await action(tag);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6829,6 +6829,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Recent`
|
* `Recent`
|
||||||
*/
|
*/
|
||||||
["com.affine.editor.at-menu.recent-docs"](): string;
|
["com.affine.editor.at-menu.recent-docs"](): string;
|
||||||
|
/**
|
||||||
|
* `Tags`
|
||||||
|
*/
|
||||||
|
["com.affine.editor.at-menu.tags"](): string;
|
||||||
/**
|
/**
|
||||||
* `Loading...`
|
* `Loading...`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1697,6 +1697,7 @@
|
|||||||
"com.affine.editor.journal-conflict.title": "Duplicate Entries in Today's Journal",
|
"com.affine.editor.journal-conflict.title": "Duplicate Entries in Today's Journal",
|
||||||
"com.affine.editor.at-menu.link-to-doc": "Search for \"{{query}}\"",
|
"com.affine.editor.at-menu.link-to-doc": "Search for \"{{query}}\"",
|
||||||
"com.affine.editor.at-menu.recent-docs": "Recent",
|
"com.affine.editor.at-menu.recent-docs": "Recent",
|
||||||
|
"com.affine.editor.at-menu.tags": "Tags",
|
||||||
"com.affine.editor.at-menu.loading": "Loading...",
|
"com.affine.editor.at-menu.loading": "Loading...",
|
||||||
"com.affine.editor.at-menu.new-doc": "New",
|
"com.affine.editor.at-menu.new-doc": "New",
|
||||||
"com.affine.editor.at-menu.create-page": "New \"{{name}}\" page",
|
"com.affine.editor.at-menu.create-page": "New \"{{name}}\" page",
|
||||||
|
|||||||
Reference in New Issue
Block a user