diff --git a/package.json b/package.json index bcc15308c9..fb1cff44e3 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "jest": "^29.3.1", "lint-staged": "^13.1.0", "prettier": "^2.7.1", - "ts-jest": "^29.0.3", "typescript": "^4.9.3" }, "eslintConfig": { diff --git a/packages/app/src/components/create-workspace/index.tsx b/packages/app/src/components/create-workspace/index.tsx index 1a0910a74c..2f33317c13 100644 --- a/packages/app/src/components/create-workspace/index.tsx +++ b/packages/app/src/components/create-workspace/index.tsx @@ -1,26 +1,29 @@ import { styled } from '@/styles'; +import { useRouter } from 'next/router'; import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; import { Button } from '@/ui/button'; import { useState } from 'react'; import Input from '@/ui/input'; -import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { KeyboardEvent } from 'react'; import { useTranslation } from '@affine/i18n'; -interface ICloseParams { - workspaceId?: string; -} +import { useWorkspaceHelper } from '@/hooks/use-workspace-helper'; interface ModalProps { open: boolean; - onClose: (opts: ICloseParams) => void; + onClose: () => void; } export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => { const [workspaceName, setWorkspaceName] = useState(''); - const { createWorkspace, setActiveWorkspace } = useTemporaryHelper(); - const handleCreateWorkspace = () => { - const workspace = createWorkspace(workspaceName); - onClose({ workspaceId: workspace.id }); - setActiveWorkspace(workspace); + const { createWorkspace } = useWorkspaceHelper(); + const router = useRouter(); + const handleCreateWorkspace = async () => { + const workspace = await createWorkspace(workspaceName); + if (workspace && workspace.room) { + router.replace(`/workspace/${workspace.room}`); + onClose(); + } else { + console.log('create error'); + } }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter') { @@ -39,7 +42,7 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => { top={6} right={6} onClick={() => { - onClose({}); + onClose(); }} /> diff --git a/packages/app/src/components/editor/index.tsx b/packages/app/src/components/editor/index.tsx new file mode 100644 index 0000000000..b0edaa684f --- /dev/null +++ b/packages/app/src/components/editor/index.tsx @@ -0,0 +1,71 @@ +import { useEffect, useRef } from 'react'; +import type { Page, Workspace } from '@blocksuite/store'; +import '@blocksuite/blocks'; +import { EditorContainer } from '@blocksuite/editor'; +import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md'; +import { styled } from '@/styles'; + +const StyledEditorContainer = styled('div')(() => { + return { + height: 'calc(100vh - 60px)', + padding: '0 32px', + }; +}); + +type Props = { + page: Page; + workspace: Workspace; + setEditor: (editor: EditorContainer) => void; +}; + +export const Editor = ({ page, workspace, setEditor }: Props) => { + const editorContainer = useRef(null); + // const { currentWorkspace, currentPage, setEditor } = useAppState(); + useEffect(() => { + const ret = () => { + const node = editorContainer.current; + while (node?.firstChild) { + node.removeChild(node.firstChild); + } + }; + + const editor = new EditorContainer(); + editor.page = page; + + editorContainer.current?.appendChild(editor); + if (page.isEmpty) { + const isFirstPage = workspace?.meta.pageMetas.length === 1; + // Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title + const { title: metaTitle } = page.meta; + const title = metaTitle + ? metaTitle + : isFirstPage + ? 'Welcome to AFFiNE Alpha "Abbey Wood"' + : ''; + workspace?.setPageMeta(page.id, { title }); + + const pageId = page.addBlock({ + flavour: 'affine:page', + title, + }); + page.addBlock({ flavour: 'affine:surface' }); + const frameId = page.addBlock({ flavour: 'affine:frame' }, pageId); + page.addBlock({ flavour: 'affine:frame' }, pageId); + // If this is a first page in workspace, init an introduction markdown + if (isFirstPage) { + editor.clipboard.importMarkdown(exampleMarkdown, `${frameId}`); + workspace.setPageMeta(page.id, { title }); + page.resetHistory(); + } + page.resetHistory(); + } + + setEditor(editor); + document.title = page?.meta.title || 'Untitled'; + return ret; + }, [workspace, page, setEditor]); + + return ; +}; + +export default Editor; diff --git a/packages/app/src/components/header/EditorHeader.tsx b/packages/app/src/components/header/EditorHeader.tsx index b343179496..76bdb9c88b 100644 --- a/packages/app/src/components/header/EditorHeader.tsx +++ b/packages/app/src/components/header/EditorHeader.tsx @@ -6,7 +6,7 @@ import { StyledTitleWrapper, } from './styles'; import { Content } from '@/ui/layout'; -import { useAppState } from '@/providers/app-state-provider/context'; +import { useAppState } from '@/providers/app-state-provider'; import EditorModeSwitch from '@/components/editor-mode-switch'; import QuickSearchButton from './QuickSearchButton'; import Header from './Header'; diff --git a/packages/app/src/components/header/header-right-items/SyncUser.tsx b/packages/app/src/components/header/header-right-items/SyncUser.tsx index 86c85926bf..60af1b461a 100644 --- a/packages/app/src/components/header/header-right-items/SyncUser.tsx +++ b/packages/app/src/components/header/header-right-items/SyncUser.tsx @@ -1,6 +1,6 @@ import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons'; import { useModal } from '@/providers/GlobalModalProvider'; -import { useAppState } from '@/providers/app-state-provider/context'; +import { useAppState } from '@/providers/app-state-provider'; import { IconButton } from '@/ui/button'; export const SyncUser = () => { diff --git a/packages/app/src/components/import/index.tsx b/packages/app/src/components/import/index.tsx index 8f867d3bfa..df621a9c9e 100644 --- a/packages/app/src/components/import/index.tsx +++ b/packages/app/src/components/import/index.tsx @@ -4,7 +4,7 @@ import { Button } from '@/ui/button'; import { Wrapper, Content } from '@/ui/layout'; import Loading from '@/components/loading'; import { usePageHelper } from '@/hooks/use-page-helper'; -import { useAppState } from '@/providers/app-state-provider/context'; +import { useAppState } from '@/providers/app-state-provider'; import { useEffect, useState } from 'react'; import { useTranslation } from '@affine/i18n'; // import { Tooltip } from '@/ui/tooltip'; diff --git a/packages/app/src/components/login-modal/LoginOptionButton.tsx b/packages/app/src/components/login-modal/LoginOptionButton.tsx index 2199b21dcc..c1384bc8eb 100644 --- a/packages/app/src/components/login-modal/LoginOptionButton.tsx +++ b/packages/app/src/components/login-modal/LoginOptionButton.tsx @@ -1,11 +1,11 @@ -import { getDataCenter } from '@affine/datacenter'; +// import { getDataCenter } from '@affine/datacenter'; import { styled } from '@/styles'; import { Button } from '@/ui/button'; -import { useModal } from '@/providers/GlobalModalProvider'; +// import { useModal } from '@/providers/GlobalModalProvider'; import { GoogleIcon, StayLogOutIcon } from './Icons'; export const GoogleLoginButton = () => { - const { triggerLoginModal } = useModal(); + // const { triggerLoginModal } = useModal(); return ( { diff --git a/packages/app/src/components/page-list/index.tsx b/packages/app/src/components/page-list/index.tsx index dca0e15cac..2501f13bac 100644 --- a/packages/app/src/components/page-list/index.tsx +++ b/packages/app/src/components/page-list/index.tsx @@ -20,7 +20,7 @@ import DateCell from '@/components/page-list/DateCell'; import { IconButton } from '@/ui/button'; import { Tooltip } from '@/ui/tooltip'; import { useRouter } from 'next/router'; -import { useAppState } from '@/providers/app-state-provider/context'; +import { useAppState } from '@/providers/app-state-provider'; import { toast } from '@/ui/toast'; import { usePageHelper } from '@/hooks/use-page-helper'; import { useTheme } from '@/providers/ThemeProvider'; diff --git a/packages/app/src/components/quick-search/Input.tsx b/packages/app/src/components/quick-search/Input.tsx index a3897675fb..69d47a16a1 100644 --- a/packages/app/src/components/quick-search/Input.tsx +++ b/packages/app/src/components/quick-search/Input.tsx @@ -17,11 +17,10 @@ export const Input = (props: { const [isComposition, setIsComposition] = useState(false); const [inputValue, setInputValue] = useState(''); const inputRef = useRef(null); - const { currentWorkspaceId, workspacesMeta, currentWorkspace } = - useAppState(); - const isPublic = workspacesMeta.find( + const { currentWorkspaceId, workspaceList, currentWorkspace } = useAppState(); + const isPublish = workspaceList.find( meta => String(meta.id) === String(currentWorkspaceId) - )?.public; + )?.isPublish; useEffect(() => { inputRef.current?.addEventListener( 'blur', @@ -80,7 +79,7 @@ export const Input = (props: { } }} placeholder={ - isPublic + isPublish ? `Search in ${currentWorkspace?.meta.name}` : 'Quick Search...' } diff --git a/packages/app/src/components/quick-search/index.tsx b/packages/app/src/components/quick-search/index.tsx index b08f69196e..8199493a47 100644 --- a/packages/app/src/components/quick-search/index.tsx +++ b/packages/app/src/components/quick-search/index.tsx @@ -22,16 +22,14 @@ const isMac = () => { return getUaHelper().isMacOs; }; export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { + const { currentMetaWorkSpace } = useAppState(); + const [query, setQuery] = useState(''); const [loading, setLoading] = useState(true); const [showCreatePage, setShowCreatePage] = useState(true); const { triggerQuickSearchModal } = useModal(); - const { currentWorkspaceId, workspacesMeta } = useAppState(); - const currentWorkspace = workspacesMeta.find( - meta => String(meta.id) === String(currentWorkspaceId) - ); - const isPublic = currentWorkspace?.public; + const isPublish = currentMetaWorkSpace?.isPublish; // Add ‘⌘+K’ shortcut keys as switches useEffect(() => { @@ -98,7 +96,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { setShowCreatePage={setShowCreatePage} /> - {isPublic ? ( + {isPublish ? ( <> ) : showCreatePage ? ( <> diff --git a/packages/app/src/components/workspace-avatar/index.tsx b/packages/app/src/components/workspace-avatar/index.tsx index b810a0e720..8300f1563c 100644 --- a/packages/app/src/components/workspace-avatar/index.tsx +++ b/packages/app/src/components/workspace-avatar/index.tsx @@ -1,30 +1,49 @@ +import { useAppState } from '@/providers/app-state-provider'; import { stringToColour } from '@/utils'; +import { useEffect, useState } from 'react'; interface IWorkspaceAvatar { size: number; name: string; + avatar: string; } export const WorkspaceAvatar = (props: IWorkspaceAvatar) => { const size = props.size || 20; const sizeStr = size + 'px'; + return ( <> -
- {(props.name || 'AFFiNE').substring(0, 1)} -
+ {props.avatar ? ( +
+ +
+ ) : ( +
+ {(props.name || 'AFFiNE').substring(0, 1)} +
+ )} ); }; diff --git a/packages/app/src/components/workspace-modal/index.tsx b/packages/app/src/components/workspace-modal/index.tsx index 69cd96c8bc..3621d22e47 100644 --- a/packages/app/src/components/workspace-modal/index.tsx +++ b/packages/app/src/components/workspace-modal/index.tsx @@ -10,27 +10,24 @@ import { UsersIcon, AddIcon, } from '@blocksuite/icons'; -import { useConfirm } from '@/providers/ConfirmProvider'; +// import { useConfirm } from '@/providers/ConfirmProvider'; import { toast } from '@/ui/toast'; import { WorkspaceAvatar } from '@/components/workspace-avatar'; -import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; +import { useAppState } from '@/providers/app-state-provider'; +import { useRouter } from 'next/router'; +import { useUserHelper } from '@/hooks/use-user-helper'; -interface LoginModalProps { +interface WorkspaceModalProps { open: boolean; onClose: () => void; } -export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => { +export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => { const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false); - const { confirm } = useConfirm(); - const { - user, - login, - workspaceMetaList, - setActiveWorkspace, - updateWorkspaceMeta, - } = useTemporaryHelper(); - + // const { confirm } = useConfirm(); + const { workspaceList, currentWorkspace } = useAppState(); + const { login, user } = useUserHelper(); + const router = useRouter(); return (
@@ -51,13 +48,14 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => { - {workspaceMetaList.map((item, index) => { + {workspaceList.map((item, index) => { return ( { - setActiveWorkspace(item); + router.replace(`/workspace/${item.id}`); onClose(); }} + active={item.id === currentWorkspace.room} key={index} > @@ -69,7 +67,11 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => { marginRight: '10px', }} > - +
{ top: '20px', }} > - {(item.type === 'local' || !item.type) && ( + {(item.provider === 'local' || !item.provider) && ( )} - {item.type === 'cloud' && ( + {item.provider === 'affine' && ( )} {item.isPublish && } @@ -154,26 +156,26 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => { { + onClose={() => { setCreateWorkspaceOpen(false); onClose(); - 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', - }).then(confirm => { - if (confirm) { - if (user) { - workspaceId && - updateWorkspaceMeta(workspaceId, { isPublish: true }); - } else { - login(); - workspaceId && - updateWorkspaceMeta(workspaceId, { isPublish: true }); - } - } - }); + // 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', + // }).then(confirm => { + // if (confirm) { + // if (user) { + // // workspaceId && + // // updateWorkspaceMeta(workspaceId, { isPublish: true }); + // } else { + // // login(); + // // workspaceId && + // // updateWorkspaceMeta(workspaceId, { isPublish: true }); + // } + // } + // }); }} > @@ -218,11 +220,17 @@ const WorkspaceList = styled('div')({ gridTemplateColumns: 'repeat(2, 1fr)', }); -const WorkspaceItem = styled('div')({ - cursor: 'pointer', - padding: '8px', - border: '1px solid #eee', - ':hover': { - background: '#eee', - }, +export const WorkspaceItem = styled.div<{ + active: boolean; +}>(({ theme, active }) => { + const backgroundColor = active ? theme.colors.hoverBackground : 'transparent'; + return { + cursor: 'pointer', + padding: '8px', + border: '1px solid #eee', + backgroundColor: backgroundColor, + ':hover': { + background: theme.colors.hoverBackground, + }, + }; }); diff --git a/packages/app/src/components/workspace-setting/ExportPage.tsx b/packages/app/src/components/workspace-setting/ExportPage.tsx index 531e889938..3742b71a3c 100644 --- a/packages/app/src/components/workspace-setting/ExportPage.tsx +++ b/packages/app/src/components/workspace-setting/ExportPage.tsx @@ -1,5 +1,5 @@ -import { Workspace } from '@/hooks/mock-data/mock'; import { styled } from '@/styles'; +import { WorkspaceInfo } from '@affine/datacenter'; export const ExportPageTitleContainer = styled('div')(() => { return { @@ -9,7 +9,7 @@ export const ExportPageTitleContainer = styled('div')(() => { flex: 1, }; }); -export const ExportPage = ({ workspace }: { workspace: Workspace }) => { +export const ExportPage = ({ workspace }: { workspace: WorkspaceInfo }) => { return ( Export Workspace{' '} diff --git a/packages/app/src/components/workspace-setting/MembersPage.tsx b/packages/app/src/components/workspace-setting/MembersPage.tsx index 0d45f4afae..32639f874f 100644 --- a/packages/app/src/components/workspace-setting/MembersPage.tsx +++ b/packages/app/src/components/workspace-setting/MembersPage.tsx @@ -10,6 +10,7 @@ import { StyledMemberRoleContainer, StyledMemberTitleContainer, StyledMoreVerticalButton, + StyledPublishExplanation, } from './style'; import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons'; import { useEffect, useState } from 'react'; @@ -17,20 +18,24 @@ import { Button, IconButton } from '@/ui/button'; import { InviteMembers } from '../invite-members/index'; import { Menu, MenuItem } from '@/ui/menu'; import { Empty } from '@/ui/empty'; -import { - deleteMember, - getMembers, - User, - Workspace, -} from '@/hooks/mock-data/mock'; +// import { +// deleteMember, +// getMembers, +// User, +// Workspace, +// } from '@/hooks/mock-data/mock'; +import { WorkspaceInfo } from '@affine/datacenter'; import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { StyledMemberWarp } from './general/style'; import { useConfirm } from '@/providers/ConfirmProvider'; // import { useAppState } from '@/providers/app-state-provider'; -export const MembersPage = ({ workspace }: { workspace: Workspace }) => { +export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => { const [isInviteModalShow, setIsInviteModalShow] = useState(false); - const [members, setMembers] = useState([]); + const [members, setMembers] = useState<[{ name: string; email: string }]>([ + { name: 'affine', email: 'tttt' }, + ]); + console.log('setMembers: ', setMembers); const { user, login, updateWorkspaceMeta } = useTemporaryHelper(); const { confirm } = useConfirm(); // const refreshMembers = useCallback(() => { @@ -48,8 +53,9 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { // }); // }, [workspace.id]); const setMembersList = () => { - const members = getMembers(workspace.id); - members && setMembers(members); + // setMembers([]); + // const members = getMembers(workspace.id); + // members && setMembers(members); }; useEffect(() => { setMembersList(); @@ -58,7 +64,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { return (
- {workspace.type === 'cloud' ? ( + {workspace.provider === 'cloud' ? ( <> @@ -67,13 +73,6 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { Access level - {members.length === 0 && ( - - )} {members.length ? ( members.map((member, index) => { return ( @@ -103,7 +102,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { <> { - deleteMember(workspace.id, 0); + // deleteMember(workspace.id, 0); setMembersList(); // confirm({ // title: 'Delete Member?', @@ -142,7 +141,11 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { ); }) ) : ( - <> + )} @@ -171,10 +174,8 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => { ) : ( -
- Collaborating with other members requires AFFiNE Cloud service. -
-
+ <>Collaborating with other members requires AFFiNE Cloud service. + -
+
)}
diff --git a/packages/app/src/components/workspace-setting/PublishPage.tsx b/packages/app/src/components/workspace-setting/PublishPage.tsx index 2e0105214b..e931041fac 100644 --- a/packages/app/src/components/workspace-setting/PublishPage.tsx +++ b/packages/app/src/components/workspace-setting/PublishPage.tsx @@ -9,20 +9,20 @@ import { import { Button } from '@/ui/button'; import Input from '@/ui/input'; import { toast } from '@/ui/toast'; -import { Workspace } from '@/hooks/mock-data/mock'; -import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useConfirm } from '@/providers/ConfirmProvider'; +// import { useAppState } from '@/providers/app-state-provider3'; +import { useWorkspaceHelper } from '@/hooks/use-workspace-helper'; +import { WorkspaceInfo } from '@affine/datacenter'; -export const PublishPage = ({ workspace }: { workspace: Workspace }) => { - console.log('workspace: ', workspace); +export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => { const shareUrl = window.location.host + '/workspace/' + workspace.id + '?share=true'; + const { publishWorkspace } = useWorkspaceHelper(); - const { login, updateWorkspaceMeta, user } = useTemporaryHelper(); const { confirm } = useConfirm(); const togglePublic = (flag: boolean) => { - updateWorkspaceMeta(workspace.id, { isPublish: flag }); + workspace.id && publishWorkspace(workspace?.id, flag); }; const copyUrl = () => { @@ -34,22 +34,23 @@ export const PublishPage = ({ workspace }: { workspace: Workspace }) => { 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', + confirmText: + workspace.provider === 'local' ? 'Enable' : 'Sign in and Enable', cancelText: 'Skip', }).then(confirm => { if (confirm) { - if (user) { - updateWorkspaceMeta(workspace.id, { type: 'cloud' }); - } else { - login(); - updateWorkspaceMeta(workspace.id, { type: 'cloud' }); - } + // if (user) { + // updateWorkspaceMeta(workspace.id, { type: 'cloud' }); + // } else { + // login(); + // updateWorkspaceMeta(workspace.id, { type: 'cloud' }); + // } } }); }; return ( <> - {workspace.type === 'cloud' ? ( + {workspace.provider === 'cloud' ? (
{workspace?.isPublish ? ( diff --git a/packages/app/src/components/workspace-setting/SyncPage.tsx b/packages/app/src/components/workspace-setting/SyncPage.tsx index c16ea3f0a2..6c15773d99 100644 --- a/packages/app/src/components/workspace-setting/SyncPage.tsx +++ b/packages/app/src/components/workspace-setting/SyncPage.tsx @@ -6,28 +6,48 @@ import { import { DownloadIcon } from '@blocksuite/icons'; import { Button } from '@/ui/button'; import { Menu, MenuItem } from '@/ui/menu'; -import { deleteMember, Workspace } from '@/hooks/mock-data/mock'; -import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; - -export const SyncPage = ({ workspace }: { workspace: Workspace }) => { - const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper(); - +import { WorkspaceInfo } from '@affine/datacenter'; +import { useWorkspaceHelper } from '@/hooks/use-workspace-helper'; +import { useAppState } from '@/providers/app-state-provider'; +import { useConfirm } from '@/providers/ConfirmProvider'; +import { toast } from '@/ui/toast'; +import { useUserHelper } from '@/hooks/use-user-helper'; +import { useRouter } from 'next/router'; +export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => { + // console.log('workspace: ', workspace); + const { enableWorkspace } = useWorkspaceHelper(); + const { currentWorkspace } = useAppState(); + const { confirm } = useConfirm(); + const { user, login } = useUserHelper(); + const router = useRouter(); return (
- {currentWorkspace?.type === 'local' ? ( + {workspace?.provider === 'local' ? ( <> - {currentWorkspace.name} is 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. + {workspace.name ?? 'Affine'} is 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.