mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-02 06:39:46 +08:00
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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user