Merge branch 'feat/datacenter' of github.com:toeverything/AFFiNE into feat/ui

This commit is contained in:
QiShaoXuan
2023-02-01 14:09:46 +08:00
25 changed files with 499 additions and 164 deletions

View File

@@ -15,7 +15,7 @@ export const NotfoundPage = () => {
router.push('/workspace'); router.push('/workspace');
}} }}
> >
Back Home {t('Back Home')}
</Button> </Button>
</p> </p>
</NotFoundTitle> </NotFoundTitle>

View File

@@ -53,13 +53,10 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
</Header> </Header>
<Content> <Content>
<ContentTitle>{t('New Workspace')}</ContentTitle> <ContentTitle>{t('New Workspace')}</ContentTitle>
<p> <p>{t('Workspace description')}</p>
Workspace is your virtual space to capture, create and plan as
just one person or together as a team.
</p>
<Input <Input
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={'Set a Workspace name'} placeholder={t('Set a Workspace name')}
onChange={value => { onChange={value => {
setWorkspaceName(value); setWorkspaceName(value);
}} }}

View File

@@ -2,6 +2,7 @@ import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { GoogleLoginButton } from './LoginOptionButton'; import { GoogleLoginButton } from './LoginOptionButton';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { useTranslation } from '@affine/i18n';
interface LoginModalProps { interface LoginModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -9,6 +10,7 @@ interface LoginModalProps {
export const LoginModal = ({ open, onClose }: LoginModalProps) => { export const LoginModal = ({ open, onClose }: LoginModalProps) => {
const { login } = useAppState(); const { login } = useAppState();
const { t } = useTranslation();
return ( return (
<Modal open={open} onClose={onClose} data-testid="login-modal"> <Modal open={open} onClose={onClose} data-testid="login-modal">
<ModalWrapper width={560} height={292}> <ModalWrapper width={560} height={292}>
@@ -20,8 +22,8 @@ export const LoginModal = ({ open, onClose }: LoginModalProps) => {
/> />
</Header> </Header>
<Content> <Content>
<ContentTitle>{'Sign in'}</ContentTitle> <ContentTitle>{t('Sign in')}</ContentTitle>
<SignDes>Set up an AFFINE account to sync data</SignDes> <SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
<span <span
onClick={async () => { onClick={async () => {
await login(); await login();

View File

@@ -3,6 +3,7 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { Check, UnCheck } from './icon'; import { Check, UnCheck } from './icon';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from '@affine/i18n';
interface LoginModalProps { interface LoginModalProps {
open: boolean; open: boolean;
onClose: (wait: boolean) => void; onClose: (wait: boolean) => void;
@@ -10,6 +11,7 @@ interface LoginModalProps {
export const LogoutModal = ({ open, onClose }: LoginModalProps) => { export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
const [localCache, setLocalCache] = useState(false); const [localCache, setLocalCache] = useState(false);
const { t } = useTranslation();
return ( return (
<Modal open={open} onClose={onClose} data-testid="logout-modal"> <Modal open={open} onClose={onClose} data-testid="logout-modal">
<ModalWrapper width={560} height={292}> <ModalWrapper width={560} height={292}>
@@ -21,8 +23,8 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
/> />
</Header> </Header>
<Content> <Content>
<ContentTitle>{'Sign out?'}</ContentTitle> <ContentTitle>{t('Sign out')}?</ContentTitle>
<SignDes>Set up an AFFINE account to sync data</SignDes> <SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
<StyleTips> <StyleTips>
{localCache ? ( {localCache ? (
<StyleCheck <StyleCheck
@@ -41,7 +43,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
<UnCheck></UnCheck> <UnCheck></UnCheck>
</StyleCheck> </StyleCheck>
)} )}
Retain local cached data {t('Retain local cached data')}
</StyleTips> </StyleTips>
<div> <div>
<Button <Button
@@ -51,7 +53,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
onClose(true); onClose(true);
}} }}
> >
Wait for Sync {t('Wait for Sync')}
</Button> </Button>
<Button <Button
type="danger" type="danger"
@@ -60,7 +62,7 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
onClose(false); onClose(false);
}} }}
> >
Force Sign Out {t('Force Sign Out')}
</Button> </Button>
</div> </div>
</Content> </Content>

View File

@@ -8,17 +8,17 @@ import React, {
import { SearchIcon } from '@blocksuite/icons'; import { SearchIcon } from '@blocksuite/icons';
import { StyledInputContent, StyledLabel } from './style'; import { StyledInputContent, StyledLabel } from './style';
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import { useAppState } from '@/providers/app-state-provider';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
export const Input = (props: { export const Input = (props: {
query: string; query: string;
setQuery: Dispatch<SetStateAction<string>>; setQuery: Dispatch<SetStateAction<string>>;
setLoading: Dispatch<SetStateAction<boolean>>; setLoading: Dispatch<SetStateAction<boolean>>;
isPublic: boolean;
publishWorkspaceName: string | undefined;
}) => { }) => {
const [isComposition, setIsComposition] = useState(false); const [isComposition, setIsComposition] = useState(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { currentWorkspace } = useAppState();
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
inputRef.current?.addEventListener( inputRef.current?.addEventListener(
@@ -78,9 +78,9 @@ export const Input = (props: {
} }
}} }}
placeholder={ placeholder={
currentWorkspace?.isPublish props.isPublic
? t('Quick search placeholder2', { ? t('Quick search placeholder2', {
workspace: currentWorkspace?.blocksuiteWorkspace?.meta.name, workspace: props.publishWorkspaceName,
}) })
: t('Quick search placeholder') : t('Quick search placeholder')
} }

View File

@@ -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>
)
) : (
<></>
)}
</>
);
};

View File

@@ -13,7 +13,8 @@ import { Command } from 'cmdk';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useModal } from '@/providers/GlobalModalProvider'; import { useModal } from '@/providers/GlobalModalProvider';
import { getUaHelper } from '@/utils'; import { getUaHelper } from '@/utils';
import { useAppState } from '@/providers/app-state-provider'; import { useRouter } from 'next/router';
import { PublishedResults } from './PublishedResults';
type TransitionsModalProps = { type TransitionsModalProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -22,10 +23,11 @@ const isMac = () => {
return getUaHelper().isMacOs; return getUaHelper().isMacOs;
}; };
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
const { currentWorkspace } = useAppState(); const router = useRouter();
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isPublic, setIsPublic] = useState(false);
const [publishWorkspaceName, setPublishWorkspaceName] = useState('');
const [showCreatePage, setShowCreatePage] = useState(true); const [showCreatePage, setShowCreatePage] = useState(true);
const { triggerQuickSearchModal } = useModal(); const { triggerQuickSearchModal } = useModal();
@@ -51,6 +53,14 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
document.removeEventListener('keydown', down, { capture: true }); document.removeEventListener('keydown', down, { capture: true });
}, [open, triggerQuickSearchModal]); }, [open, triggerQuickSearchModal]);
useEffect(() => {
if (router.pathname.startsWith('/public-workspace')) {
return setIsPublic(true);
} else {
return setIsPublic(false);
}
}, [router]);
return ( return (
<Modal <Modal
open={open} open={open}
@@ -81,28 +91,44 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
}} }}
> >
<StyledModalHeader> <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> <StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
</StyledModalHeader> </StyledModalHeader>
<StyledModalDivider /> <StyledModalDivider />
<Command.List> <Command.List>
<StyledContent> <StyledContent>
{!isPublic ? (
<Results <Results
query={query} query={query}
loading={loading} loading={loading}
setLoading={setLoading} setLoading={setLoading}
setShowCreatePage={setShowCreatePage} setShowCreatePage={setShowCreatePage}
/> />
) : (
<PublishedResults
query={query}
loading={loading}
setLoading={setLoading}
onClose={onClose}
setPublishWorkspaceName={setPublishWorkspaceName}
/>
)}
</StyledContent> </StyledContent>
{currentWorkspace?.published ? ( {!isPublic ? (
<></> showCreatePage ? (
) : showCreatePage ? (
<> <>
<StyledModalDivider /> <StyledModalDivider />
<StyledModalFooter> <StyledModalFooter>
<Footer query={query} /> <Footer query={query} />
</StyledModalFooter> </StyledModalFooter>
</> </>
) : null
) : null} ) : null}
</Command.List> </Command.List>
</Command> </Command>

