feat(core): new share menu (#7838)
close AF-1224 AF-1216 
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"@radix-ui/react-toolbar": "^1.0.4",
|
"@radix-ui/react-toolbar": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export * from './ui/scrollbar';
|
|||||||
export * from './ui/skeleton';
|
export * from './ui/skeleton';
|
||||||
export * from './ui/switch';
|
export * from './ui/switch';
|
||||||
export * from './ui/table';
|
export * from './ui/table';
|
||||||
|
export * from './ui/tabs';
|
||||||
export * from './ui/toast';
|
export * from './ui/toast';
|
||||||
export * from './ui/tooltip';
|
export * from './ui/tooltip';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|||||||
1
packages/frontend/component/src/ui/tabs/index.ts
Normal file
1
packages/frontend/component/src/ui/tabs/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './tabs';
|
||||||
52
packages/frontend/component/src/ui/tabs/tabs.css.ts
Normal file
52
packages/frontend/component/src/ui/tabs/tabs.css.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const tabsRoot = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabsList = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '12px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
position: 'relative',
|
||||||
|
'::after': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '0px',
|
||||||
|
width: '100%',
|
||||||
|
height: '1px',
|
||||||
|
backgroundColor: cssVarV2('layer/border'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabsTrigger = style({
|
||||||
|
all: 'unset',
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: '6px 4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
borderBottom: '2px solid transparent',
|
||||||
|
selectors: {
|
||||||
|
'&[data-state="active"]': {
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
borderColor: cssVarV2('button/primary'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabsContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&[data-state="inactive"]': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
80
packages/frontend/component/src/ui/tabs/tabs.tsx
Normal file
80
packages/frontend/component/src/ui/tabs/tabs.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import * as TabsGroup from '@radix-ui/react-tabs';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { forwardRef, type RefAttributes } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './tabs.css';
|
||||||
|
|
||||||
|
export const TabsRoot = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
TabsGroup.TabsProps & RefAttributes<HTMLDivElement>
|
||||||
|
>(({ children, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<TabsGroup.Root
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(className, styles.tabsRoot)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsGroup.Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TabsRoot.displayName = 'TabsRoot';
|
||||||
|
|
||||||
|
export const TabsList = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
TabsGroup.TabsListProps & RefAttributes<HTMLDivElement>
|
||||||
|
>(({ children, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<TabsGroup.List
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(className, styles.tabsList)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsGroup.List>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TabsList.displayName = 'TabsList';
|
||||||
|
|
||||||
|
export const TabsTrigger = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
TabsGroup.TabsTriggerProps & RefAttributes<HTMLButtonElement>
|
||||||
|
>(({ children, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<TabsGroup.Trigger
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(className, styles.tabsTrigger)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsGroup.Trigger>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TabsTrigger.displayName = 'TabsTrigger';
|
||||||
|
|
||||||
|
export const TabsContent = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
TabsGroup.TabsContentProps & RefAttributes<HTMLDivElement>
|
||||||
|
>(({ children, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<TabsGroup.Content
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(className, styles.tabsContent)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsGroup.Content>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TabsContent.displayName = 'TabsContent';
|
||||||
|
|
||||||
|
export const Tabs = {
|
||||||
|
Root: TabsRoot,
|
||||||
|
List: TabsList,
|
||||||
|
Trigger: TabsTrigger,
|
||||||
|
Content: TabsContent,
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
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 headerStyle = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -9,34 +10,133 @@ export const headerStyle = style({
|
|||||||
padding: '0 4px',
|
padding: '0 4px',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
});
|
});
|
||||||
|
export const content = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
export const menuStyle = style({
|
export const menuStyle = style({
|
||||||
width: '410px',
|
width: '390px',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
transform: 'translateX(-10px)',
|
});
|
||||||
|
export const menuTriggerStyle = style({
|
||||||
|
width: '150px',
|
||||||
|
padding: '4px 10px',
|
||||||
|
justifyContent: 'space-between',
|
||||||
});
|
});
|
||||||
export const menuItemStyle = style({
|
export const menuItemStyle = style({
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
|
});
|
||||||
|
export const publicItemRowStyle = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
export const publicMenuItemPrefixStyle = style({
|
||||||
|
fontSize: cssVar('fontH5'),
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
marginRight: '8px',
|
||||||
|
});
|
||||||
|
export const DoneIconStyle = style({
|
||||||
|
color: cssVarV2('button/primary'),
|
||||||
|
fontSize: cssVar('fontH5'),
|
||||||
|
marginLeft: '8px',
|
||||||
|
});
|
||||||
|
export const exportItemStyle = style({
|
||||||
|
padding: '4px',
|
||||||
|
transition: 'all 0.3s',
|
||||||
|
});
|
||||||
|
export const copyLinkContainerStyle = style({
|
||||||
|
padding: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
export const copyLinkButtonStyle = style({
|
||||||
|
flex: 1,
|
||||||
|
padding: '4px 12px',
|
||||||
|
paddingRight: '6px',
|
||||||
|
borderRight: 'none',
|
||||||
|
borderTopRightRadius: '0',
|
||||||
|
borderBottomRightRadius: '0',
|
||||||
|
color: 'transparent',
|
||||||
|
position: 'initial',
|
||||||
|
});
|
||||||
|
export const copyLinkLabelContainerStyle = style({
|
||||||
|
width: '100%',
|
||||||
|
padding: '4px 12px',
|
||||||
|
borderRight: 'none',
|
||||||
|
borderTopRightRadius: '0',
|
||||||
|
borderBottomRightRadius: '0',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
export const copyLinkLabelStyle = style({
|
||||||
|
position: 'absolute',
|
||||||
|
textAlign: 'end',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%) translateY(-50%)',
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/pureWhite'),
|
||||||
|
});
|
||||||
|
export const copyLinkShortcutStyle = style({
|
||||||
|
position: 'absolute',
|
||||||
|
textAlign: 'end',
|
||||||
|
top: '50%',
|
||||||
|
right: '52px',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
opacity: 0.5,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/pureWhite'),
|
||||||
|
});
|
||||||
|
export const copyLinkTriggerStyle = style({
|
||||||
|
width: '100%',
|
||||||
|
padding: '4px 12px',
|
||||||
|
paddingLeft: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
border: `1px solid ${cssVarV2('button/innerBlackBorder')}`,
|
||||||
|
flex: 0,
|
||||||
|
justifyContent: 'end',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: cssVarV2('button/primary'),
|
||||||
|
color: cssVarV2('button/pureWhiteText'),
|
||||||
|
borderLeft: 'none',
|
||||||
|
borderTopLeftRadius: '0',
|
||||||
|
borderBottomLeftRadius: '0',
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: cssVarV2('button/primary'),
|
||||||
|
color: cssVarV2('button/pureWhiteText'),
|
||||||
|
},
|
||||||
|
'::before': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '0',
|
||||||
|
height: '100%',
|
||||||
|
width: '1px',
|
||||||
|
backgroundColor: cssVarV2('button/innerBlackBorder'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
globalStyle(`${copyLinkTriggerStyle} svg`, {
|
||||||
|
color: cssVarV2('button/pureWhiteText'),
|
||||||
|
transform: 'translateX(2px)',
|
||||||
|
});
|
||||||
|
export const copyLinkMenuItemStyle = style({
|
||||||
|
padding: '4px',
|
||||||
transition: 'all 0.3s',
|
transition: 'all 0.3s',
|
||||||
});
|
});
|
||||||
export const descriptionStyle = style({
|
export const descriptionStyle = style({
|
||||||
wordWrap: 'break-word',
|
wordWrap: 'break-word',
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
color: cssVar('textSecondaryColor'),
|
color: cssVarV2('text/secondary'),
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
padding: '0 6px',
|
padding: '0 6px',
|
||||||
});
|
});
|
||||||
export const buttonStyle = style({
|
|
||||||
marginTop: '18px',
|
|
||||||
});
|
|
||||||
export const actionsStyle = style({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '9px',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
});
|
|
||||||
export const containerStyle = style({
|
export const containerStyle = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -46,25 +146,13 @@ export const containerStyle = style({
|
|||||||
export const indicatorContainerStyle = style({
|
export const indicatorContainerStyle = style({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
export const inputButtonRowStyle = style({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: '16px',
|
|
||||||
});
|
|
||||||
export const titleContainerStyle = style({
|
export const titleContainerStyle = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '4px',
|
fontSize: cssVar('fontXs'),
|
||||||
fontSize: cssVar('fontSm'),
|
color: cssVarV2('text/secondary'),
|
||||||
fontWeight: 500,
|
fontWeight: 400,
|
||||||
lineHeight: '22px',
|
padding: '8px 4px 0 4px',
|
||||||
padding: '0 4px',
|
|
||||||
});
|
|
||||||
export const subTitleStyle = style({
|
|
||||||
fontSize: cssVar('fontSm'),
|
|
||||||
fontWeight: 500,
|
|
||||||
lineHeight: '22px',
|
|
||||||
});
|
});
|
||||||
export const columnContainerStyle = style({
|
export const columnContainerStyle = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -78,33 +166,21 @@ export const rowContainerStyle = style({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '12px',
|
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
});
|
});
|
||||||
export const radioButtonGroup = style({
|
export const labelStyle = style({
|
||||||
display: 'flex',
|
fontSize: cssVar('fontSm'),
|
||||||
justifyContent: 'flex-end',
|
fontWeight: 500,
|
||||||
padding: '2px',
|
|
||||||
minWidth: '154px',
|
|
||||||
maxWidth: '250px',
|
|
||||||
});
|
|
||||||
export const radioButton = style({
|
|
||||||
color: cssVar('textSecondaryColor'),
|
|
||||||
selectors: {
|
|
||||||
'&[data-state="checked"]': {
|
|
||||||
color: cssVar('textPrimaryColor'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
export const disableSharePage = style({
|
export const disableSharePage = style({
|
||||||
color: cssVar('errorColor'),
|
color: cssVarV2('button/error'),
|
||||||
});
|
});
|
||||||
export const localSharePage = style({
|
export const localSharePage = style({
|
||||||
padding: '12px 8px',
|
padding: '12px 8px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
backgroundColor: cssVar('backgroundSecondaryColor'),
|
backgroundColor: cssVarV2('layer/background/secondary'),
|
||||||
minHeight: '84px',
|
minHeight: '84px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
@@ -117,12 +193,6 @@ export const cloudSvgContainer = style({
|
|||||||
bottom: '0',
|
bottom: '0',
|
||||||
right: '0',
|
right: '0',
|
||||||
});
|
});
|
||||||
export const shareIconStyle = style({
|
|
||||||
fontSize: '16px',
|
|
||||||
color: cssVar('iconColor'),
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
});
|
|
||||||
export const shareLinkStyle = style({
|
export const shareLinkStyle = style({
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
@@ -132,17 +202,38 @@ export const shareLinkStyle = style({
|
|||||||
gap: '4px',
|
gap: '4px',
|
||||||
});
|
});
|
||||||
globalStyle(`${shareLinkStyle} > span`, {
|
globalStyle(`${shareLinkStyle} > span`, {
|
||||||
color: cssVar('linkColor'),
|
color: cssVarV2('text/link'),
|
||||||
});
|
});
|
||||||
globalStyle(`${shareLinkStyle} > div > svg`, {
|
globalStyle(`${shareLinkStyle} > div > svg`, {
|
||||||
color: cssVar('linkColor'),
|
color: cssVarV2('text/link'),
|
||||||
});
|
});
|
||||||
export const shareButton = style({
|
export const buttonContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
fontWeight: 500,
|
||||||
|
});
|
||||||
|
export const button = style({
|
||||||
|
padding: '6px 8px',
|
||||||
height: 32,
|
height: 32,
|
||||||
padding: '0px 8px',
|
|
||||||
});
|
});
|
||||||
export const shortcutStyle = style({
|
export const shortcutStyle = style({
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
color: cssVar('textSecondaryColor'),
|
color: cssVarV2('text/secondary'),
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
});
|
});
|
||||||
|
export const openWorkspaceSettingsStyle = style({
|
||||||
|
color: cssVarV2('text/link'),
|
||||||
|
fontSize: cssVar('fontXs'),
|
||||||
|
fontWeight: 500,
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
width: '100%',
|
||||||
|
padding: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
globalStyle(`${openWorkspaceSettingsStyle} svg`, {
|
||||||
|
color: cssVarV2('text/link'),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,80 +1,30 @@
|
|||||||
import { MenuIcon, MenuItem } from '@affine/component';
|
|
||||||
import { Divider } from '@affine/component/ui/divider';
|
|
||||||
import { ExportMenuItems } from '@affine/core/components/page-list';
|
import { ExportMenuItems } from '@affine/core/components/page-list';
|
||||||
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
|
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
|
||||||
import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url';
|
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { CopyIcon } from '@blocksuite/icons/rc';
|
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
import type { ShareMenuProps } from './share-menu';
|
import type { ShareMenuProps } from './share-menu';
|
||||||
|
|
||||||
export const ShareExport = ({
|
export const ShareExport = ({ currentPage }: ShareMenuProps) => {
|
||||||
workspaceMetadata: workspace,
|
|
||||||
currentPage,
|
|
||||||
}: ShareMenuProps) => {
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const editor = useService(EditorService).editor;
|
const editor = useService(EditorService).editor;
|
||||||
const workspaceId = workspace.id;
|
|
||||||
const pageId = currentPage.id;
|
|
||||||
const { sharingUrl, onClickCopyLink } = useSharingUrl({
|
|
||||||
workspaceId,
|
|
||||||
pageId,
|
|
||||||
urlType: 'workspace',
|
|
||||||
});
|
|
||||||
const exportHandler = useExportPage(currentPage);
|
const exportHandler = useExportPage(currentPage);
|
||||||
const currentMode = useLiveData(editor.mode$);
|
const currentMode = useLiveData(editor.mode$);
|
||||||
const isMac = environment.isBrowser && environment.isMacOs;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.titleContainerStyle}>
|
|
||||||
{t['com.affine.share-menu.ShareViaExport']()}
|
|
||||||
</div>
|
|
||||||
<div className={styles.descriptionStyle}>
|
<div className={styles.descriptionStyle}>
|
||||||
{t['com.affine.share-menu.ShareViaExportDescription']()}
|
{t['com.affine.share-menu.ShareViaExportDescription']()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ExportMenuItems
|
<ExportMenuItems
|
||||||
exportHandler={exportHandler}
|
exportHandler={exportHandler}
|
||||||
className={styles.menuItemStyle}
|
className={styles.exportItemStyle}
|
||||||
pageMode={currentMode}
|
pageMode={currentMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{workspace.flavour !== WorkspaceFlavour.LOCAL ? (
|
|
||||||
<div className={styles.columnContainerStyle}>
|
|
||||||
<Divider size="thinner" />
|
|
||||||
<div className={styles.titleContainerStyle}>
|
|
||||||
{t['com.affine.share-menu.share-privately']()}
|
|
||||||
</div>
|
|
||||||
<div className={styles.descriptionStyle}>
|
|
||||||
{t['com.affine.share-menu.share-privately.description']()}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<MenuItem
|
|
||||||
className={styles.shareLinkStyle}
|
|
||||||
onSelect={onClickCopyLink}
|
|
||||||
block
|
|
||||||
disabled={!sharingUrl}
|
|
||||||
preFix={
|
|
||||||
<MenuIcon>
|
|
||||||
<CopyIcon fontSize={16} />
|
|
||||||
</MenuIcon>
|
|
||||||
}
|
|
||||||
endFix={
|
|
||||||
<div className={styles.shortcutStyle}>
|
|
||||||
{isMac ? '⌘ + ⌥ + C' : 'Ctrl + Shift + C'}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t['com.affine.share-menu.copy-private-link']()}
|
|
||||||
</MenuItem>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { Tabs, Tooltip } from '@affine/component';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { Divider } from '@affine/component/ui/divider';
|
|
||||||
import { Menu } from '@affine/component/ui/menu';
|
import { Menu } from '@affine/component/ui/menu';
|
||||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { WebIcon } from '@blocksuite/icons/rc';
|
import { LockIcon, PublishIcon } from '@blocksuite/icons/rc';
|
||||||
import type { Doc } from '@blocksuite/store';
|
import type { Doc } from '@blocksuite/store';
|
||||||
import {
|
import {
|
||||||
useLiveData,
|
useLiveData,
|
||||||
@@ -28,17 +28,20 @@ export const ShareMenuContent = (props: ShareMenuProps) => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<div className={styles.containerStyle}>
|
<div className={styles.containerStyle}>
|
||||||
<div className={styles.headerStyle}>
|
<Tabs.Root defaultValue="share">
|
||||||
<div className={styles.shareIconStyle}>
|
<Tabs.List>
|
||||||
<WebIcon />
|
<Tabs.Trigger value="share">
|
||||||
</div>
|
{t['com.affine.share-menu.shareButton']()}
|
||||||
{t['com.affine.share-menu.SharePage']()}
|
</Tabs.Trigger>
|
||||||
</div>
|
<Tabs.Trigger value="export">{t['Export']()}</Tabs.Trigger>
|
||||||
|
</Tabs.List>
|
||||||
|
<Tabs.Content value="share">
|
||||||
<SharePage {...props} />
|
<SharePage {...props} />
|
||||||
<div className={styles.columnContainerStyle}>
|
</Tabs.Content>
|
||||||
<Divider size="thinner" />
|
<Tabs.Content value="export">
|
||||||
</div>
|
|
||||||
<ShareExport {...props} />
|
<ShareExport {...props} />
|
||||||
|
</Tabs.Content>
|
||||||
|
</Tabs.Root>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -56,11 +59,20 @@ const DefaultShareButton = forwardRef(function DefaultShareButton(
|
|||||||
}, [shareInfoService]);
|
}, [shareInfoService]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button ref={ref} className={styles.shareButton} variant="primary">
|
<Tooltip
|
||||||
{shared
|
content={
|
||||||
? t['com.affine.share-menu.sharedButton']()
|
shared
|
||||||
: t['com.affine.share-menu.shareButton']()}
|
? 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}>
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
{shared ? <PublishIcon fontSize={16} /> : <LockIcon fontSize={16} />}
|
||||||
|
{t['com.affine.share-menu.shareButton']()}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +83,7 @@ const LocalShareMenu = (props: ShareMenuProps) => {
|
|||||||
contentOptions={{
|
contentOptions={{
|
||||||
className: styles.menuStyle,
|
className: styles.menuStyle,
|
||||||
['data-testid' as string]: 'local-share-menu',
|
['data-testid' as string]: 'local-share-menu',
|
||||||
|
align: 'end',
|
||||||
}}
|
}}
|
||||||
rootOptions={{
|
rootOptions={{
|
||||||
modal: false,
|
modal: false,
|
||||||
@@ -91,6 +104,7 @@ const CloudShareMenu = (props: ShareMenuProps) => {
|
|||||||
contentOptions={{
|
contentOptions={{
|
||||||
className: styles.menuStyle,
|
className: styles.menuStyle,
|
||||||
['data-testid' as string]: 'cloud-share-menu',
|
['data-testid' as string]: 'cloud-share-menu',
|
||||||
|
align: 'end',
|
||||||
}}
|
}}
|
||||||
rootOptions={{
|
rootOptions={{
|
||||||
modal: false,
|
modal: false,
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import { Input, notify, RadioGroup, Skeleton, Switch } from '@affine/component';
|
import { notify, Skeleton } from '@affine/component';
|
||||||
import { PublicLinkDisableModal } from '@affine/component/disable-public-link';
|
import { PublicLinkDisableModal } from '@affine/component/disable-public-link';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
|
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
|
||||||
|
import { openSettingModalAtom } from '@affine/core/atoms';
|
||||||
import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url';
|
import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url';
|
||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
import { ServerConfigService } from '@affine/core/modules/cloud';
|
import { ServerConfigService } from '@affine/core/modules/cloud';
|
||||||
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { PublicPageMode } from '@affine/graphql';
|
import { PublicPageMode } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
ArrowRightSmallIcon,
|
CollaborationIcon,
|
||||||
|
DoneIcon,
|
||||||
|
EdgelessIcon,
|
||||||
|
LinkIcon,
|
||||||
|
LockIcon,
|
||||||
|
PageIcon,
|
||||||
SingleSelectSelectSolidIcon,
|
SingleSelectSelectSolidIcon,
|
||||||
|
ViewIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import {
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
type DocMode,
|
|
||||||
DocService,
|
|
||||||
useLiveData,
|
|
||||||
useService,
|
|
||||||
} from '@toeverything/infra';
|
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { Suspense, useEffect, useMemo, useState } from 'react';
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
import { CloudSvg } from '../cloud-svg';
|
import { CloudSvg } from '../cloud-svg';
|
||||||
@@ -54,11 +59,12 @@ export const LocalSharePage = (props: ShareMenuProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AffineSharePage = (props: ShareMenuProps) => {
|
export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||||
|
const t = useI18n();
|
||||||
const {
|
const {
|
||||||
workspaceMetadata: { id: workspaceId },
|
workspaceMetadata: { id: workspaceId },
|
||||||
} = props;
|
} = props;
|
||||||
const doc = useService(DocService).doc;
|
const editor = useService(EditorService).editor;
|
||||||
const shareInfoService = useService(ShareInfoService);
|
const shareInfoService = useService(ShareInfoService);
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverConfig = useService(ServerConfigService).serverConfig;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,44 +77,28 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
isSharedPage === null || sharedMode === null || baseUrl === null;
|
isSharedPage === null || sharedMode === null || baseUrl === null;
|
||||||
const [showDisable, setShowDisable] = useState(false);
|
const [showDisable, setShowDisable] = useState(false);
|
||||||
|
|
||||||
const currentDocMode = useLiveData(doc.primaryMode$);
|
const currentDocMode = useLiveData(editor.mode$);
|
||||||
|
|
||||||
const mode = useMemo(() => {
|
const permissionService = useService(WorkspacePermissionService);
|
||||||
if (isSharedPage && sharedMode) {
|
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||||
// if it's a shared page, use the share mode
|
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||||
return sharedMode.toLowerCase() as DocMode;
|
|
||||||
|
const onOpenWorkspaceSettings = useCallback(() => {
|
||||||
|
setSettingModalAtom({
|
||||||
|
open: true,
|
||||||
|
activeTab: 'workspace:preference',
|
||||||
|
workspaceMetadata: props.workspaceMetadata,
|
||||||
|
});
|
||||||
|
}, [props.workspaceMetadata, setSettingModalAtom]);
|
||||||
|
|
||||||
|
const onClickAnyoneReadOnlyShare = useAsyncCallback(async () => {
|
||||||
|
if (isSharedPage) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// default to page mode
|
|
||||||
return currentDocMode;
|
|
||||||
}, [currentDocMode, isSharedPage, sharedMode]);
|
|
||||||
|
|
||||||
const { sharingUrl, onClickCopyLink } = useSharingUrl({
|
|
||||||
workspaceId,
|
|
||||||
pageId: doc.id,
|
|
||||||
urlType: 'share',
|
|
||||||
});
|
|
||||||
|
|
||||||
const t = useI18n();
|
|
||||||
|
|
||||||
const modeOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
{ value: 'page', label: t['com.affine.pageMode.page']() },
|
|
||||||
{
|
|
||||||
value: 'edgeless',
|
|
||||||
label: t['com.affine.pageMode.edgeless'](),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClickCreateLink = useAsyncCallback(async () => {
|
|
||||||
try {
|
try {
|
||||||
await shareInfoService.shareInfo.enableShare(
|
// TODO(@JimmFly): remove mode when we have a better way to handle it
|
||||||
mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page
|
await shareInfoService.shareInfo.enableShare(PublicPageMode.Page);
|
||||||
);
|
track.$.sharePanel.$.createShareLink();
|
||||||
track.$.sharePanel.$.createShareLink({
|
|
||||||
mode,
|
|
||||||
});
|
|
||||||
notify.success({
|
notify.success({
|
||||||
title:
|
title:
|
||||||
t[
|
t[
|
||||||
@@ -121,11 +111,6 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
style: 'normal',
|
style: 'normal',
|
||||||
icon: <SingleSelectSelectSolidIcon color={cssVar('primaryColor')} />,
|
icon: <SingleSelectSelectSolidIcon color={cssVar('primaryColor')} />,
|
||||||
});
|
});
|
||||||
if (sharingUrl) {
|
|
||||||
navigator.clipboard.writeText(sharingUrl).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.error({
|
notify.error({
|
||||||
title:
|
title:
|
||||||
@@ -139,7 +124,7 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
});
|
});
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}, [mode, shareInfoService.shareInfo, sharingUrl, t]);
|
}, [isSharedPage, shareInfoService.shareInfo, t]);
|
||||||
|
|
||||||
const onDisablePublic = useAsyncCallback(async () => {
|
const onDisablePublic = useAsyncCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -170,46 +155,29 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
setShowDisable(false);
|
setShowDisable(false);
|
||||||
}, [shareInfoService, t]);
|
}, [shareInfoService, t]);
|
||||||
|
|
||||||
const onShareModeChange = useAsyncCallback(
|
const onClickDisable = useCallback(() => {
|
||||||
async (value: DocMode) => {
|
|
||||||
try {
|
|
||||||
if (isSharedPage) {
|
if (isSharedPage) {
|
||||||
await shareInfoService.shareInfo.changeShare(
|
setShowDisable(true);
|
||||||
value === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page
|
|
||||||
);
|
|
||||||
notify.success({
|
|
||||||
title:
|
|
||||||
t[
|
|
||||||
'com.affine.share-menu.confirm-modify-mode.notification.success.title'
|
|
||||||
](),
|
|
||||||
message: t[
|
|
||||||
'com.affine.share-menu.confirm-modify-mode.notification.success.message'
|
|
||||||
]({
|
|
||||||
preMode: value === 'edgeless' ? t['Page']() : t['Edgeless'](),
|
|
||||||
currentMode: value === 'edgeless' ? t['Edgeless']() : t['Page'](),
|
|
||||||
}),
|
|
||||||
style: 'normal',
|
|
||||||
icon: (
|
|
||||||
<SingleSelectSelectSolidIcon color={cssVar('primaryColor')} />
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}, [isSharedPage]);
|
||||||
notify.error({
|
|
||||||
title:
|
const isMac = environment.isBrowser && environment.isMacOs;
|
||||||
t[
|
|
||||||
'com.affine.share-menu.confirm-modify-mode.notification.fail.title'
|
const { onClickCopyLink } = useSharingUrl({
|
||||||
](),
|
workspaceId,
|
||||||
message:
|
pageId: editor.doc.id,
|
||||||
t[
|
|
||||||
'com.affine.share-menu.confirm-modify-mode.notification.fail.message'
|
|
||||||
](),
|
|
||||||
});
|
});
|
||||||
console.error(err);
|
|
||||||
}
|
const onCopyPageLink = useCallback(() => {
|
||||||
},
|
onClickCopyLink('page');
|
||||||
[isSharedPage, shareInfoService.shareInfo, t]
|
}, [onClickCopyLink]);
|
||||||
);
|
const onCopyEdgelessLink = useCallback(() => {
|
||||||
|
onClickCopyLink('edgeless');
|
||||||
|
}, [onClickCopyLink]);
|
||||||
|
const onCopyBlockLink = useCallback(() => {
|
||||||
|
// TODO(@JimmFly): handle frame
|
||||||
|
onClickCopyLink();
|
||||||
|
}, [onClickCopyLink]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
// TODO(@eyhn): loading and error UI
|
// TODO(@eyhn): loading and error UI
|
||||||
@@ -222,110 +190,175 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.content}>
|
||||||
<div className={styles.titleContainerStyle}>
|
<div className={styles.titleContainerStyle}>
|
||||||
{t['com.affine.share-menu.publish-to-web']()}
|
{isSharedPage
|
||||||
|
? t['com.affine.share-menu.option.link.readonly.description']()
|
||||||
|
: t['com.affine.share-menu.option.link.no-access.description']()}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.columnContainerStyle}>
|
<div className={styles.columnContainerStyle}>
|
||||||
<div className={styles.descriptionStyle}>
|
|
||||||
{t['com.affine.share-menu.publish-to-web.description']()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.rowContainerStyle}>
|
<div className={styles.rowContainerStyle}>
|
||||||
<Input
|
<div className={styles.labelStyle}>
|
||||||
inputStyle={{
|
{t['com.affine.share-menu.option.link.label']()}
|
||||||
color: 'var(--affine-text-secondary-color)',
|
</div>
|
||||||
fontSize: 'var(--affine-font-xs)',
|
<Menu
|
||||||
lineHeight: '20px',
|
contentOptions={{
|
||||||
|
align: 'end',
|
||||||
}}
|
}}
|
||||||
value={(isSharedPage && sharingUrl) || `${baseUrl}/...`}
|
items={
|
||||||
readOnly
|
<>
|
||||||
/>
|
<MenuItem
|
||||||
{isSharedPage ? (
|
preFix={
|
||||||
<Button
|
<LockIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
onClick={onClickCopyLink}
|
}
|
||||||
data-testid="share-menu-copy-link-button"
|
onSelect={onClickDisable}
|
||||||
style={{ padding: '4px 12px', whiteSpace: 'nowrap' }}
|
className={styles.menuItemStyle}
|
||||||
disabled={!sharingUrl}
|
|
||||||
>
|
>
|
||||||
{t.Copy()}
|
<div className={styles.publicItemRowStyle}>
|
||||||
</Button>
|
<div>
|
||||||
) : (
|
{t['com.affine.share-menu.option.link.no-access']()}
|
||||||
<Button
|
</div>
|
||||||
onClick={onClickCreateLink}
|
{!isSharedPage && (
|
||||||
variant="primary"
|
<DoneIcon className={styles.DoneIconStyle} />
|
||||||
data-testid="share-menu-create-link-button"
|
|
||||||
style={{ padding: '4px 12px', whiteSpace: 'nowrap' }}
|
|
||||||
>
|
|
||||||
{t.Create()}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rowContainerStyle}>
|
</MenuItem>
|
||||||
<div className={styles.subTitleStyle}>
|
<MenuItem
|
||||||
{t['com.affine.share-menu.ShareMode']()}
|
preFix={
|
||||||
</div>
|
<ViewIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
|
}
|
||||||
|
className={styles.menuItemStyle}
|
||||||
|
onSelect={onClickAnyoneReadOnlyShare}
|
||||||
|
data-testid="share-link-menu-enable-share"
|
||||||
|
>
|
||||||
|
<div className={styles.publicItemRowStyle}>
|
||||||
<div>
|
<div>
|
||||||
<RadioGroup
|
{t['com.affine.share-menu.option.link.readonly']()}
|
||||||
className={styles.radioButtonGroup}
|
|
||||||
value={mode}
|
|
||||||
onChange={onShareModeChange}
|
|
||||||
items={modeOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
{isSharedPage && (
|
||||||
|
<DoneIcon className={styles.DoneIconStyle} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuTrigger
|
||||||
|
className={styles.menuTriggerStyle}
|
||||||
|
data-testid="share-link-menu-trigger"
|
||||||
|
>
|
||||||
|
{isSharedPage
|
||||||
|
? t['com.affine.share-menu.option.link.readonly']()
|
||||||
|
: t['com.affine.share-menu.option.link.no-access']()}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
{isSharedPage ? (
|
|
||||||
<>
|
|
||||||
{runtimeConfig.enableEnhanceShareMode && (
|
|
||||||
<>
|
|
||||||
<div className={styles.rowContainerStyle}>
|
<div className={styles.rowContainerStyle}>
|
||||||
<div className={styles.subTitleStyle}>Link expires</div>
|
<div className={styles.labelStyle}>
|
||||||
<div>
|
{t['com.affine.share-menu.option.permission.label']()}
|
||||||
<Menu items={<MenuItem>Never</MenuItem>}>
|
</div>
|
||||||
<MenuTrigger>Never</MenuTrigger>
|
<Menu
|
||||||
|
contentOptions={{
|
||||||
|
align: 'end',
|
||||||
|
}}
|
||||||
|
items={
|
||||||
|
<MenuItem>
|
||||||
|
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuTrigger className={styles.menuTriggerStyle} disabled>
|
||||||
|
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||||
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rowContainerStyle}>
|
{isOwner && (
|
||||||
<div className={styles.subTitleStyle}>
|
<div
|
||||||
{'Show "Created with AFFiNE"'}
|
className={styles.openWorkspaceSettingsStyle}
|
||||||
</div>
|
onClick={onOpenWorkspaceSettings}
|
||||||
<div>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.rowContainerStyle}>
|
|
||||||
<div className={styles.subTitleStyle}>
|
|
||||||
Search engine indexing
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<MenuItem
|
|
||||||
endFix={<ArrowRightSmallIcon />}
|
|
||||||
block
|
|
||||||
type="danger"
|
|
||||||
className={styles.menuItemStyle}
|
|
||||||
onSelect={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
setShowDisable(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className={styles.disableSharePage}>
|
<CollaborationIcon fontSize={16} />
|
||||||
{t['Disable Public Link']()}
|
{t['com.affine.share-menu.navigate.workspace']()}
|
||||||
</div>
|
</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>
|
||||||
|
<span className={styles.copyLinkShortcutStyle}>
|
||||||
|
{isMac ? '⌘ + ⌥ + C' : 'Ctrl + Shift + C'}
|
||||||
|
</span>
|
||||||
|
{t['com.affine.share-menu.copy']()}
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
contentOptions={{
|
||||||
|
align: 'end',
|
||||||
|
}}
|
||||||
|
items={
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<PageIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
|
}
|
||||||
|
className={styles.menuItemStyle}
|
||||||
|
onSelect={onCopyPageLink}
|
||||||
|
data-testid="share-link-menu-copy-page"
|
||||||
|
>
|
||||||
|
{t['com.affine.share-menu.copy.page']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<EdgelessIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
|
}
|
||||||
|
className={styles.menuItemStyle}
|
||||||
|
onSelect={onCopyEdgelessLink}
|
||||||
|
data-testid="share-link-menu-copy-edgeless"
|
||||||
|
>
|
||||||
|
{t['com.affine.share-menu.copy.edgeless']()}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<LinkIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
|
}
|
||||||
|
className={styles.menuItemStyle}
|
||||||
|
onSelect={onCopyBlockLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.share-menu.copy.block']()}
|
||||||
|
</MenuItem>
|
||||||
|
{currentDocMode === 'edgeless' && (
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<LinkIcon className={styles.publicMenuItemPrefixStyle} />
|
||||||
|
}
|
||||||
|
className={styles.menuItemStyle}
|
||||||
|
onSelect={onCopyBlockLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.share-menu.copy.frame']()}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuTrigger
|
||||||
|
className={styles.copyLinkTriggerStyle}
|
||||||
|
data-testid="share-menu-copy-link-button"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
<PublicLinkDisableModal
|
<PublicLinkDisableModal
|
||||||
open={showDisable}
|
open={showDisable}
|
||||||
onConfirm={onDisablePublic}
|
onConfirm={onDisablePublic}
|
||||||
onOpenChange={setShowDisable}
|
onOpenChange={setShowDisable}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -339,7 +372,7 @@ export const SharePage = (props: ShareMenuProps) => {
|
|||||||
// TODO(@eyhn): refactor this part
|
// TODO(@eyhn): refactor this part
|
||||||
<ErrorBoundary fallback={null}>
|
<ErrorBoundary fallback={null}>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<AffineSharePage {...props} />
|
<AFFiNESharePage {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
|||||||
}: PageDetailEditorProps & { page: BlockSuiteDoc }) {
|
}: PageDetailEditorProps & { page: BlockSuiteDoc }) {
|
||||||
const editor = useService(EditorService).editor;
|
const editor = useService(EditorService).editor;
|
||||||
const mode = useLiveData(editor.mode$);
|
const mode = useLiveData(editor.mode$);
|
||||||
|
|
||||||
const isSharedMode = editor.isSharedMode;
|
const isSharedMode = editor.isSharedMode;
|
||||||
const { appSettings } = useAppSettingHelper();
|
const { appSettings } = useAppSettingHelper();
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ export function useRegisterCopyLinkCommands({
|
|||||||
const isActiveView = useIsActiveView();
|
const isActiveView = useIsActiveView();
|
||||||
const workspaceId = workspaceMeta.id;
|
const workspaceId = workspaceMeta.id;
|
||||||
const isCloud = workspaceMeta.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
const isCloud = workspaceMeta.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
||||||
|
|
||||||
const { onClickCopyLink } = useSharingUrl({
|
const { onClickCopyLink } = useSharingUrl({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId: docId,
|
pageId: docId,
|
||||||
urlType: 'workspace',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,8 +39,7 @@ export function useRegisterCopyLinkCommands({
|
|||||||
label: '',
|
label: '',
|
||||||
icon: null,
|
icon: null,
|
||||||
run() {
|
run() {
|
||||||
track.$.cmdk.general.copyShareLink({ type: 'private' });
|
track.$.cmdk.general.copyShareLink();
|
||||||
|
|
||||||
isActiveView && isCloud && onClickCopyLink();
|
isActiveView && isCloud && onClickCopyLink();
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,61 +2,115 @@ import { notify } from '@affine/component';
|
|||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
|
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { Disposable } from '@blocksuite/global/utils';
|
import type { BaseSelection } from '@blocksuite/block-std';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { type DocMode } from '@toeverything/infra';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useActiveBlocksuiteEditor } from '../use-block-suite-editor';
|
import { useActiveBlocksuiteEditor } from '../use-block-suite-editor';
|
||||||
|
|
||||||
type UrlType = 'share' | 'workspace';
|
|
||||||
|
|
||||||
type UseSharingUrl = {
|
type UseSharingUrl = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
urlType: UrlType;
|
shareMode?: DocMode;
|
||||||
|
blockIds?: string[];
|
||||||
|
elementIds?: string[];
|
||||||
|
xywh?: string; // not needed currently
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to generate a url like https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode?element=seletedBlockid#seletedBlockid
|
||||||
|
*/
|
||||||
const generateUrl = ({
|
const generateUrl = ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
urlType,
|
blockIds,
|
||||||
blockId,
|
elementIds,
|
||||||
}: UseSharingUrl & { blockId?: string }) => {
|
shareMode,
|
||||||
// to generate a private url like https://app.affine.app/workspace/123/456
|
xywh, // not needed currently
|
||||||
// or https://app.affine.app/workspace/123/456#block-123
|
}: UseSharingUrl) => {
|
||||||
|
// Base URL construction
|
||||||
// to generate a public url like https://app.affine.app/share/123/456
|
|
||||||
const baseUrl = getAffineCloudBaseUrl();
|
const baseUrl = getAffineCloudBaseUrl();
|
||||||
if (!baseUrl) return null;
|
if (!baseUrl) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URL(
|
const url = new URL(`${baseUrl}/workspace/${workspaceId}/${pageId}`);
|
||||||
`${baseUrl}/${urlType}/${workspaceId}/${pageId}${urlType === 'workspace' && blockId ? `#${blockId}` : ''}`
|
if (shareMode) {
|
||||||
).toString();
|
url.searchParams.append('mode', shareMode);
|
||||||
|
}
|
||||||
|
// TODO(@JimmFly): use query string to handle blockIds
|
||||||
|
if (blockIds && blockIds.length > 0) {
|
||||||
|
// hash is used to store blockIds
|
||||||
|
url.hash = blockIds.join(',');
|
||||||
|
}
|
||||||
|
if (elementIds && elementIds.length > 0) {
|
||||||
|
url.searchParams.append('element', elementIds.join(','));
|
||||||
|
}
|
||||||
|
if (xywh) {
|
||||||
|
url.searchParams.append('xywh', xywh);
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSharingUrl = ({
|
const getShareLinkType = ({
|
||||||
workspaceId,
|
shareMode,
|
||||||
pageId,
|
blockIds,
|
||||||
urlType,
|
elementIds,
|
||||||
}: UseSharingUrl) => {
|
}: {
|
||||||
const t = useI18n();
|
shareMode?: DocMode;
|
||||||
const [blockId, setBlockId] = useState<string>('');
|
blockIds?: string[];
|
||||||
const [editor] = useActiveBlocksuiteEditor();
|
elementIds?: string[];
|
||||||
const sharingUrl = useMemo(
|
}) => {
|
||||||
() =>
|
if (shareMode === 'page') {
|
||||||
generateUrl({
|
return 'doc';
|
||||||
workspaceId,
|
} else if (shareMode === 'edgeless') {
|
||||||
pageId,
|
return 'whiteboard';
|
||||||
urlType,
|
} else if (blockIds && blockIds.length > 0) {
|
||||||
blockId: blockId.length > 0 ? blockId : undefined,
|
return 'block';
|
||||||
}),
|
} else if (elementIds && elementIds.length > 0) {
|
||||||
[workspaceId, pageId, urlType, blockId]
|
return 'element';
|
||||||
);
|
} else {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onClickCopyLink = useCallback(() => {
|
const getSelectionIds = (selections?: BaseSelection[]) => {
|
||||||
|
if (!selections || selections.length === 0) {
|
||||||
|
return { blockIds: [], elementIds: [] };
|
||||||
|
}
|
||||||
|
const blockIds: string[] = [];
|
||||||
|
const elementIds: string[] = [];
|
||||||
|
// TODO(@JimmFly): handle multiple selections and elementIds
|
||||||
|
if (selections[0].type === 'block') {
|
||||||
|
blockIds.push(selections[0].blockId);
|
||||||
|
}
|
||||||
|
return { blockIds, elementIds };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const [editor] = useActiveBlocksuiteEditor();
|
||||||
|
|
||||||
|
const onClickCopyLink = useCallback(
|
||||||
|
(shareMode?: DocMode) => {
|
||||||
|
const selectManager = editor?.host?.selection;
|
||||||
|
const selections = selectManager?.value;
|
||||||
|
const { blockIds, elementIds } = getSelectionIds(selections);
|
||||||
|
|
||||||
|
const sharingUrl = generateUrl({
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
blockIds,
|
||||||
|
elementIds,
|
||||||
|
shareMode, // if view is not provided, use the current view
|
||||||
|
});
|
||||||
|
const type = getShareLinkType({
|
||||||
|
shareMode,
|
||||||
|
blockIds,
|
||||||
|
elementIds,
|
||||||
|
});
|
||||||
if (sharingUrl) {
|
if (sharingUrl) {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(sharingUrl)
|
.writeText(sharingUrl)
|
||||||
@@ -69,45 +123,17 @@ export const useSharingUrl = ({
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
track.$.sharePanel.$.copyShareLink({
|
track.$.sharePanel.$.copyShareLink({
|
||||||
type: urlType === 'share' ? 'public' : 'private',
|
type,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notify.error({
|
notify.error({
|
||||||
title: 'Network not available',
|
title: 'Network not available',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [sharingUrl, t, urlType]);
|
},
|
||||||
|
[editor, pageId, t, workspaceId]
|
||||||
useEffect(() => {
|
);
|
||||||
let disposable: Disposable | null = null;
|
|
||||||
const selectManager = editor?.host?.selection;
|
|
||||||
if (urlType !== 'workspace' || !selectManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the block is already selected, set the blockId
|
|
||||||
const currentBlockSelection = selectManager.find('block');
|
|
||||||
if (currentBlockSelection) {
|
|
||||||
setBlockId(`#${currentBlockSelection.blockId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
disposable = selectManager.slots.changed.on(selections => {
|
|
||||||
setBlockId(prev => {
|
|
||||||
if (selections[0] && selections[0].type === 'block') {
|
|
||||||
return `#${selections[0].blockId}`;
|
|
||||||
} else if (prev.length > 0) {
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
disposable?.dispose();
|
|
||||||
};
|
|
||||||
}, [editor?.host?.selection, urlType]);
|
|
||||||
return {
|
return {
|
||||||
sharingUrl,
|
|
||||||
onClickCopyLink,
|
onClickCopyLink,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -376,7 +376,9 @@ export type EventArgs = {
|
|||||||
createDoc: { mode?: 'edgeless' | 'page' };
|
createDoc: { mode?: 'edgeless' | 'page' };
|
||||||
switchPageMode: { mode: 'edgeless' | 'page' };
|
switchPageMode: { mode: 'edgeless' | 'page' };
|
||||||
createShareLink: { mode: 'edgeless' | 'page' };
|
createShareLink: { mode: 'edgeless' | 'page' };
|
||||||
copyShareLink: { type: 'public' | 'private' };
|
copyShareLink: {
|
||||||
|
type: 'default' | 'doc' | 'whiteboard' | 'block' | 'element';
|
||||||
|
};
|
||||||
export: { type: string };
|
export: { type: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
import { ShareFooter } from './share-footer';
|
import { ShareFooter } from './share-footer';
|
||||||
@@ -50,6 +51,18 @@ export const SharePage = ({
|
|||||||
const error = useLiveData(shareReaderService.reader.error$);
|
const error = useLiveData(shareReaderService.reader.error$);
|
||||||
const data = useLiveData(shareReaderService.reader.data$);
|
const data = useLiveData(shareReaderService.reader.data$);
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const [mode, setMode] = useState<DocMode | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const queryStringMode = searchParams.get('mode') as DocMode | null;
|
||||||
|
if (queryStringMode && ['edgeless', 'page'].includes(queryStringMode)) {
|
||||||
|
setMode(queryStringMode);
|
||||||
|
}
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
shareReaderService.reader.loadShare({ workspaceId, docId });
|
shareReaderService.reader.loadShare({ workspaceId, docId });
|
||||||
}, [shareReaderService, docId, workspaceId]);
|
}, [shareReaderService, docId, workspaceId]);
|
||||||
@@ -70,7 +83,7 @@ export const SharePage = ({
|
|||||||
docId={data.docId}
|
docId={data.docId}
|
||||||
workspaceBinary={data.workspaceBinary}
|
workspaceBinary={data.workspaceBinary}
|
||||||
docBinary={data.docBinary}
|
docBinary={data.docBinary}
|
||||||
publishMode={data.publishMode}
|
publishMode={mode || data.publishMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1279,6 +1279,19 @@
|
|||||||
"com.affine.share-menu.ShareWithLink": "Share with link",
|
"com.affine.share-menu.ShareWithLink": "Share with link",
|
||||||
"com.affine.share-menu.ShareWithLinkDescription": "Create a link you can easily share with anyone. The visitors will open your doc in the form od a document",
|
"com.affine.share-menu.ShareWithLinkDescription": "Create a link you can easily share with anyone. The visitors will open your doc in the form od a document",
|
||||||
"com.affine.share-menu.SharedPage": "Shared doc",
|
"com.affine.share-menu.SharedPage": "Shared doc",
|
||||||
|
"com.affine.share-menu.option.link.label": "Anyone with the link",
|
||||||
|
"com.affine.share-menu.option.link.readonly": "Read Only",
|
||||||
|
"com.affine.share-menu.option.link.readonly.description": "Anyone can access this link.",
|
||||||
|
"com.affine.share-menu.option.link.no-access": "No Access",
|
||||||
|
"com.affine.share-menu.option.link.no-access.description": "Only workspace members can access this link.",
|
||||||
|
"com.affine.share-menu.option.permission.label": "Members in Workspace",
|
||||||
|
"com.affine.share-menu.option.permission.can-edit": "Can Edit",
|
||||||
|
"com.affine.share-menu.navigate.workspace": "Manage Workspace Members",
|
||||||
|
"com.affine.share-menu.copy": "Copy Link",
|
||||||
|
"com.affine.share-menu.copy.page": "Copy Link to Page Mode",
|
||||||
|
"com.affine.share-menu.copy.edgeless": "Copy Link to Edgeless Mode",
|
||||||
|
"com.affine.share-menu.copy.block": "Copy Link to Selected Block",
|
||||||
|
"com.affine.share-menu.copy.frame": "Copy Link to Selected Frame",
|
||||||
"com.affine.share-menu.confirm-modify-mode.notification.fail.message": "Please try again later.",
|
"com.affine.share-menu.confirm-modify-mode.notification.fail.message": "Please try again later.",
|
||||||
"com.affine.share-menu.confirm-modify-mode.notification.fail.title": "Failed to modify",
|
"com.affine.share-menu.confirm-modify-mode.notification.fail.title": "Failed to modify",
|
||||||
"com.affine.share-menu.confirm-modify-mode.notification.success.message": "You have changed the public link from {{preMode}} Mode to {{currentMode}} Mode.",
|
"com.affine.share-menu.confirm-modify-mode.notification.success.message": "You have changed the public link from {{preMode}} Mode to {{currentMode}} Mode.",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { skipOnboarding, test } from '@affine-test/kit/playwright';
|
|||||||
import {
|
import {
|
||||||
createRandomUser,
|
createRandomUser,
|
||||||
enableCloudWorkspaceFromShareButton,
|
enableCloudWorkspaceFromShareButton,
|
||||||
|
enableShare,
|
||||||
loginUser,
|
loginUser,
|
||||||
} from '@affine-test/kit/utils/cloud';
|
} from '@affine-test/kit/utils/cloud';
|
||||||
import { clickEdgelessModeButton } from '@affine-test/kit/utils/editor';
|
import { clickEdgelessModeButton } from '@affine-test/kit/utils/editor';
|
||||||
@@ -44,9 +45,11 @@ test('can enable share page', async ({ page, browser }) => {
|
|||||||
});
|
});
|
||||||
await page.keyboard.press('Enter', { delay: 50 });
|
await page.keyboard.press('Enter', { delay: 50 });
|
||||||
await page.keyboard.type('TEST CONTENT', { delay: 50 });
|
await page.keyboard.type('TEST CONTENT', { delay: 50 });
|
||||||
await page.getByTestId('cloud-share-menu-button').click();
|
|
||||||
await page.getByTestId('share-menu-create-link-button').click();
|
// enable share page and copy page link
|
||||||
|
await enableShare(page);
|
||||||
await page.getByTestId('share-menu-copy-link-button').click();
|
await page.getByTestId('share-menu-copy-link-button').click();
|
||||||
|
await page.getByTestId('share-link-menu-copy-page').click();
|
||||||
|
|
||||||
// check share page is accessible
|
// check share page is accessible
|
||||||
{
|
{
|
||||||
@@ -86,9 +89,11 @@ test('share page with default edgeless', async ({ page, browser }) => {
|
|||||||
await expect(page.locator('affine-edgeless-root')).toBeVisible({
|
await expect(page.locator('affine-edgeless-root')).toBeVisible({
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
});
|
});
|
||||||
await page.getByTestId('cloud-share-menu-button').click();
|
|
||||||
await page.getByTestId('share-menu-create-link-button').click();
|
// enable share page and copy page link
|
||||||
|
await enableShare(page);
|
||||||
await page.getByTestId('share-menu-copy-link-button').click();
|
await page.getByTestId('share-menu-copy-link-button').click();
|
||||||
|
await page.getByTestId('share-link-menu-copy-edgeless').click();
|
||||||
|
|
||||||
// check share page is accessible
|
// check share page is accessible
|
||||||
{
|
{
|
||||||
@@ -126,9 +131,10 @@ test('image preview should should be shown', async ({ page, browser }) => {
|
|||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await importImage(page, 'http://localhost:8081/large-image.png');
|
await importImage(page, 'http://localhost:8081/large-image.png');
|
||||||
|
|
||||||
await page.getByTestId('cloud-share-menu-button').click();
|
// enable share page and copy page link
|
||||||
await page.getByTestId('share-menu-create-link-button').click();
|
await enableShare(page);
|
||||||
await page.getByTestId('share-menu-copy-link-button').click();
|
await page.getByTestId('share-menu-copy-link-button').click();
|
||||||
|
await page.getByTestId('share-link-menu-copy-page').click();
|
||||||
|
|
||||||
// check share page is accessible
|
// check share page is accessible
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -216,3 +216,9 @@ export async function enableCloudWorkspaceFromShareButton(page: Page) {
|
|||||||
await waitForEditorLoad(page);
|
await waitForEditorLoad(page);
|
||||||
await clickNewPageButton(page);
|
await clickNewPageButton(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function enableShare(page: Page) {
|
||||||
|
await page.getByTestId('cloud-share-menu-button').click();
|
||||||
|
await page.getByTestId('share-link-menu-trigger').click();
|
||||||
|
await page.getByTestId('share-link-menu-enable-share').click();
|
||||||
|
}
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ __metadata:
|
|||||||
"@radix-ui/react-popover": "npm:^1.0.7"
|
"@radix-ui/react-popover": "npm:^1.0.7"
|
||||||
"@radix-ui/react-radio-group": "npm:^1.1.3"
|
"@radix-ui/react-radio-group": "npm:^1.1.3"
|
||||||
"@radix-ui/react-scroll-area": "npm:^1.0.5"
|
"@radix-ui/react-scroll-area": "npm:^1.0.5"
|
||||||
|
"@radix-ui/react-tabs": "npm:^1.1.0"
|
||||||
"@radix-ui/react-toast": "npm:^1.1.5"
|
"@radix-ui/react-toast": "npm:^1.1.5"
|
||||||
"@radix-ui/react-toolbar": "npm:^1.0.4"
|
"@radix-ui/react-toolbar": "npm:^1.0.4"
|
||||||
"@radix-ui/react-tooltip": "npm:^1.0.7"
|
"@radix-ui/react-tooltip": "npm:^1.0.7"
|
||||||
|
|||||||
Reference in New Issue
Block a user