mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): hybird search for docs, tags and collections (#11008)
Close [BS-2466](https://linear.app/affine-design/issue/BS-2466). 
This commit is contained in:
@@ -6,7 +6,7 @@ import type {
|
|||||||
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';
|
||||||
import { openFileOrFiles, type Signal } from '@blocksuite/affine/shared/utils';
|
import { openFileOrFiles } from '@blocksuite/affine/shared/utils';
|
||||||
import {
|
import {
|
||||||
CollectionsIcon,
|
CollectionsIcon,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
UploadIcon,
|
UploadIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
import type { DocMeta } from '@blocksuite/store';
|
import type { DocMeta } from '@blocksuite/store';
|
||||||
|
import { Signal } from '@preact/signals-core';
|
||||||
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 { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
@@ -33,6 +34,8 @@ export type MenuGroup = {
|
|||||||
items: MenuItem[] | Signal<MenuItem[]>;
|
items: MenuItem[] | Signal<MenuItem[]>;
|
||||||
maxDisplay?: number;
|
maxDisplay?: number;
|
||||||
overflowText?: string | Signal<string>;
|
overflowText?: string | Signal<string>;
|
||||||
|
divider?: TemplateResult<1>;
|
||||||
|
noResult?: TemplateResult<1>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuItem = {
|
export type MenuItem = {
|
||||||
@@ -45,6 +48,10 @@ export type MenuItem = {
|
|||||||
|
|
||||||
export type MenuAction = () => Promise<void> | void;
|
export type MenuAction = () => Promise<void> | void;
|
||||||
|
|
||||||
|
export function resolveSignal<T>(data: T | Signal<T>): T {
|
||||||
|
return data instanceof Signal ? data.value : data;
|
||||||
|
}
|
||||||
|
|
||||||
export class ChatPanelAddPopover extends SignalWatcher(
|
export class ChatPanelAddPopover extends SignalWatcher(
|
||||||
WithDisposable(ShadowlessElement)
|
WithDisposable(ShadowlessElement)
|
||||||
) {
|
) {
|
||||||
@@ -117,10 +124,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private accessor _query = '';
|
private accessor _query = '';
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor _searchGroup: MenuGroup = {
|
private accessor _searchGroups: MenuGroup[] = [];
|
||||||
name: 'No Result',
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _toggleMode = (mode: AddPopoverMode) => {
|
private readonly _toggleMode = (mode: AddPopoverMode) => {
|
||||||
this._mode = mode;
|
this._mode = mode;
|
||||||
@@ -189,22 +193,22 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
|
|
||||||
switch (this._mode) {
|
switch (this._mode) {
|
||||||
case AddPopoverMode.Tags:
|
case AddPopoverMode.Tags:
|
||||||
groups = [this._searchGroup];
|
groups = this._searchGroups;
|
||||||
break;
|
break;
|
||||||
case AddPopoverMode.Collections:
|
case AddPopoverMode.Collections:
|
||||||
groups = [this._searchGroup];
|
groups = this._searchGroups;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (this._query) {
|
if (this._query) {
|
||||||
groups = [this._searchGroup, this.uploadGroup];
|
groups = [...this._searchGroups, this.uploadGroup];
|
||||||
} else {
|
} else {
|
||||||
groups = [this._searchGroup, this.tcGroup, this.uploadGroup];
|
groups = [...this._searchGroups, this.tcGroup, this.uploadGroup];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process maxDisplay for each group
|
// Process maxDisplay for each group
|
||||||
return groups.map(group => {
|
return groups.map(group => {
|
||||||
let items = Array.isArray(group.items) ? group.items : group.items.value;
|
let items = resolveSignal(group.items);
|
||||||
const maxDisplay = group.maxDisplay ?? items.length;
|
const maxDisplay = group.maxDisplay ?? items.length;
|
||||||
const hasMore = items.length > maxDisplay;
|
const hasMore = items.length > maxDisplay;
|
||||||
if (!hasMore) {
|
if (!hasMore) {
|
||||||
@@ -216,10 +220,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
...items.slice(0, maxDisplay),
|
...items.slice(0, maxDisplay),
|
||||||
{
|
{
|
||||||
key: `${group.name} More`,
|
key: `${group.name} More`,
|
||||||
name:
|
name: resolveSignal(group.overflowText) ?? 'more',
|
||||||
typeof group.overflowText === 'string'
|
|
||||||
? group.overflowText
|
|
||||||
: (group.overflowText?.value ?? 'more'),
|
|
||||||
icon: MoreHorizontalIcon(),
|
icon: MoreHorizontalIcon(),
|
||||||
action: () => {
|
action: () => {
|
||||||
this._resetMaxDisplay(group);
|
this._resetMaxDisplay(group);
|
||||||
@@ -233,7 +234,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
|
|
||||||
private get _flattenMenuGroup() {
|
private get _flattenMenuGroup() {
|
||||||
return this._menuGroup.flatMap(group => {
|
return this._menuGroup.flatMap(group => {
|
||||||
return Array.isArray(group.items) ? group.items : group.items.value;
|
return resolveSignal(group.items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,9 +294,9 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private _getPlaceholder() {
|
private _getPlaceholder() {
|
||||||
switch (this._mode) {
|
switch (this._mode) {
|
||||||
case AddPopoverMode.Tags:
|
case AddPopoverMode.Tags:
|
||||||
return 'Search Tag';
|
return 'Search tags';
|
||||||
case AddPopoverMode.Collections:
|
case AddPopoverMode.Collections:
|
||||||
return 'Search Collection';
|
return 'Search collections';
|
||||||
default:
|
default:
|
||||||
return 'Search docs, tags, collections';
|
return 'Search docs, tags, collections';
|
||||||
}
|
}
|
||||||
@@ -305,19 +306,25 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
return html`<div class="divider"></div>`;
|
return html`<div class="divider"></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderNoResult() {
|
||||||
|
return html`<div class="no-result">No Result</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
private _renderMenuGroup(groups: MenuGroup[]) {
|
private _renderMenuGroup(groups: MenuGroup[]) {
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
return repeat(
|
return repeat(
|
||||||
groups,
|
groups,
|
||||||
group => group.name,
|
group => group.name,
|
||||||
(group, idx) => {
|
(group, idx) => {
|
||||||
const items = Array.isArray(group.items)
|
const items = resolveSignal(group.items);
|
||||||
? group.items
|
|
||||||
: group.items.value;
|
|
||||||
|
|
||||||
const menuGroup = html`<div class="menu-group">
|
const menuGroup = html`<div class="menu-group">
|
||||||
${this._renderMenuItems(items, startIndex)}
|
${items.length > 0
|
||||||
${idx < groups.length - 1 ? this._renderDivider() : ''}
|
? this._renderMenuItems(items, startIndex)
|
||||||
|
: (group.noResult ?? this._renderNoResult())}
|
||||||
|
${idx < groups.length - 1
|
||||||
|
? (group.divider ?? this._renderDivider())
|
||||||
|
: ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
startIndex += items.length;
|
startIndex += items.length;
|
||||||
return menuGroup;
|
return menuGroup;
|
||||||
@@ -327,28 +334,26 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
|
|
||||||
private _renderMenuItems(items: MenuItem[], startIndex: number) {
|
private _renderMenuItems(items: MenuItem[], startIndex: number) {
|
||||||
return html`<div class="menu-items">
|
return html`<div class="menu-items">
|
||||||
${items.length > 0
|
${repeat(
|
||||||
? repeat(
|
items,
|
||||||
items,
|
item => item.key,
|
||||||
item => item.key,
|
({ key, name, icon, action, suffix }, idx) => {
|
||||||
({ key, name, icon, action, suffix }, idx) => {
|
const curIdx = startIndex + idx;
|
||||||
const curIdx = startIndex + idx;
|
return html`<icon-button
|
||||||
return html`<icon-button
|
width="280px"
|
||||||
width="280px"
|
height="30px"
|
||||||
height="30px"
|
data-id=${key}
|
||||||
data-id=${key}
|
data-index=${curIdx}
|
||||||
data-index=${curIdx}
|
.text=${name}
|
||||||
.text=${name}
|
hover=${this._activatedIndex === curIdx}
|
||||||
hover=${this._activatedIndex === curIdx}
|
@click=${() => action()?.catch(console.error)}
|
||||||
@click=${() => action()?.catch(console.error)}
|
@mousemove=${() => (this._activatedIndex = curIdx)}
|
||||||
@mousemove=${() => (this._activatedIndex = curIdx)}
|
>
|
||||||
>
|
${icon}
|
||||||
${icon}
|
${suffix ? html`<div class="item-suffix">${suffix}</div>` : ''}
|
||||||
${suffix ? html`<div class="item-suffix">${suffix}</div>` : ''}
|
</icon-button>`;
|
||||||
</icon-button>`;
|
}
|
||||||
}
|
)}
|
||||||
)
|
|
||||||
: html`<div class="no-result">No Result</div>`}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,26 +370,78 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
|
|
||||||
private _updateSearchGroup() {
|
private _updateSearchGroup() {
|
||||||
switch (this._mode) {
|
switch (this._mode) {
|
||||||
case AddPopoverMode.Tags:
|
case AddPopoverMode.Tags: {
|
||||||
this._searchGroup = this.searchMenuConfig.getTagMenuGroup(
|
this._searchGroups = [
|
||||||
this._query,
|
this.searchMenuConfig.getTagMenuGroup(
|
||||||
this._addTagChip,
|
this._query,
|
||||||
this.abortController.signal
|
this._addTagChip,
|
||||||
);
|
this.abortController.signal
|
||||||
|
),
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
case AddPopoverMode.Collections:
|
}
|
||||||
this._searchGroup = this.searchMenuConfig.getCollectionMenuGroup(
|
case AddPopoverMode.Collections: {
|
||||||
this._query,
|
this._searchGroups = [
|
||||||
this._addCollectionChip,
|
this.searchMenuConfig.getCollectionMenuGroup(
|
||||||
this.abortController.signal
|
this._query,
|
||||||
);
|
this._addCollectionChip,
|
||||||
|
this.abortController.signal
|
||||||
|
),
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
this._searchGroup = this.searchMenuConfig.getDocMenuGroup(
|
default: {
|
||||||
|
const docGroup = this.searchMenuConfig.getDocMenuGroup(
|
||||||
this._query,
|
this._query,
|
||||||
this._addDocChip,
|
this._addDocChip,
|
||||||
this.abortController.signal
|
this.abortController.signal
|
||||||
);
|
);
|
||||||
|
if (!this._query) {
|
||||||
|
this._searchGroups = [docGroup];
|
||||||
|
} else {
|
||||||
|
const tagGroup = this.searchMenuConfig.getTagMenuGroup(
|
||||||
|
this._query,
|
||||||
|
this._addTagChip,
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
const collectionGroup = this.searchMenuConfig.getCollectionMenuGroup(
|
||||||
|
this._query,
|
||||||
|
this._addCollectionChip,
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
const hasNoResult =
|
||||||
|
resolveSignal(docGroup.items).length === 0 &&
|
||||||
|
resolveSignal(tagGroup.items).length === 0 &&
|
||||||
|
resolveSignal(collectionGroup.items).length === 0;
|
||||||
|
if (hasNoResult) {
|
||||||
|
this._searchGroups = [
|
||||||
|
{
|
||||||
|
name: 'No Result',
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const nothing = html``;
|
||||||
|
this._searchGroups = [
|
||||||
|
{
|
||||||
|
...docGroup,
|
||||||
|
divider: nothing,
|
||||||
|
noResult: nothing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...tagGroup,
|
||||||
|
divider: nothing,
|
||||||
|
noResult: nothing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...collectionGroup,
|
||||||
|
noResult: nothing,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user