Flrande
2025-03-27 09:45:14 +00:00
parent a189421360
commit 3303684056
4 changed files with 134 additions and 95 deletions

View File

@@ -99,6 +99,8 @@ export type LinkedMenuGroup = {
loading?: boolean | Signal<boolean>; loading?: boolean | Signal<boolean>;
// copywriting when display quantity exceeds // copywriting when display quantity exceeds
overflowText?: string | Signal<string>; overflowText?: string | Signal<string>;
// hide the group
hidden?: boolean | Signal<boolean>;
}; };
export type LinkedDocContext = { export type LinkedDocContext = {

View File

@@ -260,17 +260,22 @@ export class LinkedDocPopover extends SignalWatcher(
}); });
const actionGroups = this._actionGroup.map(group => { 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 isLoading = resolveSignal(group.loading);
const isHidden = resolveSignal(group.hidden);
return { return {
...group, ...group,
isLoading, isLoading,
isHidden,
}; };
}); });
return html`<div class="linked-doc-popover" style="${style}"> return html`<div class="linked-doc-popover" style="${style}">
${actionGroups ${actionGroups
.filter(group => group.items.length || group.isLoading) .filter(
group =>
(group.items.length > 0 || group.isLoading) && !group.isHidden
)
.map((group, idx) => { .map((group, idx) => {
return html` return html`
<div class="divider" ?hidden=${idx === 0}></div> <div class="divider" ?hidden=${idx === 0}></div>

View File

@@ -6,7 +6,7 @@ import { DocsService } from '../doc';
import { DocDisplayMetaService } from '../doc-display-meta'; import { DocDisplayMetaService } from '../doc-display-meta';
import { EditorSettingService } from '../editor-setting'; import { EditorSettingService } from '../editor-setting';
import { JournalService } from '../journal'; import { JournalService } from '../journal';
import { MemberSearchService } from '../permissions'; import { GuardService, MemberSearchService } from '../permissions';
import { SearchMenuService } from '../search-menu/services'; import { SearchMenuService } from '../search-menu/services';
import { WorkspaceScope } from '../workspace'; import { WorkspaceScope } from '../workspace';
import { AtMenuConfigService } from './services'; import { AtMenuConfigService } from './services';
@@ -23,5 +23,6 @@ export function configAtMenuConfigModule(framework: Framework) {
SearchMenuService, SearchMenuService,
WorkspaceServerService, WorkspaceServerService,
MemberSearchService, MemberSearchService,
GuardService,
]); ]);
} }

View File

@@ -25,7 +25,7 @@ import {
NewXxxEdgelessIcon, NewXxxEdgelessIcon,
NewXxxPageIcon, NewXxxPageIcon,
} from '@blocksuite/icons/lit'; } 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 { Service } from '@toeverything/infra';
import { cssVarV2 } from '@toeverything/theme/v2'; import { cssVarV2 } from '@toeverything/theme/v2';
import { html } from 'lit'; import { html } from 'lit';
@@ -42,7 +42,7 @@ import type { DocDisplayMetaService } from '../../doc-display-meta';
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 { NotificationService } from '../../notification'; import { NotificationService } from '../../notification';
import type { Member, MemberSearchService } from '../../permissions'; import type { GuardService, MemberSearchService } from '../../permissions';
import type { SearchMenuService } from '../../search-menu/services'; import type { SearchMenuService } from '../../search-menu/services';
function resolveSignal<T>(data: T | Signal<T>): T { function resolveSignal<T>(data: T | Signal<T>): T {
@@ -64,7 +64,8 @@ export class AtMenuConfigService extends Service {
private readonly docsService: DocsService, private readonly docsService: DocsService,
private readonly searchMenuService: SearchMenuService, private readonly searchMenuService: SearchMenuService,
private readonly workspaceServerService: WorkspaceServerService, private readonly workspaceServerService: WorkspaceServerService,
private readonly memberSearchService: MemberSearchService private readonly memberSearchService: MemberSearchService,
private readonly guardService: GuardService
) { ) {
super(); super();
} }
@@ -332,30 +333,23 @@ export class AtMenuConfigService extends Service {
inlineEditor: AffineInlineEditor, inlineEditor: AffineInlineEditor,
_: AbortSignal _: AbortSignal
): LinkedMenuGroup { ): LinkedMenuGroup {
const inviteItem: LinkedMenuItem = { const getMenuItem = (
key: 'invite', id: string,
name: 'Invite...', name?: string | null,
icon: MainAvatarIcon(), avatar?: string | null,
action: () => { sendNotification: boolean = true
close(); ): LinkedMenuItem => {
const avatarStyle = styleMap({
this.dialogService.open('setting', {
activeTab: 'workspace:members',
});
},
};
const convertMemberToMenuItem = (member: Member) => {
const { id, name, avatarUrl } = member;
const style = styleMap({
borderRadius: '50%', borderRadius: '50%',
border: `1px solid ${cssVarV2('layer/background/overlayPanel')}`, border: `1px solid ${cssVarV2('layer/background/overlayPanel')}`,
width: '20px', width: '20px',
height: '20px', height: '20px',
boxSizing: 'border-box', boxSizing: 'border-box',
}); });
const icon = avatarUrl const icon = avatar
? html`<img style=${style} src="${avatarUrl}" />` ? html`<img style=${avatarStyle} src="${avatar}" />`
: MainAvatarIcon(); : MainAvatarIcon();
return { return {
key: id, key: id,
name: name ?? 'Unknown', name: name ?? 'Unknown',
@@ -374,11 +368,6 @@ export class AtMenuConfigService extends Service {
const docId = doc.id; const docId = doc.id;
const mode = block.std.get(DocModeProvider).getEditorMode() ?? 'page'; const mode = block.std.get(DocModeProvider).getEditorMode() ?? 'page';
const currentUserId =
this.workspaceServerService.server?.scope.get(AuthService).session
.account$.value?.id;
if (!currentUserId) return;
close(); close();
const inlineRange = inlineEditor.getInlineRange(); const inlineRange = inlineEditor.getInlineRange();
@@ -394,91 +383,133 @@ export class AtMenuConfigService extends Service {
length: 0, length: 0,
}); });
if (!sendNotification) return;
const relativePosition = createRelativePositionFromTypeIndex( const relativePosition = createRelativePositionFromTypeIndex(
inlineEditor.yText, inlineEditor.yText,
inlineRange.index + 1 inlineRange.index + 1
); );
notificationService
if (id !== currentUserId) { .mentionUser(id, workspaceId, {
notificationService id: docId,
.mentionUser(id, workspaceId, { title: this.docDisplayMetaService.title$(docId).value,
id: docId, blockId: block.blockId,
title: this.docDisplayMetaService.title$(docId).value, mode: mode as GraphqlDocMode,
blockId: block.blockId, })
mode: mode as GraphqlDocMode, .then(notificationId => {
}) const doc = inlineEditor.yText.doc;
.then(notificationId => { if (!doc) return;
const doc = inlineEditor.yText.doc; const absolutePosition =
if (!doc) return; createAbsolutePositionFromRelativePosition(
const absolutePosition = relativePosition,
createAbsolutePositionFromRelativePosition( doc
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,
},
}
); );
}) if (!absolutePosition) return;
.catch(error => { const index = absolutePosition.index;
const err = UserFriendlyError.fromAny(error);
notify.error({ const delta = inlineEditor.getDeltaByRangeIndex(index);
title: I18n[`error.${err.name}`](err.data), 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) { const inviteItem: LinkedMenuItem = {
return { key: 'invite',
name: I18n.t('com.affine.editor.at-menu.mention-members'), name: 'Invite...',
items: [ icon: MainAvatarIcon(),
...this.memberSearchService.result$.value action: () => {
.slice(0, 3) close();
.map(member => convertMemberToMenuItem(member)), this.dialogService.open('setting', {
inviteItem, activeTab: 'workspace:members',
], });
}; },
} };
this.memberSearchService.reset();
const items = signal<LinkedMenuItem[]>([inviteItem]); const items = computed<LinkedMenuItem[]>(() => {
const loading = this.memberSearchService.isLoading$.signal; const members = this.memberSearchService.result$.signal.value;
this.memberSearchService.result$.subscribe(members => { const currentUser =
items.value = [ this.workspaceServerService.server?.scope.get(AuthService).session
...members.map(member => convertMemberToMenuItem(member)), .account$.signal.value;
inviteItem, 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 { return {
name: I18n.t('com.affine.editor.at-menu.mention-members'), name: I18n.t('com.affine.editor.at-menu.mention-members'),
items, items,
loading, loading,
hidden,
}; };
} }