@@ -1,4 +1,4 @@
|
|||||||
import type { DocRole, GetPageGrantedUsersListQuery } from '@affine/graphql';
|
import { DocRole, type GetPageGrantedUsersListQuery } from '@affine/graphql';
|
||||||
import {
|
import {
|
||||||
backoffRetry,
|
backoffRetry,
|
||||||
catchErrorInto,
|
catchErrorInto,
|
||||||
@@ -123,12 +123,18 @@ export class DocGrantedUsersService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateUserRole(userId: string, role: DocRole) {
|
async updateUserRole(userId: string, role: DocRole) {
|
||||||
await this.store.updateDocUserRole(
|
const res = await this.store.updateDocUserRole(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
this.docService.doc.id,
|
this.docService.doc.id,
|
||||||
userId,
|
userId,
|
||||||
role
|
role
|
||||||
);
|
);
|
||||||
|
if (res) {
|
||||||
|
if (role === DocRole.Owner) {
|
||||||
|
this.reset();
|
||||||
|
this.loadMore();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
this.grantedUsers$.next(
|
this.grantedUsers$.next(
|
||||||
this.grantedUsers$.value.map(user => {
|
this.grantedUsers$.value.map(user => {
|
||||||
if (user.user.id === userId) {
|
if (user.user.id === userId) {
|
||||||
@@ -139,6 +145,9 @@ export class DocGrantedUsersService extends Service {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
async updateDocDefaultRole(role: DocRole) {
|
async updateDocDefaultRole(role: DocRole) {
|
||||||
return await this.store.updateDocDefaultRole({
|
return await this.store.updateDocDefaultRole({
|
||||||
docId: this.docService.doc.id,
|
docId: this.docService.doc.id,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { cssVarV2 } from '@toeverything/theme/v2';
|
|||||||
import { globalStyle, style } from '@vanilla-extract/css';
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const copyLinkContainerStyle = style({
|
export const copyLinkContainerStyle = style({
|
||||||
padding: '4px',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -18,6 +17,7 @@ export const copyLinkButtonStyle = style({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
padding: '4px 12px',
|
padding: '4px 12px',
|
||||||
paddingRight: '6px',
|
paddingRight: '6px',
|
||||||
|
borderRadius: '4px',
|
||||||
borderRight: 'none',
|
borderRight: 'none',
|
||||||
borderTopRightRadius: '0',
|
borderTopRightRadius: '0',
|
||||||
borderBottomRightRadius: '0',
|
borderBottomRightRadius: '0',
|
||||||
@@ -35,6 +35,7 @@ export const copyLinkButtonStyle = style({
|
|||||||
export const copyLinkLabelContainerStyle = style({
|
export const copyLinkLabelContainerStyle = style({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderRight: 'none',
|
borderRight: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
borderTopRightRadius: '0',
|
borderTopRightRadius: '0',
|
||||||
borderBottomRightRadius: '0',
|
borderBottomRightRadius: '0',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -70,6 +71,7 @@ export const copyLinkShortcutStyle = style({
|
|||||||
});
|
});
|
||||||
export const copyLinkTriggerStyle = style({
|
export const copyLinkTriggerStyle = style({
|
||||||
padding: '4px 12px 4px 8px',
|
padding: '4px 12px 4px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
borderLeft: 'none',
|
borderLeft: 'none',
|
||||||
borderTopLeftRadius: '0',
|
borderTopLeftRadius: '0',
|
||||||
borderBottomLeftRadius: '0',
|
borderBottomLeftRadius: '0',
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
|
import { Menu, MenuItem, MenuTrigger, Tooltip } from '@affine/component';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { DocGrantedUsersService } from '@affine/core/modules/permissions';
|
import { DocGrantedUsersService } from '@affine/core/modules/permissions';
|
||||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||||
import { DocRole } from '@affine/graphql';
|
import { DocRole } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { InformationIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { PlanTag } from '../plan-tag';
|
import { PlanTag } from '../plan-tag';
|
||||||
@@ -42,7 +44,8 @@ export const MembersPermission = ({
|
|||||||
() => getRoleName(t, docDefaultRole),
|
() => getRoleName(t, docDefaultRole),
|
||||||
[docDefaultRole, t]
|
[docDefaultRole, t]
|
||||||
);
|
);
|
||||||
|
const showTips =
|
||||||
|
docDefaultRole === DocRole.Reader || docDefaultRole === DocRole.Editor;
|
||||||
const changePermission = useCallback(
|
const changePermission = useCallback(
|
||||||
async (docRole: DocRole) => {
|
async (docRole: DocRole) => {
|
||||||
await docGrantedUsersService.updateDocDefaultRole(docRole);
|
await docGrantedUsersService.updateDocDefaultRole(docRole);
|
||||||
@@ -76,12 +79,16 @@ export const MembersPermission = ({
|
|||||||
<div className={styles.labelStyle}>
|
<div className={styles.labelStyle}>
|
||||||
{t['com.affine.share-menu.option.permission.label']()}
|
{t['com.affine.share-menu.option.permission.label']()}
|
||||||
</div>
|
</div>
|
||||||
|
{disabled ? (
|
||||||
|
<div className={clsx(styles.menuTriggerStyle, 'disable')}>
|
||||||
|
{showTips ? <Tips disable={disabled} /> : null} {currentRoleName}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Menu
|
<Menu
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
align: 'end',
|
align: 'end',
|
||||||
}}
|
}}
|
||||||
items={
|
items={
|
||||||
disabled ? null : (
|
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onSelect={selectManage}
|
onSelect={selectManage}
|
||||||
@@ -98,7 +105,7 @@ export const MembersPermission = ({
|
|||||||
<div className={styles.publicItemRowStyle}>
|
<div className={styles.publicItemRowStyle}>
|
||||||
<div className={styles.tagContainerStyle}>
|
<div className={styles.tagContainerStyle}>
|
||||||
{t['com.affine.share-menu.option.permission.can-edit']()}
|
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||||
<PlanTag />
|
{hittingPaywall ? <PlanTag /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -109,12 +116,11 @@ export const MembersPermission = ({
|
|||||||
<div className={styles.publicItemRowStyle}>
|
<div className={styles.publicItemRowStyle}>
|
||||||
<div className={styles.tagContainerStyle}>
|
<div className={styles.tagContainerStyle}>
|
||||||
{t['com.affine.share-menu.option.permission.can-read']()}
|
{t['com.affine.share-menu.option.permission.can-read']()}
|
||||||
<PlanTag />
|
{hittingPaywall ? <PlanTag /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MenuTrigger
|
<MenuTrigger
|
||||||
@@ -123,11 +129,26 @@ export const MembersPermission = ({
|
|||||||
contentStyle={{
|
contentStyle={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
prefix={showTips ? <Tips /> : undefined}
|
||||||
>
|
>
|
||||||
{currentRoleName}
|
{currentRoleName}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Tips = ({ disable }: { disable?: boolean }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip content={t['com.affine.share-menu.option.permission.tips']()}>
|
||||||
|
<InformationIcon
|
||||||
|
className={clsx(styles.informationIcon, {
|
||||||
|
disable: disable,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
@@ -92,13 +93,18 @@ export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
<div className={styles.labelStyle}>
|
<div className={styles.labelStyle}>
|
||||||
{t['com.affine.share-menu.option.link.label']()}
|
{t['com.affine.share-menu.option.link.label']()}
|
||||||
</div>
|
</div>
|
||||||
|
{disabled ? (
|
||||||
|
<div className={clsx(styles.menuTriggerStyle, 'disable')}>
|
||||||
|
{isSharedPage
|
||||||
|
? t['com.affine.share-menu.option.link.readonly']()
|
||||||
|
: t['com.affine.share-menu.option.link.no-access']()}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Menu
|
<Menu
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
align: 'end',
|
align: 'end',
|
||||||
}}
|
}}
|
||||||
items={
|
items={
|
||||||
disabled ? null : (
|
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
prefixIcon={<LockIcon />}
|
prefixIcon={<LockIcon />}
|
||||||
@@ -122,7 +128,6 @@ export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MenuTrigger
|
<MenuTrigger
|
||||||
@@ -132,13 +137,13 @@ export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
contentStyle={{
|
contentStyle={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
|
||||||
>
|
>
|
||||||
{isSharedPage
|
{isSharedPage
|
||||||
? t['com.affine.share-menu.option.link.readonly']()
|
? t['com.affine.share-menu.option.link.readonly']()
|
||||||
: t['com.affine.share-menu.option.link.no-access']()}
|
: t['com.affine.share-menu.option.link.no-access']()}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const menuTriggerStyle = style({
|
export const menuTriggerStyle = style({
|
||||||
@@ -8,6 +9,13 @@ export const menuTriggerStyle = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
|
selectors: {
|
||||||
|
'&.disable': {
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
color: cssVarV2('text/disable'),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rowContainerStyle = style({
|
export const rowContainerStyle = style({
|
||||||
@@ -36,3 +44,13 @@ export const tagContainerStyle = style({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '8px',
|
gap: '8px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const informationIcon = style({
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
fontSize: '20px',
|
||||||
|
selectors: {
|
||||||
|
'&.disable': {
|
||||||
|
color: cssVarV2('icon/disable'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { globalStyle, style } from '@vanilla-extract/css';
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
export const headerStyle = style({
|
|
||||||
|
export const tabList = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
gap: '12px',
|
||||||
fontSize: cssVar('fontSm'),
|
height: '28px',
|
||||||
fontWeight: 600,
|
});
|
||||||
lineHeight: '22px',
|
export const tab = style({
|
||||||
padding: '0 4px',
|
padding: '0px 4px 6px',
|
||||||
gap: '4px',
|
|
||||||
});
|
});
|
||||||
export const content = style({
|
export const content = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -17,7 +17,6 @@ export const content = style({
|
|||||||
});
|
});
|
||||||
export const menuStyle = style({
|
export const menuStyle = style({
|
||||||
width: '390px',
|
width: '390px',
|
||||||
minHeight: '310px',
|
|
||||||
maxHeight: '562px',
|
maxHeight: '562px',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Input } from '@affine/component';
|
import { Input } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { SearchIcon } from '@blocksuite/icons/rc';
|
import { SearchIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
@@ -9,11 +10,12 @@ export const InviteInput = ({ onFocus }: { onFocus: () => void }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
preFix={<SearchIcon fontSize={20} />}
|
preFix={<SearchIcon className={styles.iconStyle} />}
|
||||||
className={styles.inputStyle}
|
className={styles.inputStyle}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
inputStyle={{
|
inputStyle={{
|
||||||
paddingLeft: '0',
|
paddingLeft: '0',
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
}}
|
}}
|
||||||
placeholder={t['com.affine.share-menu.invite-editor.placeholder']()}
|
placeholder={t['com.affine.share-menu.invite-editor.placeholder']()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ export const headerStyle = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottom: `1px solid ${cssVarV2('tab/divider/divider')}`,
|
borderBottom: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
padding: '4px 4px 6px',
|
padding: '0px 4px 6px',
|
||||||
|
height: '28px',
|
||||||
color: cssVarV2('text/secondary'),
|
color: cssVarV2('text/secondary'),
|
||||||
});
|
});
|
||||||
export const iconStyle = style({
|
export const iconStyle = style({
|
||||||
@@ -54,7 +55,7 @@ export const searchInput = style({
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
fontSize: '14px',
|
fontSize: cssVar('fontSm'),
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
@@ -70,6 +71,7 @@ export const InputContainer = style({
|
|||||||
padding: '4px',
|
padding: '4px',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
margin: '6px 0px',
|
||||||
border: `1px solid ${cssVarV2('input/border/default')}`,
|
border: `1px solid ${cssVarV2('input/border/default')}`,
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -135,6 +137,12 @@ export const checkbox = style({
|
|||||||
color: cssVarV2('icon/primary'),
|
color: cssVarV2('icon/primary'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const resultContainer = style({
|
||||||
|
minHeight: '155px',
|
||||||
|
maxHeight: '394px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
|
|
||||||
export const noFound = style({
|
export const noFound = style({
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
color: cssVarV2('text/secondary'),
|
color: cssVarV2('text/secondary'),
|
||||||
|
|||||||
@@ -152,11 +152,12 @@ export const InviteMemberEditor = ({
|
|||||||
|
|
||||||
const switchToMemberManagementTab = useCallback(() => {
|
const switchToMemberManagementTab = useCallback(() => {
|
||||||
workspaceDialogService.open('setting', {
|
workspaceDialogService.open('setting', {
|
||||||
activeTab: 'workspace:preference',
|
activeTab: 'workspace:members',
|
||||||
});
|
});
|
||||||
}, [workspaceDialogService]);
|
}, [workspaceDialogService]);
|
||||||
|
|
||||||
const handleClickMember = useCallback((member: Member) => {
|
const handleClickMember = useCallback(
|
||||||
|
(member: Member) => {
|
||||||
setSelectedMembers(prev => {
|
setSelectedMembers(prev => {
|
||||||
if (prev.some(m => m.id === member.id)) {
|
if (prev.some(m => m.id === member.id)) {
|
||||||
// if the member is already in the list, just return
|
// if the member is already in the list, just return
|
||||||
@@ -164,7 +165,10 @@ export const InviteMemberEditor = ({
|
|||||||
}
|
}
|
||||||
return [...prev, member];
|
return [...prev, member];
|
||||||
});
|
});
|
||||||
}, []);
|
focusInput();
|
||||||
|
},
|
||||||
|
[focusInput]
|
||||||
|
);
|
||||||
|
|
||||||
const handleRoleChange = useCallback((role: DocRole) => {
|
const handleRoleChange = useCallback((role: DocRole) => {
|
||||||
setInviteDocRoleType(role);
|
setInviteDocRoleType(role);
|
||||||
@@ -221,6 +225,7 @@ export const InviteMemberEditor = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{selectedMembers.length ? (
|
||||||
<div className={styles.sentEmail}>
|
<div className={styles.sentEmail}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className={styles.checkbox}
|
className={styles.checkbox}
|
||||||
@@ -230,8 +235,11 @@ export const InviteMemberEditor = ({
|
|||||||
{t['com.affine.share-menu.invite-editor.sent-email']()}
|
{t['com.affine.share-menu.invite-editor.sent-email']()}
|
||||||
{` (coming soon)`}
|
{` (coming soon)`}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className={styles.resultContainer}>
|
||||||
<Result onClickMember={handleClickMember} />
|
<Result onClickMember={handleClickMember} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className={styles.footerStyle}>
|
<div className={styles.footerStyle}>
|
||||||
<span
|
<span
|
||||||
className={styles.manageMemberStyle}
|
className={styles.manageMemberStyle}
|
||||||
@@ -263,6 +271,7 @@ const Result = ({
|
|||||||
onClickMember: (member: Member) => void;
|
onClickMember: (member: Member) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const memberSearchService = useService(MemberSearchService);
|
const memberSearchService = useService(MemberSearchService);
|
||||||
|
const searchText = useLiveData(memberSearchService.searchText$);
|
||||||
const result = useLiveData(memberSearchService.result$);
|
const result = useLiveData(memberSearchService.result$);
|
||||||
const isLoading = useLiveData(memberSearchService.isLoading$);
|
const isLoading = useLiveData(memberSearchService.isLoading$);
|
||||||
|
|
||||||
@@ -274,14 +283,7 @@ const Result = ({
|
|||||||
|
|
||||||
const itemContentRenderer = useCallback(
|
const itemContentRenderer = useCallback(
|
||||||
(_index: number, data: Member) => {
|
(_index: number, data: Member) => {
|
||||||
const handleSelect = () => {
|
return <MemberItem member={data} onSelect={onClickMember} />;
|
||||||
onClickMember(data);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div onClick={handleSelect}>
|
|
||||||
<MemberItem member={data} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[onClickMember]
|
[onClickMember]
|
||||||
);
|
);
|
||||||
@@ -292,6 +294,10 @@ const Result = ({
|
|||||||
memberSearchService.loadMore();
|
memberSearchService.loadMore();
|
||||||
}, [memberSearchService]);
|
}, [memberSearchService]);
|
||||||
|
|
||||||
|
if (!searchText) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!activeMembers || activeMembers.length === 0) {
|
if (!activeMembers || activeMembers.length === 0) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
@@ -303,7 +309,13 @@ const Result = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return activeMembers.length < 8 ? (
|
||||||
|
<div>
|
||||||
|
{activeMembers.map(member => (
|
||||||
|
<MemberItem key={member.id} member={member} onSelect={onClickMember} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
components={{
|
components={{
|
||||||
Scroller,
|
Scroller,
|
||||||
@@ -370,7 +382,7 @@ const RoleSelector = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.planTagContainer}>
|
<div className={styles.planTagContainer}>
|
||||||
{t['com.affine.share-menu.option.permission.can-edit']()}
|
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||||
<PlanTag />
|
{hittingPaywall ? <PlanTag /> : null}
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -379,7 +391,7 @@ const RoleSelector = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.planTagContainer}>
|
<div className={styles.planTagContainer}>
|
||||||
{t['com.affine.share-menu.option.permission.can-read']()}
|
{t['com.affine.share-menu.option.permission.can-read']()}
|
||||||
<PlanTag />
|
{hittingPaywall ? <PlanTag /> : null}
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -62,7 +62,3 @@ export const memberRoleStyle = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tooltipContentStyle = style({
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { Avatar, Tooltip } from '@affine/component';
|
import { Avatar } from '@affine/component';
|
||||||
import type { Member } from '@affine/core/modules/permissions';
|
import type { Member } from '@affine/core/modules/permissions';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import * as styles from './member-item.css';
|
import * as styles from './member-item.css';
|
||||||
|
|
||||||
export const MemberItem = ({ member }: { member: Member }) => {
|
export const MemberItem = ({
|
||||||
|
member,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
member: Member;
|
||||||
|
onSelect: (item: Member) => void;
|
||||||
|
}) => {
|
||||||
|
const handleSelect = useCallback(() => {
|
||||||
|
onSelect(member);
|
||||||
|
}, [member, onSelect]);
|
||||||
return (
|
return (
|
||||||
<div className={styles.memberItemStyle}>
|
<div className={styles.memberItemStyle} onClick={handleSelect}>
|
||||||
<div className={styles.memberContainerStyle}>
|
<div className={styles.memberContainerStyle}>
|
||||||
<Avatar
|
<Avatar
|
||||||
key={member.id}
|
key={member.id}
|
||||||
@@ -14,24 +24,8 @@ export const MemberItem = ({ member }: { member: Member }) => {
|
|||||||
size={36}
|
size={36}
|
||||||
/>
|
/>
|
||||||
<div className={styles.memberInfoStyle}>
|
<div className={styles.memberInfoStyle}>
|
||||||
<Tooltip
|
|
||||||
content={member.name}
|
|
||||||
rootOptions={{ delayDuration: 1000 }}
|
|
||||||
options={{
|
|
||||||
className: styles.tooltipContentStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={styles.memberNameStyle}>{member.name}</div>
|
<div className={styles.memberNameStyle}>{member.name}</div>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
|
||||||
content={member.email}
|
|
||||||
rootOptions={{ delayDuration: 1000 }}
|
|
||||||
options={{
|
|
||||||
className: styles.tooltipContentStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={styles.memberEmailStyle}>{member.email}</div>
|
<div className={styles.memberEmailStyle}>{member.email}</div>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const inputStyle = style({
|
export const inputStyle = style({
|
||||||
marginTop: '6px',
|
marginTop: '6px',
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
height: '30px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iconStyle = style({
|
||||||
|
fontSize: '20px',
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
MenuTrigger,
|
MenuTrigger,
|
||||||
notify,
|
notify,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { AuthService } from '@affine/core/modules/cloud';
|
import { AuthService } from '@affine/core/modules/cloud';
|
||||||
@@ -14,12 +15,13 @@ import {
|
|||||||
DocGrantedUsersService,
|
DocGrantedUsersService,
|
||||||
type GrantedUser,
|
type GrantedUser,
|
||||||
GuardService,
|
GuardService,
|
||||||
|
WorkspacePermissionService,
|
||||||
} from '@affine/core/modules/permissions';
|
} from '@affine/core/modules/permissions';
|
||||||
import { DocRole, UserFriendlyError } from '@affine/graphql';
|
import { DocRole, UserFriendlyError } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { PlanTag } from '../plan-tag';
|
import { PlanTag } from '../plan-tag';
|
||||||
import * as styles from './member-item.css';
|
import * as styles from './member-item.css';
|
||||||
@@ -130,65 +132,86 @@ const Options = ({
|
|||||||
const docGrantedUsersService = useService(DocGrantedUsersService);
|
const docGrantedUsersService = useService(DocGrantedUsersService);
|
||||||
const docService = useService(DocService);
|
const docService = useService(DocService);
|
||||||
const guardService = useService(GuardService);
|
const guardService = useService(GuardService);
|
||||||
|
const workspacePermissionService = useService(WorkspacePermissionService);
|
||||||
const canTransferOwner = useLiveData(
|
const isWorkspaceOwner = useLiveData(
|
||||||
guardService.can$('Doc_TransferOwner', docService.doc.id)
|
workspacePermissionService.permission.isOwner$
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
|
||||||
|
const canTransferOwner =
|
||||||
|
useLiveData(guardService.can$('Doc_TransferOwner', docService.doc.id)) &&
|
||||||
|
!!isWorkspaceOwner;
|
||||||
const canManageUsers = useLiveData(
|
const canManageUsers = useLiveData(
|
||||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const changeToManager = useAsyncCallback(async () => {
|
const updateUserRole = useCallback(
|
||||||
|
async (userId: string, role: DocRole) => {
|
||||||
try {
|
try {
|
||||||
await docGrantedUsersService.updateUserRole(userId, DocRole.Manager);
|
const res = await docGrantedUsersService.updateUserRole(userId, role);
|
||||||
|
if (res) {
|
||||||
|
notify.success({
|
||||||
|
title:
|
||||||
|
t['com.affine.share-menu.member-management.update-success'](),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notify.error({
|
||||||
|
title: t['com.affine.share-menu.member-management.update-fail'](),
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = UserFriendlyError.fromAnyError(error);
|
const err = UserFriendlyError.fromAnyError(error);
|
||||||
notify.error({
|
notify.error({
|
||||||
title: t[`error.${err.name}`](err.data),
|
title: t[`error.${err.name}`](err.data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [docGrantedUsersService, userId, t]);
|
},
|
||||||
|
[docGrantedUsersService, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const changeToManager = useAsyncCallback(async () => {
|
||||||
|
await updateUserRole(userId, DocRole.Manager);
|
||||||
|
}, [updateUserRole, userId]);
|
||||||
|
|
||||||
const changeToEditor = useAsyncCallback(async () => {
|
const changeToEditor = useAsyncCallback(async () => {
|
||||||
if (hittingPaywall) {
|
if (hittingPaywall) {
|
||||||
openPaywallModal();
|
openPaywallModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
await updateUserRole(userId, DocRole.Editor);
|
||||||
await docGrantedUsersService.updateUserRole(userId, DocRole.Editor);
|
}, [hittingPaywall, updateUserRole, userId, openPaywallModal]);
|
||||||
} catch (error) {
|
|
||||||
const err = UserFriendlyError.fromAnyError(error);
|
|
||||||
notify.error({
|
|
||||||
title: t[`error.${err.name}`](err.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [docGrantedUsersService, hittingPaywall, openPaywallModal, userId, t]);
|
|
||||||
|
|
||||||
const changeToReader = useAsyncCallback(async () => {
|
const changeToReader = useAsyncCallback(async () => {
|
||||||
if (hittingPaywall) {
|
if (hittingPaywall) {
|
||||||
openPaywallModal();
|
openPaywallModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
await updateUserRole(userId, DocRole.Reader);
|
||||||
await docGrantedUsersService.updateUserRole(userId, DocRole.Reader);
|
}, [hittingPaywall, updateUserRole, userId, openPaywallModal]);
|
||||||
} catch (error) {
|
|
||||||
const err = UserFriendlyError.fromAnyError(error);
|
|
||||||
notify.error({
|
|
||||||
title: t[`error.${err.name}`](err.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [docGrantedUsersService, hittingPaywall, openPaywallModal, userId, t]);
|
|
||||||
|
|
||||||
const changeToOwner = useAsyncCallback(async () => {
|
const changeToOwner = useAsyncCallback(async () => {
|
||||||
try {
|
await updateUserRole(userId, DocRole.Owner);
|
||||||
await docGrantedUsersService.updateUserRole(userId, DocRole.Owner);
|
}, [updateUserRole, userId]);
|
||||||
} catch (error) {
|
|
||||||
const err = UserFriendlyError.fromAnyError(error);
|
const openTransferOwnerModal = useCallback(() => {
|
||||||
notify.error({
|
openConfirmModal({
|
||||||
title: t[`error.${err.name}`](err.data),
|
title:
|
||||||
|
t[
|
||||||
|
'com.affine.share-menu.member-management.set-as-owner.confirm.title'
|
||||||
|
](),
|
||||||
|
description:
|
||||||
|
t[
|
||||||
|
'com.affine.share-menu.member-management.set-as-owner.confirm.description'
|
||||||
|
](),
|
||||||
|
onConfirm: changeToOwner,
|
||||||
|
confirmText: t['Confirm'](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
cancelText: t['Cancel'](),
|
||||||
});
|
});
|
||||||
}
|
}, [changeToOwner, openConfirmModal, t]);
|
||||||
}, [docGrantedUsersService, userId, t]);
|
|
||||||
|
|
||||||
const removeMember = useAsyncCallback(async () => {
|
const removeMember = useAsyncCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -213,16 +236,16 @@ const Options = ({
|
|||||||
label: t['com.affine.share-menu.option.permission.can-edit'](),
|
label: t['com.affine.share-menu.option.permission.can-edit'](),
|
||||||
onClick: changeToEditor,
|
onClick: changeToEditor,
|
||||||
role: DocRole.Editor,
|
role: DocRole.Editor,
|
||||||
showPlanTag: true,
|
showPlanTag: hittingPaywall,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t['com.affine.share-menu.option.permission.can-read'](),
|
label: t['com.affine.share-menu.option.permission.can-read'](),
|
||||||
onClick: changeToReader,
|
onClick: changeToReader,
|
||||||
role: DocRole.Reader,
|
role: DocRole.Reader,
|
||||||
showPlanTag: true,
|
showPlanTag: hittingPaywall,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [changeToEditor, changeToManager, changeToReader, t]);
|
}, [changeToEditor, changeToManager, changeToReader, hittingPaywall, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -238,7 +261,7 @@ const Options = ({
|
|||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
<MenuItem onSelect={changeToOwner} disabled={!canTransferOwner}>
|
<MenuItem onSelect={openTransferOwnerModal} disabled={!canTransferOwner}>
|
||||||
{t['com.affine.share-menu.member-management.set-as-owner']()}
|
{t['com.affine.share-menu.member-management.set-as-owner']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ export const headerStyle = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottom: `1px solid ${cssVarV2('tab/divider/divider')}`,
|
borderBottom: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
padding: '4px 4px 6px',
|
padding: '0px 4px 6px',
|
||||||
|
height: '28px',
|
||||||
color: cssVarV2('text/secondary'),
|
color: cssVarV2('text/secondary'),
|
||||||
});
|
});
|
||||||
export const iconStyle = style({
|
export const iconStyle = style({
|
||||||
@@ -46,7 +47,9 @@ export const memberListStyle = style({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: '6px',
|
paddingTop: '6px',
|
||||||
maxHeight: '455px',
|
height: '100%',
|
||||||
|
minHeight: '206px',
|
||||||
|
maxHeight: '394px',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const scrollableRootStyle = style({
|
export const scrollableRootStyle = style({
|
||||||
|
|||||||
@@ -111,6 +111,17 @@ const MemberList = ({
|
|||||||
[hittingPaywall, openPaywallModal]
|
[hittingPaywall, openPaywallModal]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
<div className={styles.memberListStyle}>
|
||||||
|
{grantedUserList.length < 8 ? (
|
||||||
|
grantedUserList.map(item => (
|
||||||
|
<MemberItem
|
||||||
|
key={item.user.id}
|
||||||
|
grantedUser={item}
|
||||||
|
openPaywallModal={openPaywallModal}
|
||||||
|
hittingPaywall={hittingPaywall}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
components={{
|
components={{
|
||||||
Scroller,
|
Scroller,
|
||||||
@@ -120,5 +131,7 @@ const MemberList = ({
|
|||||||
totalCount={grantedUserCount}
|
totalCount={grantedUserCount}
|
||||||
endReached={loadMore}
|
endReached={loadMore}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
export const result = style({
|
export const result = style({
|
||||||
minHeight: '200px',
|
minHeight: '342px',
|
||||||
maxHeight: '342px',
|
maxHeight: '342px',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -149,12 +149,13 @@ export const ShareMenuContent = (props: ShareMenuProps) => {
|
|||||||
value={currentTab}
|
value={currentTab}
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
>
|
>
|
||||||
<Tabs.List>
|
<Tabs.List className={styles.tabList}>
|
||||||
<Tabs.Trigger value={ShareMenuTab.Share}>
|
<Tabs.Trigger value={ShareMenuTab.Share} className={styles.tab}>
|
||||||
{t['com.affine.share-menu.shareButton']()}
|
{t['com.affine.share-menu.shareButton']()}
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value={ShareMenuTab.Export}
|
value={ShareMenuTab.Export}
|
||||||
|
className={styles.tab}
|
||||||
style={{
|
style={{
|
||||||
display: BUILD_CONFIG.isMobileEdition ? 'none' : undefined,
|
display: BUILD_CONFIG.isMobileEdition ? 'none' : undefined,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"it-IT": 88,
|
"it-IT": 88,
|
||||||
"it": 1,
|
"it": 1,
|
||||||
"ja": 88,
|
"ja": 88,
|
||||||
"ko": 64,
|
"ko": 63,
|
||||||
"pl": 88,
|
"pl": 88,
|
||||||
"pt-BR": 88,
|
"pt-BR": 88,
|
||||||
"ru": 88,
|
"ru": 88,
|
||||||
|
|||||||
@@ -3586,6 +3586,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Real-time syncing & collaboration for more people.`
|
* `Real-time syncing & collaboration for more people.`
|
||||||
*/
|
*/
|
||||||
["com.affine.payment.cloud.pro.benefit.g1-8"](): string;
|
["com.affine.payment.cloud.pro.benefit.g1-8"](): string;
|
||||||
|
/**
|
||||||
|
* `Granular edit access to docs.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.cloud.pro.benefit.g1-9"](): string;
|
||||||
/**
|
/**
|
||||||
* `For family and small teams.`
|
* `For family and small teams.`
|
||||||
*/
|
*/
|
||||||
@@ -5886,6 +5890,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Members in workspace`
|
* `Members in workspace`
|
||||||
*/
|
*/
|
||||||
["com.affine.share-menu.option.permission.label"](): string;
|
["com.affine.share-menu.option.permission.label"](): string;
|
||||||
|
/**
|
||||||
|
* `Workspace admins and owner automatically have Can manage permissions.`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.option.permission.tips"](): string;
|
||||||
/**
|
/**
|
||||||
* `Publish to web`
|
* `Publish to web`
|
||||||
*/
|
*/
|
||||||
@@ -5933,6 +5941,22 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Set as owner`
|
* `Set as owner`
|
||||||
*/
|
*/
|
||||||
["com.affine.share-menu.member-management.set-as-owner"](): string;
|
["com.affine.share-menu.member-management.set-as-owner"](): string;
|
||||||
|
/**
|
||||||
|
* `Make this person the owner?`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.member-management.set-as-owner.confirm.title"](): string;
|
||||||
|
/**
|
||||||
|
* `The new owner will be effective immediately, and you might lose access to this doc if other users remove you, please confirm.`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.member-management.set-as-owner.confirm.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Permission updated`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.member-management.update-success"](): string;
|
||||||
|
/**
|
||||||
|
* `Failed to update permission`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.member-management.update-fail"](): string;
|
||||||
/**
|
/**
|
||||||
* `{{memberCount}} collaborators in the doc`
|
* `{{memberCount}} collaborators in the doc`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -894,6 +894,7 @@
|
|||||||
"com.affine.payment.cloud.pro.benefit.g1-6": "Add comments on Doc and Edgeless.",
|
"com.affine.payment.cloud.pro.benefit.g1-6": "Add comments on Doc and Edgeless.",
|
||||||
"com.affine.payment.cloud.pro.benefit.g1-7": "Community support.",
|
"com.affine.payment.cloud.pro.benefit.g1-7": "Community support.",
|
||||||
"com.affine.payment.cloud.pro.benefit.g1-8": "Real-time syncing & collaboration for more people.",
|
"com.affine.payment.cloud.pro.benefit.g1-8": "Real-time syncing & collaboration for more people.",
|
||||||
|
"com.affine.payment.cloud.pro.benefit.g1-9": "Granular edit access to docs.",
|
||||||
"com.affine.payment.cloud.pro.description": "For family and small teams.",
|
"com.affine.payment.cloud.pro.description": "For family and small teams.",
|
||||||
"com.affine.payment.cloud.pro.name": "Pro",
|
"com.affine.payment.cloud.pro.name": "Pro",
|
||||||
"com.affine.payment.cloud.pro.title.billed-yearly": "annually",
|
"com.affine.payment.cloud.pro.title.billed-yearly": "annually",
|
||||||
@@ -1470,6 +1471,7 @@
|
|||||||
"com.affine.share-menu.option.permission.can-edit": "Can edit",
|
"com.affine.share-menu.option.permission.can-edit": "Can edit",
|
||||||
"com.affine.share-menu.option.permission.can-read": "Can read",
|
"com.affine.share-menu.option.permission.can-read": "Can read",
|
||||||
"com.affine.share-menu.option.permission.label": "Members in workspace",
|
"com.affine.share-menu.option.permission.label": "Members in workspace",
|
||||||
|
"com.affine.share-menu.option.permission.tips": "Workspace admins and owner automatically have Can manage permissions.",
|
||||||
"com.affine.share-menu.publish-to-web": "Publish to web",
|
"com.affine.share-menu.publish-to-web": "Publish to web",
|
||||||
"com.affine.share-menu.share-privately": "Share privately",
|
"com.affine.share-menu.share-privately": "Share privately",
|
||||||
"com.affine.share-menu.shareButton": "Share",
|
"com.affine.share-menu.shareButton": "Share",
|
||||||
@@ -1479,6 +1481,10 @@
|
|||||||
"com.affine.share-menu.member-management.member-count-more": "{{member1}}, {{member2}} and {{memberCount}} others",
|
"com.affine.share-menu.member-management.member-count-more": "{{member1}}, {{member2}} and {{memberCount}} others",
|
||||||
"com.affine.share-menu.member-management.remove": "Remove",
|
"com.affine.share-menu.member-management.remove": "Remove",
|
||||||
"com.affine.share-menu.member-management.set-as-owner": "Set as owner",
|
"com.affine.share-menu.member-management.set-as-owner": "Set as owner",
|
||||||
|
"com.affine.share-menu.member-management.set-as-owner.confirm.title": "Make this person the owner?",
|
||||||
|
"com.affine.share-menu.member-management.set-as-owner.confirm.description": "The new owner will be effective immediately, and you might lose access to this doc if other users remove you, please confirm.",
|
||||||
|
"com.affine.share-menu.member-management.update-success": "Permission updated",
|
||||||
|
"com.affine.share-menu.member-management.update-fail": "Failed to update permission",
|
||||||
"com.affine.share-menu.member-management.header": "{{memberCount}} collaborators in the doc",
|
"com.affine.share-menu.member-management.header": "{{memberCount}} collaborators in the doc",
|
||||||
"com.affine.share-menu.member-management.add-collaborators": "Add collaborators",
|
"com.affine.share-menu.member-management.add-collaborators": "Add collaborators",
|
||||||
"com.affine.share-menu.invite-editor.header": "Send invite",
|
"com.affine.share-menu.invite-editor.header": "Send invite",
|
||||||
|
|||||||
Reference in New Issue
Block a user