View File

@@ -4,6 +4,7 @@ import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { IconButton } from '@/ui/button'; import { IconButton } from '@/ui/button';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { StyledFooter, StyleUserInfo, StyleSignIn } from './styles'; import { StyledFooter, StyleUserInfo, StyleSignIn } from './styles';
import { useTranslation } from '@affine/i18n';
export const Footer = ({ export const Footer = ({
onLogin, onLogin,
@@ -13,6 +14,7 @@ export const Footer = ({
onLogout: () => void; onLogout: () => void;
}) => { }) => {
const { user } = useAppState(); const { user } = useAppState();
const { t } = useTranslation();
return ( return (
<StyledFooter> <StyledFooter>
@@ -48,7 +50,7 @@ export const Footer = ({
<span> <span>
<CloudInsyncIcon fontSize={16} /> <CloudInsyncIcon fontSize={16} />
</span> </span>
Sign in to sync with AFFINE Cloud {t('Sign in')}
</StyleSignIn> </StyleSignIn>
)} )}
</StyledFooter> </StyledFooter>

View File

@@ -8,6 +8,7 @@ import { PublishIcon, UsersIcon } from '@blocksuite/icons';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles'; import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
import { useTranslation } from '@affine/i18n';
import { FlexWrapper } from '@/ui/layout'; import { FlexWrapper } from '@/ui/layout';
export const WorkspaceCard = ({ export const WorkspaceCard = ({
@@ -18,7 +19,7 @@ export const WorkspaceCard = ({
onClick: (data: WorkspaceUnit) => void; onClick: (data: WorkspaceUnit) => void;
}) => { }) => {
const { currentWorkspace, isOwner } = useAppState(); const { currentWorkspace, isOwner } = useAppState();
const { t } = useTranslation();
return ( return (
<StyledCard <StyledCard
onClick={() => { onClick={() => {
@@ -38,29 +39,30 @@ export const WorkspaceCard = ({
workspaceData.provider === 'local' ? ( workspaceData.provider === 'local' ? (
<p> <p>
<LocalIcon /> <LocalIcon />
Local Workspace {t('Local Workspace')}
</p> </p>
) : ( ) : (
<p> <p>
<CloudIcon /> <CloudIcon />
Cloud Workspace {t('Cloud Workspace')}
</p> </p>
) )
) : ( ) : (
<p> <p>
<UsersIcon fontSize={20} color={'#FF646B'} /> <UsersIcon fontSize={20} color={'#FF646B'} />
Joined Workspace {t('Joined Workspace')}
</p> </p>
)} )}
{workspaceData.provider === 'local' && ( {workspaceData.provider === 'local' && (
<p> <p>
<OfflineIcon /> <OfflineIcon />
All data can be accessed offline {t('Available Offline')}
</p> </p>
)} )}
{workspaceData.published && ( {workspaceData.published && (
<p> <p>
<PublishIcon fontSize={16} /> Published to Web <PublishIcon fontSize={16} />
{t('Published to Web')}
</p> </p>
)} )}
</StyleWorkspaceInfo> </StyleWorkspaceInfo>

View File

@@ -57,9 +57,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
<StyledModalHeaderLeft> <StyledModalHeaderLeft>
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle> <StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
<Tooltip <Tooltip
content={t( content={t('Workspace description')}
'A workspace is your virtual space to capture, create and plan as just one person or together as a team.'
)}
placement="top-start" placement="top-start"
disablePortal={true} disablePortal={true}
> >
@@ -106,8 +104,8 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
</FlexWrapper> </FlexWrapper>
<StyleWorkspaceInfo> <StyleWorkspaceInfo>
<StyleWorkspaceTitle>New workspace</StyleWorkspaceTitle> <StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
<p>Crete or import</p> <p>{t('Create Or Import')}</p>
</StyleWorkspaceInfo> </StyleWorkspaceInfo>
</StyledCard> </StyledCard>
</StyledModalContent> </StyledModalContent>

View File

@@ -1,5 +1,5 @@
import { import {
StyledCopyButtonContainer, StyledButtonContainer,
StyledPublishContent, StyledPublishContent,
StyledPublishCopyContainer, StyledPublishCopyContainer,
StyledPublishExplanation, StyledPublishExplanation,
@@ -24,13 +24,13 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
await publishWorkspace(workspace.id.toString(), flag); await publishWorkspace(workspace.id.toString(), flag);
setLoaded(false); setLoaded(false);
} catch (e) { } catch (e) {
toast('Failed to publish workspace'); toast(t('Failed to publish workspace'));
} }
}; };
const copyUrl = () => { const copyUrl = () => {
navigator.clipboard.writeText(shareUrl); navigator.clipboard.writeText(shareUrl);
toast('Copied url to clipboard'); toast(t('Copied link to clipboard'));
}; };
return ( return (
@@ -43,8 +43,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
{workspace.published ? ( {workspace.published ? (
<> <>
<StyledPublishExplanation> <StyledPublishExplanation>
The current workspace has been published to the web, everyone {t('Published Description')}
can view the contents of this workspace through the link.
</StyledPublishExplanation> </StyledPublishExplanation>
<StyledPublishCopyContainer> <StyledPublishCopyContainer>
@@ -52,11 +51,11 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
{t('Share with link')} {t('Share with link')}
</StyledSettingH2> </StyledSettingH2>
<Input width={500} value={shareUrl} disabled={true}></Input> <Input width={500} value={shareUrl} disabled={true}></Input>
<StyledCopyButtonContainer> <StyledButtonContainer>
<Button onClick={copyUrl} type="primary" shape="circle"> <Button onClick={copyUrl} type="primary" shape="circle">
{t('Copy Link')} {t('Copy Link')}
</Button> </Button>
</StyledCopyButtonContainer> </StyledButtonContainer>
</StyledPublishCopyContainer> </StyledPublishCopyContainer>
</> </>
) : ( ) : (
@@ -101,7 +100,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
<StyledPublishContent> <StyledPublishContent>
<> <>
<StyledPublishExplanation> <StyledPublishExplanation>
Publishing to web requires AFFiNE Cloud service. {t('Publishing')}
</StyledPublishExplanation> </StyledPublishExplanation>
<div style={{ marginTop: '72px' }}> <div style={{ marginTop: '72px' }}>

View File

@@ -1,15 +1,15 @@
import { import {
StyleAsync, StyledButtonContainer,
StyledPublishContent, StyledPublishContent,
StyledPublishExplanation, StyledPublishExplanation,
StyledWorkspaceName, StyledWorkspaceName,
StyledWorkspaceType, StyledEmail,
} from './style'; } from './style';
import { DownloadIcon } from '@blocksuite/icons'; import { DownloadIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { Menu, MenuItem } from '@/ui/menu'; import { Menu, MenuItem } from '@/ui/menu';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n'; import { useTranslation, Trans } from '@affine/i18n';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar'; import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { EnableWorkspaceButton } from '../enable-workspace'; import { EnableWorkspaceButton } from '../enable-workspace';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
@@ -28,16 +28,13 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
workspaceUnit={workspace} workspaceUnit={workspace}
style={{ marginRight: '12px' }} style={{ marginRight: '12px' }}
/> />
<StyledWorkspaceName>{workspace.name};</StyledWorkspaceName> <StyledWorkspaceName>{workspace.name}&nbsp;</StyledWorkspaceName>
<StyledWorkspaceType>is a Local Workspace.</StyledWorkspaceType> <span>{t('is a Local Workspace')}</span>
</StyledPublishExplanation> </StyledPublishExplanation>
<StyledWorkspaceType> <div>{t('Local Workspace Description')}</div>
All data is stored on the current device. You can enable AFFiNE <StyledButtonContainer>
Cloud for this workspace to keep data in sync with the cloud.
</StyledWorkspaceType>
<StyleAsync>
<EnableWorkspaceButton></EnableWorkspaceButton> <EnableWorkspaceButton></EnableWorkspaceButton>
</StyleAsync> </StyledButtonContainer>
</> </>
) : ( ) : (
<> <>
@@ -48,14 +45,21 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
workspaceUnit={workspace} workspaceUnit={workspace}
style={{ marginRight: '12px' }} style={{ marginRight: '12px' }}
/> />
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>is <StyledWorkspaceName>{workspace.name}&nbsp;</StyledWorkspaceName>
Cloud Workspace. <span>{t('is a Cloud Workspace')}</span>
</StyledPublishExplanation> </StyledPublishExplanation>
<StyledWorkspaceType> <div>
All data will be synchronized and saved to the AFFiNE account{' '} <Trans i18nKey="Cloud Workspace Description">
{user?.email} All data will be synchronised and saved to the AFFiNE account
</StyledWorkspaceType> <StyledEmail>
<StyleAsync> {{
email: '{' + user?.email + '}.',
}}
</StyledEmail>
</Trans>
</div>
<StyledButtonContainer>
<Menu <Menu
content={ content={
<> <>
@@ -65,7 +69,7 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
}} }}
icon={<DownloadIcon />} icon={<DownloadIcon />}
> >
{t('Download data to device', { CoreOrAll: 'core' })} {t('Download data', { CoreOrAll: t('core') })}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
@@ -73,7 +77,7 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
}} }}
icon={<DownloadIcon />} icon={<DownloadIcon />}
> >
{t('Download data to device', { CoreOrAll: 'all' })} {t('Download data', { CoreOrAll: t('all') })}
</MenuItem> </MenuItem>
</> </>
} }
@@ -81,10 +85,10 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
disablePortal={true} disablePortal={true}
> >
<Button type="primary"> <Button type="primary">
{t('Download data to device', { CoreOrAll: 'all' })} {t('Download data', { CoreOrAll: '' })}
</Button> </Button>
</Menu> </Menu>
</StyleAsync> </StyledButtonContainer>
</> </>
)} )}
</StyledPublishContent> </StyledPublishContent>

View File

@@ -50,7 +50,7 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
return workspace ? ( return workspace ? (
<StyleGeneral> <StyleGeneral>
<div style={{ flex: 1, overflow: 'auto' }}> <div style={{ flex: 1, overflow: 'auto' }}>
<StyledSettingH2>Workspace Avatar</StyledSettingH2> <StyledSettingH2>{t('Workspace Avatar')}</StyledSettingH2>
<StyledSettingAvatarContent> <StyledSettingAvatarContent>
<StyledAvatar> <StyledAvatar>
<Upload <Upload
@@ -106,18 +106,18 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
currentWorkspace?.provider === 'local' ? ( currentWorkspace?.provider === 'local' ? (
<StyledProviderInfo> <StyledProviderInfo>
<LocalIcon /> <LocalIcon />
Local Workspace {t('Local Workspace')}
</StyledProviderInfo> </StyledProviderInfo>
) : ( ) : (
<StyledProviderInfo> <StyledProviderInfo>
<CloudIcon /> <CloudIcon />
All data can be accessed offline {t('Available Offline')}
</StyledProviderInfo> </StyledProviderInfo>
) )
) : ( ) : (
<StyledProviderInfo> <StyledProviderInfo>
<UsersIcon fontSize={20} color={'#FF646B'} /> <UsersIcon fontSize={20} color={'#FF646B'} />
Joined Workspace {t('Joined Workspace')}
</StyledProviderInfo> </StyledProviderInfo>
)} )}
</StyledSettingInputContainer> </StyledSettingInputContainer>

View File

@@ -89,9 +89,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
<StyledMemberRoleContainer> <StyledMemberRoleContainer>
{member.accepted {member.accepted
? member.type !== 99 ? member.type !== 99
? 'Member' ? t('Member')
: 'Workspace Owner' : t('Owner')
: 'Pending'} : t('Pending')}
</StyledMemberRoleContainer> </StyledMemberRoleContainer>
<StyledMoreVerticalButton> <StyledMoreVerticalButton>
<Menu <Menu
@@ -100,9 +100,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
<MenuItem <MenuItem
onClick={async () => { onClick={async () => {
const confirmRemove = await confirm({ const confirmRemove = await confirm({
title: 'Delete Member?', title: t('Delete Member?'),
content: `will delete member`, content: t('will delete member'),
confirmText: 'Delete', confirmText: t('Delete'),
confirmType: 'danger', confirmType: 'danger',
}); });
@@ -110,11 +110,15 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
return; return;
} }
await removeMember(member.id); await removeMember(member.id);
toast(`${user.name} has been removed`); toast(
t('Member has been removed', {
name: user.name,
})
);
}} }}
icon={<TrashIcon />} icon={<TrashIcon />}
> >
Delete {t('Delete')}
</MenuItem> </MenuItem>
</> </>
} }

