diff --git a/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/config.ts b/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/config.ts index 64e164dc17..8845e5ba06 100644 --- a/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/config.ts +++ b/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/config.ts @@ -99,6 +99,8 @@ export type LinkedMenuGroup = { loading?: boolean | Signal; // copywriting when display quantity exceeds overflowText?: string | Signal; + // hide the group + hidden?: boolean | Signal; }; export type LinkedDocContext = { diff --git a/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/linked-doc-popover.ts b/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/linked-doc-popover.ts index d3abe9194b..7c9b85bce9 100644 --- a/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/linked-doc-popover.ts +++ b/blocksuite/affine/blocks/block-root/src/widgets/linked-doc/linked-doc-popover.ts @@ -260,17 +260,22 @@ export class LinkedDocPopover extends SignalWatcher( }); const actionGroups = this._actionGroup.map(group => { - // Check if the group is loading + // Check if the group is loading or hidden const isLoading = resolveSignal(group.loading); + const isHidden = resolveSignal(group.hidden); return { ...group, isLoading, + isHidden, }; }); return html`
${actionGroups - .filter(group => group.items.length || group.isLoading) + .filter( + group => + (group.items.length > 0 || group.isLoading) && !group.isHidden + ) .map((group, idx) => { return html`
diff --git a/packages/frontend/core/src/modules/at-menu-config/index.ts b/packages/frontend/core/src/modules/at-menu-config/index.ts index 85b9bad666..22bfdd37bc 100644 --- a/packages/frontend/core/src/modules/at-menu-config/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/index.ts @@ -6,7 +6,7 @@ import { DocsService } from '../doc'; import { DocDisplayMetaService } from '../doc-display-meta'; import { EditorSettingService } from '../editor-setting'; import { JournalService } from '../journal'; -import { MemberSearchService } from '../permissions'; +import { GuardService, MemberSearchService } from '../permissions'; import { SearchMenuService } from '../search-menu/services'; import { WorkspaceScope } from '../workspace'; import { AtMenuConfigService } from './services'; @@ -23,5 +23,6 @@ export function configAtMenuConfigModule(framework: Framework) { SearchMenuService, WorkspaceServerService, MemberSearchService, + GuardService, ]); } diff --git a/packages/frontend/core/src/modules/at-menu-config/services/index.ts b/packages/frontend/core/src/modules/at-menu-config/services/index.ts index 78bca29e0d..a8aae452b5 100644 --- a/packages/frontend/core/src/modules/at-menu-config/services/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/services/index.ts @@ -25,7 +25,7 @@ import { NewXxxEdgelessIcon, NewXxxPageIcon, } from '@blocksuite/icons/lit'; -import { computed, Signal, signal } from '@preact/signals-core'; +import { computed, Signal } from '@preact/signals-core'; import { Service } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { html } from 'lit'; @@ -42,7 +42,7 @@ import type { DocDisplayMetaService } from '../../doc-display-meta'; import type { EditorSettingService } from '../../editor-setting'; import { type JournalService, suggestJournalDate } from '../../journal'; import { NotificationService } from '../../notification'; -import type { Member, MemberSearchService } from '../../permissions'; +import type { GuardService, MemberSearchService } from '../../permissions'; import type { SearchMenuService } from '../../search-menu/services'; function resolveSignal(data: T | Signal): T { @@ -64,7 +64,8 @@ export class AtMenuConfigService extends Service { private readonly docsService: DocsService, private readonly searchMenuService: SearchMenuService, private readonly workspaceServerService: WorkspaceServerService, - private readonly memberSearchService: MemberSearchService + private readonly memberSearchService: MemberSearchService, + private readonly guardService: GuardService ) { super(); } @@ -332,30 +333,23 @@ export class AtMenuConfigService extends Service { inlineEditor: AffineInlineEditor, _: AbortSignal ): LinkedMenuGroup { - const inviteItem: LinkedMenuItem = { - key: 'invite', - name: 'Invite...', - icon: MainAvatarIcon(), - action: () => { - close(); - - this.dialogService.open('setting', { - activeTab: 'workspace:members', - }); - }, - }; - const convertMemberToMenuItem = (member: Member) => { - const { id, name, avatarUrl } = member; - const style = styleMap({ + const getMenuItem = ( + id: string, + name?: string | null, + avatar?: string | null, + sendNotification: boolean = true + ): LinkedMenuItem => { + const avatarStyle = styleMap({ borderRadius: '50%', border: `1px solid ${cssVarV2('layer/background/overlayPanel')}`, width: '20px', height: '20px', boxSizing: 'border-box', }); - const icon = avatarUrl - ? html`` + const icon = avatar + ? html`` : MainAvatarIcon(); + return { key: id, name: name ?? 'Unknown', @@ -374,11 +368,6 @@ export class AtMenuConfigService extends Service { const docId = doc.id; const mode = block.std.get(DocModeProvider).getEditorMode() ?? 'page'; - const currentUserId = - this.workspaceServerService.server?.scope.get(AuthService).session - .account$.value?.id; - if (!currentUserId) return; - close(); const inlineRange = inlineEditor.getInlineRange(); @@ -394,91 +383,133 @@ export class AtMenuConfigService extends Service { length: 0, }); + if (!sendNotification) return; + const relativePosition = createRelativePositionFromTypeIndex( inlineEditor.yText, inlineRange.index + 1 ); - - if (id !== currentUserId) { - notificationService - .mentionUser(id, workspaceId, { - id: docId, - title: this.docDisplayMetaService.title$(docId).value, - blockId: block.blockId, - mode: mode as GraphqlDocMode, - }) - .then(notificationId => { - const doc = inlineEditor.yText.doc; - if (!doc) return; - const absolutePosition = - createAbsolutePositionFromRelativePosition( - relativePosition, - doc - ); - if (!absolutePosition) return; - const index = absolutePosition.index; - - const delta = inlineEditor.getDeltaByRangeIndex(index); - if ( - !delta || - delta.insert !== ' ' || - !delta.attributes?.mention || - delta.attributes.mention.notification || - delta.attributes.mention.member !== id - ) - return; - - inlineEditor.formatText( - { - index: index - 1, - length: 1, - }, - { - mention: { - member: id, - notification: notificationId, - }, - } + notificationService + .mentionUser(id, workspaceId, { + id: docId, + title: this.docDisplayMetaService.title$(docId).value, + blockId: block.blockId, + mode: mode as GraphqlDocMode, + }) + .then(notificationId => { + const doc = inlineEditor.yText.doc; + if (!doc) return; + const absolutePosition = + createAbsolutePositionFromRelativePosition( + relativePosition, + doc ); - }) - .catch(error => { - const err = UserFriendlyError.fromAny(error); - notify.error({ - title: I18n[`error.${err.name}`](err.data), - }); + if (!absolutePosition) return; + const index = absolutePosition.index; + + const delta = inlineEditor.getDeltaByRangeIndex(index); + if ( + !delta || + delta.insert !== ' ' || + !delta.attributes?.mention || + delta.attributes.mention.notification || + delta.attributes.mention.member !== id + ) + return; + + inlineEditor.formatText( + { + index: index - 1, + length: 1, + }, + { + mention: { + member: id, + notification: notificationId, + }, + } + ); + }) + .catch(error => { + const err = UserFriendlyError.fromAny(error); + notify.error({ + title: I18n[`error.${err.name}`](err.data), }); - } + }); }, }; }; - if (query.length === 0) { - return { - name: I18n.t('com.affine.editor.at-menu.mention-members'), - items: [ - ...this.memberSearchService.result$.value - .slice(0, 3) - .map(member => convertMemberToMenuItem(member)), - inviteItem, - ], - }; - } - this.memberSearchService.reset(); + const inviteItem: LinkedMenuItem = { + key: 'invite', + name: 'Invite...', + icon: MainAvatarIcon(), + action: () => { + close(); + this.dialogService.open('setting', { + activeTab: 'workspace:members', + }); + }, + }; - const items = signal([inviteItem]); - const loading = this.memberSearchService.isLoading$.signal; - this.memberSearchService.result$.subscribe(members => { - items.value = [ - ...members.map(member => convertMemberToMenuItem(member)), - inviteItem, + const items = computed(() => { + const members = this.memberSearchService.result$.signal.value; + const currentUser = + this.workspaceServerService.server?.scope.get(AuthService).session + .account$.signal.value; + const canUserManage = this.guardService.can$('Workspace_Users_Manage') + .signal.value; + + if (query.length === 0) { + return [ + ...(currentUser + ? [ + getMenuItem( + currentUser.id, + currentUser.info?.name, + currentUser.info?.avatarUrl, + false + ), + ] + : []), + ...members + .slice(0, 2) + .filter(member => member.id !== currentUser?.id) + .map(member => + getMenuItem(member.id, member.name, member.avatarUrl) + ), + ...(canUserManage ? [inviteItem] : []), + ]; + } + + return [ + ...members.map(member => + getMenuItem( + member.id, + member.name, + member.avatarUrl, + member.id !== currentUser?.id + ) + ), + ...(canUserManage ? [inviteItem] : []), ]; }); - this.memberSearchService.search(query); + const hidden = computed(() => { + const members = this.memberSearchService.result$.signal.value; + const loading = this.memberSearchService.isLoading$.signal.value; + return query.length > 0 && !loading && members.length === 0; + }); + const loading = this.memberSearchService.isLoading$.signal.value; + + if (query.length > 0) { + this.memberSearchService.search(query); + } return { name: I18n.t('com.affine.editor.at-menu.mention-members'), items, loading, + hidden, }; }