feat: enable share menu (#1883)

Co-authored-by: JimmFly <yangjinfei001@gmail.com>
This commit is contained in:
Himself65
2023-04-13 16:22:49 -05:00
committed by GitHub
parent 32b206a137
commit 01a686dc28
48 changed files with 2666 additions and 2113 deletions

View File

@@ -17,7 +17,7 @@
"@blocksuite/blocks": "0.0.0-20230409084303-221991d4-nightly",
"@blocksuite/editor": "0.0.0-20230409084303-221991d4-nightly",
"@blocksuite/global": "0.0.0-20230409084303-221991d4-nightly",
"@blocksuite/icons": "2.1.7",
"@blocksuite/icons": "2.1.10",
"@blocksuite/store": "0.0.0-20230409084303-221991d4-nightly"
},
"dependencies": {
@@ -31,9 +31,9 @@
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
"@emotion/styled": "^11.10.6",
"@mui/base": "5.0.0-alpha.124",
"@mui/base": "5.0.0-alpha.125",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.11.16",
"@mui/material": "^5.12.0",
"@radix-ui/react-avatar": "^1.0.2",
"@toeverything/hooks": "workspace:*",
"clsx": "^1.2.1",
@@ -48,22 +48,22 @@
"react-is": "^18.2.0"
},
"devDependencies": {
"@blocksuite/blocks": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/editor": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/global": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/icons": "^2.1.9",
"@blocksuite/store": "0.0.0-20230412041719-76e5b5b9-nightly",
"@storybook/addon-actions": "^7.0.2",
"@blocksuite/blocks": "0.0.0-20230413112150-e058f87e-nightly",
"@blocksuite/editor": "0.0.0-20230413112150-e058f87e-nightly",
"@blocksuite/global": "0.0.0-20230413112150-e058f87e-nightly",
"@blocksuite/icons": "^2.1.10",
"@blocksuite/store": "0.0.0-20230413112150-e058f87e-nightly",
"@storybook/addon-actions": "^7.0.4",
"@storybook/addon-coverage": "^0.0.8",
"@storybook/addon-essentials": "^7.0.2",
"@storybook/addon-interactions": "^7.0.2",
"@storybook/addon-links": "^7.0.2",
"@storybook/addon-storysource": "^7.0.2",
"@storybook/blocks": "^7.0.2",
"@storybook/builder-vite": "^7.0.2",
"@storybook/addon-essentials": "^7.0.4",
"@storybook/addon-interactions": "^7.0.4",
"@storybook/addon-links": "^7.0.4",
"@storybook/addon-storysource": "^7.0.4",
"@storybook/blocks": "^7.0.4",
"@storybook/builder-vite": "^7.0.4",
"@storybook/jest": "^0.1.0",
"@storybook/react": "^7.0.2",
"@storybook/react-vite": "^7.0.2",
"@storybook/react": "^7.0.4",
"@storybook/react-vite": "^7.0.4",
"@storybook/test-runner": "^0.10.0",
"@storybook/testing-library": "^0.1.0",
"@types/react": "=18.0.31",
@@ -74,7 +74,7 @@
"concurrently": "^8.0.1",
"jest-mock": "^29.5.0",
"serve": "^14.2.0",
"storybook": "^7.0.2",
"storybook": "^7.0.4",
"storybook-dark-mode": "^3.0.0",
"typescript": "^5.0.4",
"vite": "^4.2.1",

View File

@@ -1,20 +1,30 @@
import { useTranslation } from '@affine/i18n';
import { ContentParser } from '@blocksuite/blocks/content-parser';
import { ExportToHtmlIcon, ExportToMarkdownIcon } from '@blocksuite/icons';
import type { FC } from 'react';
import { useRef } from 'react';
import { Button } from '../..';
import type { ShareMenuProps } from './index';
import { actionsStyle, descriptionStyle, menuItemStyle } from './index.css';
import {
actionsStyle,
descriptionStyle,
exportButtonStyle,
menuItemStyle,
svgStyle,
} from './index.css';
import type { ShareMenuProps } from './ShareMenu';
export const Export: FC<ShareMenuProps> = props => {
const contentParserRef = useRef<ContentParser>();
const { t } = useTranslation();
return (
<div className={menuItemStyle}>
<div className={descriptionStyle}>
Download a static copy of your page to share with others.
{t('Export Shared Pages Description')}
</div>
<div className={actionsStyle}>
<Button
className={exportButtonStyle}
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(props.currentPage);
@@ -22,9 +32,11 @@ export const Export: FC<ShareMenuProps> = props => {
return contentParserRef.current.onExportHtml();
}}
>
Export to HTML
<ExportToHtmlIcon className={svgStyle} />
{t('Export to HTML')}
</Button>
<Button
className={exportButtonStyle}
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(props.currentPage);
@@ -32,7 +44,8 @@ export const Export: FC<ShareMenuProps> = props => {
return contentParserRef.current.onExportMarkdown();
}}
>
Export to Markdown
<ExportToMarkdownIcon className={svgStyle} />
{t('Export to Markdown')}
</Button>
</div>
</div>

View File

@@ -0,0 +1,145 @@
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import { ExportIcon, PublishIcon, ShareIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-blocksuite-workspace-page-is-public';
import type { FC } from 'react';
import { useRef } from 'react';
import { useCallback, useState } from 'react';
import { Menu } from '../..';
import { Export } from './Export';
import { containerStyle, indicatorContainerStyle, tabStyle } from './index.css';
import { SharePage } from './SharePage';
import { ShareWorkspace } from './ShareWorkspace';
import { StyledIndicator, StyledShareButton, TabItem } from './styles';
type SharePanel = 'SharePage' | 'Export' | 'ShareWorkspace';
const MenuItems: Record<SharePanel, FC<ShareMenuProps>> = {
SharePage: SharePage,
Export: Export,
ShareWorkspace: ShareWorkspace,
};
const tabIcons = {
SharePage: <ShareIcon />,
Export: <ExportIcon />,
ShareWorkspace: <PublishIcon />,
};
export type ShareMenuProps<
Workspace extends AffineWorkspace | LocalWorkspace =
| AffineWorkspace
| LocalWorkspace
> = {
workspace: Workspace;
currentPage: Page;
onEnableAffineCloud: (workspace: LocalWorkspace) => void;
onOpenWorkspaceSettings: (workspace: Workspace) => void;
togglePagePublic: (page: Page, isPublic: boolean) => Promise<void>;
toggleWorkspacePublish: (
workspace: Workspace,
publish: boolean
) => Promise<void>;
};
function assertInstanceOf<T, U extends T>(
obj: T,
type: new (...args: any[]) => U
): asserts obj is U {
if (!(obj instanceof type)) {
throw new Error('Object is not instance of type');
}
}
export const ShareMenu: FC<ShareMenuProps> = props => {
const [activeItem, setActiveItem] = useState<SharePanel>('SharePage');
const [isPublic] = useBlockSuiteWorkspacePageIsPublic(props.currentPage);
const [open, setOpen] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const indicatorRef = useRef<HTMLDivElement | null>(null);
const startTransaction = useCallback(() => {
if (indicatorRef.current && containerRef.current) {
const indicator = indicatorRef.current;
const activeTabElement = containerRef.current.querySelector(
`[data-tab-key="${activeItem}"]`
);
assertInstanceOf(activeTabElement, HTMLElement);
requestAnimationFrame(() => {
indicator.style.left = `${activeTabElement.offsetLeft}px`;
indicator.style.width = `${activeTabElement.offsetWidth}px`;
});
}
}, [activeItem]);
const handleMenuChange = useCallback(
(selectedItem: SharePanel) => {
setActiveItem(selectedItem);
startTransaction();
},
[setActiveItem, startTransaction]
);
const ActiveComponent = MenuItems[activeItem];
interface ShareMenuProps {
activeItem: SharePanel;
onChangeTab: (selectedItem: SharePanel) => void;
}
const ShareMenu: FC<ShareMenuProps> = ({ activeItem, onChangeTab }) => {
const handleButtonClick = (itemName: SharePanel) => {
onChangeTab(itemName);
setActiveItem(itemName);
};
return (
<div className={tabStyle} ref={containerRef}>
{Object.keys(MenuItems).map(item => (
<TabItem
isActive={activeItem === item}
key={item}
data-tab-key={item}
onClick={() => handleButtonClick(item as SharePanel)}
>
{tabIcons[item as SharePanel]}
{isPublic ? (item === 'SharePage' ? 'SharedPage' : item) : item}
</TabItem>
))}
</div>
);
};
const Share = (
<>
<ShareMenu activeItem={activeItem} onChangeTab={handleMenuChange} />
<div className={indicatorContainerStyle}>
<StyledIndicator
ref={(ref: HTMLDivElement | null) => {
indicatorRef.current = ref;
startTransaction();
}}
/>
</div>
<div className={containerStyle}>
<ActiveComponent {...props} />
</div>
</>
);
return (
<Menu
content={Share}
visible={open}
placement="bottom-end"
trigger={['click']}
width={439}
disablePortal={true}
onClickAway={() => {
setOpen(false);
}}
>
<StyledShareButton
data-testid="share-menu-button"
onClick={() => {
setOpen(!open);
}}
isShared={isPublic}
>
<div>{isPublic ? 'Shared' : 'Share'}</div>
</StyledShareButton>
</Menu>
);
};

View File

@@ -3,11 +3,22 @@ import type { LocalWorkspace } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-blocksuite-workspace-page-is-public';
import type { FC } from 'react';
import { useState } from 'react';
import { useCallback, useMemo } from 'react';
import { Button } from '../..';
import type { ShareMenuProps } from './index';
import { buttonStyle, descriptionStyle, menuItemStyle } from './index.css';
import { PublicLinkDisableModal } from './disable-public-link';
import {
descriptionStyle,
inputButtonRowStyle,
menuItemStyle,
} from './index.css';
import type { ShareMenuProps } from './ShareMenu';
import {
StyledButton,
StyledDisableButton,
StyledInput,
StyledLinkSpan,
} from './styles';
export const LocalSharePage: FC<ShareMenuProps> = props => {
return (
@@ -15,17 +26,14 @@ export const LocalSharePage: FC<ShareMenuProps> = props => {
<div className={descriptionStyle}>
Sharing page publicly requires AFFiNE Cloud service.
</div>
<Button
<StyledButton
data-testid="share-menu-enable-affine-cloud-button"
className={buttonStyle}
type="light"
shape="round"
onClick={() => {
props.onEnableAffineCloud(props.workspace as LocalWorkspace);
}}
>
Enable AFFiNE Cloud
</Button>
</StyledButton>
</div>
);
};
@@ -34,6 +42,7 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
const [isPublic, setIsPublic] = useBlockSuiteWorkspacePageIsPublic(
props.currentPage
);
const [showDisable, setShowDisable] = useState(false);
const sharingUrl = useMemo(() => {
const env = getEnvironment();
if (env.isBrowser) {
@@ -48,14 +57,59 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
const onClickCopyLink = useCallback(() => {
navigator.clipboard.writeText(sharingUrl);
}, []);
return (
<div className={menuItemStyle}>
<div className={descriptionStyle}>
Create a link you can easily share with anyone.
</div>
<span>{isPublic ? sharingUrl : 'not public'}</span>
{!isPublic && <Button onClick={onClickCreateLink}>Create</Button>}
{isPublic && <Button onClick={onClickCopyLink}>Copy Link</Button>}
<div className={inputButtonRowStyle}>
<StyledInput
type="text"
readOnly
value={isPublic ? sharingUrl : 'https://app.affine.pro/xxxx'}
/>
{!isPublic && (
<StyledButton
data-testid="affine-share-create-link"
onClick={onClickCreateLink}
>
Create
</StyledButton>
)}
{isPublic && (
<StyledButton
data-testid="affine-share-copy-link"
onClick={onClickCopyLink}
>
Copy Link
</StyledButton>
)}
</div>
<div className={descriptionStyle}>
The entire Workspace is published on the web and can be edited via
<StyledLinkSpan
onClick={() => {
props.onOpenWorkspaceSettings(props.workspace);
}}
>
Workspace Settings.
</StyledLinkSpan>
</div>
{isPublic && (
<>
<StyledDisableButton onClick={() => setShowDisable(true)}>
Disable Public Link
</StyledDisableButton>
<PublicLinkDisableModal
page={props.currentPage}
open={showDisable}
onClose={() => {
setShowDisable(false);
}}
/>
</>
)}
</div>
);
};

View File

@@ -2,27 +2,24 @@ import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import type { FC } from 'react';
import { Button } from '../..';
import type { ShareMenuProps } from '.';
import { buttonStyle, descriptionStyle, menuItemStyle } from './index.css';
import { descriptionStyle, menuItemStyle } from './index.css';
import type { ShareMenuProps } from './ShareMenu';
import { StyledButton } from './styles';
const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => {
return (
<div className={menuItemStyle}>
<div className={descriptionStyle}>
Sharing page publicly requires AFFiNE Cloud service.
Invite others to join the Workspace or publish it to web.
</div>
<Button
<StyledButton
data-testid="share-menu-enable-affine-cloud-button"
className={buttonStyle}
type="light"
shape="circle"
onClick={() => {
props.onEnableAffineCloud(props.workspace as LocalWorkspace);
props.onOpenWorkspaceSettings(props.workspace);
}}
>
Enable AFFiNE Cloud
</Button>
Open Workspace Settings
</StyledButton>
</div>
);
};
@@ -36,16 +33,14 @@ const ShareAffineWorkspace: FC<ShareMenuProps<AffineWorkspace>> = props => {
? `Current workspace has been published to the web as a public workspace.`
: `Invite others to join the Workspace or publish it to web`}
</div>
<Button
<StyledButton
data-testid="share-menu-publish-to-web-button"
onClick={() => {
props.onOpenWorkspaceSettings(props.workspace);
}}
type="light"
shape="circle"
>
Open Workspace Settings
</Button>
</StyledButton>
</div>
);
};

View File

@@ -0,0 +1,59 @@
import { useTranslation } from '@affine/i18n';
import type { Page } from '@blocksuite/store';
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-blocksuite-workspace-page-is-public';
import { useCallback } from 'react';
import { Modal, ModalCloseButton, toast } from '../../..';
import {
StyledButton,
StyledButtonContent,
StyledDangerButton,
StyledModalHeader,
StyledModalWrapper,
StyledTextContent,
} from './style';
export type PublicLinkDisableProps = {
page: Page;
open: boolean;
onClose: () => void;
};
export const PublicLinkDisableModal = ({
page,
open,
onClose,
}: PublicLinkDisableProps) => {
const { t } = useTranslation();
const [, setIsPublic] = useBlockSuiteWorkspacePageIsPublic(page);
const handleDisable = useCallback(() => {
setIsPublic(false);
toast('Successfully disabled', {
portal: document.body,
});
onClose();
}, []);
return (
<Modal open={open} onClose={onClose}>
<StyledModalWrapper>
<ModalCloseButton onClick={onClose} top={12} right={12} />
<StyledModalHeader>{t('Disable Public Link ?')}</StyledModalHeader>
<StyledTextContent>
{t('Disable Public Link Description')}
</StyledTextContent>
<StyledButtonContent>
<StyledButton onClick={onClose}>{t('Cancel')}</StyledButton>
<StyledDangerButton
data-testid="disable-public-link-confirm-button"
onClick={handleDisable}
style={{ marginLeft: '24px' }}
>
{t('Disable')}
</StyledDangerButton>
</StyledButtonContent>
</StyledModalWrapper>
</Modal>
);
};

View File

@@ -0,0 +1,63 @@
import { styled, TextButton } from '@affine/component';
export const StyledModalWrapper = styled('div')(({ theme }) => {
return {
position: 'relative',
padding: '0px',
width: '560px',
background: theme.colors.popoverBackground,
borderRadius: '12px',
// height: '312px',
};
});
export const StyledModalHeader = styled('div')(({ theme }) => {
return {
margin: '44px 0px 12px 0px',
width: '560px',
fontWeight: '600',
fontSize: theme.font.h6,
textAlign: 'center',
};
});
export const StyledTextContent = styled('div')(({ theme }) => {
return {
margin: 'auto',
width: '560px',
padding: '0px 84px',
fontWeight: '400',
fontSize: theme.font.base,
textAlign: 'center',
};
});
export const StyledButtonContent = styled('div')(() => {
return {
margin: '32px 0',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
};
});
export const StyledButton = styled(TextButton)(({ theme }) => {
return {
color: theme.colors.primaryColor,
height: '32px',
background: '#F3F0FF',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});
export const StyledDangerButton = styled(TextButton)(({ theme }) => {
return {
color: '#FF631F',
height: '32px',
background:
'linear-gradient(0deg, rgba(255, 99, 31, 0.1), rgba(255, 99, 31, 0.1)), #FFFFFF;',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});

View File

@@ -2,22 +2,27 @@ import { style } from '@vanilla-extract/css';
export const tabStyle = style({
display: 'flex',
justifyContent: 'space-around',
flex: '1',
width: '100%',
padding: '0 10px',
margin: '0',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
marginTop: '4px',
marginLeft: '10px',
marginRight: '10px',
});
export const menuItemStyle = style({
marginLeft: '20px',
marginRight: '20px',
marginTop: '30px',
padding: '4px 18px',
paddingBottom: '16px',
width: '100%',
});
export const descriptionStyle = style({
fontSize: '1rem',
wordWrap: 'break-word',
// wordBreak: 'break-all',
fontSize: '16px',
marginTop: '16px',
marginBottom: '16px',
});
export const buttonStyle = style({
@@ -30,5 +35,32 @@ export const actionsStyle = style({
gap: '9px',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'start',
alignItems: 'flex-start',
});
export const containerStyle = style({
display: 'flex',
width: '100%',
flexDirection: 'column',
});
export const indicatorContainerStyle = style({
position: 'relative',
});
export const inputButtonRowStyle = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginTop: '16px',
});
export const exportButtonStyle = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '0',
border: 'none',
});
export const svgStyle = style({
fontSize: '20px',
marginRight: '12px',
verticalAlign: 'top',
});

View File

@@ -1,100 +1,2 @@
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import { ExportIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import type { FC } from 'react';
import { useCallback, useState } from 'react';
import { Menu } from '../..';
import { Export } from './Export';
import { tabStyle } from './index.css';
import { SharePage } from './SharePage';
import { ShareWorkspace } from './ShareWorkspace';
import { StyledIndicator, StyledShareButton, TabItem } from './styles';
type SharePanel = 'SharePage' | 'Export' | 'ShareWorkspace';
const MenuItems: Record<SharePanel, FC<ShareMenuProps>> = {
SharePage: SharePage,
Export: Export,
ShareWorkspace: ShareWorkspace,
};
export type ShareMenuProps<
Workspace extends AffineWorkspace | LocalWorkspace =
| AffineWorkspace
| LocalWorkspace
> = {
workspace: Workspace;
currentPage: Page;
onEnableAffineCloud: (workspace: LocalWorkspace) => void;
onOpenWorkspaceSettings: (workspace: Workspace) => void;
togglePagePublic: (page: Page, publish: boolean) => Promise<void>;
toggleWorkspacePublish: (
workspace: Workspace,
publish: boolean
) => Promise<void>;
};
export const ShareMenu: FC<ShareMenuProps> = props => {
const [activeItem, setActiveItem] = useState<SharePanel>('SharePage');
const [open, setOpen] = useState(false);
const handleMenuChange = useCallback((selectedItem: SharePanel) => {
setActiveItem(selectedItem);
}, []);
const ActiveComponent = MenuItems[activeItem];
interface ShareMenuProps {
activeItem: SharePanel;
onChangeTab: (selectedItem: SharePanel) => void;
}
const ShareMenu: FC<ShareMenuProps> = ({ activeItem, onChangeTab }) => {
const handleButtonClick = (itemName: SharePanel) => {
onChangeTab(itemName);
setActiveItem(itemName);
};
return (
<div className={tabStyle}>
{Object.keys(MenuItems).map(item => (
<TabItem
isActive={activeItem === item}
key={item}
onClick={() => handleButtonClick(item as SharePanel)}
>
{item}
</TabItem>
))}
</div>
);
};
const activeIndex = Object.keys(MenuItems).indexOf(activeItem);
const Share = (
<>
<ShareMenu activeItem={activeItem} onChangeTab={handleMenuChange} />
<StyledIndicator activeIndex={activeIndex} />
<ActiveComponent {...props} />
</>
);
return (
<Menu
content={Share}
visible={open}
width={439}
placement="bottom-end"
trigger={['click']}
disablePortal={true}
onClickAway={() => {
setOpen(false);
}}
>
<StyledShareButton
data-testid="share-menu-button"
onClick={() => {
setOpen(!open);
}}
>
<ExportIcon />
<div>Share</div>
</StyledShareButton>
</Menu>
);
};
export * from './disable-public-link';
export * from './ShareMenu';

View File

@@ -1,13 +1,20 @@
import { displayFlex, styled, TextButton } from '../..';
import { Button, displayFlex, styled, TextButton } from '../..';
export const StyledShareButton = styled(TextButton)(({ theme }) => {
export const StyledShareButton = styled(TextButton, {
shouldForwardProp: (prop: string) => prop !== 'isShared',
})<{ isShared?: boolean }>(({ theme, isShared }) => {
return {
padding: '4px 8px',
marginLeft: '4px',
marginRight: '16px',
border: `1px solid ${theme.colors.primaryColor}`,
color: theme.colors.primaryColor,
border: `1px solid ${
isShared ? theme.colors.primaryColor : theme.colors.iconColor
}`,
color: isShared ? theme.colors.primaryColor : theme.colors.iconColor,
borderRadius: '8px',
':hover': {
border: `1px solid ${theme.colors.primaryColor}`,
},
span: {
...displayFlex('center', 'center'),
},
@@ -26,21 +33,41 @@ export const TabItem = styled('li')<{ isActive?: boolean }>(
{
return {
...displayFlex('center', 'center'),
width: 'calc(100% / 3)',
height: '34px',
flex: '1',
height: '30px',
color: theme.colors.textColor,
opacity: isActive ? 1 : 0.2,
fontWeight: '500',
fontSize: theme.font.h6,
fontSize: theme.font.base,
lineHeight: theme.font.lineHeight,
cursor: 'pointer',
transition: 'all 0.15s ease',
padding: '0 10px',
marginBottom: '4px',
borderRadius: '4px',
position: 'relative',
':hover': {
background: theme.colors.hoverBackground,
opacity: 1,
color: isActive
? theme.colors.textColor
: theme.colors.secondaryTextColor,
svg: {
fill: isActive
? theme.colors.textColor
: theme.colors.secondaryTextColor,
},
},
svg: {
fontSize: '20px',
marginRight: '12px',
},
':after': {
content: '""',
position: 'absolute',
bottom: '-2px',
left: '-2px',
width: 'calc(100% + 4px)',
bottom: '-6px',
left: '0',
width: '100%',
height: '2px',
background: theme.colors.textColor,
opacity: 0.2,
@@ -49,16 +76,54 @@ export const TabItem = styled('li')<{ isActive?: boolean }>(
}
}
);
export const StyledIndicator = styled('div')<{ activeIndex: number }>(
({ theme, activeIndex }) => {
return {
height: '2px',
margin: '0 10px',
background: theme.colors.textColor,
position: 'absolute',
left: `calc(${activeIndex * 100}% / 3)`,
width: `calc(100% / 3)`,
transition: 'left .3s, width .3s',
};
}
);
export const StyledIndicator = styled('div')(({ theme }) => {
return {
height: '2px',
background: theme.colors.textColor,
position: 'absolute',
left: '0',
transition: 'left .3s, width .3s',
};
});
export const StyledInput = styled('input')(({ theme }) => {
return {
padding: '4px 8px',
height: '28px',
color: theme.colors.placeHolderColor,
border: `1px solid ${theme.colors.placeHolderColor}`,
cursor: 'default',
overflow: 'hidden',
userSelect: 'text',
borderRadius: '4px',
flexGrow: 1,
marginRight: '10px',
};
});
export const StyledButton = styled(TextButton)(({ theme }) => {
return {
color: theme.colors.primaryColor,
height: '32px',
background: '#F3F0FF',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});
export const StyledDisableButton = styled(Button)(() => {
return {
color: '#FF631F',
height: '32px',
border: 'none',
marginTop: '16px',
borderRadius: '8px',
padding: '0',
};
});
export const StyledLinkSpan = styled('span')(({ theme }) => {
return {
marginLeft: '4px',
color: theme.colors.primaryColor,
fontWeight: '500',
cursor: 'pointer',
};
});

View File

@@ -6,7 +6,7 @@ import type { Page } from '@blocksuite/store';
import { expect } from '@storybook/jest';
import type { StoryFn } from '@storybook/react';
import { ShareMenu } from '../components/share-menu';
import { ShareMenu } from '../components/share-menu/ShareMenu';
import toast from '../ui/toast/toast';
export default {

View File

@@ -4,7 +4,7 @@
"main": "./src/index.ts",
"module": "./src/index.ts",
"devDependencies": {
"@blocksuite/global": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/global": "0.0.0-20230413112150-e058f87e-nightly",
"next": "=13.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -204,5 +204,11 @@
"Discover what's new!": "Discover what's new!",
"Navigation Path": "Navigation Path",
"View Navigation Path": "View Navigation Path",
"Back to Quick Search": "Back to Quick Search"
"Back to Quick Search": "Back to Quick Search",
"Shared Pages": "Shared Pages",
"Disable Public Sharing": "Disable Public Sharing",
"Disable": "Disable",
"Disable Public Link ?": "Disable Public Link ?",
"Disable Public Link Description": "Disabling this public link will prevent anyone with the link from accessing this page.",
"Export Shared Pages Description": "Download a static copy of your page to share with others."
}

View File

@@ -381,7 +381,9 @@ export function createWorkspaceApis(prefixUrl = '/') {
method: 'GET',
}
).then(r =>
r.ok ? r.arrayBuffer() : Promise.reject(new Error(`${r.status}`))
r.ok
? r.arrayBuffer()
: Promise.reject(new RequestError(MessageCode.noPermission))
);
},
downloadWorkspace: async (

View File

@@ -25,8 +25,8 @@
"idb": "^7.1.1"
},
"devDependencies": {
"@blocksuite/blocks": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/store": "0.0.0-20230412041719-76e5b5b9-nightly",
"@blocksuite/blocks": "0.0.0-20230413112150-e058f87e-nightly",
"@blocksuite/store": "0.0.0-20230413112150-e058f87e-nightly",
"vite": "^4.2.1",
"vite-plugin-dts": "^2.2.0",
"y-indexeddb": "^9.0.10"