View File

@@ -90,7 +90,7 @@ export const StyledSettingH2 = styled('h2')<{
marginBottom?: number; marginBottom?: number;
}>(({ marginTop, marginBottom, theme }) => { }>(({ marginTop, marginBottom, theme }) => {
return { return {
fontWeight: '500', // fontWeight: '500',
fontSize: theme.font.base, fontSize: theme.font.base,
lineHeight: theme.font.lineHeightBase, lineHeight: theme.font.lineHeightBase,
marginTop: marginTop ? `${marginTop}px` : '0px', marginTop: marginTop ? `${marginTop}px` : '0px',
@@ -101,24 +101,18 @@ export const StyledSettingH2 = styled('h2')<{
export const StyledPublishExplanation = styled('div')(() => { export const StyledPublishExplanation = styled('div')(() => {
return { return {
paddingRight: '48px', paddingRight: '48px',
fontWeight: '500', // fontWeight: '500',
fontSize: '18px',
lineHeight: '26px',
marginBottom: '22px', marginBottom: '22px',
}; };
}); });
export const StyledWorkspaceName = styled('span')(() => { export const StyledWorkspaceName = styled('span')(() => {
return { return {
fontWeight: '400', fontWeight: '400',
fontSize: '18px',
lineHeight: '26px',
}; };
}); });
export const StyledWorkspaceType = styled('span')(() => { export const StyledWorkspaceType = styled('span')(() => {
return { return {
fontWeight: '500', // fontWeight: '500',
fontSize: '18px',
lineHeight: '26px',
}; };
}); });
@@ -136,22 +130,24 @@ export const StyledStopPublishContainer = styled('div')(() => {
}; };
}); });
export const StyledCopyButtonContainer = styled('div')(() => { export const StyledButtonContainer = styled('div')(() => {
return { return {
marginTop: '64px', marginTop: '64px',
}; };
}); });
export const StyledEmail = styled('span')(() => {
return {
color: '#E8178A',
};
});
export const StyledPublishContent = styled('div')(() => { export const StyledPublishContent = styled('div')(({ theme }) => {
return { return {
display: 'flex', display: 'flex',
fontWeight: '500',
flexDirection: 'column', flexDirection: 'column',
fontSize: theme.font.base,
lineHeight: theme.font.lineHeightBase,
flex: 1, flex: 1,
}; };
}); });
export const StyleAsync = styled('div')(() => {
return {
marginTop: '64px',
};
});

View File

@@ -1,4 +1,4 @@
import { uuidv4 } from '@blocksuite/store'; import { uuidv4, Workspace } from '@blocksuite/store';
import { QueryContent } from '@blocksuite/store/dist/workspace/search'; import { QueryContent } from '@blocksuite/store/dist/workspace/search';
import { PageMeta, useAppState } from '@/providers/app-state-provider'; import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { EditorContainer } from '@blocksuite/editor'; import { EditorContainer } from '@blocksuite/editor';
@@ -20,7 +20,10 @@ export type EditorHandlers = {
toggleDeletePage: (pageId: string) => Promise<boolean>; toggleDeletePage: (pageId: string) => Promise<boolean>;
toggleFavoritePage: (pageId: string) => Promise<boolean>; toggleFavoritePage: (pageId: string) => Promise<boolean>;
permanentlyDeletePage: (pageId: string) => void; permanentlyDeletePage: (pageId: string) => void;
search: (query: QueryContent) => Map<string, string | undefined>; search: (
query: QueryContent,
workspace?: Workspace
) => Map<string, string | undefined>;
// changeEditorMode: (pageId: string) => void; // changeEditorMode: (pageId: string) => void;
changePageMode: ( changePageMode: (
pageId: string, pageId: string,
@@ -83,9 +86,16 @@ export const usePageHelper = (): EditorHandlers => {
}); });
return trash; return trash;
}, },
search: (query: QueryContent) => { search: (query: QueryContent, workspace?: Workspace) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (workspace) {
return currentWorkspace!.blocksuiteWorkspace!.search(query); return workspace.search(query);
}
if (currentWorkspace) {
if (currentWorkspace.blocksuiteWorkspace) {
return currentWorkspace.blocksuiteWorkspace.search(query);
}
}
return new Map();
}, },
changePageMode: async (pageId, mode) => { changePageMode: async (pageId, mode) => {
const pageMeta = getPageMeta(currentWorkspace, pageId); const pageMeta = getPageMeta(currentWorkspace, pageId);

View File

@@ -3,9 +3,11 @@ import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast'; import { toast } from '@/ui/toast';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import router from 'next/router'; import router from 'next/router';
import { useTranslation } from '@affine/i18n';
export const useWorkspaceHelper = () => { export const useWorkspaceHelper = () => {
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const { t } = useTranslation();
const { dataCenter, currentWorkspace, user, login } = useAppState(); const { dataCenter, currentWorkspace, user, login } = useAppState();
const createWorkspace = async (name: string) => { const createWorkspace = async (name: string) => {
const workspaceInfo = await dataCenter.createWorkspace({ const workspaceInfo = await dataCenter.createWorkspace({
@@ -38,10 +40,10 @@ export const useWorkspaceHelper = () => {
const enableWorkspace = async () => { const enableWorkspace = async () => {
confirm({ confirm({
title: 'Enable AFFiNE Cloud?', title: `${t('Enable AFFiNE Cloud')}?`,
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`, content: t('Enable AFFiNE Cloud Description'),
confirmText: user ? 'Enable' : 'Sign in and Enable', confirmText: user ? t('Enable') : t('Sign in and Enable'),
cancelText: 'Skip', cancelText: t('Skip'),
confirmType: 'primary', confirmType: 'primary',
buttonDirection: 'column', buttonDirection: 'column',
}).then(async confirm => { }).then(async confirm => {
@@ -53,7 +55,7 @@ export const useWorkspaceHelper = () => {
currentWorkspace currentWorkspace
); );
workspace && router.push(`/workspace/${workspace.id}/setting`); workspace && router.push(`/workspace/${workspace.id}/setting`);
toast('Enabled success'); toast(t('Enabled success'));
} }
}); });
}; };

View File

@@ -1,11 +1,17 @@
import { ReactElement, useEffect, useState } from 'react'; import { ReactElement, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app'; import type { NextPageWithLayout } from '../..//_app';
import { styled } from '@/styles'; import { displayFlex, styled } from '@/styles';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Page as PageStore, Workspace } from '@blocksuite/store'; import { Page as PageStore, Workspace } from '@blocksuite/store';
import { PageLoading } from '@/components/loading'; 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'), { const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false, ssr: false,
@@ -16,12 +22,16 @@ const Page: NextPageWithLayout = () => {
const { dataCenter } = useAppState(); const { dataCenter } = useAppState();
const router = useRouter(); const router = useRouter();
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
const [workspaceName, setWorkspaceName] = useState('');
const [pageTitle, setPageTitle] = useState('');
const { triggerQuickSearchModal } = useModal();
useEffect(() => { useEffect(() => {
dataCenter dataCenter
.loadPublicWorkspace(router.query.workspaceId as string) .loadPublicWorkspace(router.query.workspaceId as string)
.then(data => { .then(data => {
if (data && data.blocksuiteWorkspace) { setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
if (data.blocksuiteWorkspace) {
setWorkspace(data.blocksuiteWorkspace); setWorkspace(data.blocksuiteWorkspace);
if ( if (
router.query.pageId && router.query.pageId &&
@@ -32,6 +42,7 @@ const Page: NextPageWithLayout = () => {
const page = data.blocksuiteWorkspace.getPage( const page = data.blocksuiteWorkspace.getPage(
router.query.pageId as string router.query.pageId as string
); );
page && setPageTitle(page.meta.title);
page && setPage(page); page && setPage(page);
} else { } else {
router.push('/404'); router.push('/404');
@@ -46,6 +57,30 @@ const Page: NextPageWithLayout = () => {
<> <>
{!loaded && <PageLoading />} {!loaded && <PageLoading />}
<PageContainer> <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 && ( {workspace && page && (
<DynamicBlocksuite <DynamicBlocksuite
page={page} page={page}
@@ -69,9 +104,43 @@ export default Page;
export const PageContainer = styled.div(({ theme }) => { export const PageContainer = styled.div(({ theme }) => {
return { return {
height: 'calc(100vh)', height: '100vh',
padding: '78px 72px',
overflowY: 'auto', overflowY: 'auto',
backgroundColor: theme.colors.pageBackground, 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',
};
});

View File

@@ -2,16 +2,28 @@ import { PageList } from '@/components/page-list';
import { ReactElement, useEffect, useState } from 'react'; import { ReactElement, useEffect, useState } from 'react';
import { PageMeta, useAppState } from '@/providers/app-state-provider'; import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router'; 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 All = () => {
const { dataCenter } = useAppState(); const { dataCenter } = useAppState();
const router = useRouter(); const router = useRouter();
const [pageList, setPageList] = useState<PageMeta[]>([]); const [pageList, setPageList] = useState<PageMeta[]>([]);
const [workspaceName, setWorkspaceName] = useState('');
const { triggerQuickSearchModal } = useModal();
useEffect(() => { useEffect(() => {
dataCenter dataCenter
.loadPublicWorkspace(router.query.workspaceId as string) .loadPublicWorkspace(router.query.workspaceId as string)
.then(data => { .then(data => {
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]); setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
}) })
.catch(() => { .catch(() => {
router.push('/404'); router.push('/404');
@@ -20,6 +32,23 @@ const All = () => {
return ( return (
<PageContainer> <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={pageList.filter(p => !p.trash)} pageList={pageList.filter(p => !p.trash)}
showFavoriteTag={false} showFavoriteTag={false}

View File

@@ -19,41 +19,37 @@ import { WorkspaceUnit } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { PageListHeader } from '@/components/header'; import { PageListHeader } from '@/components/header';
type TabNames = 'General' | 'Sync' | 'Collaboration' | 'Publish' | 'Export'; const useTabMap = () => {
const { t } = useTranslation();
const { isOwner } = useAppState();
const tabMap: { const tabMap: {
name: TabNames; name: string;
panelRender: (workspace: WorkspaceUnit) => ReactNode; panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = [ }[] = [
{ {
name: 'General', name: t('General'),
panelRender: workspace => <GeneralPage workspace={workspace} />, panelRender: workspace => <GeneralPage workspace={workspace} />,
}, },
{ {
name: 'Sync', name: t('Sync'),
panelRender: workspace => <SyncPage workspace={workspace} />, panelRender: workspace => <SyncPage workspace={workspace} />,
}, },
{ {
name: 'Collaboration', name: t('Collaboration'),
panelRender: workspace => <MembersPage workspace={workspace} />, panelRender: workspace => <MembersPage workspace={workspace} />,
}, },
{ {
name: 'Publish', name: t('Publish'),
panelRender: workspace => <PublishPage workspace={workspace} />, panelRender: workspace => <PublishPage workspace={workspace} />,
}, },
// TODO: next version will finish this feature // TODO: next version will finish this feature
// { // {
// name: 'Export', // name: t('Export'),
// panelRender: workspace => <ExportPage workspace={workspace} />, // panelRender: workspace => <ExportPage workspace={workspace} />,
// }, // },
]; ];
const [activeTab, setActiveTab] = useState<string>(tabMap[0].name);
const WorkspaceSetting = () => { const handleTabChange = (tab: string) => {
const { t } = useTranslation();
const { currentWorkspace, isOwner } = useAppState();
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
const handleTabChange = (tab: TabNames) => {
setActiveTab(tab); setActiveTab(tab);
}; };
@@ -61,7 +57,7 @@ const WorkspaceSetting = () => {
tab => tab.name === activeTab tab => tab.name === activeTab
)?.panelRender; )?.panelRender;
let tableArr: { let tableArr: {
name: TabNames; name: string;
panelRender: (workspace: WorkspaceUnit) => ReactNode; panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = tabMap; }[] = tabMap;
if (!isOwner) { 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 ( return (
<> <>
<StyledSettingContainer> <StyledSettingContainer>

View File

@@ -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,
};
});

View File

@@ -13,7 +13,11 @@ import { getApis, Workspace } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis'; import type { Apis, WorkspaceDetail, Callback } from './apis';
import { token } from './apis/token.js'; import { token } from './apis/token.js';
import { WebsocketClient } from './channel'; import { WebsocketClient } from './channel';
import { loadWorkspaceUnit, createWorkspaceUnit } from './utils.js'; import {
loadWorkspaceUnit,
createWorkspaceUnit,
migrateBlobDB,
} from './utils.js';
import { WorkspaceUnit } from '../../workspace-unit.js'; import { WorkspaceUnit } from '../../workspace-unit.js';
import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js'; import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js';
import type { SyncMode } from '../../workspace-unit'; import type { SyncMode } from '../../workspace-unit';
@@ -390,6 +394,8 @@ export class AffineProvider extends BaseProvider {
syncMode: 'core', syncMode: 'core',
}); });
await migrateBlobDB(workspaceUnit.id, id);
const blocksuiteWorkspace = createBlocksuiteWorkspace(id); const blocksuiteWorkspace = createBlocksuiteWorkspace(id);
assert(workspaceUnit.blocksuiteWorkspace); assert(workspaceUnit.blocksuiteWorkspace);
await applyUpdate( await applyUpdate(

View File

@@ -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),
};
}

View File

@@ -4,6 +4,7 @@ import { createBlocksuiteWorkspace } from '../../utils/index.js';
import type { Apis } from './apis'; import type { Apis } from './apis';
import { setDefaultAvatar } from '../utils.js'; import { setDefaultAvatar } from '../utils.js';
import { applyUpdate } from '../../utils/index.js'; import { applyUpdate } from '../../utils/index.js';
import { getDatabase } from './idb-kv.js';
export const loadWorkspaceUnit = async ( export const loadWorkspaceUnit = async (
params: WorkspaceUnitCtorParams, params: WorkspaceUnitCtorParams,
@@ -54,3 +55,34 @@ export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
return workspaceUnit; 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();
};

View File

@@ -69,7 +69,7 @@
"Remove from favourites": "Remove from favourites", "Remove from favourites": "Remove from favourites",
"Removed from Favourites": "Removed from Favourites", "Removed from Favourites": "Removed from Favourites",
"New Workspace": "New Workspace", "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", "Create": "Create",
"Select": "Select", "Select": "Select",
"Text": "Text (coming soon)", "Text": "Text (coming soon)",
@@ -108,10 +108,10 @@
"emptyTrash": "Click Add to Trash and the page will appear here.", "emptyTrash": "Click Add to Trash and the page will appear here.",
"still designed": "(This page is still being designed.)", "still designed": "(This page is still being designed.)",
"My Workspaces": "My Workspaces", "My Workspaces": "My Workspaces",
"Create Or Import": "Create Or Import", "Create Or Import": "Create or Import",
"Tips": "Tips: ", "Tips": "Tips: ",
"login success": "Login success", "login success": "Login success",
"Sign out": "Sign out of AFFiNE Cloud", "Sign out": "Sign out",
"Delete Workspace": "Delete Workspace", "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 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", "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.", "Publishing Description": "After publishing to the web, everyone can view the content of this workspace through the link.",
"Stop publishing": "Stop publishing", "Stop publishing": "Stop publishing",
"Publish to web": "Publish to web", "Publish to web": "Publish to web",
"Download data to device": "Download {{CoreOrAll}} data to device", "Download data": "Download {{CoreOrAll}} data",
"General": "General", "General": "General",
"Sync": "Sync", "Sync": "Sync",
"Collaboration": "Collaboration", "Collaboration": "Collaboration",
@@ -143,11 +143,36 @@
"Workspace Settings": "Workspace Settings", "Workspace Settings": "Workspace Settings",
"Export Workspace": "Export Workspace <1>{{workspace}}</1> is coming soon", "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.", "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 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.", "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.", "Delete Workspace Description": "Deleting (<1>{{workspace}}</1>) cannot be undone, please proceed with caution. All contents will be lost.",
"core": "core", "core": "core",
"all": "all", "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"
} }