mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 13:02:21 +08:00
Merge branch 'feat/workspace' into feat/dev
# Conflicts: # packages/app/src/components/header/page-header.tsx # packages/app/src/components/workspace-slider-bar/index.tsx
This commit is contained in:
@@ -16,13 +16,16 @@ import {
|
|||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import { useEditor } from '@/providers/editor-provider';
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
import ThemeModeSwitch from '@/components/theme-mode-switch';
|
import ThemeModeSwitch from '@/components/theme-mode-switch';
|
||||||
import { IconButton } from '@/ui/button';
|
import { IconButton, Button } from '@/ui/button';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { getWarningMessage, shouldShowWarning } from './utils';
|
import { getWarningMessage, shouldShowWarning } from './utils';
|
||||||
import { Menu, MenuItem } from '@/ui/menu';
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useConfirm } from '@/providers/confirm-provider';
|
||||||
|
|
||||||
const PopoverContent = () => {
|
const PopoverContent = () => {
|
||||||
const { editor, mode, setMode } = useEditor();
|
const { editor, mode, setMode } = useEditor();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -81,9 +84,71 @@ const BrowserWarning = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header = ({ children }: PropsWithChildren<{}>) => {
|
const HeaderRight = () => {
|
||||||
const [showWarning, setShowWarning] = useState(shouldShowWarning());
|
const { pageList, toggleDeletePage, permanentlyDeletePage } = useEditor();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
const router = useRouter();
|
||||||
|
const currentPageMeta = pageList.find(p => p.id === router.query.pageId);
|
||||||
|
const isTrash = !!currentPageMeta?.trash;
|
||||||
|
|
||||||
|
if (isTrash) {
|
||||||
|
const { id } = currentPageMeta;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
bold={true}
|
||||||
|
shape="round"
|
||||||
|
style={{ marginRight: '24px' }}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDeletePage(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Restore it
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
bold={true}
|
||||||
|
shape="round"
|
||||||
|
type="danger"
|
||||||
|
onClick={() => {
|
||||||
|
confirm({
|
||||||
|
title: 'Permanently delete',
|
||||||
|
content:
|
||||||
|
"Once deleted, you can't undo this action. Do you confirm?",
|
||||||
|
confirmText: 'Delete',
|
||||||
|
confirmType: 'danger',
|
||||||
|
}).then(confirm => {
|
||||||
|
if (confirm) {
|
||||||
|
router.push({ pathname: '/page-list/all' });
|
||||||
|
permanentlyDeletePage(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete permanently
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ThemeModeSwitch />
|
||||||
|
<Menu content={<PopoverContent />} placement="bottom-end">
|
||||||
|
<IconButton>
|
||||||
|
<MoreVertical_24pxIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = ({ children }: PropsWithChildren<{}>) => {
|
||||||
|
const { pageList } = useEditor();
|
||||||
|
|
||||||
|
const [showWarning, setShowWarning] = useState(shouldShowWarning());
|
||||||
|
const router = useRouter();
|
||||||
|
const currentPageMeta = pageList.find(p => p.id === router.query.pageId);
|
||||||
|
const isTrash = !!currentPageMeta?.trash;
|
||||||
|
console.log('isTrash', isTrash);
|
||||||
return (
|
return (
|
||||||
<StyledHeaderContainer hasWarning={showWarning}>
|
<StyledHeaderContainer hasWarning={showWarning}>
|
||||||
<BrowserWarning
|
<BrowserWarning
|
||||||
@@ -95,12 +160,7 @@ export const Header = ({ children }: PropsWithChildren<{}>) => {
|
|||||||
<StyledHeader hasWarning={showWarning}>
|
<StyledHeader hasWarning={showWarning}>
|
||||||
{children}
|
{children}
|
||||||
<StyledHeaderRightSide>
|
<StyledHeaderRightSide>
|
||||||
<ThemeModeSwitch />
|
<HeaderRight />
|
||||||
<Menu content={<PopoverContent />} placement="bottom-end">
|
|
||||||
<IconButton>
|
|
||||||
<MoreVertical_24pxIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Menu>
|
|
||||||
</StyledHeaderRightSide>
|
</StyledHeaderRightSide>
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
</StyledHeaderContainer>
|
</StyledHeaderContainer>
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { StyledTitle, StyledTitleWrapper } from './styles';
|
import {
|
||||||
|
StyledSearchArrowWrapper,
|
||||||
|
StyledSwitchWrapper,
|
||||||
|
StyledTitle,
|
||||||
|
StyledTitleWrapper,
|
||||||
|
} from './styles';
|
||||||
|
import { IconButton } from '@/ui/button';
|
||||||
|
import { Content } from '@/ui/layout';
|
||||||
import { useEditor } from '@/providers/editor-provider';
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
import EditorModeSwitch from '@/components/editor-mode-switch';
|
import EditorModeSwitch from '@/components/editor-mode-switch';
|
||||||
import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons';
|
import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/global-modal-provider';
|
||||||
import IconButton from '@/ui/button/icon-button';
|
|
||||||
import Header from './header';
|
import Header from './header';
|
||||||
|
|
||||||
export const PageHeader = () => {
|
export const PageHeader = () => {
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('Untitled');
|
||||||
const [isHover, setIsHover] = useState(false);
|
const [isHover, setIsHover] = useState(false);
|
||||||
|
|
||||||
const { editor } = useEditor();
|
const { editor } = useEditor();
|
||||||
@@ -24,35 +31,35 @@ export const PageHeader = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Header>
|
<Header>
|
||||||
{title ? (
|
<StyledTitle
|
||||||
<StyledTitle
|
onMouseEnter={() => {
|
||||||
onMouseEnter={() => {
|
setIsHover(true);
|
||||||
setIsHover(true);
|
}}
|
||||||
}}
|
onMouseLeave={() => {
|
||||||
onMouseLeave={() => {
|
setIsHover(false);
|
||||||
setIsHover(false);
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<StyledTitleWrapper>
|
||||||
<EditorModeSwitch
|
<StyledSwitchWrapper>
|
||||||
isHover={isHover}
|
<EditorModeSwitch
|
||||||
style={{
|
isHover={isHover}
|
||||||
marginRight: '12px',
|
style={{
|
||||||
}}
|
marginRight: '12px',
|
||||||
/>
|
}}
|
||||||
<StyledTitleWrapper>{title}</StyledTitleWrapper>
|
/>
|
||||||
<IconButton
|
</StyledSwitchWrapper>
|
||||||
style={{
|
<Content ellipsis={true}>{title}</Content>
|
||||||
marginLeft: '6px',
|
<StyledSearchArrowWrapper>
|
||||||
}}
|
<IconButton
|
||||||
>
|
|
||||||
<MiddleIconArrowDownSmallIcon
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
triggerQuickSearchModal(true);
|
triggerQuickSearchModal(true);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</IconButton>
|
<MiddleIconArrowDownSmallIcon />
|
||||||
</StyledTitle>
|
</IconButton>
|
||||||
) : null}
|
</StyledSearchArrowWrapper>
|
||||||
|
</StyledTitleWrapper>
|
||||||
|
</StyledTitle>
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,10 +40,9 @@ export const StyledTitle = styled('div')(({ theme }) => ({
|
|||||||
|
|
||||||
export const StyledTitleWrapper = styled('div')({
|
export const StyledTitleWrapper = styled('div')({
|
||||||
maxWidth: '720px',
|
maxWidth: '720px',
|
||||||
overflow: 'hidden',
|
height: '100%',
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
...displayFlex('center', 'center'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledHeaderRightSide = styled('div')({
|
export const StyledHeaderRightSide = styled('div')({
|
||||||
@@ -89,3 +88,25 @@ export const StyledCloseButton = styled.div(({ theme }) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledSwitchWrapper = styled.div(() => {
|
||||||
|
return {
|
||||||
|
position: 'absolute',
|
||||||
|
right: '100%',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto',
|
||||||
|
...displayFlex('center', 'center'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledSearchArrowWrapper = styled.div(() => {
|
||||||
|
return {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 'calc(100% + 4px)',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto',
|
||||||
|
...displayFlex('center', 'center'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const Empty = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<p>Tips: Click Add to Favourites/Trash and the page will appear here.</p>
|
||||||
|
<p>(Designer is grappling with designing)</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Empty;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { PageMeta, useEditor } from '@/providers/editor-provider';
|
||||||
|
import {
|
||||||
|
MiddleFavouritedStatus2Icon,
|
||||||
|
MiddleFavouritesIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import {
|
||||||
|
StyledFavoriteButton,
|
||||||
|
StyledTableContainer,
|
||||||
|
StyledTableRow,
|
||||||
|
StyledTitleContent,
|
||||||
|
StyledTitleWrapper,
|
||||||
|
} from './styles';
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
|
||||||
|
import { OperationCell, TrashOperationCell } from './operation-cell';
|
||||||
|
import Empty from './empty';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import React from 'react';
|
||||||
|
const FavoriteTag = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||||
|
const { toggleFavoritePage } = useEditor();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledFavoriteButton
|
||||||
|
className="favorite-button"
|
||||||
|
favorite={pageMeta.favorite}
|
||||||
|
onClick={() => {
|
||||||
|
toggleFavoritePage(pageMeta.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pageMeta.favorite ? (
|
||||||
|
<MiddleFavouritedStatus2Icon />
|
||||||
|
) : (
|
||||||
|
<MiddleFavouritesIcon />
|
||||||
|
)}
|
||||||
|
</StyledFavoriteButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageList = ({
|
||||||
|
pageList,
|
||||||
|
showFavoriteTag = false,
|
||||||
|
isTrash = false,
|
||||||
|
}: {
|
||||||
|
pageList: PageMeta[];
|
||||||
|
showFavoriteTag?: boolean;
|
||||||
|
isTrash?: boolean;
|
||||||
|
}) => {
|
||||||
|
if (pageList.length === 0) {
|
||||||
|
return <Empty />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledTableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell proportion={0.5}>Documents</TableCell>
|
||||||
|
<TableCell proportion={0.2}>Created</TableCell>
|
||||||
|
<TableCell proportion={0.2}>
|
||||||
|
{isTrash ? 'Uploaded' : 'Moved to Trash'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell proportion={0.1}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{pageList.map((pageMeta, index) => {
|
||||||
|
return (
|
||||||
|
<StyledTableRow key={`${pageMeta.id}-${index}`}>
|
||||||
|
<TableCell>
|
||||||
|
<StyledTitleWrapper>
|
||||||
|
<Link
|
||||||
|
href={{ pathname: '/', query: { pageId: pageMeta.id } }}
|
||||||
|
>
|
||||||
|
<StyledTitleContent>
|
||||||
|
{pageMeta.title || pageMeta.id}
|
||||||
|
</StyledTitleContent>
|
||||||
|
</Link>
|
||||||
|
{showFavoriteTag && <FavoriteTag pageMeta={pageMeta} />}
|
||||||
|
</StyledTitleWrapper>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
|
||||||
|
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{isTrash ? (
|
||||||
|
<TrashOperationCell pageMeta={pageMeta} />
|
||||||
|
) : (
|
||||||
|
<OperationCell pageMeta={pageMeta} />
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</StyledTableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageList;
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { PageMeta, useEditor } from '@/providers/editor-provider';
|
||||||
|
import { useConfirm } from '@/providers/confirm-provider';
|
||||||
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
|
import { Wrapper } from '@/ui/layout';
|
||||||
|
import { IconButton } from '@/ui/button';
|
||||||
|
import {
|
||||||
|
MoreVertical_24pxIcon,
|
||||||
|
RestoreIcon,
|
||||||
|
TrashDeleteforeverIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||||
|
const { id, favorite } = pageMeta;
|
||||||
|
const { openPage, toggleFavoritePage, toggleDeletePage } = useEditor();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
|
||||||
|
const OperationMenu = (
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
toggleFavoritePage(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{favorite ? 'Remove' : 'Add'} to favourites
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
openPage(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open in new tab
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
confirm({
|
||||||
|
title: 'Delete',
|
||||||
|
content:
|
||||||
|
'Deleted items will be moved to Trash Bin. Do you confirm?',
|
||||||
|
confirmText: 'Delete',
|
||||||
|
confirmType: 'danger',
|
||||||
|
}).then(confirm => {
|
||||||
|
confirm && toggleDeletePage(id);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Wrapper alignItems="center" justifyContent="center">
|
||||||
|
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
|
||||||
|
<IconButton hoverBackground="#E0E6FF">
|
||||||
|
<MoreVertical_24pxIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||||
|
const { id } = pageMeta;
|
||||||
|
const { permanentlyDeletePage, toggleDeletePage } = useEditor();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<IconButton
|
||||||
|
hoverBackground="#E0E6FF"
|
||||||
|
style={{ marginRight: '12px' }}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDeletePage(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RestoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
hoverBackground="#E0E6FF"
|
||||||
|
onClick={() => {
|
||||||
|
confirm({
|
||||||
|
title: 'Permanently delete',
|
||||||
|
content:
|
||||||
|
"Once deleted, you can't undo this action. Do you confirm?",
|
||||||
|
confirmText: 'Delete',
|
||||||
|
confirmType: 'danger',
|
||||||
|
}).then(confirm => {
|
||||||
|
confirm && permanentlyDeletePage(id);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrashDeleteforeverIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { displayFlex, styled, textEllipsis } from '@/styles';
|
||||||
|
import { TableRow } from '@/ui/table';
|
||||||
|
|
||||||
|
export const StyledTableContainer = styled.div(() => {
|
||||||
|
return {
|
||||||
|
height: 'calc(100vh - 60px)',
|
||||||
|
padding: '78px 72px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledTitleWrapper = styled.div(({ theme }) => {
|
||||||
|
return {
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
a: {
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
|
'a:visited': {
|
||||||
|
color: 'unset',
|
||||||
|
},
|
||||||
|
'a:hover': {
|
||||||
|
color: theme.colors.primaryColor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledTitleContent = styled.div(({ theme }) => {
|
||||||
|
return {
|
||||||
|
maxWidth: '90%',
|
||||||
|
marginRight: '18px',
|
||||||
|
...textEllipsis(1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledFavoriteButton = styled.button<{ favorite: boolean }>(
|
||||||
|
({ theme, favorite }) => {
|
||||||
|
return {
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'none',
|
||||||
|
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
|
||||||
|
'&:hover': {
|
||||||
|
color: theme.colors.primaryColor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export const StyledTableRow = styled(TableRow)(({ theme }) => {
|
||||||
|
return {
|
||||||
|
'&:hover': {
|
||||||
|
'.favorite-button': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -8,14 +8,51 @@ import {
|
|||||||
StyledSubListItem,
|
StyledSubListItem,
|
||||||
} from './style';
|
} from './style';
|
||||||
import { Arrow } from './icons';
|
import { Arrow } from './icons';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEditor } from '@/providers/editor-provider';
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/global-modal-provider';
|
||||||
|
|
||||||
|
import { IconButton } from '@/ui/button';
|
||||||
|
|
||||||
|
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||||
|
const { pageList, openPage } = useEditor();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const favoriteList = pageList.filter(p => p.favorite);
|
||||||
|
return (
|
||||||
|
<Collapse in={showList}>
|
||||||
|
{favoriteList.map((pageMeta, index) => {
|
||||||
|
const active = router.query.pageId === pageMeta.id;
|
||||||
|
return (
|
||||||
|
<StyledSubListItem
|
||||||
|
active={active}
|
||||||
|
key={`${pageMeta}-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openPage(pageMeta.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pageMeta.title || pageMeta.id}
|
||||||
|
</StyledSubListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{favoriteList.length === 0 && (
|
||||||
|
<StyledSubListItem disable={true}>No item</StyledSubListItem>
|
||||||
|
)}
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
export const WorkSpaceSliderBar = () => {
|
export const WorkSpaceSliderBar = () => {
|
||||||
const { triggerQuickSearchModal } = useModal();
|
const { triggerQuickSearchModal } = useModal();
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
|
const [showSubFavorite, setShowSubFavorite] = useState(false);
|
||||||
const { createPage } = useEditor();
|
const { createPage } = useEditor();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledSliderBar show={show}>
|
<StyledSliderBar show={show}>
|
||||||
@@ -38,18 +75,40 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
>
|
>
|
||||||
Back to Doc
|
Back to Doc
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
<Link href={{ pathname: '/all-page', query: { name: 'test' } }}>
|
<Link href={{ pathname: '/page-list/all' }}>
|
||||||
<StyledListItem>All pages</StyledListItem>
|
<StyledListItem active={router.pathname === '/page-list/all'}>
|
||||||
|
All pages
|
||||||
|
</StyledListItem>
|
||||||
</Link>
|
</Link>
|
||||||
<StyledListItem>Favourites</StyledListItem>
|
<StyledListItem active={router.pathname === '/page-list/favorite'}>
|
||||||
<StyledSubListItem>
|
<Link
|
||||||
document 1, this is a paper icondocument 1
|
href={{ pathname: '/page-list/favorite' }}
|
||||||
</StyledSubListItem>
|
style={{ flexGrow: 1, textAlign: 'left', color: 'inherit' }}
|
||||||
<StyledSubListItem>document 2</StyledSubListItem>
|
>
|
||||||
<StyledSubListItem>document 4</StyledSubListItem>
|
Favourites
|
||||||
<StyledListItem>Import</StyledListItem>
|
</Link>
|
||||||
<StyledListItem>Bin</StyledListItem>
|
<IconButton
|
||||||
|
hoverBackground="#E0E6FF"
|
||||||
|
onClick={() => {
|
||||||
|
setShowSubFavorite(!showSubFavorite);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MiddleIconArrowDownSmallIcon
|
||||||
|
style={{
|
||||||
|
transform: `rotate(${showSubFavorite ? '180' : '0'}deg)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</StyledListItem>
|
||||||
|
<FavoriteList showList={showSubFavorite} />
|
||||||
|
|
||||||
|
<StyledListItem>Import</StyledListItem>
|
||||||
|
|
||||||
|
<Link href={{ pathname: '/page-list/trash' }}>
|
||||||
|
<StyledListItem active={router.pathname === '/page-list/trash'}>
|
||||||
|
Trash
|
||||||
|
</StyledListItem>
|
||||||
|
</Link>
|
||||||
<StyledNewPageButton
|
<StyledNewPageButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
createPage();
|
createPage();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const StyledSliderBar = styled.div<{ show: boolean }>(
|
|||||||
return {
|
return {
|
||||||
width: show ? '320px' : '0',
|
width: show ? '320px' : '0',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
background: '#FBFBFC',
|
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
|
||||||
boxShadow: theme.shadow.modal,
|
boxShadow: theme.shadow.modal,
|
||||||
transition: 'width .15s',
|
transition: 'width .15s',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -42,22 +42,25 @@ export const StyledArrowButton = styled.button<{ isShow: boolean }>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const StyledListItem = styled.button(({ theme }) => {
|
export const StyledListItem = styled.button<{ active?: boolean }>(
|
||||||
return {
|
({ theme, active }) => {
|
||||||
width: '296px',
|
return {
|
||||||
height: '32px',
|
width: '296px',
|
||||||
marginTop: '12px',
|
height: '32px',
|
||||||
fontSize: theme.font.sm,
|
marginTop: '12px',
|
||||||
color: theme.colors.popoverColor,
|
fontSize: theme.font.sm,
|
||||||
padding: '0 12px',
|
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
||||||
borderRadius: '5px',
|
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
|
||||||
...displayFlex('flex-start', 'center'),
|
paddingLeft: '12px',
|
||||||
':hover': {
|
borderRadius: '5px',
|
||||||
color: theme.colors.primaryColor,
|
...displayFlex('flex-start', 'center'),
|
||||||
backgroundColor: theme.colors.hoverBackground,
|
':hover': {
|
||||||
},
|
color: theme.colors.primaryColor,
|
||||||
};
|
backgroundColor: theme.colors.hoverBackground,
|
||||||
});
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
|
export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
|
||||||
return {
|
return {
|
||||||
@@ -69,20 +72,32 @@ export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledSubListItem = styled.button(({ theme }) => {
|
export const StyledSubListItem = styled.button<{
|
||||||
|
disable?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
}>(({ theme, disable, active }) => {
|
||||||
return {
|
return {
|
||||||
width: '296px',
|
width: '296px',
|
||||||
height: '32px',
|
height: '32px',
|
||||||
marginTop: '4px',
|
marginTop: '4px',
|
||||||
fontSize: theme.font.sm,
|
fontSize: theme.font.sm,
|
||||||
color: theme.colors.popoverColor,
|
color: disable
|
||||||
|
? theme.colors.iconColor
|
||||||
|
: active
|
||||||
|
? theme.colors.primaryColor
|
||||||
|
: theme.colors.popoverColor,
|
||||||
|
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
|
||||||
|
|
||||||
|
cursor: disable ? 'not-allowed' : 'pointer',
|
||||||
paddingLeft: '45px',
|
paddingLeft: '45px',
|
||||||
lineHeight: '32px',
|
lineHeight: '32px',
|
||||||
textAlign: 'start',
|
textAlign: 'start',
|
||||||
...textEllipsis(1),
|
...textEllipsis(1),
|
||||||
':hover': {
|
':hover': disable
|
||||||
color: theme.colors.primaryColor,
|
? {}
|
||||||
backgroundColor: theme.colors.hoverBackground,
|
: {
|
||||||
},
|
color: theme.colors.primaryColor,
|
||||||
|
backgroundColor: theme.colors.hoverBackground,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import { ThemeModeSwitch } from '@/components/theme-mode-switch';
|
|||||||
import { Loading } from '@/components/loading';
|
import { Loading } from '@/components/loading';
|
||||||
import Modal from '@/ui/modal';
|
import Modal from '@/ui/modal';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
|
import {
|
||||||
|
MiddleFavouritedStatus2Icon,
|
||||||
|
MiddleFavouritesIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
export const StyledHeader = styled('div')({
|
export const StyledHeader = styled('div')({
|
||||||
height: '60px',
|
height: '60px',
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
@@ -35,6 +40,20 @@ const Affine = () => {
|
|||||||
<div>hi</div>
|
<div>hi</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Loading />
|
<Loading />
|
||||||
|
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />}>click me!</Button>
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />} type={'primary'}>
|
||||||
|
click me!
|
||||||
|
</Button>
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />} type={'warning'}>
|
||||||
|
click me!
|
||||||
|
</Button>
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />} type={'danger'}>
|
||||||
|
click me!
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />}></Button>
|
||||||
|
<Button icon={<MiddleFavouritedStatus2Icon />} shape="round"></Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Header } from '@/components/header';
|
|
||||||
import { displayFlex, styled, textEllipsis } from '@/styles';
|
|
||||||
import { Table, TableCell, TableHead, TableRow, TableBody } from '../ui/table';
|
|
||||||
import { useConfirm } from '@/providers/confirm-provider';
|
|
||||||
import { IconButton } from '@/ui/button';
|
|
||||||
import { MoreVertical_24pxIcon } from '@blocksuite/icons';
|
|
||||||
import { Menu, MenuItem } from '@/ui/menu';
|
|
||||||
import { PageMeta, useEditor } from '@/providers/editor-provider';
|
|
||||||
import { Wrapper } from '@/ui/layout';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MiddleFavouritesIcon,
|
|
||||||
MiddleFavouritedStatus2Icon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
|
|
||||||
const StyledTableContainer = styled.div(() => {
|
|
||||||
return {
|
|
||||||
height: 'calc(100vh - 60px)',
|
|
||||||
padding: '78px 72px',
|
|
||||||
overflowY: 'auto',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const StyledTitleWrapper = styled.div(({ theme }) => {
|
|
||||||
return {
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
'favorite-heart': {},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const StyledTitleContent = styled.div(({ theme }) => {
|
|
||||||
return {
|
|
||||||
maxWidth: '90%',
|
|
||||||
marginRight: '18px',
|
|
||||||
...textEllipsis(1),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const StyledFavoriteButton = styled.button<{ favorite: boolean }>(
|
|
||||||
({ theme, favorite }) => {
|
|
||||||
return {
|
|
||||||
width: '32px',
|
|
||||||
height: '32px',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
display: 'none',
|
|
||||||
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
|
|
||||||
'&:hover': {
|
|
||||||
color: theme.colors.primaryColor,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const StyledTableRow = styled(TableRow)(({ theme }) => {
|
|
||||||
return {
|
|
||||||
'&:hover': {
|
|
||||||
'.favorite-button': {
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const OperationArea = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
|
||||||
const { id, favorite } = pageMeta;
|
|
||||||
const { openPage, toggleFavoritePage, toggleDeletePage } = useEditor();
|
|
||||||
|
|
||||||
const OperationMenu = (
|
|
||||||
<>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
toggleFavoritePage(id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{favorite ? 'Remove' : 'Add'} to favourites
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
openPage(id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Open in new tab
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
toggleDeletePage(id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Wrapper alignItems="center" justifyContent="center">
|
|
||||||
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
|
|
||||||
<IconButton hoverBackground="#E0E6FF">
|
|
||||||
<MoreVertical_24pxIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Menu>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AllPage = () => {
|
|
||||||
const { confirm } = useConfirm();
|
|
||||||
const { pageList, toggleFavoritePage } = useEditor();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header />
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
confirm({
|
|
||||||
title: 'Permanently delete',
|
|
||||||
content: "Once deleted, you can't undo this action.Do you confirm?",
|
|
||||||
confirmText: 'Delete',
|
|
||||||
confirmType: 'danger',
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
click to show confirm
|
|
||||||
</button>
|
|
||||||
<StyledTableContainer>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell proportion={0.5}>Documents</TableCell>
|
|
||||||
<TableCell proportion={0.2}>Created</TableCell>
|
|
||||||
<TableCell proportion={0.2}>Uploaded</TableCell>
|
|
||||||
<TableCell proportion={0.1}></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{pageList.map((pageMeta, index) => {
|
|
||||||
return (
|
|
||||||
<StyledTableRow key={`${pageMeta.id}-${index}`}>
|
|
||||||
<TableCell>
|
|
||||||
<StyledTitleWrapper>
|
|
||||||
<StyledTitleContent>
|
|
||||||
{pageMeta.title || pageMeta.id}
|
|
||||||
</StyledTitleContent>
|
|
||||||
<StyledFavoriteButton
|
|
||||||
className="favorite-button"
|
|
||||||
favorite={pageMeta.favorite}
|
|
||||||
onClick={() => {
|
|
||||||
toggleFavoritePage(pageMeta.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pageMeta.favorite ? (
|
|
||||||
<MiddleFavouritedStatus2Icon />
|
|
||||||
) : (
|
|
||||||
<MiddleFavouritesIcon />
|
|
||||||
)}
|
|
||||||
</StyledFavoriteButton>
|
|
||||||
</StyledTitleWrapper>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
|
|
||||||
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<OperationArea pageMeta={pageMeta} />
|
|
||||||
</TableCell>
|
|
||||||
</StyledTableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</StyledTableContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AllPage;
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Header } from '@/components/header';
|
||||||
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
|
import { PageList } from '@/components/page-list';
|
||||||
|
|
||||||
|
export const All = () => {
|
||||||
|
const { pageList: allPageList } = useEditor();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<PageList
|
||||||
|
pageList={allPageList.filter(p => !p.trash)}
|
||||||
|
showFavoriteTag={true}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default All;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Header } from '@/components/header';
|
||||||
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
|
import { PageList } from '@/components/page-list';
|
||||||
|
|
||||||
|
export const Favorite = () => {
|
||||||
|
const { pageList: allPageList } = useEditor();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<PageList pageList={allPageList.filter(p => p.favorite && !p.trash)} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Favorite;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Header } from '@/components/header';
|
||||||
|
import { useEditor } from '@/providers/editor-provider';
|
||||||
|
import { PageList } from '@/components/page-list';
|
||||||
|
|
||||||
|
export const Trash = () => {
|
||||||
|
const { pageList: allPageList } = useEditor();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<PageList pageList={allPageList.filter(p => p.trash)} isTrash={true} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Trash;
|
||||||
@@ -3,12 +3,12 @@ import type { PropsWithChildren } from 'react';
|
|||||||
import { Confirm, ConfirmProps } from '@/ui/confirm';
|
import { Confirm, ConfirmProps } from '@/ui/confirm';
|
||||||
|
|
||||||
type ConfirmContextValue = {
|
type ConfirmContextValue = {
|
||||||
confirm: (props: ConfirmProps) => void;
|
confirm: (props: ConfirmProps) => Promise<boolean>;
|
||||||
};
|
};
|
||||||
type ConfirmContextProps = PropsWithChildren<{}>;
|
type ConfirmContextProps = PropsWithChildren<{}>;
|
||||||
|
|
||||||
export const ConfirmContext = createContext<ConfirmContextValue>({
|
export const ConfirmContext = createContext<ConfirmContextValue>({
|
||||||
confirm: () => {},
|
confirm: () => Promise.resolve(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useConfirm = () => useContext(ConfirmContext);
|
export const useConfirm = () => useContext(ConfirmContext);
|
||||||
@@ -22,21 +22,33 @@ export const ConfirmProvider = ({
|
|||||||
return (
|
return (
|
||||||
<ConfirmContext.Provider
|
<ConfirmContext.Provider
|
||||||
value={{
|
value={{
|
||||||
confirm: (props: ConfirmProps) => {
|
confirm: ({ onClose, onCancel, onConfirm, ...props }: ConfirmProps) => {
|
||||||
const confirmId = String(Date.now());
|
return new Promise((resolve, reject) => {
|
||||||
setConfirmRecord(oldConfirmRecord => {
|
const confirmId = String(Date.now());
|
||||||
return {
|
const closeHandler = () => {
|
||||||
...oldConfirmRecord,
|
delete confirmRecord[confirmId];
|
||||||
[confirmId]: (
|
setConfirmRecord({ ...confirmRecord });
|
||||||
<Confirm
|
|
||||||
{...props}
|
|
||||||
onClose={() => {
|
|
||||||
delete confirmRecord[confirmId];
|
|
||||||
setConfirmRecord({ ...confirmRecord });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
setConfirmRecord(oldConfirmRecord => {
|
||||||
|
return {
|
||||||
|
...oldConfirmRecord,
|
||||||
|
[confirmId]: (
|
||||||
|
<Confirm
|
||||||
|
{...props}
|
||||||
|
onCancel={() => {
|
||||||
|
closeHandler();
|
||||||
|
onCancel?.();
|
||||||
|
resolve(false);
|
||||||
|
}}
|
||||||
|
onConfirm={() => {
|
||||||
|
closeHandler();
|
||||||
|
onConfirm?.();
|
||||||
|
resolve(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type EditorHandlers = {
|
|||||||
favoritePage: (pageId: string) => void;
|
favoritePage: (pageId: string) => void;
|
||||||
unFavoritePage: (pageId: string) => void;
|
unFavoritePage: (pageId: string) => void;
|
||||||
toggleFavoritePage: (pageId: string) => void;
|
toggleFavoritePage: (pageId: string) => void;
|
||||||
|
permanentlyDeletePage: (pageId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EditorContextProps = PropsWithChildren<{}>;
|
type EditorContextProps = PropsWithChildren<{}>;
|
||||||
@@ -59,6 +60,7 @@ export const EditorContext = createContext<EditorContextValue>({
|
|||||||
favoritePage: () => {},
|
favoritePage: () => {},
|
||||||
unFavoritePage: () => {},
|
unFavoritePage: () => {},
|
||||||
toggleFavoritePage: () => {},
|
toggleFavoritePage: () => {},
|
||||||
|
permanentlyDeletePage: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useEditor = () => useContext(EditorContext);
|
export const useEditor = () => useContext(EditorContext);
|
||||||
@@ -108,19 +110,23 @@ export const EditorProvider = ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
deletePage: pageId => {
|
deletePage: pageId => {
|
||||||
workspace?.setPage(pageId, { trash: true });
|
workspace?.setPageMeta(pageId, { trash: true });
|
||||||
},
|
},
|
||||||
recyclePage: pageId => {
|
recyclePage: pageId => {
|
||||||
workspace?.setPage(pageId, { trash: false });
|
workspace?.setPageMeta(pageId, { trash: false });
|
||||||
},
|
},
|
||||||
toggleDeletePage: pageId => {
|
toggleDeletePage: pageId => {
|
||||||
const pageMeta = workspace?.meta.pages.find(p => p.id === pageId);
|
const pageMeta = workspace?.meta.pages.find(p => p.id === pageId);
|
||||||
if (pageMeta) {
|
if (pageMeta) {
|
||||||
workspace?.setPage(pageId, { trash: !pageMeta.trash });
|
workspace?.setPageMeta(pageId, { trash: !pageMeta.trash });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
favoritePage: pageId => {
|
favoritePage: pageId => {
|
||||||
workspace?.setPage(pageId, { favorite: true });
|
workspace?.setPageMeta(pageId, { favorite: true });
|
||||||
|
},
|
||||||
|
permanentlyDeletePage: pageId => {
|
||||||
|
// TODO: workspace.meta.removePage or workspace.removePage?
|
||||||
|
workspace?.meta.removePage(pageId);
|
||||||
},
|
},
|
||||||
unFavoritePage: pageId => {
|
unFavoritePage: pageId => {
|
||||||
workspace?.setPageMeta(pageId, { favorite: true });
|
workspace?.setPageMeta(pageId, { favorite: true });
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ThemeProvider as EmotionThemeProvider,
|
ThemeProvider as EmotionThemeProvider,
|
||||||
Global,
|
Global,
|
||||||
css,
|
css,
|
||||||
} from '@emotion/react';
|
} from '@emotion/react';
|
||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
import {
|
||||||
|
ThemeProvider as MuiThemeProvider,
|
||||||
|
createTheme as MuiCreateTheme,
|
||||||
|
} from '@mui/material/styles';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import {
|
import {
|
||||||
Theme,
|
Theme,
|
||||||
@@ -26,6 +30,7 @@ export const ThemeContext = createContext<ThemeProviderValue>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useTheme = () => useContext(ThemeContext);
|
export const useTheme = () => useContext(ThemeContext);
|
||||||
|
const muiTheme = MuiCreateTheme();
|
||||||
|
|
||||||
export const ThemeProvider = ({
|
export const ThemeProvider = ({
|
||||||
defaultTheme = 'light',
|
defaultTheme = 'light',
|
||||||
@@ -87,16 +92,21 @@ export const ThemeProvider = ({
|
|||||||
// }, [mode]);
|
// }, [mode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
|
// Use MuiThemeProvider is just because some Transitions in Mui components need it
|
||||||
<Global
|
<MuiThemeProvider theme={muiTheme}>
|
||||||
styles={css`
|
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
|
||||||
:root {
|
<Global
|
||||||
${globalThemeVariables(mode, themeStyle) as {}}
|
styles={css`
|
||||||
}
|
:root {
|
||||||
`}
|
${globalThemeVariables(mode, themeStyle) as {}}
|
||||||
/>
|
}
|
||||||
<EmotionThemeProvider theme={themeStyle}>{children}</EmotionThemeProvider>
|
`}
|
||||||
</ThemeContext.Provider>
|
/>
|
||||||
|
<EmotionThemeProvider theme={themeStyle}>
|
||||||
|
{children}
|
||||||
|
</EmotionThemeProvider>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
</MuiThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
HTMLAttributes,
|
||||||
|
cloneElement,
|
||||||
|
ReactElement,
|
||||||
|
Children,
|
||||||
|
CSSProperties,
|
||||||
|
forwardRef,
|
||||||
|
PropsWithChildren,
|
||||||
|
} from 'react';
|
||||||
|
import { StyledButton } from './styles';
|
||||||
|
|
||||||
|
import { ButtonProps } from './interface';
|
||||||
|
import { getSize } from './utils';
|
||||||
|
|
||||||
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
size = 'default',
|
||||||
|
disabled = false,
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
shape = 'default',
|
||||||
|
icon,
|
||||||
|
type = 'default',
|
||||||
|
children,
|
||||||
|
bold = false,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { iconSize } = getSize(size);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
ref={ref}
|
||||||
|
disabled={disabled}
|
||||||
|
size={size}
|
||||||
|
shape={shape}
|
||||||
|
hoverBackground={hoverBackground}
|
||||||
|
hoverColor={hoverColor}
|
||||||
|
hoverStyle={hoverStyle}
|
||||||
|
// @ts-ignore
|
||||||
|
type={type}
|
||||||
|
bold={bold}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon &&
|
||||||
|
cloneElement(Children.only(icon), {
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
className: `affine-button-icon ${icon.props.className ?? ''}`,
|
||||||
|
})}
|
||||||
|
{children && <span>{children}</span>}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export default Button;
|
||||||
@@ -11,7 +11,7 @@ import { StyledIconButton } from './styles';
|
|||||||
const SIZE_SMALL = 'small' as const;
|
const SIZE_SMALL = 'small' as const;
|
||||||
const SIZE_MIDDLE = 'middle' as const;
|
const SIZE_MIDDLE = 'middle' as const;
|
||||||
const SIZE_NORMAL = 'normal' as const;
|
const SIZE_NORMAL = 'normal' as const;
|
||||||
// TODO: Designer is not sure about the size of the icon button
|
// TODO: IconButton should merge into Button, but it has not been designed yet
|
||||||
const SIZE_CONFIG = {
|
const SIZE_CONFIG = {
|
||||||
[SIZE_SMALL]: {
|
[SIZE_SMALL]: {
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './icon-button';
|
export * from './icon-button';
|
||||||
|
export * from './button';
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
CSSProperties,
|
||||||
|
HTMLAttributes,
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactElement,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
export const SIZE_SMALL = 'small' as const;
|
||||||
|
export const SIZE_MIDDLE = 'middle' as const;
|
||||||
|
export const SIZE_DEFAULT = 'default' as const;
|
||||||
|
|
||||||
|
export type ButtonProps = PropsWithChildren &
|
||||||
|
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
|
||||||
|
size?: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT;
|
||||||
|
disabled?: boolean;
|
||||||
|
hoverBackground?: CSSProperties['background'];
|
||||||
|
hoverColor?: CSSProperties['color'];
|
||||||
|
hoverStyle?: CSSProperties;
|
||||||
|
icon?: ReactElement;
|
||||||
|
shape?: 'default' | 'round' | 'circle';
|
||||||
|
type?: 'primary' | 'warning' | 'danger' | 'default';
|
||||||
|
bold?: boolean;
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { absoluteCenter, displayInlineFlex, styled } from '@/styles';
|
import { absoluteCenter, displayInlineFlex, styled } from '@/styles';
|
||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
import { ButtonProps } from '@/ui/button/interface';
|
||||||
|
import { getSize, getButtonColors } from './utils';
|
||||||
|
|
||||||
export const StyledIconButton = styled.button<{
|
export const StyledIconButton = styled.button<{
|
||||||
width: number;
|
width: number;
|
||||||
@@ -53,3 +55,67 @@ export const StyledIconButton = styled.button<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
export const StyledButton = styled.button<
|
||||||
|
Pick<
|
||||||
|
ButtonProps,
|
||||||
|
| 'size'
|
||||||
|
| 'disabled'
|
||||||
|
| 'hoverBackground'
|
||||||
|
| 'hoverColor'
|
||||||
|
| 'hoverStyle'
|
||||||
|
| 'shape'
|
||||||
|
| 'type'
|
||||||
|
| 'bold'
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
({
|
||||||
|
theme,
|
||||||
|
size = 'default',
|
||||||
|
disabled,
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
bold = false,
|
||||||
|
shape = 'default',
|
||||||
|
type = 'default',
|
||||||
|
}) => {
|
||||||
|
const { fontSize, borderRadius, padding, height } = getSize(size);
|
||||||
|
|
||||||
|
return {
|
||||||
|
height,
|
||||||
|
paddingLeft: padding,
|
||||||
|
paddingRight: padding,
|
||||||
|
border: '1px solid',
|
||||||
|
...displayInlineFlex('flex-start', 'center'),
|
||||||
|
position: 'relative',
|
||||||
|
...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}),
|
||||||
|
transition: 'background .15s',
|
||||||
|
// TODO: Implement circle shape
|
||||||
|
borderRadius: shape === 'default' ? borderRadius : height / 2,
|
||||||
|
fontSize,
|
||||||
|
fontWeight: bold ? '500' : '400',
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.iconColor,
|
||||||
|
},
|
||||||
|
'>span': {
|
||||||
|
marginLeft: '5px',
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
...getButtonColors(theme, type, {
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
}),
|
||||||
|
|
||||||
|
//
|
||||||
|
// ':hover': {
|
||||||
|
// color: hoverColor ?? theme.colors.primaryColor,
|
||||||
|
// background: hoverBackground ?? theme.colors.hoverBackground,
|
||||||
|
// '.affine-button-icon':{
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// ...(hoverStyle ?? {}),
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { AffineTheme } from '@/styles';
|
||||||
|
import {
|
||||||
|
SIZE_SMALL,
|
||||||
|
SIZE_MIDDLE,
|
||||||
|
SIZE_DEFAULT,
|
||||||
|
ButtonProps,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
|
// TODO: Designer is not sure about the size, Now, is just use default size
|
||||||
|
export const SIZE_CONFIG = {
|
||||||
|
[SIZE_SMALL]: {
|
||||||
|
iconSize: 16,
|
||||||
|
fontSize: 16,
|
||||||
|
borderRadius: 6,
|
||||||
|
height: 26,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
[SIZE_MIDDLE]: {
|
||||||
|
iconSize: 20,
|
||||||
|
fontSize: 16,
|
||||||
|
borderRadius: 6,
|
||||||
|
height: 32,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
[SIZE_DEFAULT]: {
|
||||||
|
iconSize: 24,
|
||||||
|
fontSize: 16,
|
||||||
|
height: 38,
|
||||||
|
padding: 24,
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getSize = (
|
||||||
|
size: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT
|
||||||
|
) => {
|
||||||
|
return SIZE_CONFIG[size];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getButtonColors = (
|
||||||
|
theme: AffineTheme,
|
||||||
|
type: ButtonProps['type'],
|
||||||
|
extend?: {
|
||||||
|
hoverBackground: ButtonProps['hoverBackground'];
|
||||||
|
hoverColor: ButtonProps['hoverColor'];
|
||||||
|
hoverStyle: ButtonProps['hoverStyle'];
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'primary':
|
||||||
|
return {
|
||||||
|
background: theme.colors.primaryColor,
|
||||||
|
color: '#fff',
|
||||||
|
borderColor: theme.colors.primaryColor,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'warning':
|
||||||
|
return {
|
||||||
|
background: theme.colors.warningBackground,
|
||||||
|
color: theme.colors.warningColor,
|
||||||
|
borderColor: theme.colors.warningBackground,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.warningColor,
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.warningColor,
|
||||||
|
color: extend?.hoverColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'danger':
|
||||||
|
return {
|
||||||
|
background: theme.colors.errorBackground,
|
||||||
|
color: theme.colors.errorColor,
|
||||||
|
borderColor: theme.colors.errorBackground,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.errorColor,
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.errorColor,
|
||||||
|
color: extend?.hoverColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
color: theme.colors.popoverColor,
|
||||||
|
borderColor: theme.colors.borderColor,
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.primaryColor,
|
||||||
|
color: extend?.hoverColor ?? theme.colors.primaryColor,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: extend?.hoverColor ?? theme.colors.primaryColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
StyledConfirmContent,
|
StyledConfirmContent,
|
||||||
StyledConfirmTitle,
|
StyledConfirmTitle,
|
||||||
StyledModalWrapper,
|
StyledModalWrapper,
|
||||||
StyledButton,
|
|
||||||
} from '@/ui/confirm/styles';
|
} from '@/ui/confirm/styles';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
export type ConfirmProps = {
|
export type ConfirmProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
@@ -40,23 +39,28 @@ export const Confirm = ({
|
|||||||
<StyledConfirmContent>{content}</StyledConfirmContent>
|
<StyledConfirmContent>{content}</StyledConfirmContent>
|
||||||
|
|
||||||
<StyledButtonWrapper>
|
<StyledButtonWrapper>
|
||||||
<StyledButton
|
<Button
|
||||||
|
shape="round"
|
||||||
|
bold={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
}}
|
}}
|
||||||
|
style={{ marginRight: '24px' }}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</StyledButton>
|
</Button>
|
||||||
<StyledButton
|
<Button
|
||||||
confirmType={confirmType}
|
type={confirmType}
|
||||||
|
shape="round"
|
||||||
|
bold={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
onConfirm?.();
|
onConfirm?.();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{confirmText}
|
{confirmText}
|
||||||
</StyledButton>
|
</Button>
|
||||||
</StyledButtonWrapper>
|
</StyledButtonWrapper>
|
||||||
</StyledModalWrapper>
|
</StyledModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -78,21 +78,3 @@ const getButtonColors = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StyledButton = styled.button<Pick<ConfirmProps, 'confirmType'>>(
|
|
||||||
({ theme, confirmType }) => {
|
|
||||||
return {
|
|
||||||
width: '100px',
|
|
||||||
height: '38px',
|
|
||||||
borderRadius: '19px',
|
|
||||||
border: '1px solid',
|
|
||||||
...getButtonColors(theme, confirmType),
|
|
||||||
fontSize: theme.font.sm,
|
|
||||||
fontWeight: 500,
|
|
||||||
|
|
||||||
'&:first-of-type': {
|
|
||||||
marginRight: '24px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { StyledTableCell } from './styles';
|
|||||||
|
|
||||||
export const TableCell = ({
|
export const TableCell = ({
|
||||||
children,
|
children,
|
||||||
ellipsis,
|
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<
|
}: PropsWithChildren<
|
||||||
TableCellProps & HTMLAttributes<HTMLTableCellElement>
|
TableCellProps & HTMLAttributes<HTMLTableCellElement>
|
||||||
|
|||||||
Reference in New Issue
Block a user