mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor!: next generation AFFiNE code structure (#1176)
This commit is contained in:
@@ -1,13 +1,56 @@
|
||||
import Head from 'next/head';
|
||||
import { Button, displayFlex, styled } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
import NotfoundPage from '@/components/404';
|
||||
import ErrorImg from '../../public/imgs/invite-error.svg';
|
||||
|
||||
export const StyledContainer = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
|
||||
img: {
|
||||
width: '360px',
|
||||
height: '270px',
|
||||
},
|
||||
p: {
|
||||
fontSize: '22px',
|
||||
fontWeight: 600,
|
||||
margin: '24px 0',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const NotfoundPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<StyledContainer data-testid="notFound">
|
||||
<Image alt="404" src={ErrorImg}></Image>
|
||||
|
||||
<p>{t('404 - Page Not Found')}</p>
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
router.push('/');
|
||||
}}
|
||||
>
|
||||
{t('Back Home')}
|
||||
</Button>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Custom404() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>404 - AFFiNE</title>
|
||||
</Head>
|
||||
<Helmet>
|
||||
s<title>404 - AFFiNE</title>
|
||||
</Helmet>
|
||||
<NotfoundPage></NotfoundPage>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,107 +1,96 @@
|
||||
import '../../public/globals.css';
|
||||
import './temporary.css';
|
||||
import '@fontsource/space-mono';
|
||||
import '@fontsource/poppins';
|
||||
import '../utils/print-build-info';
|
||||
import '@affine/i18n';
|
||||
import '@blocksuite/editor/themes/affine.css';
|
||||
import '../styles/globals.css';
|
||||
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Logger } from '@toeverything/pathfinder-logger';
|
||||
import type { NextPage } from 'next';
|
||||
import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
||||
import { appWithTranslation, createI18n, I18nextProvider } from '@affine/i18n';
|
||||
import createCache from '@emotion/cache';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import { Provider } from 'jotai';
|
||||
import { useAtomsDebugValue } from 'jotai-devtools';
|
||||
import { AppProps } from 'next/app';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import React, { memo, ReactElement, Suspense, useEffect, useMemo } from 'react';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
import { SWRConfig, SWRConfiguration } from 'swr';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { MessageCenterHandler } from '@/components/message-center-handler';
|
||||
import ProviderComposer from '@/components/provider-composer';
|
||||
import { AppStateProvider } from '@/providers/app-state-provider';
|
||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||
import { ThemeProvider } from '@/providers/ThemeProvider';
|
||||
import { GlobalAppProvider } from '@/store/app';
|
||||
import { DataCenterPreloader } from '@/store/app/datacenter';
|
||||
import { ModalProvider } from '@/store/globalModal';
|
||||
|
||||
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<
|
||||
P,
|
||||
IP
|
||||
> & {
|
||||
getLayout?: (page: ReactElement) => ReactNode;
|
||||
};
|
||||
import { jotaiStore } from '../atoms';
|
||||
import { AffineErrorBoundary } from '../components/affine/affine-error-eoundary';
|
||||
import { ProviderComposer } from '../components/provider-composer';
|
||||
import { PageLoading } from '../components/pure/loading';
|
||||
import { AffineSWRConfigProvider } from '../providers/AffineSWRConfigProvider';
|
||||
import { ModalProvider } from '../providers/ModalProvider';
|
||||
import { ThemeProvider } from '../providers/ThemeProvider';
|
||||
import { NextPageWithLayout } from '../shared';
|
||||
import { config } from '../shared/env';
|
||||
|
||||
type AppPropsWithLayout = AppProps & {
|
||||
Component: NextPageWithLayout;
|
||||
};
|
||||
|
||||
// Page list which do not rely on app state
|
||||
const NoNeedAppStatePageList = [
|
||||
'/404',
|
||||
'/public-workspace/[workspaceId]',
|
||||
'/public-workspace/[workspaceId]/[pageId]',
|
||||
];
|
||||
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
const getLayout = Component.getLayout || (page => page);
|
||||
const { i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const EmptyLayout = (page: ReactElement) => page;
|
||||
|
||||
React.useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
}, [i18n.language]);
|
||||
const DebugAtoms = memo(function DebugAtoms() {
|
||||
useAtomsDebugValue();
|
||||
return null;
|
||||
});
|
||||
|
||||
const helmetContext = {};
|
||||
|
||||
const defaultSWRConfig: SWRConfiguration = {
|
||||
suspense: true,
|
||||
fetcher: () => {
|
||||
const error = new Error(
|
||||
'you might forget to warp your page with AffineSWRConfigProvider'
|
||||
);
|
||||
console.log(error);
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
|
||||
const cache = createCache({ key: 'affine' });
|
||||
|
||||
const App = function App({ Component, pageProps }: AppPropsWithLayout) {
|
||||
const getLayout = Component.getLayout || EmptyLayout;
|
||||
const i18n = useMemo(() => createI18n(), []);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// I know what I'm doing
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
console.log('Runtime Preset', config);
|
||||
}, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta name="theme-color" content="#fafafa" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/icons/apple-touch-icon.png"
|
||||
/>
|
||||
<title>AFFiNE</title>
|
||||
</Head>
|
||||
<Logger />
|
||||
<GlobalAppProvider key="BlockSuiteProvider">
|
||||
<ProviderComposer
|
||||
contexts={[
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
<AppStateProvider key="appStateProvider" />,
|
||||
<ModalProvider key="ModalProvider" />,
|
||||
<ConfirmProvider key="ConfirmProvider" />,
|
||||
]}
|
||||
>
|
||||
{NoNeedAppStatePageList.includes(router.route) ? (
|
||||
getLayout(<Component {...pageProps} />)
|
||||
) : (
|
||||
<Suspense fallback={<PageLoading />}>
|
||||
<DataCenterPreloader>
|
||||
<MessageCenterHandler>
|
||||
<AppDefender>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</AppDefender>
|
||||
</MessageCenterHandler>
|
||||
</DataCenterPreloader>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<CacheProvider value={cache}>
|
||||
<DebugAtoms />
|
||||
<SWRConfig value={defaultSWRConfig}>
|
||||
<AffineErrorBoundary router={useRouter()}>
|
||||
<Suspense fallback={<PageLoading key="RootPageLoading" />}>
|
||||
<ProviderComposer
|
||||
contexts={useMemo(
|
||||
() => [
|
||||
<AffineSWRConfigProvider key="AffineSWRConfigProvider" />,
|
||||
<Provider key="JotaiProvider" store={jotaiStore} />,
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
<ModalProvider key="ModalProvider" />,
|
||||
],
|
||||
[]
|
||||
)}
|
||||
>
|
||||
<HelmetProvider key="HelmetProvider" context={helmetContext}>
|
||||
<Helmet>
|
||||
<title>AFFiNE</title>
|
||||
</Helmet>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</HelmetProvider>
|
||||
</ProviderComposer>
|
||||
</Suspense>
|
||||
)}
|
||||
</ProviderComposer>
|
||||
</GlobalAppProvider>
|
||||
</>
|
||||
</AffineErrorBoundary>
|
||||
</SWRConfig>
|
||||
</CacheProvider>
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const AppDefender = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
if (['/index.html', '/'].includes(router.asPath)) {
|
||||
router.replace('/workspace');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default appWithTranslation(App as any);
|
||||
|
||||
@@ -1,47 +1,20 @@
|
||||
import { cache } from '@emotion/css';
|
||||
import createEmotionServer from '@emotion/server/create-instance';
|
||||
import Document, {
|
||||
DocumentContext,
|
||||
Head,
|
||||
Html,
|
||||
Main,
|
||||
NextScript,
|
||||
} from 'next/document';
|
||||
import Document, { Head, Html, Main, NextScript } from 'next/document';
|
||||
import * as React from 'react';
|
||||
|
||||
export const renderStatic = async (html: string) => {
|
||||
if (html === undefined) {
|
||||
throw new Error('did you forget to return html from renderToString?');
|
||||
}
|
||||
const { extractCritical } = createEmotionServer(cache);
|
||||
const { ids, css } = extractCritical(html);
|
||||
|
||||
return { html, ids, css };
|
||||
};
|
||||
|
||||
export default class AppDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const page = await ctx.renderPage();
|
||||
const { css, ids } = await renderStatic(page.html);
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return {
|
||||
...initialProps,
|
||||
styles: (
|
||||
<React.Fragment>
|
||||
{initialProps.styles}
|
||||
<style
|
||||
data-emotion={`css ${ids.join(' ')}`}
|
||||
dangerouslySetInnerHTML={{ __html: css }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Head>
|
||||
<meta name="theme-color" content="#fafafa" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="icon" sizes="192x192" href="/chrome-192x192.png" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
|
||||
@@ -1,7 +1,75 @@
|
||||
import type { NextPage } from 'next';
|
||||
import { useAtom } from 'jotai';
|
||||
import { NextPage } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return <div title="Home Page"></div>;
|
||||
import { currentWorkspaceIdAtom } from '../atoms';
|
||||
import { PageLoading } from '../components/pure/loading';
|
||||
import { refreshDataCenter, useWorkspaces } from '../hooks/use-workspaces';
|
||||
|
||||
const IndexPage: NextPage = () => {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
refreshDataCenter(controller.signal);
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, []);
|
||||
const [workspaceId] = useAtom(currentWorkspaceIdAtom);
|
||||
const workspaces = useWorkspaces();
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const targetWorkspace = workspaces.find(w => w.id === workspaceId);
|
||||
if (workspaceId && targetWorkspace) {
|
||||
const pageId =
|
||||
targetWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id;
|
||||
if (pageId) {
|
||||
router.replace({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
router.replace({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const firstWorkspace = workspaces.at(0);
|
||||
if (firstWorkspace) {
|
||||
const pageId =
|
||||
firstWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id;
|
||||
if (pageId) {
|
||||
router.replace({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: firstWorkspace.id,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
router.replace({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId: firstWorkspace.id,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [router, workspaceId, workspaces]);
|
||||
return <PageLoading />;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
export default IndexPage;
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
import { Permission } from '@affine/datacenter';
|
||||
import {
|
||||
SucessfulDuotoneIcon,
|
||||
UnsucessfulDuotoneIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
|
||||
import inviteError from '../../../public/imgs/invite-error.svg';
|
||||
import inviteSuccess from '../../../public/imgs/invite-success.svg';
|
||||
|
||||
export default function DevPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const [inviteData, setInviteData] = useState<Permission | null>(null);
|
||||
const { acceptInvite } = useWorkspaceHelper();
|
||||
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
const data = await dataCenter.acceptInvitation(
|
||||
router.query.invite_code as string
|
||||
);
|
||||
|
||||
setInviteData(data as Permission);
|
||||
setLoading(false);
|
||||
};
|
||||
init();
|
||||
}, [router, acceptInvite, dataCenter]);
|
||||
|
||||
if (loading) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (inviteData?.accepted) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Image src={inviteSuccess} alt="" />
|
||||
<Button
|
||||
type="primary"
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
router.push(`/workspace/${inviteData?.workspace_id}/all`);
|
||||
}}
|
||||
>
|
||||
Go to Workspace
|
||||
</Button>
|
||||
<p>
|
||||
<SucessfulDuotoneIcon />
|
||||
Successfully joined
|
||||
</p>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (inviteData?.accepted === false) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Image src={inviteError} alt="" />
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
router.push(`/`);
|
||||
}}
|
||||
>
|
||||
Back to Home
|
||||
</Button>
|
||||
<p>
|
||||
<UnsucessfulDuotoneIcon />
|
||||
The link has expired
|
||||
</p>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
height: '100vh',
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
img: {
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
},
|
||||
p: {
|
||||
...displayFlex('center', 'center'),
|
||||
marginTop: '24px',
|
||||
svg: {
|
||||
color: theme.colors.primaryColor,
|
||||
fontSize: '24px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
125
apps/web/src/pages/preview/[previewId].tsx
Normal file
125
apps/web/src/pages/preview/[previewId].tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
GetStaticPaths,
|
||||
GetStaticProps,
|
||||
InferGetStaticPropsType,
|
||||
NextPage,
|
||||
} from 'next';
|
||||
import Head from 'next/head';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { PageDetailEditor } from '../../components/page-detail-editor';
|
||||
import { PageLoading } from '../../components/pure/loading';
|
||||
import { StyledPage, StyledWrapper } from '../../layouts/styles';
|
||||
import { BlockSuiteWorkspace } from '../../shared';
|
||||
import { createEmptyBlockSuiteWorkspace } from '../../utils';
|
||||
|
||||
export type PreviewPageProps = {
|
||||
text: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type PreviewPageParams = {
|
||||
previewId: string;
|
||||
};
|
||||
|
||||
const PreviewPage: NextPage<PreviewPageProps> = ({
|
||||
text,
|
||||
title,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const [blockSuiteWorkspace, setBlockSuiteWorkspace] =
|
||||
useState<BlockSuiteWorkspace | null>(null);
|
||||
useEffect(() => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace('preview');
|
||||
blockSuiteWorkspace.signals.pageAdded.once(() => {
|
||||
setBlockSuiteWorkspace(blockSuiteWorkspace);
|
||||
});
|
||||
blockSuiteWorkspace.createPage('preview');
|
||||
return () => {
|
||||
blockSuiteWorkspace.removePage('preview');
|
||||
};
|
||||
}, []);
|
||||
if (!blockSuiteWorkspace || !blockSuiteWorkspace.getPage('preview')) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
</Head>
|
||||
<StyledPage>
|
||||
<StyledWrapper>
|
||||
<PageDetailEditor
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
pageId="preview"
|
||||
onInit={(page, editor) => {
|
||||
blockSuiteWorkspace.setPageMeta(page.id, { title });
|
||||
const pageBlockId = page.addBlockByFlavour('affine:page', {
|
||||
title,
|
||||
});
|
||||
page.addBlockByFlavour('affine:surface', {}, null);
|
||||
const frameId = page.addBlockByFlavour(
|
||||
'affine:frame',
|
||||
{},
|
||||
pageBlockId
|
||||
);
|
||||
page.addBlockByFlavour('affine:paragraph', {}, frameId);
|
||||
editor.clipboard.importMarkdown(text, frameId).then(() => {
|
||||
page.resetHistory();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
</StyledPage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewPage;
|
||||
|
||||
export const getStaticProps: GetStaticProps<
|
||||
PreviewPageProps,
|
||||
PreviewPageParams
|
||||
> = async context => {
|
||||
const name = context.params?.previewId;
|
||||
const fs = await import('node:fs/promises');
|
||||
const path = await import('node:path');
|
||||
const markdown: string = await fs.readFile(
|
||||
path.resolve(process.cwd(), 'src', 'templates', `${name}.md`),
|
||||
'utf8'
|
||||
);
|
||||
const title = markdown
|
||||
.split('\n')
|
||||
.splice(0, 1)
|
||||
.join('')
|
||||
.replaceAll('#', '')
|
||||
.trim();
|
||||
if (!name) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
},
|
||||
props: {
|
||||
text: '',
|
||||
title: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
text: markdown.split('\n').slice(1).join('\n'),
|
||||
title,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths<PreviewPageParams> = () => {
|
||||
return {
|
||||
paths: [
|
||||
{ params: { previewId: 'AFFiNE-Docs' } },
|
||||
{ params: { previewId: 'Welcome-to-AFFiNE-Abbey-Alpha-Wood' } },
|
||||
{ params: { previewId: 'Welcome-to-AFFiNE-Alpha-Downhills' } },
|
||||
{ params: { previewId: 'Welcome-to-the-AFFiNE-Alpha' } },
|
||||
],
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
77
apps/web/src/pages/public-workspace/[workspaceId].tsx
Normal file
77
apps/web/src/pages/public-workspace/[workspaceId].tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { Suspense, useCallback, useEffect } from 'react';
|
||||
|
||||
import { currentWorkspaceIdAtom } from '../../atoms';
|
||||
import {
|
||||
publicBlockSuiteAtom,
|
||||
publicWorkspaceIdAtom,
|
||||
} from '../../atoms/public-workspace';
|
||||
import { QueryParamError } from '../../components/affine/affine-error-eoundary';
|
||||
import { BlockSuitePublicPageList } from '../../components/blocksuite/block-suite-page-list';
|
||||
import { PageLoading } from '../../components/pure/loading';
|
||||
import { WorkspaceLayout } from '../../layouts';
|
||||
import { NextPageWithLayout } from '../../shared';
|
||||
|
||||
const ListPageInner: React.FC<{
|
||||
workspaceId: string;
|
||||
}> = ({ workspaceId }) => {
|
||||
const router = useRouter();
|
||||
const blockSuiteWorkspace = useAtomValue(publicBlockSuiteAtom);
|
||||
const handleClickPage = useCallback(
|
||||
(pageId: string) => {
|
||||
return router.push({
|
||||
pathname: `/public-workspace/[workspaceId]/[pageId]`,
|
||||
query: {
|
||||
workspaceId,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[router, workspaceId]
|
||||
);
|
||||
if (!blockSuiteWorkspace) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
return (
|
||||
<BlockSuitePublicPageList
|
||||
onOpenPage={handleClickPage}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// This is affine only page, so we don't need to dynamic use WorkspacePlugin
|
||||
const ListPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const workspaceId = router.query.workspaceId;
|
||||
const setWorkspaceId = useSetAtom(publicWorkspaceIdAtom);
|
||||
const setCurrentWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
if (typeof workspaceId === 'string') {
|
||||
setWorkspaceId(workspaceId);
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
}
|
||||
}, [router.isReady, setCurrentWorkspaceId, setWorkspaceId, workspaceId]);
|
||||
const value = useAtomValue(publicWorkspaceIdAtom);
|
||||
if (!router.isReady || !value) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (typeof workspaceId !== 'string') {
|
||||
throw new QueryParamError('workspaceId', workspaceId);
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={<PageLoading />}>
|
||||
<ListPageInner workspaceId={workspaceId} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPage;
|
||||
|
||||
ListPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
@@ -1,117 +1,27 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import { Breadcrumbs } from '@affine/component';
|
||||
import { IconButton } from '@affine/component';
|
||||
import {
|
||||
Breadcrumbs,
|
||||
displayFlex,
|
||||
IconButton,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon } from '@blocksuite/icons';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactElement, useEffect, useMemo } from 'react';
|
||||
import React, { Suspense, useEffect } from 'react';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useLoadPublicWorkspace } from '@/hooks/use-load-public-workspace';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import {
|
||||
publicBlockSuiteAtom,
|
||||
publicWorkspaceIdAtom,
|
||||
} from '../../../atoms/public-workspace';
|
||||
import { QueryParamError } from '../../../components/affine/affine-error-eoundary';
|
||||
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
||||
import { WorkspaceAvatar } from '../../../components/pure/footer';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { NextPageWithLayout } from '../../../shared';
|
||||
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceId, pageId } = router.query as Record<string, string>;
|
||||
const { status, workspace: workspaceUnit } =
|
||||
useLoadPublicWorkspace(workspaceId);
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const page = useMemo(() => {
|
||||
if (workspaceUnit?.blocksuiteWorkspace) {
|
||||
return workspaceUnit.blocksuiteWorkspace.getPage(pageId);
|
||||
}
|
||||
return null;
|
||||
}, [workspaceUnit, pageId]);
|
||||
|
||||
const workspace = workspaceUnit?.blocksuiteWorkspace;
|
||||
const pageTitle = page?.meta.title;
|
||||
const workspaceName = workspace?.meta.name;
|
||||
|
||||
useEffect(() => {
|
||||
const pageNotFound = workspace?.meta.pageMetas.every(p => p.id !== pageId);
|
||||
if (workspace && pageNotFound) {
|
||||
router.push('/404');
|
||||
}
|
||||
}, [workspace, router, pageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'error') {
|
||||
router.push('/404');
|
||||
}
|
||||
}, [router, status]);
|
||||
|
||||
if (status === 'loading') {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs href={`/public-workspace/${workspaceId}`}>
|
||||
<WorkspaceUnitAvatar
|
||||
size={24}
|
||||
name={workspaceName}
|
||||
workspaceUnit={workspaceUnit}
|
||||
/>
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${workspaceId}/${pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : t('Untitled')}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
|
||||
{workspace && page && (
|
||||
<DynamicBlocksuite
|
||||
page={page}
|
||||
workspace={workspace}
|
||||
setEditor={editor => {
|
||||
editor.readonly = true;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Page.getLayout = function getLayout(page: ReactElement) {
|
||||
return <div>{page}</div>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export const PageContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const NavContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
@@ -121,7 +31,8 @@ export const NavContainer = styled.div(({ theme }) => {
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
|
||||
export const StyledBreadcrumbs = styled(Link)(({ theme }) => {
|
||||
return {
|
||||
flex: 1,
|
||||
...displayFlex('center', 'center'),
|
||||
@@ -139,6 +50,7 @@ export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
@@ -147,3 +59,89 @@ export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
padding: '0 24px',
|
||||
};
|
||||
});
|
||||
|
||||
const PublicWorkspaceDetailPageInner: React.FC<{
|
||||
pageId: string;
|
||||
}> = ({ pageId }) => {
|
||||
const blockSuiteWorkspace = useAtomValue(publicBlockSuiteAtom);
|
||||
if (!blockSuiteWorkspace) {
|
||||
throw new Error('cannot find workspace');
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
const name = blockSuiteWorkspace.meta.name;
|
||||
const pageTitle = blockSuiteWorkspace.meta.getPageMeta(pageId)?.title;
|
||||
return (
|
||||
<>
|
||||
<PageDetailEditor
|
||||
pageId={pageId}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onLoad={(_, editor) => {
|
||||
editor.readonly = true;
|
||||
}}
|
||||
header={
|
||||
<NavContainer
|
||||
// fixme(himself65): this is a hack to make the breadcrumbs work
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
}}
|
||||
>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${blockSuiteWorkspace.room}`}
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
size={24}
|
||||
name={name}
|
||||
avatar={blockSuiteWorkspace.meta.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${
|
||||
blockSuiteWorkspace.room as string
|
||||
}/${pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : t('Untitled')}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
</NavContainer>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PublicWorkspaceDetailPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const workspaceId = router.query.workspaceId;
|
||||
const pageId = router.query.pageId;
|
||||
const setWorkspaceId = useSetAtom(publicWorkspaceIdAtom);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
if (typeof workspaceId === 'string') {
|
||||
setWorkspaceId(workspaceId);
|
||||
}
|
||||
}, [router.isReady, setWorkspaceId, workspaceId]);
|
||||
const value = useAtomValue(publicWorkspaceIdAtom);
|
||||
if (!router.isReady || !value) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (typeof workspaceId !== 'string' || typeof pageId !== 'string') {
|
||||
throw new QueryParamError('workspaceId, pageId', workspaceId);
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={<PageLoading />}>
|
||||
<PublicWorkspaceDetailPageInner pageId={pageId} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicWorkspaceDetailPage;
|
||||
|
||||
PublicWorkspaceDetailPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Breadcrumbs } from '@affine/component';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactElement, useEffect, useMemo } from 'react';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { PageList } from '@/components/page-list';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useLoadPublicWorkspace } from '@/hooks/use-load-public-workspace';
|
||||
import { PageMeta } from '@/providers/app-state-provider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
|
||||
import {
|
||||
NavContainer,
|
||||
PageContainer,
|
||||
SearchButton,
|
||||
StyledBreadcrumbs,
|
||||
} from './[pageId]';
|
||||
|
||||
const All = () => {
|
||||
const router = useRouter();
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
const { status, workspace } = useLoadPublicWorkspace(
|
||||
router.query.workspaceId as string
|
||||
);
|
||||
|
||||
const pageList = useMemo(() => {
|
||||
return (workspace?.blocksuiteWorkspace?.meta.pageMetas ?? []) as PageMeta[];
|
||||
}, [workspace]);
|
||||
|
||||
const workspaceName = workspace?.blocksuiteWorkspace?.meta.name;
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'error') {
|
||||
router.push('/404');
|
||||
}
|
||||
}, [router, status]);
|
||||
|
||||
if (status === 'loading') {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar
|
||||
size={24}
|
||||
name={workspaceName}
|
||||
workspaceUnit={workspace}
|
||||
/>
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={false}
|
||||
isPublic={true}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
All.getLayout = function getLayout(page: ReactElement) {
|
||||
return <div>{page}</div>;
|
||||
};
|
||||
|
||||
export default All;
|
||||
@@ -1,15 +0,0 @@
|
||||
.affine-default-page-block-title-container {
|
||||
margin-top: 78px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.affine-default-page-block-container {
|
||||
max-width: 686px;
|
||||
}
|
||||
|
||||
affine-block-hub {
|
||||
position: unset !important;
|
||||
}
|
||||
.block-hub-menu-container {
|
||||
position: unset !important;
|
||||
}
|
||||
@@ -1,152 +1,73 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { assertEquals } from '@blocksuite/store';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { EditorHeader } from '@/components/header';
|
||||
import MobileModal from '@/components/mobile-modal';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useGlobalState, useGlobalStateApi } from '@/store/app';
|
||||
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhills.md';
|
||||
import { Unreachable } from '../../../components/affine/affine-error-eoundary';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { useCurrentPageId } from '../../../hooks/current/use-current-page-id';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useLoadWorkspace } from '../../../hooks/use-load-workspace';
|
||||
import { useSyncRouterWithCurrentWorkspaceAndPage } from '../../../hooks/use-sync-router-with-current-workspace-and-page';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { WorkspacePlugins } from '../../../plugins';
|
||||
import { NextPageWithLayout, RemWorkspaceFlavour } from '../../../shared';
|
||||
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const BlockHubAppender = () => {
|
||||
const setBlockHub = useGlobalState(store => store.setBlockHub);
|
||||
const editor = useGlobalState(store => store.editor);
|
||||
const WorkspaceDetail: React.FC = () => {
|
||||
const [pageId] = useCurrentPageId();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const [, rerender] = useState(false);
|
||||
// fixme(himself65): this is a hack
|
||||
useEffect(() => {
|
||||
// todo(himself65): refactor with `useRef`
|
||||
const abortController = new AbortController();
|
||||
let blockHubElement: HTMLElement | null = null;
|
||||
abortController.signal.addEventListener('abort', () => {
|
||||
blockHubElement?.remove();
|
||||
const toolWrapper = document.querySelector('#toolWrapper');
|
||||
if (toolWrapper) {
|
||||
assertEquals(toolWrapper.childNodes.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
editor?.createBlockHub().then(blockHub => {
|
||||
const toolWrapper = document.querySelector('#toolWrapper');
|
||||
if (!toolWrapper) {
|
||||
// In an invitation page there is no toolWrapper, which contains helper icon and blockHub icon
|
||||
return;
|
||||
}
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
blockHubElement = blockHub;
|
||||
setBlockHub(blockHub);
|
||||
toolWrapper.appendChild(blockHub);
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [editor, setBlockHub]);
|
||||
return null;
|
||||
};
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
const currentPage = useGlobalState(store => store.currentPage);
|
||||
const setEditor = useGlobalState(store => store.setEditor);
|
||||
const currentWorkspace = useGlobalState(
|
||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Only first workspace and first page will have template markdown
|
||||
const shouldInitTemplateContent =
|
||||
currentPage?.isEmpty &&
|
||||
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas.length === 1;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{currentPage?.meta?.title || t('Untitled')} - AFFiNE</title>
|
||||
</Head>
|
||||
<EditorHeader />
|
||||
<MobileModal />
|
||||
|
||||
{currentPage && currentWorkspace?.blocksuiteWorkspace && (
|
||||
<>
|
||||
<DynamicBlocksuite
|
||||
page={currentPage}
|
||||
workspace={currentWorkspace.blocksuiteWorkspace}
|
||||
setEditor={setEditor}
|
||||
templateMarkdown={
|
||||
shouldInitTemplateContent ? exampleMarkdown : undefined
|
||||
}
|
||||
templateTitle={
|
||||
shouldInitTemplateContent
|
||||
? 'Welcome to AFFiNE Alpha "Downhills"'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<BlockHubAppender key={currentWorkspace.id + currentPage.id} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PageDefender = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
const [pageLoaded, setPageLoaded] = useState(false);
|
||||
const loadPage = useGlobalState(store => store.loadPage);
|
||||
const currentWorkspace = useGlobalState(
|
||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||
);
|
||||
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||
const { createPage } = usePageHelper();
|
||||
|
||||
useEffect(() => {
|
||||
const initPage = async () => {
|
||||
const pageId = router.query.pageId as string;
|
||||
const isPageExist =
|
||||
currentWorkspace?.blocksuiteWorkspace?.meta?.pageMetas.find(
|
||||
p => p.id === pageId
|
||||
);
|
||||
if (!isPageExist) {
|
||||
await createPage({ pageId });
|
||||
}
|
||||
await loadPage(pageId);
|
||||
setPageLoaded(true);
|
||||
};
|
||||
initPage();
|
||||
}, [createPage, currentWorkspace, loadPage, router.query.pageId]);
|
||||
const api = useGlobalStateApi();
|
||||
useEffect(
|
||||
() =>
|
||||
dataCenter.onWorkspacesChange(({ deleted }) => {
|
||||
if (deleted?.some(workspace => workspace.id === currentWorkspace?.id)) {
|
||||
router.replace('/404?code=kicked');
|
||||
const dispose = currentWorkspace?.blockSuiteWorkspace.signals.pageAdded.on(
|
||||
id => {
|
||||
if (pageId === id) {
|
||||
rerender(prev => !prev);
|
||||
}
|
||||
}),
|
||||
[api, currentWorkspace?.id, dataCenter, router]
|
||||
);
|
||||
|
||||
return <>{pageLoaded ? children : null}</>;
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
dispose?.dispose();
|
||||
};
|
||||
}, [currentWorkspace?.blockSuiteWorkspace.signals.pageAdded, pageId]);
|
||||
if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!pageId) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!currentWorkspace.blockSuiteWorkspace.getPage(pageId)) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
|
||||
const PageDetail = WorkspacePlugins[currentWorkspace.flavour].PageDetail;
|
||||
return (
|
||||
<PageDetail currentWorkspace={currentWorkspace} currentPageId={pageId} />
|
||||
);
|
||||
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
const PageDetail = WorkspacePlugins[currentWorkspace.flavour].PageDetail;
|
||||
return (
|
||||
<PageDetail currentWorkspace={currentWorkspace} currentPageId={pageId} />
|
||||
);
|
||||
}
|
||||
throw new Unreachable();
|
||||
};
|
||||
|
||||
Page.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<WorkspaceLayout>
|
||||
<PageDefender>{page}</PageDefender>
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
const WorkspaceDetailPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
useLoadWorkspace(useCurrentWorkspace()[0]);
|
||||
useSyncRouterWithCurrentWorkspaceAndPage(router);
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
} else if (
|
||||
typeof router.query.pageId !== 'string' ||
|
||||
typeof router.query.workspaceId !== 'string'
|
||||
) {
|
||||
throw new Error('Invalid router query');
|
||||
}
|
||||
return <WorkspaceDetail />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export default WorkspaceDetailPage;
|
||||
|
||||
WorkspaceDetailPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
|
||||
@@ -1,35 +1,87 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { FolderIcon } from '@blocksuite/icons';
|
||||
import Head from 'next/head';
|
||||
import { ReactElement, useCallback } from 'react';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { PageListHeader } from '@/components/header';
|
||||
import { PageList } from '@/components/page-list';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
|
||||
const All = () => {
|
||||
const pageList = useGlobalState(
|
||||
useCallback(store => store.dataCenterPageList, [])
|
||||
);
|
||||
import {
|
||||
QueryParamError,
|
||||
Unreachable,
|
||||
} from '../../../components/affine/affine-error-eoundary';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useLoadWorkspace } from '../../../hooks/use-load-workspace';
|
||||
import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { WorkspacePlugins } from '../../../plugins';
|
||||
import { NextPageWithLayout, RemWorkspaceFlavour } from '../../../shared';
|
||||
const AllPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t('All pages')} - AFFiNE</title>
|
||||
</Head>
|
||||
<PageListHeader icon={<FolderIcon />}>{t('All pages')}</PageListHeader>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={true}
|
||||
listType="all"
|
||||
/>
|
||||
</>
|
||||
useLoadWorkspace(currentWorkspace);
|
||||
useSyncRouterWithCurrentWorkspace(router);
|
||||
const onClickPage = useCallback(
|
||||
(pageId: string, newTab?: boolean) => {
|
||||
assertExists(currentWorkspace);
|
||||
if (newTab) {
|
||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||
} else {
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[currentWorkspace, router]
|
||||
);
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (typeof router.query.workspaceId !== 'string') {
|
||||
throw new QueryParamError('workspaceId', router.query.workspaceId);
|
||||
}
|
||||
if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
|
||||
const PageList = WorkspacePlugins[currentWorkspace.flavour].PageList;
|
||||
if (currentWorkspace.firstBinarySynced) {
|
||||
return (
|
||||
<>
|
||||
<WorkspaceTitle icon={<FolderIcon />}>
|
||||
{t('All pages')}
|
||||
</WorkspaceTitle>
|
||||
<PageList
|
||||
onOpenPage={onClickPage}
|
||||
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
const PageList = WorkspacePlugins[currentWorkspace.flavour].PageList;
|
||||
return (
|
||||
<>
|
||||
<WorkspaceTitle icon={<FolderIcon />}>{t('All pages')}</WorkspaceTitle>
|
||||
<PageList
|
||||
onOpenPage={onClickPage}
|
||||
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
throw new Unreachable();
|
||||
};
|
||||
|
||||
All.getLayout = function getLayout(page: ReactElement) {
|
||||
export default AllPage;
|
||||
|
||||
AllPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
|
||||
export default All;
|
||||
|
||||
@@ -1,31 +1,64 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { FavoriteIcon } from '@blocksuite/icons';
|
||||
import Head from 'next/head';
|
||||
import { ReactElement } from 'react';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
import { PageListHeader } from '@/components/header';
|
||||
import { PageList } from '@/components/page-list';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
import PageList from '../../../components/blocksuite/block-suite-page-list/page-list';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useLoadWorkspace } from '../../../hooks/use-load-workspace';
|
||||
import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { NextPageWithLayout } from '../../../shared';
|
||||
|
||||
export const Favorite = () => {
|
||||
const pageList = useGlobalState(store => store.dataCenterPageList);
|
||||
const FavouritePage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const { t } = useTranslation();
|
||||
useLoadWorkspace(currentWorkspace);
|
||||
useSyncRouterWithCurrentWorkspace(router);
|
||||
const onClickPage = useCallback(
|
||||
(pageId: string, newTab?: boolean) => {
|
||||
assertExists(currentWorkspace);
|
||||
if (newTab) {
|
||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||
} else {
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[currentWorkspace, router]
|
||||
);
|
||||
if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
assertExists(blockSuiteWorkspace);
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<Helmet>
|
||||
<title>{t('Favorites')} - AFFiNE</title>
|
||||
</Head>
|
||||
<PageListHeader icon={<FavoriteIcon />}>{t('Favorites')}</PageListHeader>
|
||||
</Helmet>
|
||||
<WorkspaceTitle icon={<FavoriteIcon />}>{t('Favorites')}</WorkspaceTitle>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => p.favorite && !p.trash)}
|
||||
showFavoriteTag={true}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onClickPage={onClickPage}
|
||||
listType="favorite"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Favorite.getLayout = function getLayout(page: ReactElement) {
|
||||
|
||||
export default FavouritePage;
|
||||
|
||||
FavouritePage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
export default Favorite;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
|
||||
const WorkspaceIndex = () => {
|
||||
const router = useRouter();
|
||||
const currentWorkspace = useGlobalState(
|
||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||
);
|
||||
const { createPage } = usePageHelper();
|
||||
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
|
||||
|
||||
useEffect(() => {
|
||||
const initPage = async () => {
|
||||
if (!workspaceLoaded) {
|
||||
return;
|
||||
}
|
||||
const savedPageId =
|
||||
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas.find(
|
||||
meta => !meta.trash
|
||||
)?.id;
|
||||
if (savedPageId) {
|
||||
router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const pageId = await createPage();
|
||||
router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
|
||||
};
|
||||
initPage();
|
||||
}, [
|
||||
currentWorkspace,
|
||||
createPage,
|
||||
router,
|
||||
workspaceLoaded,
|
||||
activeWorkspaceId,
|
||||
]);
|
||||
|
||||
return <PageLoading />;
|
||||
};
|
||||
|
||||
export default WorkspaceIndex;
|
||||
@@ -1,177 +1,167 @@
|
||||
import { styled } from '@affine/component';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import Head from 'next/head';
|
||||
import {
|
||||
CSSProperties,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
startTransition,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
import { PageListHeader } from '@/components/header';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { Unreachable } from '../../../components/affine/affine-error-eoundary';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useLoadWorkspace } from '../../../hooks/use-load-workspace';
|
||||
import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace';
|
||||
import { useWorkspacesHelper } from '../../../hooks/use-workspaces';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { WorkspacePlugins } from '../../../plugins';
|
||||
import {
|
||||
ExportPage,
|
||||
GeneralPage,
|
||||
MembersPage,
|
||||
PublishPage,
|
||||
SyncPage,
|
||||
} from '@/components/workspace-setting';
|
||||
import {
|
||||
StyledSettingContainer,
|
||||
StyledSettingContent,
|
||||
WorkspaceSettingTagItem,
|
||||
} from '@/components/workspace-setting/style';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
NextPageWithLayout,
|
||||
RemWorkspaceFlavour,
|
||||
SettingPanel,
|
||||
settingPanel,
|
||||
settingPanelValues,
|
||||
} from '../../../shared';
|
||||
|
||||
const useTabMap = () => {
|
||||
const settingPanelAtom = atomWithStorage<SettingPanel>(
|
||||
'workspaceId',
|
||||
settingPanel.General
|
||||
);
|
||||
|
||||
const SettingPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const { t } = useTranslation();
|
||||
const isOwner = useGlobalState(store => store.isOwner);
|
||||
const tabMap: {
|
||||
id: string;
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
id: 'General',
|
||||
name: t('General'),
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: add it back for desktop version
|
||||
{
|
||||
id: 'Sync',
|
||||
name: t('Sync'),
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
id: 'Collaboration',
|
||||
name: t('Collaboration'),
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
id: 'Publish',
|
||||
name: t('Publish'),
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: next version will finish this feature
|
||||
{
|
||||
id: 'Export',
|
||||
name: t('Export'),
|
||||
panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
},
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState<string>(tabMap[0].id);
|
||||
const handleTabChange = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
};
|
||||
|
||||
const activeTabPanelRender = tabMap.find(
|
||||
tab => tab.id === activeTab
|
||||
)?.panelRender;
|
||||
|
||||
return {
|
||||
activeTabPanelRender,
|
||||
tabMap: isOwner ? tabMap : tabMap.slice(0, 1),
|
||||
handleTabChange,
|
||||
activeTab,
|
||||
};
|
||||
};
|
||||
|
||||
const StyledIndicator = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: '2px',
|
||||
background: theme.colors.primaryColor,
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
bottom: '0',
|
||||
transition: 'left .3s, width .3s',
|
||||
};
|
||||
});
|
||||
const StyledTabButtonWrapper = styled.div(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
const WorkspaceSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
const currentWorkspace = useGlobalState(
|
||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||
);
|
||||
const user = useGlobalState(store => store.user);
|
||||
const { activeTabPanelRender, tabMap, handleTabChange, activeTab } =
|
||||
useTabMap();
|
||||
|
||||
const [indicatorState, setIndicatorState] = useState<
|
||||
Pick<CSSProperties, 'left' | 'width'>
|
||||
>({
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
const shouldHideSyncTab =
|
||||
currentWorkspace?.owner?.id !== user?.id ||
|
||||
currentWorkspace?.provider === 'local';
|
||||
|
||||
useEffect(() => {
|
||||
const tabButton = document.querySelector(
|
||||
`[data-setting-tab-button="${activeTab}"]`
|
||||
);
|
||||
if (tabButton instanceof HTMLElement) {
|
||||
startTransition(() => {
|
||||
setIndicatorState({
|
||||
width: `${tabButton.offsetWidth}px`,
|
||||
left: `${tabButton.offsetLeft}px`,
|
||||
});
|
||||
useLoadWorkspace(currentWorkspace);
|
||||
useSyncRouterWithCurrentWorkspace(router);
|
||||
const [currentTab, setCurrentTab] = useAtom(settingPanelAtom);
|
||||
useEffect(() => {});
|
||||
const onChangeTab = useCallback(
|
||||
(tab: SettingPanel) => {
|
||||
setCurrentTab(tab as SettingPanel);
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: tab,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t('Workspace Settings')} - AFFiNE</title>
|
||||
</Head>
|
||||
<PageListHeader icon={<SettingsIcon />}>
|
||||
{t('Workspace Settings')}
|
||||
</PageListHeader>
|
||||
|
||||
<StyledSettingContainer>
|
||||
<StyledTabButtonWrapper>
|
||||
{tabMap.map(({ id, name }) => {
|
||||
if (shouldHideSyncTab && id === 'Sync') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<WorkspaceSettingTagItem
|
||||
key={id}
|
||||
isActive={activeTab === id}
|
||||
data-setting-tab-button={id}
|
||||
onClick={() => {
|
||||
handleTabChange(id);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</WorkspaceSettingTagItem>
|
||||
);
|
||||
})}
|
||||
<StyledIndicator style={indicatorState} />
|
||||
</StyledTabButtonWrapper>
|
||||
|
||||
<StyledSettingContent>
|
||||
{currentWorkspace && activeTabPanelRender?.(currentWorkspace)}
|
||||
</StyledSettingContent>
|
||||
</StyledSettingContainer>
|
||||
</>
|
||||
},
|
||||
[router, setCurrentTab]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const queryCurrentTab =
|
||||
typeof router.query.currentTab === 'string'
|
||||
? router.query.currentTab
|
||||
: null;
|
||||
if (
|
||||
queryCurrentTab !== null &&
|
||||
settingPanelValues.indexOf(queryCurrentTab as SettingPanel) === -1
|
||||
) {
|
||||
setCurrentTab(settingPanel.General);
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: settingPanel.General,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else if (settingPanelValues.indexOf(currentTab as SettingPanel) === -1) {
|
||||
setCurrentTab(settingPanel.General);
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: settingPanel.General,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else if (queryCurrentTab !== currentTab) {
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: currentTab,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, [currentTab, router, setCurrentTab]);
|
||||
|
||||
const helper = useWorkspacesHelper();
|
||||
|
||||
const onDeleteWorkspace = useCallback(() => {
|
||||
assertExists(currentWorkspace);
|
||||
const workspaceId = currentWorkspace.id;
|
||||
helper.deleteWorkspace(workspaceId);
|
||||
}, [currentWorkspace, helper]);
|
||||
const onTransformWorkspace = useCallback(
|
||||
(targetWorkspaceId: string) => {
|
||||
router
|
||||
.replace({
|
||||
pathname: `/workspace/[workspaceId]/all`,
|
||||
query: {
|
||||
workspaceId: targetWorkspaceId,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
router.reload();
|
||||
});
|
||||
},
|
||||
[router]
|
||||
);
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
} else if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
} else if (settingPanelValues.indexOf(currentTab as SettingPanel) === -1) {
|
||||
return <PageLoading />;
|
||||
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
|
||||
const Setting = WorkspacePlugins[currentWorkspace.flavour].SettingsDetail;
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t('Workspace Settings')} - AFFiNE</title>
|
||||
</Helmet>
|
||||
<WorkspaceTitle icon={<SettingsIcon />}>
|
||||
{t('Workspace Settings')}
|
||||
</WorkspaceTitle>
|
||||
<Setting
|
||||
onTransformWorkspace={onTransformWorkspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
currentWorkspace={currentWorkspace}
|
||||
currentTab={currentTab as SettingPanel}
|
||||
onChangeTab={onChangeTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
const Setting = WorkspacePlugins[currentWorkspace.flavour].SettingsDetail;
|
||||
return (
|
||||
<>
|
||||
<WorkspaceTitle icon={<SettingsIcon />}>
|
||||
{t('Workspace Settings')}
|
||||
</WorkspaceTitle>
|
||||
<Setting
|
||||
onTransformWorkspace={onTransformWorkspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
currentWorkspace={currentWorkspace}
|
||||
currentTab={currentTab as SettingPanel}
|
||||
onChangeTab={onChangeTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
throw new Unreachable();
|
||||
};
|
||||
WorkspaceSetting.getLayout = function getLayout(page: ReactElement) {
|
||||
|
||||
export default SettingPage;
|
||||
|
||||
SettingPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
|
||||
export default WorkspaceSetting;
|
||||
|
||||
@@ -1,37 +1,69 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { DeleteTemporarilyIcon } from '@blocksuite/icons';
|
||||
import Head from 'next/head';
|
||||
import { ReactElement, useCallback } from 'react';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
import { PageListHeader } from '@/components/header';
|
||||
import { PageList } from '@/components/page-list';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
import PageList from '../../../components/blocksuite/block-suite-page-list/page-list';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useLoadWorkspace } from '../../../hooks/use-load-workspace';
|
||||
import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace';
|
||||
import { WorkspaceLayout } from '../../../layouts';
|
||||
import { NextPageWithLayout } from '../../../shared';
|
||||
|
||||
export const Trash = () => {
|
||||
const pageList = useGlobalState(
|
||||
useCallback(store => store.dataCenterPageList, [])
|
||||
);
|
||||
const TrashPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const { t } = useTranslation();
|
||||
useLoadWorkspace(currentWorkspace);
|
||||
useSyncRouterWithCurrentWorkspace(router);
|
||||
const onClickPage = useCallback(
|
||||
(pageId: string, newTab?: boolean) => {
|
||||
assertExists(currentWorkspace);
|
||||
if (newTab) {
|
||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||
} else {
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[currentWorkspace, router]
|
||||
);
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
} else if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
// todo(himself65): refactor to plugin
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
assertExists(blockSuiteWorkspace);
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<Helmet>
|
||||
<title>{t('Trash')} - AFFiNE</title>
|
||||
</Head>
|
||||
<PageListHeader icon={<DeleteTemporarilyIcon />}>
|
||||
</Helmet>
|
||||
<WorkspaceTitle icon={<DeleteTemporarilyIcon />}>
|
||||
{t('Trash')}
|
||||
</PageListHeader>
|
||||
</WorkspaceTitle>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => p.trash)}
|
||||
isTrash={true}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onClickPage={onClickPage}
|
||||
listType="trash"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Trash.getLayout = function getLayout(page: ReactElement) {
|
||||
export default TrashPage;
|
||||
|
||||
TrashPage.getLayout = page => {
|
||||
return <WorkspaceLayout>{page}</WorkspaceLayout>;
|
||||
};
|
||||
|
||||
export default Trash;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
|
||||
import { useGlobalState } from '@/store/app';
|
||||
|
||||
export const WorkspaceIndex = () => {
|
||||
const router = useRouter();
|
||||
const currentWorkspace = useGlobalState(
|
||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||
);
|
||||
const { workspaceLoaded } = useEnsureWorkspace();
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceLoaded) {
|
||||
router.push(`/workspace/${currentWorkspace?.id}`);
|
||||
}
|
||||
}, [currentWorkspace, router, workspaceLoaded]);
|
||||
|
||||
return <PageLoading />;
|
||||
};
|
||||
|
||||
export default WorkspaceIndex;
|
||||
Reference in New Issue
Block a user