Feat/land page (#391)

* feat: update landing page

* feat: update landing page

* fix: react warning

* feat(route): rename variables

* feat(route): better refresh

* fix: update logo and i18n

* feat(code): remove unused comment

* refactor(workspace): rename workspaceId

* refactor(page): rename pageId

* feat(edit): more robust editing experience

* fix: landing page mobile

* fix: landing page mobile

Co-authored-by: tzhangchi <terry.zhangchi@outlook.com>
Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
This commit is contained in:
zuomeng wang
2022-09-09 10:56:10 +08:00
committed by GitHub
parent bd2668335c
commit b35c9b2c14
35 changed files with 374 additions and 145 deletions

View File

@@ -21,7 +21,7 @@ export function LigoVirgoRoutes() {
/> />
<Route path="/ui" element={<UIPage />} /> <Route path="/ui" element={<UIPage />} />
<Route <Route
path="/:workspace_id/*" path="/:workspaceId/*"
element={ element={
<RoutePrivate> <RoutePrivate>
<WorkspaceContainer /> <WorkspaceContainer />

View File

@@ -14,7 +14,7 @@ type EdgelessProps = {
}; };
export const Edgeless = (props: EdgelessProps) => { export const Edgeless = (props: EdgelessProps) => {
const { page_id } = useParams(); const { pageId } = useParams();
const { user } = useUserAndSpaces(); const { user } = useUserAndSpaces();
useEffect(() => { useEffect(() => {
@@ -31,7 +31,5 @@ export const Edgeless = (props: EdgelessProps) => {
update_recent_pages(); update_recent_pages();
}, [user, props.workspace]); }, [user, props.workspace]);
return ( return <MemoAffineBoard workspace={props.workspace} rootBlockId={pageId} />;
<MemoAffineBoard workspace={props.workspace} rootBlockId={page_id} />
);
}; };

View File

@@ -5,22 +5,23 @@ import { useNavigate, useParams } from 'react-router-dom';
export function WorkspaceHome() { export function WorkspaceHome() {
const navigate = useNavigate(); const navigate = useNavigate();
const { workspace_id } = useParams(); const { workspaceId } = useParams();
const { user } = useUserAndSpaces(); const { user } = useUserAndSpaces();
useEffect(() => { useEffect(() => {
const navigate_to_user_initial_page = async () => { const navigateToUserInitialPage = async () => {
const [recent_pages, user_initial_page_id] = await Promise.all([ const [recentPages, userInitialPageId] = await Promise.all([
services.api.userConfig.getRecentPages(workspace_id, user.id), services.api.userConfig.getRecentPages(workspaceId, user.id),
services.api.userConfig.getUserInitialPage( services.api.userConfig.getUserInitialPage(
workspace_id, workspaceId,
user.id user.id
), ),
]); ]);
if (recent_pages.length === 0) { // if recent pages if null, run initialize task
if (recentPages.length === 0) {
await services.api.editorBlock.copyTemplateToPage( await services.api.editorBlock.copyTemplateToPage(
workspace_id, workspaceId,
user_initial_page_id, userInitialPageId,
TemplateFactory.generatePageTemplateByGroupKeys({ TemplateFactory.generatePageTemplateByGroupKeys({
name: '👋 Get Started with AFFiNE', name: '👋 Get Started with AFFiNE',
groupKeys: [ groupKeys: [
@@ -31,11 +32,12 @@ export function WorkspaceHome() {
}) })
); );
} }
if (userInitialPageId) {
navigate(`/${workspace_id}/${user_initial_page_id}`); navigate(`/${workspaceId}/${userInitialPageId}`);
}
}; };
navigate_to_user_initial_page(); navigateToUserInitialPage();
}, [navigate, user.id, workspace_id]); }, [navigate, user.id, workspaceId]);
return null; return null;
} }

View File

@@ -7,27 +7,20 @@ import { WorkspaceHome } from './Home';
import Pages from './pages'; import Pages from './pages';
export function WorkspaceContainer() { export function WorkspaceContainer() {
const { workspace_id } = useParams(); const { workspaceId } = useParams();
const { user, currentSpaceId } = useUserAndSpaces(); const { user, currentSpaceId } = useUserAndSpaces();
if (
user &&
![currentSpaceId, 'affine2vin277tcmafwq'].includes(workspace_id)
) {
// return <Navigate to={`/${currentSpaceId}`} replace={true} />;
}
return ( return (
<Routes> <Routes>
<Route path="/" element={<WorkspaceRootContainer />}> <Route path="/" element={<WorkspaceRootContainer />}>
<Route path="/pages" element={<Pages />} /> <Route path="/pages" element={<Pages />} />
<Route <Route
path="/:page_id/edgeless" path="/:pageId/edgeless"
element={<Edgeless workspace={workspace_id} />} element={<Edgeless workspace={workspaceId} />}
/> />
<Route <Route
path="/:page_id" path="/:pageId"
element={<Page workspace={workspace_id} />} element={<Page workspace={workspaceId} />}
/> />
<Route path="/" element={<WorkspaceHome />} /> <Route path="/" element={<WorkspaceHome />} />
</Route> </Route>

View File

@@ -34,7 +34,7 @@ export function Page(props: PageProps) {
const [activeTab, setActiveTab] = useState( const [activeTab, setActiveTab] = useState(
TabMap.get(TAB_TITLE.PAGES).value TabMap.get(TAB_TITLE.PAGES).value
); );
const { page_id } = useParams(); const { pageId } = useParams();
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } = const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
useShowSpaceSidebar(); useShowSpaceSidebar();
const dailyNotesFlag = useFlag('BooleanDailyNotes', false); const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
@@ -79,7 +79,7 @@ export function Page(props: PageProps) {
</div> </div>
<div> <div>
<CollapsiblePageTree title="PAGES"> <CollapsiblePageTree title="PAGES">
{page_id ? <PageTree /> : null} {pageId ? <PageTree /> : null}
</CollapsiblePageTree> </CollapsiblePageTree>
</div> </div>
</div> </div>
@@ -91,7 +91,7 @@ export function Page(props: PageProps) {
</WorkspaceSidebarContent> </WorkspaceSidebarContent>
</WorkspaceSidebar> </WorkspaceSidebar>
</LigoLeftContainer> </LigoLeftContainer>
<EditorContainer workspace={props.workspace} pageId={page_id} /> <EditorContainer workspace={props.workspace} pageId={pageId} />
</LigoApp> </LigoApp>
); );
} }

View File

@@ -38,7 +38,7 @@ export type CollapsiblePageTreeProps = {
export function CollapsiblePageTree(props: CollapsiblePageTreeProps) { export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
const { className, style, children, title, initialOpen = true } = props; const { className, style, children, title, initialOpen = true } = props;
const navigate = useNavigate(); const navigate = useNavigate();
const { workspace_id, page_id } = useParams(); const { workspaceId, pageId } = useParams();
const { handleAddPage } = usePageTree(); const { handleAddPage } = usePageTree();
const { addPageToday } = useCalendarHeatmap(); const { addPageToday } = useCalendarHeatmap();
@@ -46,18 +46,18 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
const [open, setOpen] = useState(initialOpen); const [open, setOpen] = useState(initialOpen);
const create_page = useCallback(async () => { const create_page = useCallback(async () => {
if (page_id) { if (pageId) {
const newPage = await services.api.editorBlock.create({ const newPage = await services.api.editorBlock.create({
workspace: workspace_id, workspace: workspaceId,
type: 'page' as const, type: 'page' as const,
}); });
await handleAddPage(newPage.id); await handleAddPage(newPage.id);
addPageToday(); addPageToday();
navigate(`/${workspace_id}/${newPage.id}`); navigate(`/${workspaceId}/${newPage.id}`);
} }
}, [addPageToday, handleAddPage, navigate, page_id, workspace_id]); }, [addPageToday, handleAddPage, navigate, pageId, workspaceId]);
const [newPageBtnVisible, setNewPageBtnVisible] = useState<boolean>(false); const [newPageBtnVisible, setNewPageBtnVisible] = useState<boolean>(false);

View File

@@ -116,7 +116,7 @@ const renderTOCContent = tocDataSource => {
}; };
export const TOC = () => { export const TOC = () => {
const { page_id } = useParams(); const { pageId } = useParams();
const [tocDataSource, setTocDataSource] = useState<TOCType[]>([]); const [tocDataSource, setTocDataSource] = useState<TOCType[]>([]);
const [activeBlockId, setActiveBlockId] = useState(''); const [activeBlockId, setActiveBlockId] = useState('');
@@ -124,7 +124,7 @@ export const TOC = () => {
const listenerMapRef = useRef<ListenerMap>(new Map()); const listenerMapRef = useRef<ListenerMap>(new Map());
const { currentEditors } = useCurrentEditors(); const { currentEditors } = useCurrentEditors();
const editor = currentEditors[page_id] as Virgo; const editor = currentEditors[pageId] as Virgo;
const updateTocDataSource = useCallback(async () => { const updateTocDataSource = useCallback(async () => {
if (!editor) { if (!editor) {
@@ -134,7 +134,7 @@ export const TOC = () => {
const listenerMap = listenerMapRef.current; const listenerMap = listenerMapRef.current;
/* page listener: trigger update-notice when add new group */ /* page listener: trigger update-notice when add new group */
const pageAsyncBlock = (await editor.getBlockByIds([page_id]))?.[0]; const pageAsyncBlock = (await editor.getBlockByIds([pageId]))?.[0];
if (!listenerMap.has(pageAsyncBlock.id)) { if (!listenerMap.has(pageAsyncBlock.id)) {
listenerMap.set( listenerMap.set(
pageAsyncBlock.id, pageAsyncBlock.id,
@@ -144,7 +144,7 @@ export const TOC = () => {
/* block listener: trigger update-notice when change block content */ /* block listener: trigger update-notice when change block content */
const { children = [] } = const { children = [] } =
(await editor.queryByPageId(page_id))?.[0] || {}; (await editor.queryByPageId(pageId))?.[0] || {};
const asyncBlocks = (await editor.getBlockByIds(children)) || []; const asyncBlocks = (await editor.getBlockByIds(children)) || [];
const { tocContents } = await getContentByAsyncBlocks( const { tocContents } = await getContentByAsyncBlocks(
asyncBlocks, asyncBlocks,
@@ -155,7 +155,7 @@ export const TOC = () => {
/* toc: flat content */ /* toc: flat content */
const tocDataSource = getPageTOC(asyncBlocks, tocContents); const tocDataSource = getPageTOC(asyncBlocks, tocContents);
setTocDataSource(tocDataSource); setTocDataSource(tocDataSource);
}, [editor, page_id]); }, [editor, pageId]);
/* init toc and add page/block update-listener & unmount-listener */ /* init toc and add page/block update-listener & unmount-listener */
useEffect(() => { useEffect(() => {

View File

@@ -90,13 +90,14 @@ export const AFFiNEFooter = ({
sx={{ sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
maxWidth: '400px', flexWrap: 'wrap',
maxWidth: '420px',
margin: 'auto', margin: 'auto',
marginBottom: '2em', marginBottom: '2em',
'--joy-shadow-sm': 0, '--joy-shadow-sm': 0,
}} }}
> >
<Box sx={{ display: 'flex', width: '100%' }}> <Box sx={{ display: 'flex', width: '84px' }}>
<Button <Button
variant="plain" variant="plain"
sx={{ sx={{
@@ -144,7 +145,7 @@ export const AFFiNEFooter = ({
</Grid> </Grid>
</Button> </Button>
</Box> </Box>
<Box sx={{ display: 'flex', width: '100%' }}> <Box sx={{ display: 'flex', width: '84px' }}>
<Button <Button
variant="plain" variant="plain"
sx={{ sx={{
@@ -190,7 +191,7 @@ export const AFFiNEFooter = ({
</Grid> </Grid>
</Button> </Button>
</Box> </Box>
<Box sx={{ display: 'flex', width: '100%' }}> <Box sx={{ display: 'flex', width: '84px' }}>
<Button <Button
variant="plain" variant="plain"
sx={{ sx={{
@@ -239,7 +240,7 @@ export const AFFiNEFooter = ({
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
width: '100%', width: '84px',
}} }}
> >
<Button <Button
@@ -285,7 +286,7 @@ export const AFFiNEFooter = ({
</Grid> </Grid>
</Button> </Button>
</Box> </Box>
<Box sx={{ display: 'flex', width: '100%' }}> <Box sx={{ display: 'flex', width: '84px' }}>
<Button <Button
variant="plain" variant="plain"
sx={{ sx={{

View File

@@ -11,6 +11,7 @@ import GitHubIcon from '@mui/icons-material/GitHub';
import { styled, useMediaQuery } from '@mui/material'; import { styled, useMediaQuery } from '@mui/material';
import AFFiNETextLogo from './affine-text-logo.png'; import AFFiNETextLogo from './affine-text-logo.png';
import { HoverMenu } from './HoverMenu'; import { HoverMenu } from './HoverMenu';
import { MobileHeader } from './MobileHeader';
import { options } from '../i18n'; import { options } from '../i18n';
@@ -22,7 +23,10 @@ export const AFFiNEHeader = () => {
const changeLanguage = (event: any) => { const changeLanguage = (event: any) => {
i18n.changeLanguage(event); i18n.changeLanguage(event);
}; };
const matchesIPAD = useMediaQuery('(max-width: 768px)'); const matchesIPAD = useMediaQuery('(max-width: 1080px)');
if (matchesIPAD) {
return <MobileHeader />;
}
return ( return (
<Container container spacing={2}> <Container container spacing={2}>
<Grid xs={6} sx={{ display: 'flex', alignItems: 'center' }}> <Grid xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
@@ -135,6 +139,34 @@ export const AFFiNEHeader = () => {
window.open(href); window.open(href);
}} }}
/> />
<HoverMenu
title={t('ContactUs')}
options={[
{
title: 'Discord',
value: 'https://discord.gg/Arn7TqJBvG',
},
{
title: 'Telegram',
value: 'https://t.me/affineworkos',
},
{
title: 'Reddit',
value: 'https://www.reddit.com/r/Affine/',
},
{
title: 'Medium',
value: 'https://medium.com/@affineworkos',
},
{
title: 'Email',
value: 'mailto:contact@toeverything.info',
},
]}
onSelect={href => {
window.open(href);
}}
/>
<Select <Select
defaultValue="en" defaultValue="en"
sx={{ display: matchesIPAD ? 'none' : 'intial' }} sx={{ display: matchesIPAD ? 'none' : 'intial' }}

View File

@@ -0,0 +1,208 @@
import Close from '@mui/icons-material/Close';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import GitHubIcon from '@mui/icons-material/GitHub';
import Menu from '@mui/icons-material/Menu';
import { Button, Grid } from '@mui/joy';
import { useRef, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import {
Checkbox,
Collapse,
Drawer,
List,
ListItemButton,
ListItemText,
styled,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import AFFiNETextLogo from './affine-text-logo.png';
export const MobileHeader = () => {
const navigate = useNavigate();
const { i18n, t } = useTranslation();
const anchor = useRef<HTMLDivElement | null>(null);
const [openedDrawer, setDrawerState] = useState(false);
const handleDrawerOpenState = () => {
setDrawerState(!openedDrawer);
};
const [contactUsCollapse, setContactUsCollapse] = useState(false);
const handleContactUsClick = () => {
setContactUsCollapse(!contactUsCollapse);
};
const [languageCollapse, setLanguageCollapse] = useState(false);
const handleLanguageClick = () => {
setLanguageCollapse(!languageCollapse);
};
return (
<Container ref={anchor} container spacing={2}>
<Grid
xs={12}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<StyledImage
src={AFFiNETextLogo}
alt="affine"
onClick={() => navigate('/')}
/>
<div>
<Button
variant="plain"
onClick={() =>
window.open(
'https://github.com/toeverything/AFFiNE'
)
}
>
<GitHubIcon />
</Button>
<Button onClick={handleDrawerOpenState} variant="plain">
{openedDrawer ? <Close /> : <Menu />}
</Button>
</div>
</Grid>
<Drawer
anchor="top"
open={openedDrawer}
onClose={() => setDrawerState(false)}
>
<StyledDrawerContainer>
<List>
<ListItemButton onClick={() => navigate('/aboutus')}>
<ListItemText primary={t('AboutUs') as string} />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open('https://blog.affine.pro')
}
>
<ListItemText primary={t('Blog') as string} />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open('https://docs.affine.pro/')
}
>
<ListItemText primary={t('Docs') as string} />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open('https://feedback.affine.pro/')
}
>
<ListItemText primary={t('Feedback') as string} />
</ListItemButton>
<ListItemButton onClick={handleContactUsClick}>
<ListItemText primary={t('ContactUs') as string} />
{contactUsCollapse ? (
<ExpandLess />
) : (
<ExpandMore />
)}
</ListItemButton>
<Collapse
in={contactUsCollapse}
timeout="auto"
unmountOnExit
>
<ListItemButton
onClick={() =>
window.open('https://discord.gg/Arn7TqJBvG')
}
>
<ListItemText primary="Discord" />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open('https://t.me/affineworkos')
}
>
<ListItemText primary="Telegram" />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open(
'https://www.reddit.com/r/Affine/'
)
}
>
<ListItemText primary="Reddit" />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open(
'https://medium.com/@affineworkos'
)
}
>
<ListItemText primary="Medium" />
</ListItemButton>
<ListItemButton
onClick={() =>
window.open(
'mailto:contact@toeverything.info'
)
}
>
<ListItemText primary="Email" />
</ListItemButton>
</Collapse>
<ListItemButton onClick={handleLanguageClick}>
<ListItemText primary={t('language') as string} />
{languageCollapse ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse
in={languageCollapse}
timeout="auto"
unmountOnExit
>
<ListItemButton
onClick={() => i18n.changeLanguage('en')}
>
<ListItemText primary="English" />
<Checkbox checked={i18n.language === 'en'} />
</ListItemButton>
<ListItemButton
onClick={() => i18n.changeLanguage('zh')}
>
<ListItemText primary="简体中文" />
<Checkbox checked={i18n.language === 'zh'} />
</ListItemButton>
</Collapse>
</List>
</StyledDrawerContainer>
</Drawer>
</Container>
);
};
const Container = styled(Grid)({
position: 'fixed',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
width: '100%',
paddingTop: '1em',
backgroundColor: '#fff',
zIndex: 1500,
maxWidth: '1440px',
margin: 'auto',
marginTop: '0 !important',
});
const StyledImage = styled('img')({
height: '24px',
marginRight: '16px',
cursor: 'pointer',
});
const StyledDrawerContainer = styled('div')({
paddingTop: '72px',
});

View File

@@ -10,6 +10,7 @@
"Alternative": "Alternative", "Alternative": "Alternative",
"Check GitHub": "Check GitHub", "Check GitHub": "Check GitHub",
"Try it Online": "Try it Online", "Try it Online": "Try it Online",
"language": "Language",
"description1": { "description1": {
"part1": "Affine is the next-generation collaborative knowledge base for professionals.", "part1": "Affine is the next-generation collaborative knowledge base for professionals.",
"part2": "It's not just a collection of Docs, whiteboard, and tables.", "part2": "It's not just a collection of Docs, whiteboard, and tables.",

View File

@@ -10,6 +10,7 @@
"Alternative": "的另一种选择", "Alternative": "的另一种选择",
"Check GitHub": "GitHub中查看", "Check GitHub": "GitHub中查看",
"Try it Online": "在线试用", "Try it Online": "在线试用",
"language": "语言",
"description1": { "description1": {
"part1": "Affine是面向专业人士的下一代协同知识库", "part1": "Affine是面向专业人士的下一代协同知识库",
"part2": "它不仅仅是一个文档、白板和表格的集合。", "part2": "它不仅仅是一个文档、白板和表格的集合。",

View File

@@ -2,8 +2,8 @@ import { useNavigate, useParams } from 'react-router-dom';
import { Descendant } from 'slate'; import { Descendant } from 'slate';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { BlockSearchItem } from '@toeverything/datasource/jwt';
import { styled } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import { BlockSearchItem } from '@toeverything/datasource/jwt';
import { BlockPreview } from '../../block-preview'; import { BlockPreview } from '../../block-preview';
@@ -26,14 +26,14 @@ type InlineRefLinkProps = {
}; };
export const InlineRefLink = ({ block, pageId }: InlineRefLinkProps) => { export const InlineRefLink = ({ block, pageId }: InlineRefLinkProps) => {
const { workspace_id } = useParams(); const { workspaceId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
if (block) { if (block) {
return ( return (
<BlockPreviewContainer <BlockPreviewContainer
block={block} block={block}
onClick={() => navigate(`/${workspace_id}/${pageId}`)} onClick={() => navigate(`/${workspaceId}/${pageId}`)}
/> />
); );
} }

View File

@@ -164,9 +164,6 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
}; };
useEffect(() => { useEffect(() => {
handleLangChange(langType ? langType : DEFAULT_LANG); handleLangChange(langType ? langType : DEFAULT_LANG);
// setTimeout(() => {
// focusCode();
// }, 100);
}, []); }, []);
const copyCode = () => { const copyCode = () => {

View File

@@ -16,7 +16,7 @@ import {
} from '../../components/text-manage'; } from '../../components/text-manage';
export const PageView = ({ block, editor }: CreateView) => { export const PageView = ({ block, editor }: CreateView) => {
const { workspace_id } = useParams(); const { workspaceId } = useParams();
const textRef = useRef<ExtendedTextUtils>(null); const textRef = useRef<ExtendedTextUtils>(null);
const [backLinks, setBackLinks] = const [backLinks, setBackLinks] =
useState<Awaited<ReturnType<typeof editor.search>>>(); useState<Awaited<ReturnType<typeof editor.search>>>();
@@ -100,7 +100,7 @@ export const PageView = ({ block, editor }: CreateView) => {
className={style9(styles.content)} className={style9(styles.content)}
suppressContentEditableWarning suppressContentEditableWarning
> */} > */}
<BackLink blocks={backLinks} workspaceId={workspace_id} /> <BackLink blocks={backLinks} workspaceId={workspaceId} />
{/*{block.childrenIds.map(childId => (*/} {/*{block.childrenIds.map(childId => (*/}
{/* <RenderBlock key={childId} blockId={childId} />*/} {/* <RenderBlock key={childId} blockId={childId} />*/}
{/*))}*/} {/*))}*/}

View File

@@ -6,16 +6,16 @@ import { CreateView } from '@toeverything/framework/virgo';
type RefLinkView = CreateView; type RefLinkView = CreateView;
export const RefLinkView = ({ block, editor }: RefLinkView) => { export const RefLinkView = ({ block, editor }: RefLinkView) => {
const page_id = useMemo(() => block.getProperty('reference'), [block]); const pageId = useMemo(() => block.getProperty('reference'), [block]);
const [block_content, set_block] = const [block_content, set_block] =
useState<Awaited<ReturnType<typeof editor.search>>[number]>(); useState<Awaited<ReturnType<typeof editor.search>>[number]>();
useEffect(() => { useEffect(() => {
editor editor
.search({ tag: `id:${page_id}` }) .search({ tag: `id:${pageId}` })
.then(block => set_block(block[0])); .then(block => set_block(block[0]));
}, [editor, page_id]); }, [editor, pageId]);
return <InlineRefLink block={block_content} pageId={page_id} />; return <InlineRefLink block={block_content} pageId={pageId} />;
}; };

View File

@@ -90,7 +90,9 @@ export const TextView = ({
await block.removeChildren(); await block.removeChildren();
await block.after(nextBlock); await block.after(nextBlock);
editor.selectionManager.activeNodeByNodeId(nextBlock.id); setTimeout(() => {
editor.selectionManager.activeNodeByNodeId(nextBlock.id, 'start');
});
return true; return true;
}; };

View File

@@ -1,6 +1,6 @@
import { useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { services } from '@toeverything/datasource/db-service'; import { services } from '@toeverything/datasource/db-service';
import { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { WithEditorSelectionType } from '../menu/inline-menu/types'; import { WithEditorSelectionType } from '../menu/inline-menu/types';
export const useAddComment = ({ export const useAddComment = ({
@@ -8,7 +8,7 @@ export const useAddComment = ({
selectionInfo, selectionInfo,
setShow, setShow,
}: WithEditorSelectionType) => { }: WithEditorSelectionType) => {
const { workspace_id: workspaceId, page_id: pageId } = useParams(); const { workspaceId, pageId } = useParams();
const [currentComment, setCurrentComment] = useState(''); const [currentComment, setCurrentComment] = useState('');
const createComment = useCallback(async (): Promise<{ const createComment = useCallback(async (): Promise<{

View File

@@ -44,7 +44,7 @@ export const DoubleLinkMenu = ({
hooks, hooks,
style, style,
}: DoubleLinkMenuProps) => { }: DoubleLinkMenuProps) => {
const { page_id: curPageId } = useParams(); const { pageId: curPageId } = useParams();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const dialogRef = useRef<HTMLDivElement>(); const dialogRef = useRef<HTMLDivElement>();

View File

@@ -42,7 +42,7 @@ const normalizeUrl = (url: string) => {
}; };
export const LinkMenu = ({ editor, hooks }: LinkMenuProps) => { export const LinkMenu = ({ editor, hooks }: LinkMenuProps) => {
const { page_id: curPageId } = useParams(); const { pageId: curPageId } = useParams();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const dialogRef = useRef<HTMLDivElement>(); const dialogRef = useRef<HTMLDivElement>();

View File

@@ -47,7 +47,7 @@ type SearchProps = {
}; };
export const Search = (props: SearchProps) => { export const Search = (props: SearchProps) => {
const { workspace_id: workspaceId } = useParams(); const { workspaceId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);

View File

@@ -16,7 +16,7 @@ export const Switcher = () => {
if (targetViewMode === pageViewMode) { if (targetViewMode === pageViewMode) {
return; return;
} }
const workspaceId = params['workspace_id']; const workspaceId = params['workspaceId'];
/** /**
* There are two possible modes: * There are two possible modes:
* Page mode: /{workspaceId}/{pageId} * Page mode: /{workspaceId}/{pageId}

View File

@@ -46,7 +46,7 @@ export const AffineHeader = () => {
useUserAndSpaces(); useUserAndSpaces();
const showCenterTab = const showCenterTab =
(params['workspace_id'] || pathname.includes('/space/')) && params['*']; (params['workspaceId'] || pathname.includes('/space/')) && params['*'];
if (hideAffineHeader(pathname)) { if (hideAffineHeader(pathname)) {
return null; return null;
@@ -82,7 +82,7 @@ export const AffineHeader = () => {
isEdgelessView isEdgelessView
? navigate( ? navigate(
`/${ `/${
params['workspace_id'] || params['workspaceId'] ||
'space' 'space'
}/${params['*'].slice(0, -11)}` }/${params['*'].slice(0, -11)}`
) )
@@ -108,7 +108,7 @@ export const AffineHeader = () => {
? null ? null
: navigate( : navigate(
`/${ `/${
params['workspace_id'] || params['workspaceId'] ||
'space' 'space'
}/${params['*']}` + '/edgeless' }/${params['*']}` + '/edgeless'
) )

View File

@@ -50,7 +50,7 @@ const MESSAGES = {
function PageSettingPortal() { function PageSettingPortal() {
const [alertOpen, setAlertOpen] = useState(false); const [alertOpen, setAlertOpen] = useState(false);
const { workspace_id } = useParams(); const { workspaceId } = useParams();
const [pageBlock, setPageBlock] = useState<PageBlock>(); const [pageBlock, setPageBlock] = useState<PageBlock>();
const params = useParams(); const params = useParams();
@@ -64,7 +64,7 @@ function PageSettingPortal() {
const fetchPageBlock = useCallback(async () => { const fetchPageBlock = useCallback(async () => {
const dbPageBlock = await services.api.editorBlock.getBlock( const dbPageBlock = await services.api.editorBlock.getBlock(
workspace_id, workspaceId,
pageId pageId
); );
if (!dbPageBlock) return; if (!dbPageBlock) return;
@@ -78,11 +78,11 @@ function PageSettingPortal() {
//@ts-ignore //@ts-ignore
text.value[0].text, text.value[0].text,
}); });
}, [workspace_id, pageId]); }, [workspaceId, pageId]);
useEffect(() => { useEffect(() => {
fetchPageBlock(); fetchPageBlock();
}, [workspace_id, pageId, fetchPageBlock]); }, [workspaceId, pageId, fetchPageBlock]);
const redirectToPage = (newWorkspaceId: string, newPageId: string) => { const redirectToPage = (newWorkspaceId: string, newPageId: string) => {
navigate('/' + newWorkspaceId + '/' + newPageId); navigate('/' + newWorkspaceId + '/' + newPageId);
}; };
@@ -90,23 +90,23 @@ function PageSettingPortal() {
const handleDuplicatePage = async () => { const handleDuplicatePage = async () => {
//create page //create page
const newPage = await services.api.editorBlock.create({ const newPage = await services.api.editorBlock.create({
workspace: workspace_id, workspace: workspaceId,
type: 'page' as const, type: 'page' as const,
}); });
//add page to tree //add page to tree
await services.api.pageTree.addNextPageToWorkspace( await services.api.pageTree.addNextPageToWorkspace(
workspace_id, workspaceId,
pageId, pageId,
newPage.id newPage.id
); );
//copy source page to new page //copy source page to new page
await services.api.editorBlock.copyPage( await services.api.editorBlock.copyPage(
workspace_id, workspaceId,
pageId, pageId,
newPage.id newPage.id
); );
redirectToPage(workspace_id, newPage.id); redirectToPage(workspaceId, newPage.id);
}; };
const handleCopy = () => { const handleCopy = () => {
@@ -128,7 +128,7 @@ function PageSettingPortal() {
.inspector() .inspector()
.load() .load()
.then(() => { .then(() => {
window.location.href = `/${workspace_id}/`; window.location.href = `/${workspaceId}/`;
}); });
}; };
@@ -145,7 +145,7 @@ function PageSettingPortal() {
fullWidthChecked: checked, fullWidthChecked: checked,
}, },
id: pageId, id: pageId,
workspace: workspace_id, workspace: workspaceId,
}); });
}; };

View File

@@ -12,7 +12,7 @@ import { useUserAndSpaces } from '@toeverything/datasource/state';
export const CurrentPageTitle = () => { export const CurrentPageTitle = () => {
const { user } = useUserAndSpaces(); const { user } = useUserAndSpaces();
const params = useParams(); const params = useParams();
const { workspace_id } = params; const { workspaceId } = params;
const [pageId, setPageId] = useState<string>(''); const [pageId, setPageId] = useState<string>('');
const [pageTitle, setPageTitle] = useState<string | undefined>(); const [pageTitle, setPageTitle] = useState<string | undefined>();
/* card.7 */ /* card.7 */
@@ -25,9 +25,9 @@ export const CurrentPageTitle = () => {
}, [params]); }, [params]);
const fetchPageTitle = useCallback(async () => { const fetchPageTitle = useCallback(async () => {
if (!workspace_id || !pageId) return; if (!workspaceId || !pageId) return;
const [pageEditorBlock] = await services.api.editorBlock.get({ const [pageEditorBlock] = await services.api.editorBlock.get({
workspace: workspace_id, workspace: workspaceId,
ids: [pageId], ids: [pageId],
}); });
/* card.7 */ /* card.7 */
@@ -39,21 +39,21 @@ export const CurrentPageTitle = () => {
?.map(v => v.text) ?.map(v => v.text)
.join('') ?? 'Untitled' .join('') ?? 'Untitled'
); );
}, [pageId, workspace_id]); }, [pageId, workspaceId]);
useEffect(() => { useEffect(() => {
if (user) fetchPageTitle(); if (user) fetchPageTitle();
}, [fetchPageTitle, user]); }, [fetchPageTitle, user]);
useEffect(() => { useEffect(() => {
if (!user || !workspace_id || !pageId || pageTitle === undefined) if (!user || !workspaceId || !pageId || pageTitle === undefined)
return () => {}; return () => {};
let unobserve: () => void; let unobserve: () => void;
const auto_update_title = async () => { const auto_update_title = async () => {
// console.log(';; title registration auto update'); // console.log(';; title registration auto update');
unobserve = await services.api.editorBlock.observe( unobserve = await services.api.editorBlock.observe(
{ workspace: workspace_id, id: pageId }, { workspace: workspaceId, id: pageId },
businessBlock => { businessBlock => {
// console.log(';; auto_update_title', businessBlock); // console.log(';; auto_update_title', businessBlock);
fetchPageTitle(); fetchPageTitle();
@@ -65,7 +65,7 @@ export const CurrentPageTitle = () => {
return () => { return () => {
// unobserve?.(); // unobserve?.();
}; };
}, [fetchPageTitle, pageId, pageTitle, user, workspace_id]); }, [fetchPageTitle, pageId, pageTitle, user, workspaceId]);
useEffect(() => { useEffect(() => {
document.title = pageTitle || ''; document.title = pageTitle || '';

View File

@@ -9,7 +9,7 @@ import { useParams } from 'react-router-dom';
import type { CommentInfo } from './type'; import type { CommentInfo } from './type';
export const useComments = () => { export const useComments = () => {
const { workspace_id: workspaceId, page_id: pageId } = useParams(); const { workspaceId, pageId } = useParams();
const [comments, setComments] = useState<CommentInfo[]>([]); const [comments, setComments] = useState<CommentInfo[]>([]);
const [observeIds, setObserveIds] = useState<string[]>([]); const [observeIds, setObserveIds] = useState<string[]>([]);
@@ -71,7 +71,7 @@ export const useComments = () => {
}; };
export const useActiveComment = () => { export const useActiveComment = () => {
const { workspace_id: workspaceId, page_id: pageId } = useParams(); const { workspaceId, pageId } = useParams();
const { currentEditors } = useCurrentEditors(); const { currentEditors } = useCurrentEditors();
const editor = useMemo(() => { const editor = useMemo(() => {
return currentEditors[pageId] as Virgo; return currentEditors[pageId] as Virgo;

View File

@@ -7,7 +7,7 @@ interface UseWorkspaceAndPageIdReturn {
export const useWorkspaceAndPageId = (): UseWorkspaceAndPageIdReturn => { export const useWorkspaceAndPageId = (): UseWorkspaceAndPageIdReturn => {
const params = useParams(); const params = useParams();
const workspaceId = params['workspace_id']; const workspaceId = params['workspaceId'];
const pageId = params['*'].split('/')[0]; const pageId = params['*'].split('/')[0];
return { return {
workspaceId, workspaceId,

View File

@@ -47,8 +47,8 @@ export async function fetchActivitiesHeatmap(
// const pages = await db.getByType('page'); // const pages = await db.getByType('page');
const pages_with_ids = (await Promise.all( const pages_with_ids = (await Promise.all(
flattenedItems.map(async (page_item: any) => { flattenedItems.map(async (page_item: any) => {
const page_id = page_item.id; const pageId = page_item.id;
return [page_id, await db.get(page_id as 'page')]; return [pageId, await db.get(pageId as 'page')];
}) })
)) as [string, BlockImplInstance][]; )) as [string, BlockImplInstance][];
const pages = new Map(pages_with_ids); const pages = new Map(pages_with_ids);

View File

@@ -57,7 +57,9 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
}, },
ref ref
) => { ) => {
const { workspace_id, page_id } = useParams(); const params = useParams();
const workspaceId = params['workspaceId'];
const curPageId = params['pageId'];
const BooleanPageTreeItemMoreActions = useFlag( const BooleanPageTreeItemMoreActions = useFlag(
'BooleanPageTreeItemMoreActions', 'BooleanPageTreeItemMoreActions',
true true
@@ -71,7 +73,7 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
disableSelection={disableSelection} disableSelection={disableSelection}
disableInteraction={disableInteraction} disableInteraction={disableInteraction}
spacing={`${indentationWidth * depth + 12}px`} spacing={`${indentationWidth * depth + 12}px`}
active={pageId === page_id} active={pageId === curPageId}
{...props} {...props}
> >
{childCount !== 0 ? ( {childCount !== 0 ? (
@@ -89,12 +91,12 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
)} )}
<TreeItemContent {...handleProps}> <TreeItemContent {...handleProps}>
<TextLink to={`/${workspace_id}/${pageId}`}> <TextLink to={`/${workspaceId}/${pageId}`}>
{value} {value}
</TextLink> </TextLink>
{BooleanPageTreeItemMoreActions && ( {BooleanPageTreeItemMoreActions && (
<MoreActions <MoreActions
workspaceId={workspace_id} workspaceId={workspaceId}
pageId={pageId} pageId={pageId}
onRemove={onRemove} onRemove={onRemove}
/> />

View File

@@ -19,7 +19,7 @@ import {
const page_tree_atom = atom<TreeItems | undefined>([]); const page_tree_atom = atom<TreeItems | undefined>([]);
export const usePageTree = ({ indentationWidth = 16 }: DndTreeProps = {}) => { export const usePageTree = ({ indentationWidth = 16 }: DndTreeProps = {}) => {
const { workspace_id, page_id } = useParams(); const { workspaceId, pageId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [items] = useAtom(page_tree_atom); const [items] = useAtom(page_tree_atom);
const [activeId, setActiveId] = useState<string | undefined>(undefined); const [activeId, setActiveId] = useState<string | undefined>(undefined);
@@ -56,11 +56,11 @@ export const usePageTree = ({ indentationWidth = 16 }: DndTreeProps = {}) => {
const savePageTreeData = useCallback( const savePageTreeData = useCallback(
async (treeData?: TreeItem[]) => { async (treeData?: TreeItem[]) => {
await services.api.pageTree.setPageTree<TreeItem>( await services.api.pageTree.setPageTree<TreeItem>(
workspace_id, workspaceId,
treeData || [] treeData || []
); );
}, },
[workspace_id] [workspaceId]
); );
const resetState = useCallback(() => { const resetState = useCallback(() => {
@@ -129,19 +129,19 @@ export const usePageTree = ({ indentationWidth = 16 }: DndTreeProps = {}) => {
const handleRemove = useCallback( const handleRemove = useCallback(
async (id: string) => { async (id: string) => {
await savePageTreeData(removeItem(items, id)); await savePageTreeData(removeItem(items, id));
await services.api.userConfig.removePage(workspace_id, id); await services.api.userConfig.removePage(workspaceId, id);
//remove page from jwst //remove page from jwst
await services.api.pageTree.removePage(workspace_id, id); await services.api.pageTree.removePage(workspaceId, id);
if (id === page_id) { if (id === pageId) {
navigate(`/${workspace_id}`); navigate(`/${workspaceId}`);
} }
}, },
[items, savePageTreeData, workspace_id] [items, savePageTreeData, workspaceId]
); );
const handleAddPage = useCallback( const handleAddPage = useCallback(
async (page_id?: string) => { async (pageId?: string) => {
await savePageTreeData([{ id: page_id, children: [] }, ...items]); await savePageTreeData([{ id: pageId, children: [] }, ...items]);
}, },
[items, savePageTreeData] [items, savePageTreeData]
); );
@@ -177,25 +177,25 @@ export const usePageTree = ({ indentationWidth = 16 }: DndTreeProps = {}) => {
export const useDndTreeAutoUpdate = () => { export const useDndTreeAutoUpdate = () => {
const [, set_items] = useAtom(page_tree_atom); const [, set_items] = useAtom(page_tree_atom);
const { workspace_id, page_id } = useParams(); const { workspaceId, pageId } = useParams();
const fetch_page_tree_data = useCallback(async () => { const fetch_page_tree_data = useCallback(async () => {
const pages = await services.api.pageTree.getPageTree<TreeItem>( const pages = await services.api.pageTree.getPageTree<TreeItem>(
workspace_id workspaceId
); );
set_items(pages); set_items(pages);
}, [set_items, workspace_id]); }, [set_items, workspaceId]);
useEffect(() => { useEffect(() => {
fetch_page_tree_data(); fetch_page_tree_data();
}, [fetch_page_tree_data]); }, [fetch_page_tree_data]);
useEffect(() => { useEffect(() => {
if (!page_id) return () => {}; if (!pageId) return () => {};
let unobserve: () => void; let unobserve: () => void;
const auto_update_page_tree = async () => { const auto_update_page_tree = async () => {
unobserve = await services.api.pageTree.observe( unobserve = await services.api.pageTree.observe(
{ workspace: workspace_id, page: page_id }, { workspace: workspaceId, page: pageId },
() => { () => {
fetch_page_tree_data(); fetch_page_tree_data();
} }
@@ -206,5 +206,5 @@ export const useDndTreeAutoUpdate = () => {
return () => { return () => {
unobserve?.(); unobserve?.();
}; };
}, [fetch_page_tree_data, page_id, workspace_id]); }, [fetch_page_tree_data, pageId, workspaceId]);
}; };

View File

@@ -265,37 +265,34 @@ export class EditorBlock extends ServiceBaseClass {
} }
} }
async copyPage( async copyPage(
workspace_id: string, workspaceId: string,
source_page_id: string, sourcePageId: string,
new_page_id: string newPageId: string
): Promise<boolean> { ): Promise<boolean> {
const db = await this.database.getDatabase(workspace_id); const db = await this.database.getDatabase(workspaceId);
const source_page = await this.getBlock( const sourcePage = await this.getBlock(
workspace_id, workspaceId,
source_page_id as 'block' sourcePageId as 'block'
); );
const new_page = await this.getBlock( const newPage = await this.getBlock(workspaceId, newPageId as 'block');
workspace_id, if (!sourcePage) {
new_page_id as 'block'
);
if (!source_page) {
return false; return false;
} }
const source_page_children = source_page.children; const sourcePageChildren = sourcePage.children;
const decorations = source_page.getDecorations(); const decorations = sourcePage.getDecorations();
Object.entries(decorations).forEach(([key, value]) => { Object.entries(decorations).forEach(([key, value]) => {
new_page?.setDecoration(key, source_page.getDecoration(key)); newPage?.setDecoration(key, sourcePage.getDecoration(key));
}); });
//@ts-ignore //@ts-ignore
this.decorate_page_title(new_page, 'copy from '); this.decorate_page_title(newPage, 'copy from ');
for (let i = 0; i < source_page_children.length; i++) { for (let i = 0; i < sourcePageChildren.length; i++) {
const source_page_child = await db.get( const sourcePageChild = await db.get(
source_page_children[i] as 'block' sourcePageChildren[i] as 'block'
); );
new_page?.insertChildren(source_page_child); newPage?.insertChildren(sourcePageChild);
} }
return true; return true;
} }

View File

@@ -350,7 +350,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
const [file] = (await fromEvent(handles)) as File[]; const [file] = (await fromEvent(handles)) as File[];
const binary = await file?.arrayBuffer(); const binary = await file?.arrayBuffer();
console.log(this._provider.providers); console.log(this._provider.providers);
let { indexeddb } = ( const { indexeddb } = (
this._provider.providers as any[] this._provider.providers as any[]
).find(p => p.indexeddb); ).find(p => p.indexeddb);
await indexeddb?.idb?.clearData(); await indexeddb?.idb?.clearData();
@@ -391,9 +391,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
}, },
parse: () => this._doc.toJSON(), parse: () => this._doc.toJSON(),
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
parse_page: (page_id: string) => { parse_page: (pageId: string) => {
const blocks = this._blocks.toJSON(); const blocks = this._blocks.toJSON();
return resolve_block(blocks, page_id); return resolve_block(blocks, pageId);
}, },
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
parse_pages: (resolve = false) => { parse_pages: (resolve = false) => {

View File

@@ -9,7 +9,7 @@ const _currentEditors = atom<EditorsMap>({} as EditorsMap);
/** hook for using editors outside page */ /** hook for using editors outside page */
export const useCurrentEditors = () => { export const useCurrentEditors = () => {
const { workspace_id: workspaceId, page_id: pageId } = useParams(); const { workspaceId, pageId } = useParams();
const { pathname } = useLocation(); const { pathname } = useLocation();
const [currentEditors, setCurrentEditors] = useAtom(_currentEditors); const [currentEditors, setCurrentEditors] = useAtom(_currentEditors);

View File

@@ -5,7 +5,6 @@ import {
} from 'firebase/auth'; } from 'firebase/auth';
import { atom, useAtom } from 'jotai'; import { atom, useAtom } from 'jotai';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { useIdentifyUser } from '@toeverything/datasource/feature-flags'; import { useIdentifyUser } from '@toeverything/datasource/feature-flags';
import { UserInfo } from '@toeverything/utils'; import { UserInfo } from '@toeverything/utils';
@@ -63,15 +62,11 @@ const BRAND_ID = 'AFFiNE';
const _localTrigger = atom<boolean>(false); const _localTrigger = atom<boolean>(false);
const _useUserAndSpacesForFreeLogin = () => { const _useUserAndSpacesForFreeLogin = () => {
const location = useLocation();
const navigate = useNavigate();
const [user, setUser] = useAtom(_userAtom); const [user, setUser] = useAtom(_userAtom);
const [loading, setLoading] = useAtom(_loadingAtom); const [loading, setLoading] = useAtom(_loadingAtom);
const [localTrigger] = useAtom(_localTrigger);
useEffect(() => { useEffect(() => {
if (loading) { if (loading) {
navigate(`/${BRAND_ID}`);
setLoading(false); setLoading(false);
} }
}, []); }, []);

View File

@@ -6,8 +6,8 @@ export function getUserDisplayName(user?: UserInfo) {
} }
/** /**
* Get page_id from URL * Get pageId from URL
* @returns page_id * @returns pageId
*/ */
export function getPageId() { export function getPageId() {
const path = window.location.pathname.match(/\/(\w+)\/(\w+)/); const path = window.location.pathname.match(/\/(\w+)\/(\w+)/);