mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-03 02:20:19 +08:00
Merge branch 'feat/datacenter' of github.com:toeverything/AFFiNE into feat/ui
This commit is contained in:
@@ -15,7 +15,7 @@ export const NotfoundPage = () => {
|
||||
router.push('/workspace');
|
||||
}}
|
||||
>
|
||||
Back Home
|
||||
{t('Back Home')}
|
||||
</Button>
|
||||
</p>
|
||||
</NotFoundTitle>
|
||||
|
||||
@@ -53,13 +53,10 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<p>
|
||||
Workspace is your virtual space to capture, create and plan as
|
||||
just one person or together as a team.
|
||||
</p>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<Input
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={'Set a Workspace name'}
|
||||
placeholder={t('Set a Workspace name')}
|
||||
onChange={value => {
|
||||
setWorkspaceName(value);
|
||||
}}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { GoogleLoginButton } from './LoginOptionButton';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -9,6 +10,7 @@ interface LoginModalProps {
|
||||
|
||||
export const LoginModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const { login } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="login-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
@@ -20,8 +22,8 @@ export const LoginModal = ({ open, onClose }: LoginModalProps) => {
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign in'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<ContentTitle>{t('Sign in')}</ContentTitle>
|
||||
<SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
|
||||
<span
|
||||
onClick={async () => {
|
||||
await login();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Check, UnCheck } from './icon';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: (wait: boolean) => void;
|
||||
@@ -10,6 +11,7 @@ interface LoginModalProps {
|
||||
|
||||
export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const [localCache, setLocalCache] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
@@ -21,8 +23,8 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign out?'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<ContentTitle>{t('Sign out')}?</ContentTitle>
|
||||
<SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
|
||||
<StyleTips>
|
||||
{localCache ? (
|
||||
<StyleCheck
|
||||
@@ -41,7 +43,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
<UnCheck></UnCheck>
|
||||
</StyleCheck>
|
||||
)}
|
||||
Retain local cached data
|
||||
{t('Retain local cached data')}
|
||||
</StyleTips>
|
||||
<div>
|
||||
<Button
|
||||
@@ -51,7 +53,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
Wait for Sync
|
||||
{t('Wait for Sync')}
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
@@ -60,7 +62,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
Force Sign Out
|
||||
{t('Force Sign Out')}
|
||||
</Button>
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
@@ -8,17 +8,17 @@ import React, {
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { StyledInputContent, StyledLabel } from './style';
|
||||
import { Command } from 'cmdk';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
export const Input = (props: {
|
||||
query: string;
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
isPublic: boolean;
|
||||
publishWorkspaceName: string | undefined;
|
||||
}) => {
|
||||
const [isComposition, setIsComposition] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
inputRef.current?.addEventListener(
|
||||
@@ -78,9 +78,9 @@ export const Input = (props: {
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
currentWorkspace?.isPublish
|
||||
props.isPublic
|
||||
? t('Quick search placeholder2', {
|
||||
workspace: currentWorkspace?.blocksuiteWorkspace?.meta.name,
|
||||
workspace: props.publishWorkspaceName,
|
||||
})
|
||||
: t('Quick search placeholder')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState, PageMeta } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
|
||||
export const PublishedResults = (props: {
|
||||
query: string;
|
||||
loading: boolean;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setPublishWorkspaceName: Dispatch<SetStateAction<string>>;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const [workspace, setWorkspace] = useState<Workspace>();
|
||||
const { query, loading, setLoading, onClose, setPublishWorkspaceName } =
|
||||
props;
|
||||
const { search } = usePageHelper();
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
setPublishWorkspaceName(data.blocksuiteWorkspace.meta.name);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
});
|
||||
}, [router, dataCenter, setPublishWorkspaceName]);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
setResults(search(query, workspace));
|
||||
setLoading(false);
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query, setResults, setLoading]);
|
||||
const pageIds = [...results.values()];
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
);
|
||||
|
||||
return loading ? null : (
|
||||
<>
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,8 @@ import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
type TransitionsModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -22,10 +23,11 @@ const isMac = () => {
|
||||
return getUaHelper().isMacOs;
|
||||
};
|
||||
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
|
||||
const router = useRouter();
|
||||
const [query, setQuery] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isPublic, setIsPublic] = useState(false);
|
||||
const [publishWorkspaceName, setPublishWorkspaceName] = useState('');
|
||||
const [showCreatePage, setShowCreatePage] = useState(true);
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
@@ -51,6 +53,14 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
document.removeEventListener('keydown', down, { capture: true });
|
||||
}, [open, triggerQuickSearchModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname.startsWith('/public-workspace')) {
|
||||
return setIsPublic(true);
|
||||
} else {
|
||||
return setIsPublic(false);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
@@ -81,28 +91,44 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<Input query={query} setQuery={setQuery} setLoading={setLoading} />
|
||||
<Input
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
setLoading={setLoading}
|
||||
isPublic={isPublic}
|
||||
publishWorkspaceName={publishWorkspaceName}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider />
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
{!isPublic ? (
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
) : (
|
||||
<PublishedResults
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={onClose}
|
||||
setPublishWorkspaceName={setPublishWorkspaceName}
|
||||
/>
|
||||
)}
|
||||
</StyledContent>
|
||||
{currentWorkspace?.published ? (
|
||||
<></>
|
||||
) : showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
{!isPublic ? (
|
||||
showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyledFooter, StyleUserInfo, StyleSignIn } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
|
||||
export const Footer = ({
|
||||
onLogin,
|
||||
@@ -13,6 +14,7 @@ export const Footer = ({
|
||||
onLogout: () => void;
|
||||
}) => {
|
||||
const { user } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
@@ -48,7 +50,7 @@ export const Footer = ({
|
||||
<span>
|
||||
<CloudInsyncIcon fontSize={16} />
|
||||
</span>
|
||||
Sign in to sync with AFFINE Cloud
|
||||
{t('Sign in')}
|
||||
</StyleSignIn>
|
||||
)}
|
||||
</StyledFooter>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PublishIcon, UsersIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
|
||||
export const WorkspaceCard = ({
|
||||
@@ -18,7 +19,7 @@ export const WorkspaceCard = ({
|
||||
onClick: (data: WorkspaceUnit) => void;
|
||||
}) => {
|
||||
const { currentWorkspace, isOwner } = useAppState();
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledCard
|
||||
onClick={() => {
|
||||
@@ -38,29 +39,30 @@ export const WorkspaceCard = ({
|
||||
workspaceData.provider === 'local' ? (
|
||||
<p>
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
{t('Local Workspace')}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<CloudIcon />
|
||||
Cloud Workspace
|
||||
{t('Cloud Workspace')}
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<p>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
{t('Joined Workspace')}
|
||||
</p>
|
||||
)}
|
||||
{workspaceData.provider === 'local' && (
|
||||
<p>
|
||||
<OfflineIcon />
|
||||
All data can be accessed offline
|
||||
{t('Available Offline')}
|
||||
</p>
|
||||
)}
|
||||
{workspaceData.published && (
|
||||
<p>
|
||||
<PublishIcon fontSize={16} /> Published to Web
|
||||
<PublishIcon fontSize={16} />
|
||||
{t('Published to Web')}
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
|
||||
@@ -57,9 +57,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'A workspace is your virtual space to capture, create and plan as just one person or together as a team.'
|
||||
)}
|
||||
content={t('Workspace description')}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
@@ -106,8 +104,8 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||
</FlexWrapper>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>New workspace</StyleWorkspaceTitle>
|
||||
<p>Crete or import</p>
|
||||
<StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
|
||||
<p>{t('Create Or Import')}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
</StyledModalContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
StyledCopyButtonContainer,
|
||||
StyledButtonContainer,
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
@@ -24,13 +24,13 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
await publishWorkspace(workspace.id.toString(), flag);
|
||||
setLoaded(false);
|
||||
} catch (e) {
|
||||
toast('Failed to publish workspace');
|
||||
toast(t('Failed to publish workspace'));
|
||||
}
|
||||
};
|
||||
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast('Copied url to clipboard');
|
||||
toast(t('Copied link to clipboard'));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -43,8 +43,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
{workspace.published ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
The current workspace has been published to the web, everyone
|
||||
can view the contents of this workspace through the link.
|
||||
{t('Published Description')}
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<StyledPublishCopyContainer>
|
||||
@@ -52,11 +51,11 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
{t('Share with link')}
|
||||
</StyledSettingH2>
|
||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||
<StyledCopyButtonContainer>
|
||||
<StyledButtonContainer>
|
||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||
{t('Copy Link')}
|
||||
</Button>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledButtonContainer>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
@@ -101,7 +100,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledPublishContent>
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
Publishing to web requires AFFiNE Cloud service.
|
||||
{t('Publishing')}
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<div style={{ marginTop: '72px' }}>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
StyleAsync,
|
||||
StyledButtonContainer,
|
||||
StyledPublishContent,
|
||||
StyledPublishExplanation,
|
||||
StyledWorkspaceName,
|
||||
StyledWorkspaceType,
|
||||
StyledEmail,
|
||||
} from './style';
|
||||
import { DownloadIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useTranslation, Trans } from '@affine/i18n';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { EnableWorkspaceButton } from '../enable-workspace';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -28,16 +28,13 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name};</StyledWorkspaceName>
|
||||
<StyledWorkspaceType>is a Local Workspace.</StyledWorkspaceType>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<span>{t('is a Local Workspace')}</span>
|
||||
</StyledPublishExplanation>
|
||||
<StyledWorkspaceType>
|
||||
All data is stored on the current device. You can enable AFFiNE
|
||||
Cloud for this workspace to keep data in sync with the cloud.
|
||||
</StyledWorkspaceType>
|
||||
<StyleAsync>
|
||||
<div>{t('Local Workspace Description')}</div>
|
||||
<StyledButtonContainer>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</StyleAsync>
|
||||
</StyledButtonContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -48,14 +45,21 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>is
|
||||
Cloud Workspace.
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<span>{t('is a Cloud Workspace')}</span>
|
||||
</StyledPublishExplanation>
|
||||
<StyledWorkspaceType>
|
||||
All data will be synchronized and saved to the AFFiNE account{' '}
|
||||
{user?.email}
|
||||
</StyledWorkspaceType>
|
||||
<StyleAsync>
|
||||
<div>
|
||||
<Trans i18nKey="Cloud Workspace Description">
|
||||
All data will be synchronised and saved to the AFFiNE account
|
||||
<StyledEmail>
|
||||
{{
|
||||
email: '{' + user?.email + '}.',
|
||||
}}
|
||||
</StyledEmail>
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<StyledButtonContainer>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
@@ -65,7 +69,7 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'core' })}
|
||||
{t('Download data', { CoreOrAll: t('core') })}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
@@ -73,7 +77,7 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
{t('Download data', { CoreOrAll: t('all') })}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
@@ -81,10 +85,10 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button type="primary">
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
{t('Download data', { CoreOrAll: '' })}
|
||||
</Button>
|
||||
</Menu>
|
||||
</StyleAsync>
|
||||
</StyledButtonContainer>
|
||||
</>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
|
||||
@@ -50,7 +50,7 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
return workspace ? (
|
||||
<StyleGeneral>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
<StyledSettingH2>Workspace Avatar</StyledSettingH2>
|
||||
<StyledSettingH2>{t('Workspace Avatar')}</StyledSettingH2>
|
||||
<StyledSettingAvatarContent>
|
||||
<StyledAvatar>
|
||||
<Upload
|
||||
@@ -106,18 +106,18 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
currentWorkspace?.provider === 'local' ? (
|
||||
<StyledProviderInfo>
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
{t('Local Workspace')}
|
||||
</StyledProviderInfo>
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<CloudIcon />
|
||||
All data can be accessed offline
|
||||
{t('Available Offline')}
|
||||
</StyledProviderInfo>
|
||||
)
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
{t('Joined Workspace')}
|
||||
</StyledProviderInfo>
|
||||
)}
|
||||
</StyledSettingInputContainer>
|
||||
|
||||
@@ -89,9 +89,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledMemberRoleContainer>
|
||||
{member.accepted
|
||||
? member.type !== 99
|
||||
? 'Member'
|
||||
: 'Workspace Owner'
|
||||
: 'Pending'}
|
||||
? t('Member')
|
||||
: t('Owner')
|
||||
: t('Pending')}
|
||||
</StyledMemberRoleContainer>
|
||||
<StyledMoreVerticalButton>
|
||||
<Menu
|
||||
@@ -100,9 +100,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const confirmRemove = await confirm({
|
||||
title: 'Delete Member?',
|
||||
content: `will delete member`,
|
||||
confirmText: 'Delete',
|
||||
title: t('Delete Member?'),
|
||||
content: t('will delete member'),
|
||||
confirmText: t('Delete'),
|
||||
confirmType: 'danger',
|
||||
});
|
||||
|
||||
@@ -110,11 +110,15 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
return;
|
||||
}
|
||||
await removeMember(member.id);
|
||||
toast(`${user.name} has been removed`);
|
||||
toast(
|
||||
t('Member has been removed', {
|
||||
name: user.name,
|
||||
})
|
||||
);
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
{t('Delete')}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export const StyledSettingH2 = styled('h2')<{
|
||||
marginBottom?: number;
|
||||
}>(({ marginTop, marginBottom, theme }) => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
// fontWeight: '500',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
marginTop: marginTop ? `${marginTop}px` : '0px',
|
||||
@@ -101,24 +101,18 @@ export const StyledSettingH2 = styled('h2')<{
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
// fontWeight: '500',
|
||||
marginBottom: '22px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceType = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
// fontWeight: '500',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -136,22 +130,24 @@ export const StyledStopPublishContainer = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCopyButtonContainer = styled('div')(() => {
|
||||
export const StyledButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
export const StyledEmail = styled('span')(() => {
|
||||
return {
|
||||
color: '#E8178A',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishContent = styled('div')(() => {
|
||||
export const StyledPublishContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
fontWeight: '500',
|
||||
flexDirection: 'column',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleAsync = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
@@ -20,7 +20,10 @@ export type EditorHandlers = {
|
||||
toggleDeletePage: (pageId: string) => Promise<boolean>;
|
||||
toggleFavoritePage: (pageId: string) => Promise<boolean>;
|
||||
permanentlyDeletePage: (pageId: string) => void;
|
||||
search: (query: QueryContent) => Map<string, string | undefined>;
|
||||
search: (
|
||||
query: QueryContent,
|
||||
workspace?: Workspace
|
||||
) => Map<string, string | undefined>;
|
||||
// changeEditorMode: (pageId: string) => void;
|
||||
changePageMode: (
|
||||
pageId: string,
|
||||
@@ -83,9 +86,16 @@ export const usePageHelper = (): EditorHandlers => {
|
||||
});
|
||||
return trash;
|
||||
},
|
||||
search: (query: QueryContent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return currentWorkspace!.blocksuiteWorkspace!.search(query);
|
||||
search: (query: QueryContent, workspace?: Workspace) => {
|
||||
if (workspace) {
|
||||
return workspace.search(query);
|
||||
}
|
||||
if (currentWorkspace) {
|
||||
if (currentWorkspace.blocksuiteWorkspace) {
|
||||
return currentWorkspace.blocksuiteWorkspace.search(query);
|
||||
}
|
||||
}
|
||||
return new Map();
|
||||
},
|
||||
changePageMode: async (pageId, mode) => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
|
||||
@@ -3,9 +3,11 @@ import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import router from 'next/router';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
|
||||
export const useWorkspaceHelper = () => {
|
||||
const { confirm } = useConfirm();
|
||||
const { t } = useTranslation();
|
||||
const { dataCenter, currentWorkspace, user, login } = useAppState();
|
||||
const createWorkspace = async (name: string) => {
|
||||
const workspaceInfo = await dataCenter.createWorkspace({
|
||||
@@ -38,10 +40,10 @@ export const useWorkspaceHelper = () => {
|
||||
|
||||
const enableWorkspace = async () => {
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
title: `${t('Enable AFFiNE Cloud')}?`,
|
||||
content: t('Enable AFFiNE Cloud Description'),
|
||||
confirmText: user ? t('Enable') : t('Sign in and Enable'),
|
||||
cancelText: t('Skip'),
|
||||
confirmType: 'primary',
|
||||
buttonDirection: 'column',
|
||||
}).then(async confirm => {
|
||||
@@ -53,7 +55,7 @@ export const useWorkspaceHelper = () => {
|
||||
currentWorkspace
|
||||
);
|
||||
workspace && router.push(`/workspace/${workspace.id}/setting`);
|
||||
toast('Enabled success');
|
||||
toast(t('Enabled success'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
import { styled } from '@/styles';
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Page as PageStore, Workspace } from '@blocksuite/store';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
@@ -16,12 +22,16 @@ const Page: NextPageWithLayout = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [pageTitle, setPageTitle] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
if (data && data.blocksuiteWorkspace) {
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
if (
|
||||
router.query.pageId &&
|
||||
@@ -32,6 +42,7 @@ const Page: NextPageWithLayout = () => {
|
||||
const page = data.blocksuiteWorkspace.getPage(
|
||||
router.query.pageId as string
|
||||
);
|
||||
page && setPageTitle(page.meta.title);
|
||||
page && setPage(page);
|
||||
} else {
|
||||
router.push('/404');
|
||||
@@ -46,6 +57,30 @@ const Page: NextPageWithLayout = () => {
|
||||
<>
|
||||
{!loaded && <PageLoading />}
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}/${router.query.pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : 'Untitled'}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
|
||||
{workspace && page && (
|
||||
<DynamicBlocksuite
|
||||
page={page}
|
||||
@@ -69,9 +104,43 @@ export default Page;
|
||||
|
||||
export const PageContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: 'calc(100vh)',
|
||||
padding: '78px 72px',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const NavContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
padding: '0 12px',
|
||||
height: '60px',
|
||||
...displayFlex('start', 'center'),
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
return {
|
||||
flex: 1,
|
||||
...displayFlex('center', 'center'),
|
||||
paddingLeft: '12px',
|
||||
span: {
|
||||
padding: '0 12px',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
},
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
transition: 'all .15s',
|
||||
':visited': {
|
||||
color: theme.colors.popoverColor,
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
},
|
||||
};
|
||||
});
|
||||
export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: '24px',
|
||||
marginLeft: 'auto',
|
||||
padding: '0 24px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,16 +2,28 @@ import { PageList } from '@/components/page-list';
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PageContainer } from './[pageId]';
|
||||
import {
|
||||
PageContainer,
|
||||
NavContainer,
|
||||
StyledBreadcrumbs,
|
||||
SearchButton,
|
||||
} from './[pageId]';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
const All = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
@@ -20,6 +32,23 @@ const All = () => {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={false}
|
||||
|
||||
@@ -19,41 +19,37 @@ import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { PageListHeader } from '@/components/header';
|
||||
|
||||
type TabNames = 'General' | 'Sync' | 'Collaboration' | 'Publish' | 'Export';
|
||||
|
||||
const tabMap: {
|
||||
name: TabNames;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: 'General',
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Sync',
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Collaboration',
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Publish',
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: next version will finish this feature
|
||||
// {
|
||||
// name: 'Export',
|
||||
// panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
// },
|
||||
];
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const useTabMap = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace, isOwner } = useAppState();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
||||
const handleTabChange = (tab: TabNames) => {
|
||||
const { isOwner } = useAppState();
|
||||
const tabMap: {
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: t('General'),
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Sync'),
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Collaboration'),
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Publish'),
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: next version will finish this feature
|
||||
// {
|
||||
// name: t('Export'),
|
||||
// panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
// },
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState<string>(tabMap[0].name);
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
@@ -61,7 +57,7 @@ const WorkspaceSetting = () => {
|
||||
tab => tab.name === activeTab
|
||||
)?.panelRender;
|
||||
let tableArr: {
|
||||
name: TabNames;
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = tabMap;
|
||||
if (!isOwner) {
|
||||
@@ -72,6 +68,14 @@ const WorkspaceSetting = () => {
|
||||
},
|
||||
];
|
||||
}
|
||||
return { activeTabPanelRender, tableArr, handleTabChange, activeTab };
|
||||
};
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { activeTabPanelRender, tableArr, handleTabChange, activeTab } =
|
||||
useTabMap();
|
||||
return (
|
||||
<>
|
||||
<StyledSettingContainer>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const Breadcrumbs = styled(MuiBreadcrumbs)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
@@ -13,7 +13,11 @@ import { getApis, Workspace } from './apis/index.js';
|
||||
import type { Apis, WorkspaceDetail, Callback } from './apis';
|
||||
import { token } from './apis/token.js';
|
||||
import { WebsocketClient } from './channel';
|
||||
import { loadWorkspaceUnit, createWorkspaceUnit } from './utils.js';
|
||||
import {
|
||||
loadWorkspaceUnit,
|
||||
createWorkspaceUnit,
|
||||
migrateBlobDB,
|
||||
} from './utils.js';
|
||||
import { WorkspaceUnit } from '../../workspace-unit.js';
|
||||
import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js';
|
||||
import type { SyncMode } from '../../workspace-unit';
|
||||
@@ -390,6 +394,8 @@ export class AffineProvider extends BaseProvider {
|
||||
syncMode: 'core',
|
||||
});
|
||||
|
||||
await migrateBlobDB(workspaceUnit.id, id);
|
||||
|
||||
const blocksuiteWorkspace = createBlocksuiteWorkspace(id);
|
||||
assert(workspaceUnit.blocksuiteWorkspace);
|
||||
await applyUpdate(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createStore, keys, setMany, getMany } from 'idb-keyval';
|
||||
import * as idb from 'lib0/indexeddb.js';
|
||||
|
||||
type IDBInstance<T = ArrayBufferLike> = {
|
||||
keys: () => Promise<string[]>;
|
||||
deleteDB: () => Promise<void>;
|
||||
setMany: (entries: [string, T][]) => Promise<void>;
|
||||
getMany: (keys: string[]) => Promise<T[]>;
|
||||
};
|
||||
|
||||
export function getDatabase<T = ArrayBufferLike>(
|
||||
type: string,
|
||||
database: string
|
||||
): IDBInstance<T> {
|
||||
const name = `${database}_${type}`;
|
||||
const db = createStore(name, type);
|
||||
return {
|
||||
keys: () => keys(db),
|
||||
deleteDB: () => idb.deleteDB(name),
|
||||
setMany: entries => setMany(entries, db),
|
||||
getMany: keys => getMany(keys, db),
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { createBlocksuiteWorkspace } from '../../utils/index.js';
|
||||
import type { Apis } from './apis';
|
||||
import { setDefaultAvatar } from '../utils.js';
|
||||
import { applyUpdate } from '../../utils/index.js';
|
||||
import { getDatabase } from './idb-kv.js';
|
||||
|
||||
export const loadWorkspaceUnit = async (
|
||||
params: WorkspaceUnitCtorParams,
|
||||
@@ -54,3 +55,34 @@ export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
|
||||
|
||||
return workspaceUnit;
|
||||
};
|
||||
|
||||
interface PendingTask {
|
||||
id: string;
|
||||
blob: ArrayBufferLike;
|
||||
}
|
||||
|
||||
export const migrateBlobDB = async (
|
||||
oldWorkspaceId: string,
|
||||
newWorkspaceId: string
|
||||
) => {
|
||||
const oldDB = getDatabase('blob', oldWorkspaceId);
|
||||
const oldPendingDB = getDatabase<PendingTask>('pending', newWorkspaceId);
|
||||
|
||||
const newDB = getDatabase('blob', newWorkspaceId);
|
||||
const newPendingDB = getDatabase<PendingTask>('pending', newWorkspaceId);
|
||||
|
||||
const keys = await oldDB.keys();
|
||||
const values = await oldDB.getMany(keys);
|
||||
const entries = keys.map((key, index) => {
|
||||
return [key, values[index]] as [string, ArrayBufferLike];
|
||||
});
|
||||
await newDB.setMany(entries);
|
||||
|
||||
const pendingEntries = entries.map(([id, blob]) => {
|
||||
return [id, { id, blob }] as [string, PendingTask];
|
||||
});
|
||||
await newPendingDB.setMany(pendingEntries);
|
||||
|
||||
await oldDB.deleteDB();
|
||||
await oldPendingDB.deleteDB();
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"Remove from favourites": "Remove from favourites",
|
||||
"Removed from Favourites": "Removed from Favourites",
|
||||
"New Workspace": "New Workspace",
|
||||
"Workspace description": "Workspace is your virtual space to capture, create and plan as just one person or together as a team.",
|
||||
"Workspace description": "A workspace is your virtual space to capture, create and plan as just one person or together as a team.",
|
||||
"Create": "Create",
|
||||
"Select": "Select",
|
||||
"Text": "Text (coming soon)",
|
||||
@@ -108,10 +108,10 @@
|
||||
"emptyTrash": "Click Add to Trash and the page will appear here.",
|
||||
"still designed": "(This page is still being designed.)",
|
||||
"My Workspaces": "My Workspaces",
|
||||
"Create Or Import": "Create Or Import",
|
||||
"Create Or Import": "Create or Import",
|
||||
"Tips": "Tips: ",
|
||||
"login success": "Login success",
|
||||
"Sign out": "Sign out of AFFiNE Cloud",
|
||||
"Sign out": "Sign out",
|
||||
"Delete Workspace": "Delete Workspace",
|
||||
"Delete Workspace Description2": "Deleting (<1>{{workspace}}</1>) will delete both local and cloud data, this operation cannot be undone, please proceed with caution.",
|
||||
"Delete Workspace placeholder": "Please type “Delete” to confirm",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Publishing Description": "After publishing to the web, everyone can view the content of this workspace through the link.",
|
||||
"Stop publishing": "Stop publishing",
|
||||
"Publish to web": "Publish to web",
|
||||
"Download data to device": "Download {{CoreOrAll}} data to device",
|
||||
"Download data": "Download {{CoreOrAll}} data",
|
||||
"General": "General",
|
||||
"Sync": "Sync",
|
||||
"Collaboration": "Collaboration",
|
||||
@@ -143,11 +143,36 @@
|
||||
"Workspace Settings": "Workspace Settings",
|
||||
"Export Workspace": "Export Workspace <1>{{workspace}}</1> is coming soon",
|
||||
"Leave Workspace Description": "After you leave, you will no longer be able to access the contents of this workspace.",
|
||||
"Sign in": "Sign in to AFFiNE Cloud",
|
||||
"Sign in": "Sign in AFFiNE Cloud",
|
||||
"Sync Description": "{{workspaceName}} is a Local Workspace. All data is stored on the current device. You can enable AFFiNE Cloud for this workspace to keep data in sync with the cloud.",
|
||||
"Sync Description2": "<1>{{workspaceName}}</1> is a Cloud Workspace. All data will be synchronised and saved to AFFiNE Cloud.",
|
||||
"Delete Workspace Description": "Deleting (<1>{{workspace}}</1>) cannot be undone, please proceed with caution. All contents will be lost.",
|
||||
"core": "core",
|
||||
"all": "all",
|
||||
"A workspace is your virtual space to capture, create and plan as just one person or together as a team.": "A workspace is your virtual space to capture, create and plan as just one person or together as a team."
|
||||
"Back Home": "Back Home",
|
||||
"Set a Workspace name": "Set a Workspace name",
|
||||
"Retain local cached data": "Retain local cached data",
|
||||
"Wait for Sync": "Wait for Sync",
|
||||
"Force Sign Out": "Force Sign Out",
|
||||
"Local Workspace": "Local Workspace",
|
||||
"Cloud Workspace": "Cloud Workspace",
|
||||
"Joined Workspace": "Joined Workspace",
|
||||
"Available Offline": "Available Offline",
|
||||
"Published to Web": "Published to Web",
|
||||
"Workspace Avatar": "Workspace Avatar",
|
||||
"Owner": "Owner",
|
||||
"Member": "Member",
|
||||
"Delete Member?": "Delete Member?",
|
||||
"will delete member": "will delete member",
|
||||
"Member has been removed": "{{name}} has been removed",
|
||||
"Failed to publish workspace": "Failed to publish workspace",
|
||||
"Copied link to clipboard": "Copied link to clipboard",
|
||||
"Published Description": " The current workspace has been published to the web, everyone can view the contents of this workspace through the link.",
|
||||
"is a Local Workspace": "is a Local Workspace.",
|
||||
"is a Cloud Workspace": "is a Cloud Workspace.",
|
||||
"Local Workspace Description": "All data is stored on the current device. You can enable AFFiNE Cloud for this workspace to keep data in sync with the cloud.",
|
||||
"Cloud Workspace Description": "All data will be synchronized and saved to the AFFiNE account <1>{{email}}</1>",
|
||||
"Download data Description1": "It takes up more space on your device.",
|
||||
"Download data Description2": "It takes up little space on your device.",
|
||||
"Enabled success": "Enabled success"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user