Merge branch 'feat/fileSystem' into develop

This commit is contained in:
QiShaoXuan
2022-12-16 16:34:24 +08:00
32 changed files with 439 additions and 223 deletions
+3 -3
View File
@@ -9,10 +9,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@blocksuite/blocks": "0.3.0-alpha.8", "@blocksuite/blocks": "0.3.0-alpha.9",
"@blocksuite/editor": "0.3.0-alpha.8", "@blocksuite/editor": "0.3.0-alpha.9",
"@blocksuite/icons": "^2.0.0", "@blocksuite/icons": "^2.0.0",
"@blocksuite/store": "0.3.0-alpha.8", "@blocksuite/store": "0.3.0-alpha.9",
"@emotion/css": "^11.10.0", "@emotion/css": "^11.10.0",
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/server": "^11.10.0", "@emotion/server": "^11.10.0",
@@ -66,7 +66,9 @@ export const EditorModeSwitch = ({
style = {}, style = {},
}: AnimateRadioProps) => { }: AnimateRadioProps) => {
const { mode: themeMode } = useTheme(); const { mode: themeMode } = useTheme();
const { mode, setMode } = useEditor(); const { mode, setMode, getPageMeta } = useEditor();
const pageMeta = getPageMeta();
const modifyRadioItemStatus = (): RadioItemStatus => { const modifyRadioItemStatus = (): RadioItemStatus => {
return { return {
left: isHover left: isHover
@@ -99,6 +101,7 @@ export const EditorModeSwitch = ({
data-testid="editor-mode-switcher" data-testid="editor-mode-switcher"
shrink={!isHover} shrink={!isHover}
style={style} style={style}
disabled={!!pageMeta?.trash}
> >
<AnimateRadioItem <AnimateRadioItem
isLeft={true} isLeft={true}
@@ -5,41 +5,42 @@ import type { ItemStatus } from './type';
const ANIMATE_DURATION = 500; const ANIMATE_DURATION = 500;
export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>( export const StyledAnimateRadioContainer = styled('div')<{
({ shrink, theme }) => { shrink: boolean;
const animateScaleStretch = keyframes`${toString( disabled: boolean;
spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) }>(({ shrink, theme, disabled }) => {
)}`; const animateScaleStretch = keyframes`${toString(
const animateScaleShrink = keyframes( spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' })
`${toString( )}`;
spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) const animateScaleShrink = keyframes(
)}` `${toString(
); spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' })
const shrinkStyle = shrink )}`
? { );
animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`, const shrinkStyle = shrink
background: 'transparent', ? {
} animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`,
: { background: 'transparent',
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`, }
}; : {
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`,
};
return { return {
height: '36px', height: '36px',
borderRadius: '18px', borderRadius: '18px',
background: theme.colors.hoverBackground, background: disabled ? 'transparent' : theme.colors.hoverBackground,
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`, transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`,
border: '1px solid transparent', border: '1px solid transparent',
...shrinkStyle, ...(disabled ? { pointerEvents: 'none' } : shrinkStyle),
':hover': { ':hover': {
border: `1px solid ${theme.colors.primaryColor}`, border: disabled ? '' : `1px solid ${theme.colors.primaryColor}`,
}, },
}; };
} });
);
export const StyledMiddleLine = styled('div')<{ export const StyledMiddleLine = styled('div')<{
hidden: boolean; hidden: boolean;
-76
View File
@@ -1,76 +0,0 @@
import { useState } from 'react';
import {
StyledFAQ,
StyledIconWrapper,
StyledFAQWrapper,
StyledTransformIcon,
} from './style';
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import Grow from '@mui/material/Grow';
import { Tooltip } from '@/ui/tooltip';
import { useEditor } from '@/providers/editor-provider';
import { useModal } from '@/providers/global-modal-provider';
import { useTheme } from '@/providers/themeProvider';
export const FAQ = () => {
const [showContent, setShowContent] = useState(false);
const { mode } = useTheme();
const { mode: editorMode } = useEditor();
const { triggerShortcutsModal, triggerContactModal } = useModal();
const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless';
return (
<>
<StyledFAQ
className=""
onMouseEnter={() => {
setShowContent(true);
}}
onMouseLeave={() => {
setShowContent(false);
}}
>
<Grow in={showContent}>
<StyledFAQWrapper>
<Tooltip content="Contact Us" placement="left-end">
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerContactModal();
}}
>
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
<Tooltip content="Keyboard Shortcuts" placement="left-end">
<StyledIconWrapper
data-testid="shortcuts-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerShortcutsModal();
}}
>
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
</StyledFAQWrapper>
</Grow>
<div style={{ position: 'relative' }}>
<StyledIconWrapper
data-testid="faq-icon"
isEdgelessDark={isEdgelessDark}
>
<HelpIcon />
</StyledIconWrapper>
<StyledTransformIcon in={showContent}>
<CloseIcon />
</StyledTransformIcon>
</div>
</StyledFAQ>
</>
);
};
@@ -5,25 +5,21 @@ import {
StyledTitle, StyledTitle,
StyledTitleWrapper, StyledTitleWrapper,
} from './styles'; } from './styles';
import { IconButton } from '@/ui/button';
import { Content } from '@/ui/layout'; 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 { ArrowDownIcon } from '@blocksuite/icons'; import QuickSearchButton from './quick-search-button';
import { useModal } from '@/providers/global-modal-provider';
import Header from './header'; import Header from './header';
export const PageHeader = () => { export const EditorHeader = () => {
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [isHover, setIsHover] = useState(false); const [isHover, setIsHover] = useState(false);
const { editor, onPropsUpdated } = useEditor(); const { editor, onPropsUpdated, getPageMeta } = useEditor();
const { triggerQuickSearchModal } = useModal();
useEffect(() => { useEffect(() => {
onPropsUpdated(editor => { onPropsUpdated(editor => {
setTitle(editor.model.title); setTitle(editor.model.title || 'Untitled');
}); });
}, [onPropsUpdated]); }, [onPropsUpdated]);
@@ -31,14 +27,20 @@ export const PageHeader = () => {
setTitle(editor?.model.title || 'Untitled'); setTitle(editor?.model.title || 'Untitled');
}, [editor]); }, [editor]);
const pageMeta = getPageMeta();
return ( return (
<Header> <Header>
{title && ( {title && (
<StyledTitle <StyledTitle
onMouseEnter={() => { onMouseEnter={() => {
if (pageMeta?.trash) return;
setIsHover(true); setIsHover(true);
}} }}
onMouseLeave={() => { onMouseLeave={() => {
if (pageMeta?.trash) return;
setIsHover(false); setIsHover(false);
}} }}
> >
@@ -53,13 +55,7 @@ export const PageHeader = () => {
</StyledSwitchWrapper> </StyledSwitchWrapper>
<Content ellipsis={true}>{title}</Content> <Content ellipsis={true}>{title}</Content>
<StyledSearchArrowWrapper> <StyledSearchArrowWrapper>
<IconButton <QuickSearchButton />
onClick={() => {
triggerQuickSearchModal();
}}
>
<ArrowDownIcon />
</IconButton>
</StyledSearchArrowWrapper> </StyledSearchArrowWrapper>
</StyledTitleWrapper> </StyledTitleWrapper>
</StyledTitle> </StyledTitle>
@@ -68,4 +64,4 @@ export const PageHeader = () => {
); );
}; };
export default PageHeader; export default EditorHeader;
+2 -2
View File
@@ -1,3 +1,3 @@
export * from './header'; export * from './header';
export * from './page-header'; export * from './editor-header';
export { StyledTitle as HeaderWrapper } from './styles'; export * from './page-list-header';
@@ -0,0 +1,21 @@
import { PropsWithChildren, ReactNode } from 'react';
import Header from './header';
import { StyledPageListTittleWrapper } from './styles';
import QuickSearchButton from './quick-search-button';
export type PageListHeaderProps = PropsWithChildren<{
icon?: ReactNode;
}>;
export const PageListHeader = ({ icon, children }: PageListHeaderProps) => {
return (
<Header>
<StyledPageListTittleWrapper>
{icon}
{children}
<QuickSearchButton style={{ marginLeft: '5px' }} />
</StyledPageListTittleWrapper>
</Header>
);
};
export default PageListHeader;
@@ -0,0 +1,25 @@
import React from 'react';
import { IconButton, IconButtonProps } from '@/ui/button';
import { ArrowDownIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/global-modal-provider';
export const QuickSearchButton = ({
onClick,
...props
}: Omit<IconButtonProps, 'children'>) => {
const { triggerQuickSearchModal } = useModal();
return (
<IconButton
{...props}
onClick={e => {
onClick?.(e);
triggerQuickSearchModal();
}}
>
<ArrowDownIcon />
</IconButton>
);
};
export default QuickSearchButton;
@@ -110,3 +110,14 @@ export const StyledSearchArrowWrapper = styled.div(() => {
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
}; };
}); });
export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
return {
fontSize: theme.font.sm,
color: theme.colors.textColor,
'>svg': {
fontSize: '20px',
marginRight: '12px',
},
};
});
@@ -0,0 +1,86 @@
import { useState } from 'react';
import {
StyledIsland,
StyledIconWrapper,
StyledIslandWrapper,
StyledTransformIcon,
} from './style';
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import Grow from '@mui/material/Grow';
import { Tooltip } from '@/ui/tooltip';
import { useEditor } from '@/providers/editor-provider';
import { useModal } from '@/providers/global-modal-provider';
import { useTheme } from '@/providers/themeProvider';
export type IslandItemNames = 'contact' | 'shortcuts';
// export type IslandShowMap = Record<IslandItemNames, boolean>;
export const HelpIsland = ({
showList = ['contact', 'shortcuts'],
}: {
showList?: IslandItemNames[];
}) => {
const [showContent, setShowContent] = useState(false);
const { mode } = useTheme();
const { mode: editorMode } = useEditor();
const { triggerShortcutsModal, triggerContactModal } = useModal();
const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless';
return (
<>
<StyledIsland
className=""
onMouseEnter={() => {
setShowContent(true);
}}
onMouseLeave={() => {
setShowContent(false);
}}
>
<Grow in={showContent}>
<StyledIslandWrapper>
{showList.includes('contact') && (
<Tooltip content="Contact Us" placement="left-end">
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerContactModal();
}}
>
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip content="Keyboard Shortcuts" placement="left-end">
<StyledIconWrapper
data-testid="shortcuts-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerShortcutsModal();
}}
>
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledIslandWrapper>
</Grow>
<div style={{ position: 'relative' }}>
<StyledIconWrapper isEdgelessDark={isEdgelessDark}>
<HelpIcon />
</StyledIconWrapper>
<StyledTransformIcon in={showContent}>
<CloseIcon />
</StyledTransformIcon>
</div>
</StyledIsland>
</>
);
};
export default HelpIsland;
@@ -1,6 +1,6 @@
import { displayFlex, styled } from '@/styles'; import { displayFlex, styled } from '@/styles';
export const StyledFAQ = styled('div')(({ theme }) => { export const StyledIsland = styled('div')(({ theme }) => {
return { return {
width: '32px', width: '32px',
height: '32px', height: '32px',
@@ -57,7 +57,7 @@ export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>(
} }
); );
export const StyledFAQWrapper = styled('div')(({ theme }) => { export const StyledIslandWrapper = styled('div')(({ theme }) => {
return { return {
position: 'absolute', position: 'absolute',
bottom: '100%', bottom: '100%',
@@ -9,16 +9,17 @@ dayjs.extend(localizedFormat);
export const DateCell = ({ export const DateCell = ({
pageMeta, pageMeta,
dateKey, dateKey,
backupKey = '',
}: { }: {
pageMeta: PageMeta; pageMeta: PageMeta;
dateKey: keyof PageMeta; dateKey: keyof PageMeta;
backupKey?: keyof PageMeta;
}) => { }) => {
// dayjs().format('L LT'); // dayjs().format('L LT');
const value = pageMeta[dateKey] ?? pageMeta[backupKey];
return ( return (
<TableCell ellipsis={true}> <TableCell ellipsis={true}>
{pageMeta[dateKey] === undefined {value ? dayjs(value as string).format('YYYY-MM-DD HH:mm') : '--'}
? '--'
: dayjs(pageMeta[dateKey] as string).format('YYYY-MM-DD HH:mm')}
</TableCell> </TableCell>
); );
}; };
@@ -85,6 +85,7 @@ export const PageList = ({
<DateCell <DateCell
pageMeta={pageMeta} pageMeta={pageMeta}
dateKey={isTrash ? 'trashDate' : 'updatedDate'} dateKey={isTrash ? 'trashDate' : 'updatedDate'}
backupKey={isTrash ? 'trashDate' : 'createDate'}
/> />
<TableCell style={{ padding: 0 }}> <TableCell style={{ padding: 0 }}>
{isTrash ? ( {isTrash ? (
@@ -40,9 +40,8 @@ export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
<MenuItem <MenuItem
onClick={() => { onClick={() => {
confirm({ confirm({
title: 'Delete', title: 'Delete page?',
content: content: `${pageMeta.title} will be moved to Trash`,
'Deleted items will be moved to Trash Bin. Do you confirm?',
confirmText: 'Delete', confirmText: 'Delete',
confirmType: 'danger', confirmType: 'danger',
}).then(confirm => { }).then(confirm => {
+104
View File
@@ -0,0 +1,104 @@
// Copyright: https://github.com/toeverything/blocksuite/commit/8032ef3ab97aefce01664b36502fc392c5db8b78#diff-bf5b41be21936f9165a8400c7f20e24d3dbc49644ba57b9258e0943f0dc1c464
import { css, html, TemplateResult } from 'lit';
export const sleep = (ms = 0) =>
new Promise(resolve => setTimeout(resolve, ms));
let ToastContainer: HTMLDivElement | null = null;
/**
* DO NOT USE FOR USER INPUT
* See https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*/
const htmlToElement = <T extends ChildNode>(html: string | TemplateResult) => {
const template = document.createElement('template');
if (typeof html === 'string') {
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
} else {
const { strings, values } = html;
const v = [...values, '']; // + last emtpty part
template.innerHTML = strings.reduce((acc, cur, i) => acc + cur + v[i], '');
}
return template.content.firstChild as T;
};
const createToastContainer = () => {
const styles = css`
position: fixed;
z-index: 9999;
top: 16px;
left: 16px;
right: 16px;
bottom: 78px;
pointer-events: none;
display: flex;
flex-direction: column-reverse;
align-items: center;
`;
const template = html`<div style="${styles}"></div>`;
const element = htmlToElement<HTMLDivElement>(template);
document.body.appendChild(element);
return element;
};
/**
* @example
* ```ts
* toast('Hello World');
* ```
*/
export const toast = (message: string, duration = 2500) => {
if (!ToastContainer) {
ToastContainer = createToastContainer();
}
const styles = css`
max-width: 480px;
text-align: center;
font-family: var(--affine-font-family);
font-size: var(--affine-font-sm);
padding: 6px 12px;
margin: 10px 0 0 0;
color: var(--affine-tooltip-color);
background: var(--affine-tooltip-background);
box-shadow: var(--affine-tooltip-shadow);
border-radius: 10px;
transition: all 230ms cubic-bezier(0.21, 1.02, 0.73, 1);
opacity: 0;
`;
const template = html`<div style="${styles}"></div>`;
const element = htmlToElement<HTMLDivElement>(template);
// message is not trusted
element.textContent = message;
ToastContainer.appendChild(element);
const fadeIn = [
{
opacity: 0,
},
{ opacity: 1 },
];
const options = {
duration: 230,
easing: 'cubic-bezier(0.21, 1.02, 0.73, 1)',
fill: 'forwards' as const,
}; // satisfies KeyframeAnimationOptions;
element.animate(fadeIn, options);
setTimeout(async () => {
const fadeOut = fadeIn.reverse();
const animation = element.animate(fadeOut, options);
await animation.finished;
element.style.maxHeight = '0';
element.style.margin = '0';
element.style.padding = '0';
// wait for transition
await sleep(230);
element.remove();
}, duration);
return element;
};
export default toast;
@@ -20,10 +20,12 @@ import {
AddIcon, AddIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import Link from 'next/link'; import Link from 'next/link';
import { Tooltip } from '@/ui/tooltip';
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'; import { IconButton } from '@/ui/button';
import useLocalStorage from '@/hooks/use-local-storage';
const FavoriteList = ({ showList }: { showList: boolean }) => { const FavoriteList = ({ showList }: { showList: boolean }) => {
const { pageList, openPage } = useEditor(); const { pageList, openPage } = useEditor();
@@ -45,7 +47,7 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
openPage(pageMeta.id); openPage(pageMeta.id);
}} }}
> >
{pageMeta.title || pageMeta.id} {pageMeta.title || 'Untitled'}
</StyledSubListItem> </StyledSubListItem>
); );
})} })}
@@ -57,11 +59,13 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
}; };
export const WorkSpaceSliderBar = () => { export const WorkSpaceSliderBar = () => {
const { triggerQuickSearchModal, triggerImportModal } = useModal(); const { triggerQuickSearchModal, triggerImportModal } = useModal();
const [show, setShow] = useState(false);
const [showSubFavorite, setShowSubFavorite] = useState(true); const [showSubFavorite, setShowSubFavorite] = useState(true);
const { createPage, getPageMeta, openPage } = useEditor(); const { createPage, getPageMeta, openPage } = useEditor();
const router = useRouter(); const router = useRouter();
const [showTip, setShowTip] = useState(false);
const [show, setShow] = useLocalStorage('AFFINE_SLIDE_BAR', false, true);
return ( return (
<> <>
<StyledSliderBar show={show}> <StyledSliderBar show={show}>
@@ -120,14 +124,27 @@ export const WorkSpaceSliderBar = () => {
<AddIcon /> New Page <AddIcon /> New Page
</StyledNewPageButton> </StyledNewPageButton>
</StyledSliderBar> </StyledSliderBar>
<StyledArrowButton <Tooltip
isShow={show} content={show ? 'Collapse sidebar' : 'Expand sidebar'}
onClick={() => { placement="right"
setShow(!show); visible={showTip}
}}
> >
<Arrow /> <StyledArrowButton
</StyledArrowButton> isShow={show}
onClick={() => {
setShow(!show);
setShowTip(false);
}}
onMouseEnter={() => {
setShowTip(true);
}}
onMouseLeave={() => {
setShowTip(false);
}}
>
<Arrow />
</StyledArrowButton>
</Tooltip>
</> </>
); );
}; };
@@ -0,0 +1,33 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
export type UseLocalStorage = <S>(
key: string,
initialState: S | (() => S),
// If initialState is different from the initial local data, set it
initialValue?: S
) => [S, Dispatch<SetStateAction<S>>];
export const useLocalStorage: UseLocalStorage = (
key,
defaultValue,
initialValue
) => {
const [item, setItem] = useState(defaultValue);
useEffect(() => {
const saved = localStorage.getItem(key);
if (saved) {
setItem(JSON.parse(saved));
} else if (initialValue) {
setItem(initialValue);
}
}, [initialValue, key, setItem]);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(item));
}, [item, key]);
return [item, setItem];
};
export default useLocalStorage;
+8
View File
@@ -13,6 +13,9 @@ import '../utils/print-build-info';
import { styled } from '@/styles'; import { styled } from '@/styles';
import ProviderComposer from '@/components/provider-composer'; import ProviderComposer from '@/components/provider-composer';
import ConfirmProvider from '@/providers/confirm-provider'; import ConfirmProvider from '@/providers/confirm-provider';
import HelpIsland from '@/components/help-island';
import { useRouter } from 'next/router';
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), { const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
ssr: false, ssr: false,
}); });
@@ -35,6 +38,8 @@ const StyledWrapper = styled('div')(({ theme }) => {
}); });
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
return ( return (
<> <>
<Logger /> <Logger />
@@ -50,6 +55,9 @@ function MyApp({ Component, pageProps }: AppProps) {
<WorkSpaceSliderBar /> <WorkSpaceSliderBar />
<StyledWrapper> <StyledWrapper>
<Component {...pageProps} /> <Component {...pageProps} />
<HelpIsland
showList={router.pathname !== '/' ? ['contact'] : undefined}
/>
</StyledWrapper> </StyledWrapper>
</StyledPage> </StyledPage>
</ProviderComposer> </ProviderComposer>
+9 -1
View File
@@ -5,6 +5,7 @@ import Modal from '@/ui/modal';
import { useState } from 'react'; import { useState } from 'react';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { FavouritedIcon } from '@blocksuite/icons'; import { FavouritedIcon } from '@blocksuite/icons';
import { toast } from '@/components/toast';
export const StyledHeader = styled('div')({ export const StyledHeader = styled('div')({
height: '60px', height: '60px',
width: '100vw', width: '100vw',
@@ -38,7 +39,14 @@ const Affine = () => {
</Modal> </Modal>
<Loading /> <Loading />
<Button icon={<FavouritedIcon />}>click me!</Button> <Button
icon={<FavouritedIcon />}
onClick={() => {
toast('hello, world!!');
}}
>
click me!
</Button>
<Button icon={<FavouritedIcon />} type={'primary'}> <Button icon={<FavouritedIcon />} type={'primary'}>
click me! click me!
</Button> </Button>
+2 -4
View File
@@ -1,7 +1,6 @@
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import { styled } from '@/styles'; import { styled } from '@/styles';
import { PageHeader } from '@/components/header'; import { EditorHeader } from '@/components/header';
import { FAQ } from '@/components/faq';
import EdgelessToolbar from '@/components/edgeless-toolbar'; import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal'; import MobileModal from '@/components/mobile-modal';
import Editor from '@/components/editor'; import Editor from '@/components/editor';
@@ -15,12 +14,11 @@ const StyledEditorContainer = styled('div')(({ theme }) => {
const Home: NextPage = () => { const Home: NextPage = () => {
return ( return (
<> <>
<PageHeader /> <EditorHeader />
<MobileModal /> <MobileModal />
<StyledEditorContainer> <StyledEditorContainer>
<Editor /> <Editor />
</StyledEditorContainer> </StyledEditorContainer>
<FAQ />
<EdgelessToolbar /> <EdgelessToolbar />
</> </>
); );
+2 -20
View File
@@ -1,32 +1,14 @@
import { Header } from '@/components/header';
import { useEditor } from '@/providers/editor-provider'; import { useEditor } from '@/providers/editor-provider';
import { PageList } from '@/components/page-list'; import { PageList } from '@/components/page-list';
import { AllPagesIcon } from '@blocksuite/icons'; import { AllPagesIcon } from '@blocksuite/icons';
import { HeaderWrapper } from '@/components/header'; import { PageListHeader } from '@/components/header';
import { styled } from '@/styles';
export const StyledWrapper = styled(HeaderWrapper)(({ theme }) => {
return {
fontSize: theme.font.sm,
color: theme.colors.textColor,
'>svg': {
fontSize: '20px',
marginRight: '12px',
},
};
});
export const All = () => { export const All = () => {
const { pageList: allPageList } = useEditor(); const { pageList: allPageList } = useEditor();
return ( return (
<> <>
<Header> <PageListHeader icon={<AllPagesIcon />}>All Page</PageListHeader>
<StyledWrapper>
<AllPagesIcon />
All Pages
</StyledWrapper>
</Header>
<PageList <PageList
pageList={allPageList.filter(p => !p.trash)} pageList={allPageList.filter(p => !p.trash)}
showFavoriteTag={true} showFavoriteTag={true}
@@ -1,19 +1,13 @@
import { Header } from '@/components/header'; import { PageListHeader } from '@/components/header';
import { useEditor } from '@/providers/editor-provider'; import { useEditor } from '@/providers/editor-provider';
import { PageList } from '@/components/page-list'; import { PageList } from '@/components/page-list';
import { StyledWrapper } from './all';
import { FavouritesIcon } from '@blocksuite/icons'; import { FavouritesIcon } from '@blocksuite/icons';
export const Favorite = () => { export const Favorite = () => {
const { pageList: allPageList } = useEditor(); const { pageList: allPageList } = useEditor();
return ( return (
<> <>
<Header> <PageListHeader icon={<FavouritesIcon />}>Favorites</PageListHeader>
<StyledWrapper>
<FavouritesIcon />
Favorites
</StyledWrapper>
</Header>
<PageList pageList={allPageList.filter(p => p.favorite && !p.trash)} /> <PageList pageList={allPageList.filter(p => p.favorite && !p.trash)} />
</> </>
); );
+2 -8
View File
@@ -1,18 +1,12 @@
import { Header } from '@/components/header'; import { PageListHeader } from '@/components/header';
import { useEditor } from '@/providers/editor-provider'; import { useEditor } from '@/providers/editor-provider';
import { PageList } from '@/components/page-list'; import { PageList } from '@/components/page-list';
import { StyledWrapper } from './all';
import { TrashIcon } from '@blocksuite/icons'; import { TrashIcon } from '@blocksuite/icons';
export const Trash = () => { export const Trash = () => {
const { pageList: allPageList } = useEditor(); const { pageList: allPageList } = useEditor();
return ( return (
<> <>
<Header> <PageListHeader icon={<TrashIcon />}>Trash</PageListHeader>
<StyledWrapper>
<TrashIcon />
Trash
</StyledWrapper>
</Header>
<PageList pageList={allPageList.filter(p => p.trash)} isTrash={true} /> <PageList pageList={allPageList.filter(p => p.trash)} isTrash={true} />
</> </>
); );
@@ -42,7 +42,7 @@ export const EditorProvider = ({
const { mode, setMode } = useMode({ workspace, page }); const { mode, setMode } = useMode({ workspace, page });
const editorHandlers = useEditorHandler({ workspace, editor }); const editorHandlers = useEditorHandler({ workspace, editor, page });
const onPropsUpdated = usePropsUpdated(editor); const onPropsUpdated = usePropsUpdated(editor);
const onHistoryUpdated = useHistoryUpdated(page); const onHistoryUpdated = useHistoryUpdated(page);
// Modify the updatedDate when history change // Modify the updatedDate when history change
@@ -113,6 +113,11 @@ const EditorReactor = ({
editor.mode = pageMeta.mode; editor.mode = pageMeta.mode;
} }
if (pageMeta?.trash) {
// @ts-ignore
editor.readonly = true;
}
setEditor(editor); setEditor(editor);
}, [workspace, currentPage, setEditor]); }, [workspace, currentPage, setEditor]);
@@ -1,15 +1,17 @@
import { QueryContent } from '@blocksuite/store/dist/workspace/search'; import { QueryContent } from '@blocksuite/store/dist/workspace/search';
import { createPage, initialPage, generateDefaultPageId } from '../utils'; import { createPage, initialPage, generateDefaultPageId } from '../utils';
import { Workspace } from '@blocksuite/store'; import { Workspace, Page } from '@blocksuite/store';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { EditorHandlers, PageMeta } from '../interface'; import { EditorHandlers, PageMeta } from '../interface';
import { EditorContainer } from '@blocksuite/editor'; import { EditorContainer } from '@blocksuite/editor';
import toast from '@/components/toast';
export const useEditorHandler = ({ export const useEditorHandler = ({
editor, editor,
workspace, workspace,
page,
}: { }: {
workspace?: Workspace; workspace?: Workspace;
page?: Page;
editor?: EditorContainer; editor?: EditorContainer;
}): EditorHandlers => { }): EditorHandlers => {
const router = useRouter(); const router = useRouter();
@@ -20,8 +22,8 @@ export const useEditorHandler = ({
initialPage(page, title); initialPage(page, title);
return page; return page;
}, },
getPageMeta(pageId: string) { getPageMeta(pageId) {
pageId = pageId.replace('space:', ''); pageId = (pageId ?? page!.id).replace('space:', '');
return workspace!.meta.pageMetas.find( return workspace!.meta.pageMetas.find(
page => page.id === pageId page => page.id === pageId
) as PageMeta; ) as PageMeta;
@@ -42,22 +44,26 @@ export const useEditorHandler = ({
trash: !pageMeta.trash, trash: !pageMeta.trash,
trashDate: +new Date(), trashDate: +new Date(),
}); });
if (pageMeta.trash) {
toast('Removed to trash');
const editor = document.querySelector('editor-container');
// @ts-ignore
editor?.setAttribute('readonly', 'false');
}
} }
}, },
favoritePage: pageId => {
workspace!.setPageMeta(pageId, { favorite: true });
},
permanentlyDeletePage: pageId => { permanentlyDeletePage: pageId => {
// TODO: workspace.meta.removePage or workspace.removePage? // TODO: workspace.meta.removePage or workspace.removePage?
workspace!.meta.removePage(pageId); workspace!.meta.removePage(pageId);
}, },
unFavoritePage: pageId => {
workspace!.setPageMeta(pageId, { favorite: true });
},
toggleFavoritePage: pageId => { toggleFavoritePage: pageId => {
const pageMeta = workspace?.meta.pageMetas.find(p => p.id === pageId); const pageMeta = workspace?.meta.pageMetas.find(p => p.id === pageId);
if (pageMeta) { if (pageMeta) {
workspace!.setPageMeta(pageId, { favorite: !pageMeta.favorite }); workspace!.setPageMeta(pageId, { favorite: !pageMeta.favorite });
toast(
pageMeta.favorite ? 'Removed to Favourites' : 'Added to Favourites'
);
} }
}, },
search: (query: QueryContent) => { search: (query: QueryContent) => {
@@ -31,10 +31,8 @@ export type EditorHandlers = {
pageId: string, pageId: string,
query?: { [key: string]: string } query?: { [key: string]: string }
) => Promise<boolean>; ) => Promise<boolean>;
getPageMeta: (pageId: string) => PageMeta | void; getPageMeta: (pageId?: string) => PageMeta | void;
toggleDeletePage: (pageId: string) => void; toggleDeletePage: (pageId: string) => void;
favoritePage: (pageId: string) => void;
unFavoritePage: (pageId: string) => void;
toggleFavoritePage: (pageId: string) => void; toggleFavoritePage: (pageId: string) => void;
permanentlyDeletePage: (pageId: string) => void; permanentlyDeletePage: (pageId: string) => void;
search: (query: QueryContent) => Map<string, string | undefined>; search: (query: QueryContent) => Map<string, string | undefined>;
+4 -2
View File
@@ -16,6 +16,7 @@ export const getLightTheme = (
pageBackground: '#fff', pageBackground: '#fff',
hoverBackground: '#F1F3FF', hoverBackground: '#F1F3FF',
innerHoverBackground: '#E0E6FF',
popoverBackground: '#fff', popoverBackground: '#fff',
tooltipBackground: '#6880FF', tooltipBackground: '#6880FF',
codeBackground: '#f2f5f9', codeBackground: '#f2f5f9',
@@ -24,7 +25,7 @@ export const getLightTheme = (
textColor: '#3A4C5C', textColor: '#3A4C5C',
edgelessTextColor: '#3A4C5C', edgelessTextColor: '#3A4C5C',
iconColor: '#9096A5', iconColor: '#888a9e',
linkColor: '#6880FF', linkColor: '#6880FF',
linkColor2: '#6880FF', linkColor2: '#6880FF',
linkVisitedColor: '#ABB8FE', linkVisitedColor: '#ABB8FE',
@@ -87,6 +88,7 @@ export const getDarkTheme = (
pageBackground: '#2c2c2c', pageBackground: '#2c2c2c',
hoverBackground: '#3C3C42', hoverBackground: '#3C3C42',
innerHoverBackground: '#E0E6FF',
popoverBackground: '#1F2021', popoverBackground: '#1F2021',
tooltipBackground: '#1F2021', tooltipBackground: '#1F2021',
codeBackground: codeBackground:
@@ -98,7 +100,7 @@ export const getDarkTheme = (
textColor: '#fff', textColor: '#fff',
edgelessTextColor: '#3A4C5C', edgelessTextColor: '#3A4C5C',
iconColor: '#9096A5', iconColor: '#888a9e',
linkColor: '#7D91FF', linkColor: '#7D91FF',
linkColor2: '#6880FF', linkColor2: '#6880FF',
linkVisitedColor: '#505FAB', linkVisitedColor: '#505FAB',
+1
View File
@@ -23,6 +23,7 @@ export interface AffineTheme {
popoverBackground: string; popoverBackground: string;
tooltipBackground: string; tooltipBackground: string;
hoverBackground: string; hoverBackground: string;
innerHoverBackground: string;
codeBackground: string; codeBackground: string;
warningBackground: string; warningBackground: string;
errorBackground: string; errorBackground: string;
+1 -3
View File
@@ -4,8 +4,7 @@ import { ModalWrapper } from '@/ui/modal';
export const StyledModalWrapper = styled(ModalWrapper)(({ theme }) => { export const StyledModalWrapper = styled(ModalWrapper)(({ theme }) => {
return { return {
width: '460px', width: '460px',
height: '240px', padding: '46px 60px 32px',
padding: '0 60px',
}; };
}); });
@@ -14,7 +13,6 @@ export const StyledConfirmTitle = styled.div(({ theme }) => {
fontSize: theme.font.h6, fontSize: theme.font.h6,
fontWeight: 600, fontWeight: 600,
textAlign: 'center', textAlign: 'center',
marginTop: '45px',
color: theme.colors.popoverColor, color: theme.colors.popoverColor,
}; };
}); });
+15 -15
View File
@@ -22,10 +22,10 @@ importers:
packages/app: packages/app:
specifiers: specifiers:
'@blocksuite/blocks': 0.3.0-alpha.8 '@blocksuite/blocks': 0.3.0-alpha.9
'@blocksuite/editor': 0.3.0-alpha.8 '@blocksuite/editor': 0.3.0-alpha.9
'@blocksuite/icons': ^2.0.0 '@blocksuite/icons': ^2.0.0
'@blocksuite/store': 0.3.0-alpha.8 '@blocksuite/store': 0.3.0-alpha.9
'@emotion/css': ^11.10.0 '@emotion/css': ^11.10.0
'@emotion/react': ^11.10.4 '@emotion/react': ^11.10.4
'@emotion/server': ^11.10.0 '@emotion/server': ^11.10.0
@@ -55,10 +55,10 @@ importers:
react-dom: 18.2.0 react-dom: 18.2.0
typescript: 4.8.3 typescript: 4.8.3
dependencies: dependencies:
'@blocksuite/blocks': 0.3.0-alpha.8 '@blocksuite/blocks': 0.3.0-alpha.9
'@blocksuite/editor': 0.3.0-alpha.8 '@blocksuite/editor': 0.3.0-alpha.9
'@blocksuite/icons': 2.0.0_w5j4k42lgipnm43s3brx6h3c34 '@blocksuite/icons': 2.0.0_w5j4k42lgipnm43s3brx6h3c34
'@blocksuite/store': 0.3.0-alpha.8 '@blocksuite/store': 0.3.0-alpha.9
'@emotion/css': 11.10.0 '@emotion/css': 11.10.0
'@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34
'@emotion/server': 11.10.0_@emotion+css@11.10.0 '@emotion/server': 11.10.0_@emotion+css@11.10.0
@@ -176,10 +176,10 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: false dev: false
/@blocksuite/blocks/0.3.0-alpha.8: /@blocksuite/blocks/0.3.0-alpha.9:
resolution: {integrity: sha512-Q7md4N1H0lTMG0nX1PnAdSlTjBfNX4nFLlcX3SrdgjSM9QOu0G6E6OvfHHHCj4SJPnzyHH5xb6Lb0jU9Lpas6Q==} resolution: {integrity: sha512-7Zw2lL5DZ+JB6dtmGKfuW19nG5gMvLPcTakK9oMszbhgETnwHUUN382Pz3urecuDA88yaLj8qbC1VdbAQWDW5w==}
dependencies: dependencies:
'@blocksuite/store': 0.3.0-alpha.8 '@blocksuite/store': 0.3.0-alpha.9
'@tldraw/intersect': 1.8.0 '@tldraw/intersect': 1.8.0
'@tldraw/vec': 1.8.0 '@tldraw/vec': 1.8.0
hotkeys-js: 3.10.0 hotkeys-js: 3.10.0
@@ -193,11 +193,11 @@ packages:
- utf-8-validate - utf-8-validate
dev: false dev: false
/@blocksuite/editor/0.3.0-alpha.8: /@blocksuite/editor/0.3.0-alpha.9:
resolution: {integrity: sha512-Sx5E69LMZkUcc37q2PLXAnNTHy9okn/UFBfcWG8i5LO/T8QpYJgbllZGJDx7A3qP8Uz+BQyhgI5gp8S/dnra7g==} resolution: {integrity: sha512-dnrk87akzwRRKxS18dec2rY2ZvfIpvNq9SIHEPCCk2UNLI2R66HUO5v1RhLi6/oglbWtizcqzQWCa/DjjY1ljA==}
dependencies: dependencies:
'@blocksuite/blocks': 0.3.0-alpha.8 '@blocksuite/blocks': 0.3.0-alpha.9
'@blocksuite/store': 0.3.0-alpha.8 '@blocksuite/store': 0.3.0-alpha.9
lit: 2.4.0 lit: 2.4.0
marked: 4.1.1 marked: 4.1.1
turndown: 7.1.1 turndown: 7.1.1
@@ -217,8 +217,8 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@blocksuite/store/0.3.0-alpha.8: /@blocksuite/store/0.3.0-alpha.9:
resolution: {integrity: sha512-u63FWZSiYID8++tEIaQZ5FSj7NZu9XM2ZdTaTU+7PDEwucK5Gqo8+YrEC9gVl+2jkSW5cp43pxf2kZolUgkyYg==} resolution: {integrity: sha512-5wdnE6JUL1/y/LufqcRg2lRL5mYvdXc+MOj98eNfFAv+JFbY4vuGZ8N9HFn6dQ/rQaQsgaoAZkWyogbbC8x4IA==}
dependencies: dependencies:
buffer: 6.0.3 buffer: 6.0.3
flexsearch: 0.7.21 flexsearch: 0.7.21