feat: workspace level share settings (#14201)

fix #13698
This commit is contained in:
DarkSky
2026-01-03 01:13:27 +08:00
committed by GitHub
parent 60de882a30
commit 9a7f8e7d4d
36 changed files with 560 additions and 34 deletions
@@ -1,8 +1,11 @@
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { WorkspaceShareSettingService } from '@affine/core/modules/share-setting';
import type { Workspace } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { Store } from '@blocksuite/affine/store';
import { useCallback } from 'react';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect } from 'react';
import { ShareMenu } from './share-menu';
export { CloudSvg } from './cloud-svg';
@@ -14,6 +17,10 @@ type SharePageModalProps = {
};
export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
const t = useI18n();
const shareSetting = useService(WorkspaceShareSettingService).sharePreview;
const enableSharing = useLiveData(shareSetting.enableSharing$);
const confirmEnableCloud = useEnableCloud();
const handleOpenShareModal = useCallback((open: boolean) => {
if (open) {
@@ -21,6 +28,18 @@ export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
}
}, []);
useEffect(() => {
if (workspace.meta.flavour === 'local') {
return;
}
shareSetting.revalidate();
}, [shareSetting, workspace.meta.flavour]);
const sharingDisabled = enableSharing === false;
const disabledReason = sharingDisabled
? t['com.affine.share-menu.workspace-sharing.disabled.tooltip']()
: undefined;
return (
<ShareMenu
workspaceMetadata={workspace.meta}
@@ -31,6 +50,8 @@ export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
})
}
onOpenShareModal={handleOpenShareModal}
disabled={sharingDisabled}
disabledReason={disabledReason}
/>
);
};
@@ -35,6 +35,8 @@ export interface ShareMenuProps extends PropsWithChildren {
onOpenShareModal?: (open: boolean) => void;
openPaywallModal?: () => void;
hittingPaywall?: boolean;
disabled?: boolean;
disabledReason?: string;
}
export enum ShareMenuTab {
@@ -203,7 +205,7 @@ export const ShareMenuContent = (props: ShareMenuProps) => {
};
const DefaultShareButton = forwardRef(function DefaultShareButton(
_,
props: { disabled?: boolean; tooltip?: string },
ref: Ref<HTMLButtonElement>
) {
const t = useI18n();
@@ -211,18 +213,26 @@ const DefaultShareButton = forwardRef(function DefaultShareButton(
const shared = useLiveData(shareInfoService.shareInfo.isShared$);
useEffect(() => {
if (props.disabled) {
return;
}
shareInfoService.shareInfo.revalidate();
}, [shareInfoService]);
}, [props.disabled, shareInfoService]);
const tooltip =
props.tooltip ??
(shared
? t['com.affine.share-menu.option.link.readonly.description']()
: t['com.affine.share-menu.option.link.no-access.description']());
return (
<Tooltip
content={
shared
? t['com.affine.share-menu.option.link.readonly.description']()
: t['com.affine.share-menu.option.link.no-access.description']()
}
>
<Button ref={ref} className={styles.button} variant="primary">
<Tooltip content={tooltip}>
<Button
ref={ref}
className={styles.button}
variant="primary"
disabled={props.disabled}
>
<div className={styles.buttonContainer}>
{shared ? <PublishIcon fontSize={16} /> : <LockIcon fontSize={16} />}
{t['com.affine.share-menu.shareButton']()}
@@ -233,6 +243,13 @@ const DefaultShareButton = forwardRef(function DefaultShareButton(
});
const LocalShareMenu = (props: ShareMenuProps) => {
if (props.disabled) {
return (
<div data-testid="local-share-menu-button">
<DefaultShareButton disabled tooltip={props.disabledReason} />
</div>
);
}
return (
<Menu
items={<ShareMenuContent {...props} />}
@@ -254,6 +271,13 @@ const LocalShareMenu = (props: ShareMenuProps) => {
};
const CloudShareMenu = (props: ShareMenuProps) => {
if (props.disabled) {
return (
<div data-testid="cloud-share-menu-button">
<DefaultShareButton disabled tooltip={props.disabledReason} />
</div>
);
}
return (
<Menu
items={<ShareMenuContent {...props} />}
@@ -16,6 +16,7 @@ import type { WorkspaceService } from '../../workspace';
import type { WorkspaceShareSettingStore } from '../stores/share-setting';
type EnableAi = GetWorkspaceConfigQuery['workspace']['enableAi'];
type EnableSharing = GetWorkspaceConfigQuery['workspace']['enableSharing'];
type EnableUrlPreview =
GetWorkspaceConfigQuery['workspace']['enableUrlPreview'];
@@ -23,6 +24,7 @@ const logger = new DebugLogger('affine:workspace-permission');
export class WorkspaceShareSetting extends Entity {
enableAi$ = new LiveData<EnableAi | null>(null);
enableSharing$ = new LiveData<EnableSharing | null>(null);
enableUrlPreview$ = new LiveData<EnableUrlPreview | null>(null);
inviteLink$ = new LiveData<InviteLink | null>(null);
isLoading$ = new LiveData(false);
@@ -48,12 +50,13 @@ export class WorkspaceShareSetting extends Entity {
tap(value => {
if (value) {
this.enableAi$.next(value.enableAi);
this.enableSharing$.next(value.enableSharing);
this.enableUrlPreview$.next(value.enableUrlPreview);
this.inviteLink$.next(value.inviteLink);
}
}),
catchErrorInto(this.error$, error => {
logger.error('Failed to fetch enableUrlPreview', error);
logger.error('Failed to fetch workspace share settings', error);
}),
onStart(() => this.isLoading$.setValue(true)),
onComplete(() => this.isLoading$.setValue(false))
@@ -74,6 +77,14 @@ export class WorkspaceShareSetting extends Entity {
await this.waitForRevalidation();
}
async setEnableSharing(enableSharing: EnableSharing) {
await this.store.updateWorkspaceEnableSharing(
this.workspaceService.workspace.id,
enableSharing
);
await this.waitForRevalidation();
}
async setEnableAi(enableAi: EnableAi) {
await this.store.updateWorkspaceEnableAi(
this.workspaceService.workspace.id,
@@ -2,6 +2,7 @@ import type { WorkspaceServerService } from '@affine/core/modules/cloud';
import {
getWorkspaceConfigQuery,
setEnableAiMutation,
setEnableSharingMutation,
setEnableUrlPreviewMutation,
} from '@affine/graphql';
import { Store } from '@toeverything/infra';
@@ -47,6 +48,26 @@ export class WorkspaceShareSettingStore extends Store {
});
}
async updateWorkspaceEnableSharing(
workspaceId: string,
enableSharing: boolean,
signal?: AbortSignal
) {
if (!this.workspaceServerService.server) {
throw new Error('No Server');
}
await this.workspaceServerService.server.gql({
query: setEnableSharingMutation,
variables: {
id: workspaceId,
enableSharing,
},
context: {
signal,
},
});
}
async updateWorkspaceEnableUrlPreview(
workspaceId: string,
enableUrlPreview: boolean,