fix(editor): disable at menu member group for collaborator (#11208)
Close [BS-2893](https://linear.app/affine-design/issue/BS-2893/无邀请权限的member-时不需要显示invite入口) Close [BS-2891](https://linear.app/affine-design/issue/BS-2891/mention-members-sention默认需显示3个人)
This commit is contained in:
@@ -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 = {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user