refactor(core): separate editor & doc mode (#7873)

doc.mode -> primaryMode
(*new) editor.mode

New Service:
editor service

Change Mode:

```
const editor = useService(EditorService).editor;
editor.setMode('page')
```

Change primary mode

```
const editor = useService(EditorService).editor;
editor.doc.setPrimaryMode('page')
```
This commit is contained in:
EYHN
2024-08-14 11:43:03 +00:00
parent 50948318e0
commit 89537e6892
51 changed files with 453 additions and 379 deletions
@@ -2,6 +2,7 @@ import { Scrollable } from '@affine/component';
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
import { AuthService } from '@affine/core/modules/cloud';
import { type Editor, EditorsService } from '@affine/core/modules/editor';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
import { noop } from '@blocksuite/global/utils';
@@ -121,6 +122,7 @@ export const Component = () => {
const t = useI18n();
const [workspace, setWorkspace] = useState<Workspace | null>(null);
const [page, setPage] = useState<Doc | null>(null);
const [editor, setEditor] = useState<Editor | null>(null);
const [_, setActiveBlocksuiteEditor] = useActiveBlocksuiteEditor();
const defaultCloudProvider = workspacesService.framework.get(
@@ -177,6 +179,10 @@ export const Component = () => {
);
setPage(doc);
const editor = doc.scope.get(EditorsService).createEditor(publishMode);
setEditor(editor);
})
.catch(err => {
console.error(err);
@@ -188,6 +194,7 @@ export const Component = () => {
workspaceArrayBuffer,
workspaceId,
workspacesService,
publishMode,
]);
const pageTitle = useLiveData(page?.title$);
@@ -204,58 +211,60 @@ export const Component = () => {
[setActiveBlocksuiteEditor]
);
if (!workspace || !page) {
if (!workspace || !page || !editor) {
return;
}
return (
<FrameworkScope scope={workspace.scope}>
<FrameworkScope scope={page.scope}>
<AppContainer>
<MainContainer>
<div className={styles.root}>
<div className={styles.mainContainer}>
<ShareHeader
pageId={page.id}
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
/>
<Scrollable.Root>
<Scrollable.Viewport
className={clsx(
'affine-page-viewport',
styles.editorContainer
)}
>
<PageDetailEditor
isPublic
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
pageId={page.id}
onLoad={onEditorLoad}
/>
{publishMode === 'page' ? <ShareFooter /> : null}
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
{loginStatus !== 'authenticated' ? (
<a
href="https://affine.pro"
target="_blank"
className={styles.link}
rel="noreferrer"
>
<span className={styles.linkText}>
{t['com.affine.share-page.footer.built-with']()}
</span>
<Logo1Icon fontSize={20} />
</a>
) : null}
<FrameworkScope scope={editor.scope}>
<AppContainer>
<MainContainer>
<div className={styles.root}>
<div className={styles.mainContainer}>
<ShareHeader
pageId={page.id}
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
/>
<Scrollable.Root>
<Scrollable.Viewport
className={clsx(
'affine-page-viewport',
styles.editorContainer
)}
>
<PageDetailEditor
isPublic
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
pageId={page.id}
onLoad={onEditorLoad}
/>
{publishMode === 'page' ? <ShareFooter /> : null}
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
{loginStatus !== 'authenticated' ? (
<a
href="https://affine.pro"
target="_blank"
className={styles.link}
rel="noreferrer"
>
<span className={styles.linkText}>
{t['com.affine.share-page.footer.built-with']()}
</span>
<Logo1Icon fontSize={20} />
</a>
) : null}
</div>
</div>
</div>
</MainContainer>
<PeekViewManagerModal />
</AppContainer>
</MainContainer>
<PeekViewManagerModal />
</AppContainer>
</FrameworkScope>
</FrameworkScope>
</FrameworkScope>
);
@@ -17,12 +17,8 @@ export function ShareHeader({
}) {
return (
<div className={styles.header}>
<EditorModeSwitch isPublic pageId={pageId} publicMode={publishMode} />
<BlocksuiteHeaderTitle
docCollection={docCollection}
pageId={pageId}
isPublic={true}
/>
<EditorModeSwitch />
<BlocksuiteHeaderTitle docId={pageId} />
<div className={styles.spacer} />
<ShareHeaderRightItem
workspaceId={docCollection.id}
@@ -15,15 +15,10 @@ import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite
import { useRegisterCopyLinkCommands } from '@affine/core/hooks/affine/use-register-copy-link-commands';
import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
import { track } from '@affine/core/mixpanel';
import { EditorService } from '@affine/core/modules/editor';
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
import type { Doc } from '@blocksuite/store';
import {
DocService,
useLiveData,
useService,
type Workspace,
} from '@toeverything/infra';
import { useLiveData, useService, type Workspace } from '@toeverything/infra';
import { useAtom, useAtomValue } from 'jotai';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
@@ -81,7 +76,7 @@ export function JournalPageHeader({ page, workspace }: PageHeaderProps) {
<Header className={styles.header} ref={containerRef}>
<ViewTitle title={title} />
<ViewIcon icon="journal" />
<EditorModeSwitch pageId={page?.id} />
<EditorModeSwitch />
<div className={styles.journalWeekPicker}>
<JournalWeekDatePicker
docCollection={workspace.docCollection}
@@ -125,22 +120,17 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
}, []);
const title = useDocCollectionPageTitle(workspace.docCollection, page?.id);
const doc = useService(DocService).doc;
const currentMode = useLiveData(doc.mode$);
const onEditSave = useCallback(() => {
track.$.header.actions.renameDoc();
}, []);
const editor = useService(EditorService).editor;
const currentMode = useLiveData(editor.mode$);
return (
<Header className={styles.header} ref={containerRef}>
<ViewTitle title={title} />
<ViewIcon icon={currentMode ?? 'page'} />
<EditorModeSwitch pageId={page?.id} />
<EditorModeSwitch />
<BlocksuiteHeaderTitle
docId={page.id}
inputHandleRef={titleInputHandleRef}
pageId={page?.id}
docCollection={workspace.docCollection}
onEditSave={onEditSave}
/>
<div className={styles.iconButtonContainer}>
{hideCollect ? null : (
@@ -6,6 +6,8 @@ import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import type { Editor } from '@affine/core/modules/editor';
import { EditorService, EditorsService } from '@affine/core/modules/editor';
import { RecentDocsService } from '@affine/core/modules/quicksearch';
import { ViewService } from '@affine/core/modules/workbench/services/view';
import type { PageRootService } from '@blocksuite/blocks';
@@ -30,6 +32,7 @@ import {
GlobalContextService,
useLiveData,
useService,
useServices,
WorkspaceService,
} from '@toeverything/infra';
import clsx from 'clsx';
@@ -71,18 +74,37 @@ import { EditorJournalPanel } from './tabs/journal';
import { EditorOutlinePanel } from './tabs/outline';
const DetailPageImpl = memo(function DetailPageImpl() {
const workbench = useService(WorkbenchService).workbench;
const view = useService(ViewService).view;
const {
workbenchService,
viewService,
editorService,
docService,
workspaceService,
globalContextService,
} = useServices({
WorkbenchService,
ViewService,
EditorService,
DocService,
WorkspaceService,
GlobalContextService,
});
const workbench = workbenchService.workbench;
const editor = editorService.editor;
const view = viewService.view;
const workspace = workspaceService.workspace;
const docCollection = workspace.docCollection;
const globalContext = globalContextService.globalContext;
const doc = docService.doc;
const mode = useLiveData(editor.mode$);
const activeSidebarTab = useLiveData(view.activeSidebarTab$);
const doc = useService(DocService).doc;
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper();
const [editor, setEditor] = useState<AffineEditorContainer | null>(null);
const workspace = useService(WorkspaceService).workspace;
const globalContext = useService(GlobalContextService).globalContext;
const docCollection = workspace.docCollection;
const mode = useLiveData(doc.mode$);
const [editorContainer, setEditorContainer] =
useState<AffineEditorContainer | null>(null);
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
const { appSettings } = useAppSettingHelper();
const chatPanelRef = useRef<ChatPanel | null>(null);
@@ -94,9 +116,9 @@ const DetailPageImpl = memo(function DetailPageImpl() {
useEffect(() => {
if (isActiveView) {
setActiveBlockSuiteEditor(editor);
setActiveBlockSuiteEditor(editorContainer);
}
}, [editor, isActiveView, setActiveBlockSuiteEditor]);
}, [editorContainer, isActiveView, setActiveBlockSuiteEditor]);
useEffect(() => {
const disposable = AIProvider.slots.requestOpenWithChat.on(params => {
@@ -152,7 +174,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
return;
}, [globalContext, isActiveView, isInTrash]);
useRegisterBlocksuiteEditorCommands();
useRegisterBlocksuiteEditorCommands(editor);
const title = useLiveData(doc.title$);
usePageDocumentTitle(title);
@@ -218,7 +240,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
);
}
setEditor(editor);
setEditorContainer(editor);
return () => {
disposable.dispose();
@@ -236,7 +258,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
}, [workbench, view]);
return (
<>
<FrameworkScope scope={editor.scope}>
<ViewHeader>
<DetailPageHeader page={doc.blockSuiteDoc} workspace={workspace} />
</ViewHeader>
@@ -271,7 +293,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
/>
</Scrollable.Root>
<EditorOutlineViewer
editor={editor}
editor={editorContainer}
show={mode === 'page' && !isSideBarOpen}
openOutlinePanel={openOutlinePanel}
/>
@@ -281,7 +303,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
</ViewBody>
<ViewSidebarTab tabId="chat" icon={<AiIcon />} unmountOnInactive={false}>
<EditorChatPanel editor={editor} ref={chatPanelRef} />
<EditorChatPanel editor={editorContainer} ref={chatPanelRef} />
</ViewSidebarTab>
<ViewSidebarTab tabId="journal" icon={<TodayIcon />}>
@@ -289,16 +311,16 @@ const DetailPageImpl = memo(function DetailPageImpl() {
</ViewSidebarTab>
<ViewSidebarTab tabId="outline" icon={<TocIcon />}>
<EditorOutlinePanel editor={editor} />
<EditorOutlinePanel editor={editorContainer} />
</ViewSidebarTab>
<ViewSidebarTab tabId="frame" icon={<FrameIcon />}>
<EditorFramePanel editor={editor} />
<EditorFramePanel editor={editorContainer} />
</ViewSidebarTab>
<GlobalPageHistoryModal />
<PageAIOnboarding />
</>
</FrameworkScope>
);
});
@@ -310,6 +332,7 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
const docRecord = useLiveData(docRecordList.doc$(pageId));
const [doc, setDoc] = useState<Doc | null>(null);
const [editor, setEditor] = useState<Editor | null>(null);
useLayoutEffect(() => {
if (!docRecord) {
@@ -322,6 +345,19 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
};
}, [docRecord, docsService, pageId]);
useLayoutEffect(() => {
if (!doc) {
return;
}
const editor = doc.scope
.get(EditorsService)
.createEditor(doc.getPrimaryMode() || 'page');
setEditor(editor);
return () => {
editor.dispose();
};
}, [doc]);
// set sync engine priority target
useEffect(() => {
currentWorkspace.engine.doc.setPriority(pageId, 10);
@@ -346,13 +382,15 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
return <PageNotFound noPermission />;
}
if (!doc) {
if (!doc || !editor) {
return <PageDetailSkeleton key="current-page-is-null" />;
}
return (
<FrameworkScope scope={doc.scope}>
<DetailPageImpl />
<FrameworkScope scope={editor.scope}>
<DetailPageImpl />
</FrameworkScope>
</FrameworkScope>
);
};
@@ -48,7 +48,7 @@ interface PageItemProps
right?: ReactNode;
}
const PageItem = ({ docRecord, right, className, ...attrs }: PageItemProps) => {
const mode = useLiveData(docRecord.mode$);
const mode = useLiveData(docRecord.primaryMode$);
const workspace = useService(WorkspaceService).workspace;
const title = useDocCollectionPageTitle(
workspace.docCollection,
@@ -1,9 +1,10 @@
import { EditorService } from '@affine/core/modules/editor';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useViewPosition } from '@affine/core/modules/workbench/view/use-view-position';
import { DocService, useLiveData, useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
export const useDetailPageHeaderResponsive = (availableWidth: number) => {
const mode = useLiveData(useService(DocService).doc.mode$);
const mode = useLiveData(useService(EditorService).editor.mode$);
const workbench = useService(WorkbenchService).workbench;
const viewPosition = useViewPosition();