mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
feat(core): add copy link button to local share menu (#9271)
close AF-1838
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { Button, Menu, MenuItem, MenuTrigger } from '@affine/component';
|
||||
import {
|
||||
getSelectedNodes,
|
||||
useSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import { BlockIcon, EdgelessIcon, PageIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import * as styles from './index.css';
|
||||
|
||||
export const CopyLinkButton = ({
|
||||
workspaceId,
|
||||
secondary,
|
||||
}: {
|
||||
secondary?: boolean;
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
|
||||
const editor = useService(EditorService).editor;
|
||||
const currentMode = useLiveData(editor.mode$);
|
||||
const editorContainer = useLiveData(editor.editorContainer$);
|
||||
|
||||
const { blockIds, elementIds } = useMemo(
|
||||
() => getSelectedNodes(editorContainer?.host || null, currentMode),
|
||||
[editorContainer, currentMode]
|
||||
);
|
||||
const { onClickCopyLink } = useSharingUrl({
|
||||
workspaceId,
|
||||
pageId: editor.doc.id,
|
||||
});
|
||||
|
||||
const onCopyPageLink = useCallback(() => {
|
||||
onClickCopyLink('page' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyEdgelessLink = useCallback(() => {
|
||||
onClickCopyLink('edgeless' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyBlockLink = useCallback(() => {
|
||||
onClickCopyLink(currentMode, blockIds, elementIds);
|
||||
}, [onClickCopyLink, currentMode, blockIds, elementIds]);
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.copyLinkContainerStyle, { secondary: secondary })}
|
||||
>
|
||||
<Button
|
||||
className={styles.copyLinkButtonStyle}
|
||||
onClick={onCopyBlockLink}
|
||||
withoutHover
|
||||
variant={secondary ? 'secondary' : 'primary'}
|
||||
>
|
||||
<span
|
||||
className={clsx(styles.copyLinkLabelStyle, {
|
||||
secondary: secondary,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.share-menu.copy']()}
|
||||
</span>
|
||||
{BUILD_CONFIG.isDesktopEdition && (
|
||||
<span
|
||||
className={clsx(styles.copyLinkShortcutStyle, {
|
||||
secondary: secondary,
|
||||
})}
|
||||
>
|
||||
{environment.isMacOs ? '⌘ + ⌥ + C' : 'Ctrl + Shift + C'}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Menu
|
||||
contentOptions={{
|
||||
align: 'end',
|
||||
}}
|
||||
items={
|
||||
<>
|
||||
<MenuItem
|
||||
prefixIcon={<PageIcon />}
|
||||
onSelect={onCopyPageLink}
|
||||
data-testid="share-link-menu-copy-page"
|
||||
>
|
||||
{t['com.affine.share-menu.copy.page']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<EdgelessIcon />}
|
||||
onSelect={onCopyEdgelessLink}
|
||||
data-testid="share-link-menu-copy-edgeless"
|
||||
>
|
||||
{t['com.affine.share-menu.copy.edgeless']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<BlockIcon />}
|
||||
onSelect={onCopyBlockLink}
|
||||
disabled={blockIds.length + elementIds.length === 0}
|
||||
>
|
||||
{t['com.affine.share-menu.copy.block']()}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuTrigger
|
||||
variant={secondary ? 'secondary' : 'primary'}
|
||||
className={clsx(styles.copyLinkTriggerStyle, {
|
||||
secondary: secondary,
|
||||
})}
|
||||
data-testid="share-menu-copy-link-button"
|
||||
suffixStyle={{ width: 20, height: 20 }}
|
||||
withoutHover
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -54,6 +54,12 @@ export const copyLinkContainerStyle = style({
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
selectors: {
|
||||
'&.secondary': {
|
||||
padding: 0,
|
||||
marginTop: '12px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const copyLinkButtonStyle = style({
|
||||
flex: 1,
|
||||
@@ -64,6 +70,14 @@ export const copyLinkButtonStyle = style({
|
||||
borderBottomRightRadius: '0',
|
||||
color: 'transparent',
|
||||
position: 'initial',
|
||||
selectors: {
|
||||
'&.dark': {
|
||||
backgroundColor: cssVarV2('layer/pureBlack'),
|
||||
},
|
||||
'&.dark::hover': {
|
||||
backgroundColor: cssVarV2('layer/pureBlack'),
|
||||
},
|
||||
},
|
||||
});
|
||||
export const copyLinkLabelContainerStyle = style({
|
||||
width: '100%',
|
||||
@@ -80,6 +94,11 @@ export const copyLinkLabelStyle = style({
|
||||
transform: 'translateX(-50%) translateY(-50%)',
|
||||
lineHeight: '20px',
|
||||
color: cssVarV2('text/pureWhite'),
|
||||
selectors: {
|
||||
'&.secondary': {
|
||||
color: cssVarV2('text/primary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
export const copyLinkShortcutStyle = style({
|
||||
position: 'absolute',
|
||||
@@ -90,6 +109,11 @@ export const copyLinkShortcutStyle = style({
|
||||
opacity: 0.5,
|
||||
lineHeight: '20px',
|
||||
color: cssVarV2('text/pureWhite'),
|
||||
selectors: {
|
||||
'&.secondary': {
|
||||
color: cssVarV2('text/secondary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
export const copyLinkTriggerStyle = style({
|
||||
padding: '4px 12px 4px 8px',
|
||||
@@ -109,11 +133,25 @@ export const copyLinkTriggerStyle = style({
|
||||
width: '1px',
|
||||
backgroundColor: cssVarV2('button/innerBlackBorder'),
|
||||
},
|
||||
selectors: {
|
||||
'&.secondary': {
|
||||
backgroundColor: cssVarV2('button/secondary'),
|
||||
color: cssVarV2('text/secondary'),
|
||||
},
|
||||
'&.secondary:hover': {
|
||||
backgroundColor: cssVarV2('button/secondary'),
|
||||
color: cssVarV2('text/secondary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
globalStyle(`${copyLinkTriggerStyle} svg`, {
|
||||
color: cssVarV2('button/pureWhiteText'),
|
||||
transform: 'translateX(2px)',
|
||||
});
|
||||
globalStyle(`${copyLinkTriggerStyle}.secondary svg`, {
|
||||
color: cssVarV2('text/secondary'),
|
||||
transform: 'translateX(2px)',
|
||||
});
|
||||
export const copyLinkMenuItemStyle = style({
|
||||
padding: '4px',
|
||||
transition: 'all 0.3s',
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
import { notify, Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
|
||||
import {
|
||||
getSelectedNodes,
|
||||
useSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
import { PublicPageMode } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
BlockIcon,
|
||||
CollaborationIcon,
|
||||
DoneIcon,
|
||||
EdgelessIcon,
|
||||
LockIcon,
|
||||
PageIcon,
|
||||
SingleSelectCheckSolidIcon,
|
||||
ViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Suspense, useCallback, useEffect } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
import { CloudSvg } from '../cloud-svg';
|
||||
import { CopyLinkButton } from './copy-link-button';
|
||||
import * as styles from './index.css';
|
||||
import type { ShareMenuProps } from './share-menu';
|
||||
|
||||
export const LocalSharePage = (props: ShareMenuProps) => {
|
||||
const t = useI18n();
|
||||
|
||||
const {
|
||||
workspaceMetadata: { id: workspaceId },
|
||||
} = props;
|
||||
return (
|
||||
<div className={styles.localSharePage}>
|
||||
<div className={styles.columnContainerStyle} style={{ gap: '12px' }}>
|
||||
<div className={styles.descriptionStyle} style={{ maxWidth: '230px' }}>
|
||||
{t['com.affine.share-menu.EnableCloudDescription']()}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={props.onEnableAffineCloud}
|
||||
variant="primary"
|
||||
data-testid="share-menu-enable-affine-cloud-button"
|
||||
<>
|
||||
<div className={styles.localSharePage}>
|
||||
<div className={styles.columnContainerStyle} style={{ gap: '12px' }}>
|
||||
<div
|
||||
className={styles.descriptionStyle}
|
||||
style={{ maxWidth: '230px' }}
|
||||
>
|
||||
{t['Enable AFFiNE Cloud']()}
|
||||
</Button>
|
||||
{t['com.affine.share-menu.EnableCloudDescription']()}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={props.onEnableAffineCloud}
|
||||
variant="primary"
|
||||
data-testid="share-menu-enable-affine-cloud-button"
|
||||
>
|
||||
{t['Enable AFFiNE Cloud']()}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.cloudSvgContainer}>
|
||||
<CloudSvg />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.cloudSvgContainer}>
|
||||
<CloudSvg />
|
||||
</div>
|
||||
</div>
|
||||
<CopyLinkButton workspaceId={workspaceId} secondary />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -65,9 +65,6 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
const {
|
||||
workspaceMetadata: { id: workspaceId },
|
||||
} = props;
|
||||
const editor = useService(EditorService).editor;
|
||||
const currentMode = useLiveData(editor.mode$);
|
||||
const editorContainer = useLiveData(editor.editorContainer$);
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const serverService = useService(ServerService);
|
||||
useEffect(() => {
|
||||
@@ -152,25 +149,6 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
}
|
||||
}, [shareInfoService, t]);
|
||||
|
||||
const { blockIds, elementIds } = useMemo(
|
||||
() => getSelectedNodes(editorContainer?.host || null, currentMode),
|
||||
[editorContainer, currentMode]
|
||||
);
|
||||
const { onClickCopyLink } = useSharingUrl({
|
||||
workspaceId,
|
||||
pageId: editor.doc.id,
|
||||
});
|
||||
|
||||
const onCopyPageLink = useCallback(() => {
|
||||
onClickCopyLink('page' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyEdgelessLink = useCallback(() => {
|
||||
onClickCopyLink('edgeless' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyBlockLink = useCallback(() => {
|
||||
onClickCopyLink(currentMode, blockIds, elementIds);
|
||||
}, [onClickCopyLink, currentMode, blockIds, elementIds]);
|
||||
|
||||
if (isLoading) {
|
||||
// TODO(@eyhn): loading and error UI
|
||||
return (
|
||||
@@ -254,61 +232,7 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
{t['com.affine.share-menu.navigate.workspace']()}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.copyLinkContainerStyle}>
|
||||
<Button
|
||||
className={styles.copyLinkButtonStyle}
|
||||
onClick={onCopyBlockLink}
|
||||
variant="primary"
|
||||
withoutHover
|
||||
>
|
||||
<span className={styles.copyLinkLabelStyle}>
|
||||
{t['com.affine.share-menu.copy']()}
|
||||
</span>
|
||||
{BUILD_CONFIG.isDesktopEdition && (
|
||||
<span className={styles.copyLinkShortcutStyle}>
|
||||
{environment.isMacOs ? '⌘ + ⌥ + C' : 'Ctrl + Shift + C'}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Menu
|
||||
contentOptions={{
|
||||
align: 'end',
|
||||
}}
|
||||
items={
|
||||
<>
|
||||
<MenuItem
|
||||
prefixIcon={<PageIcon />}
|
||||
onSelect={onCopyPageLink}
|
||||
data-testid="share-link-menu-copy-page"
|
||||
>
|
||||
{t['com.affine.share-menu.copy.page']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<EdgelessIcon />}
|
||||
onSelect={onCopyEdgelessLink}
|
||||
data-testid="share-link-menu-copy-edgeless"
|
||||
>
|
||||
{t['com.affine.share-menu.copy.edgeless']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<BlockIcon />}
|
||||
onSelect={onCopyBlockLink}
|
||||
disabled={blockIds.length + elementIds.length === 0}
|
||||
>
|
||||
{t['com.affine.share-menu.copy.block']()}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuTrigger
|
||||
variant="primary"
|
||||
className={styles.copyLinkTriggerStyle}
|
||||
data-testid="share-menu-copy-link-button"
|
||||
suffixStyle={{ width: 20, height: 20 }}
|
||||
withoutHover
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
<CopyLinkButton workspaceId={workspaceId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user