mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +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:
@@ -0,0 +1,31 @@
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import { EditorScope } from '../scopes/editor';
|
||||
|
||||
export class Editor extends Entity<{ defaultMode: DocMode }> {
|
||||
readonly scope = this.framework.createScope(EditorScope, {
|
||||
editor: this as Editor,
|
||||
});
|
||||
|
||||
readonly mode$ = new LiveData(this.props.defaultMode);
|
||||
readonly doc = this.docService.doc;
|
||||
readonly isSharedMode =
|
||||
this.workspaceService.workspace.openOptions.isSharedMode;
|
||||
|
||||
toggleMode() {
|
||||
this.mode$.next(this.mode$.value === 'edgeless' ? 'page' : 'edgeless');
|
||||
}
|
||||
|
||||
setMode(mode: DocMode) {
|
||||
this.mode$.next(mode);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly docService: DocService,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { Editor } from './entities/editor';
|
||||
import { EditorScope } from './scopes/editor';
|
||||
import { EditorService } from './services/editor';
|
||||
import { EditorsService } from './services/editors';
|
||||
|
||||
export { Editor } from './entities/editor';
|
||||
export { EditorScope } from './scopes/editor';
|
||||
export { EditorService } from './services/editor';
|
||||
export { EditorsService } from './services/editors';
|
||||
|
||||
export function configureEditorModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.scope(DocScope)
|
||||
.service(EditorsService)
|
||||
.entity(Editor, [DocService, WorkspaceService])
|
||||
.scope(EditorScope)
|
||||
.service(EditorService, [EditorScope]);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Scope } from '@toeverything/infra';
|
||||
|
||||
import type { Editor } from '../entities/editor';
|
||||
|
||||
export class EditorScope extends Scope<{
|
||||
editor: Editor;
|
||||
}> {}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import type { EditorScope } from '../scopes/editor';
|
||||
|
||||
export class EditorService extends Service {
|
||||
readonly editor = this.scope.props.editor;
|
||||
|
||||
constructor(readonly scope: EditorScope) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { Editor } from '../entities/editor';
|
||||
|
||||
export class EditorsService extends Service {
|
||||
createEditor(defaultMode: DocMode) {
|
||||
return this.framework.createEntity(Editor, { defaultMode });
|
||||
}
|
||||
}
|
||||
@@ -56,25 +56,25 @@ export const ExplorerDocNode = ({
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
const docRecord = useLiveData(docsService.list.doc$(docId));
|
||||
const docMode = useLiveData(docRecord?.mode$);
|
||||
const docPrimaryMode = useLiveData(docRecord?.primaryMode$);
|
||||
const docTitle = useLiveData(docRecord?.title$);
|
||||
const isInTrash = useLiveData(docRecord?.trash$);
|
||||
|
||||
const Icon = useCallback(
|
||||
({ className }: { className?: string }) => {
|
||||
return isLinked ? (
|
||||
docMode === 'edgeless' ? (
|
||||
docPrimaryMode === 'edgeless' ? (
|
||||
<LinkedEdgelessIcon className={className} />
|
||||
) : (
|
||||
<LinkedPageIcon className={className} />
|
||||
)
|
||||
) : docMode === 'edgeless' ? (
|
||||
) : docPrimaryMode === 'edgeless' ? (
|
||||
<EdgelessIcon className={className} />
|
||||
) : (
|
||||
<PageIcon className={className} />
|
||||
);
|
||||
},
|
||||
[docMode, isLinked]
|
||||
[docPrimaryMode, isLinked]
|
||||
);
|
||||
|
||||
const children = useLiveData(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { configureCloudModule } from './cloud';
|
||||
import { configureCollectionModule } from './collection';
|
||||
import { configureDocLinksModule } from './doc-link';
|
||||
import { configureDocsSearchModule } from './docs-search';
|
||||
import { configureEditorModule } from './editor';
|
||||
import { configureExplorerModule } from './explorer';
|
||||
import { configureFavoriteModule } from './favorite';
|
||||
import { configureFindInPageModule } from './find-in-page';
|
||||
@@ -39,4 +40,5 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureFavoriteModule(framework);
|
||||
configureExplorerModule(framework);
|
||||
configureThemeEditorModule(framework);
|
||||
configureEditorModule(framework);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { WorkbenchService } from '../../../workbench';
|
||||
import { PeekViewService } from '../../services/peek-view';
|
||||
import { useDoc } from '../utils';
|
||||
import { useEditor } from '../utils';
|
||||
import * as styles from './doc-peek-view.css';
|
||||
|
||||
const logger = new DebugLogger('doc-peek-view');
|
||||
@@ -69,33 +69,36 @@ export function DocPeekPreview({
|
||||
mode?: DocMode;
|
||||
xywh?: `[${number},${number},${number},${number}]`;
|
||||
}) {
|
||||
const { doc, workspace, loading } = useDoc(docId);
|
||||
const { doc, workspace, loading } = useEditor(docId, mode);
|
||||
const { jumpToTag } = useNavigateHelper();
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const peekView = useService(PeekViewService).peekView;
|
||||
const [editor, setEditor] = useState<AffineEditorContainer | null>(null);
|
||||
const [editorElement, setEditorElement] =
|
||||
useState<AffineEditorContainer | null>(null);
|
||||
|
||||
const onRef = (editor: AffineEditorContainer) => {
|
||||
setEditor(editor);
|
||||
setEditorElement(editor);
|
||||
};
|
||||
|
||||
const docs = useService(DocsService);
|
||||
const [resolvedMode, setResolvedMode] = useState<DocMode | undefined>(mode);
|
||||
|
||||
useEffect(() => {
|
||||
editor?.updateComplete
|
||||
editorElement?.updateComplete
|
||||
.then(() => {
|
||||
if (resolvedMode === 'edgeless') {
|
||||
fitViewport(editor, xywh);
|
||||
fitViewport(editorElement, xywh);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
return;
|
||||
}, [editor, resolvedMode, xywh]);
|
||||
}, [editorElement, resolvedMode, xywh]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mode || !resolvedMode) {
|
||||
setResolvedMode(docs.list.doc$(docId).value?.mode$.value || 'page');
|
||||
setResolvedMode(
|
||||
docs.list.doc$(docId).value?.primaryMode$.value || 'page'
|
||||
);
|
||||
}
|
||||
}, [docId, docs.list, resolvedMode, mode]);
|
||||
|
||||
@@ -115,14 +118,15 @@ export function DocPeekPreview({
|
||||
|
||||
useEffect(() => {
|
||||
const disposableGroup = new DisposableGroup();
|
||||
if (editor) {
|
||||
editor.updateComplete
|
||||
if (editorElement) {
|
||||
editorElement.updateComplete
|
||||
.then(() => {
|
||||
if (!editor.host) {
|
||||
if (!editorElement.host) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootService = editor.host.std.spec.getService('affine:page');
|
||||
const rootService =
|
||||
editorElement.host.std.spec.getService('affine:page');
|
||||
// doc change event inside peek view should be handled by peek view
|
||||
disposableGroup.add(
|
||||
rootService.slots.docLinkClicked.on(({ docId, blockId }) => {
|
||||
@@ -142,7 +146,7 @@ export function DocPeekPreview({
|
||||
return () => {
|
||||
disposableGroup.dispose();
|
||||
};
|
||||
}, [editor, jumpToTag, peekView, workspace.id]);
|
||||
}, [editorElement, jumpToTag, peekView, workspace.id]);
|
||||
|
||||
const openOutlinePanel = useCallback(() => {
|
||||
workbench.openDoc(docId);
|
||||
@@ -176,7 +180,7 @@ export function DocPeekPreview({
|
||||
/>
|
||||
</FrameworkScope>
|
||||
<EditorOutlineViewer
|
||||
editor={editor}
|
||||
editor={editorElement}
|
||||
show={resolvedMode === 'page'}
|
||||
openOutlinePanel={openOutlinePanel}
|
||||
/>
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { PeekViewService } from '../../services/peek-view';
|
||||
import { useDoc } from '../utils';
|
||||
import { useEditor } from '../utils';
|
||||
import { useZoomControls } from './hooks/use-zoom';
|
||||
import * as styles from './index.css';
|
||||
|
||||
@@ -108,7 +108,7 @@ const ImagePreviewModalImpl = ({
|
||||
onBlockIdChange: (blockId: string) => void;
|
||||
onClose: () => void;
|
||||
}): ReactElement | null => {
|
||||
const { doc, workspace } = useDoc(docId);
|
||||
const { doc, workspace } = useEditor(docId);
|
||||
const blocksuiteDoc = doc?.blockSuiteDoc;
|
||||
const docCollection = workspace.docCollection;
|
||||
const blockModel = useMemo(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { WorkbenchService } from '../../workbench';
|
||||
import { PeekViewService } from '../services/peek-view';
|
||||
import * as styles from './peek-view-controls.css';
|
||||
import { useDoc } from './utils';
|
||||
import { useEditor } from './utils';
|
||||
|
||||
type ControlButtonProps = {
|
||||
nameKey: string;
|
||||
@@ -100,7 +100,7 @@ export const DocPeekViewControls = ({
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const { jumpToPageBlock } = useNavigateHelper();
|
||||
const t = useI18n();
|
||||
const { doc, workspace } = useDoc(docId);
|
||||
const { doc, workspace } = useEditor(docId);
|
||||
const controls = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -115,12 +115,15 @@ export const DocPeekViewControls = ({
|
||||
nameKey: 'open',
|
||||
onClick: () => {
|
||||
// TODO(@Peng): for frame blocks, we should mimic "view in edgeless" button behavior
|
||||
if (mode) {
|
||||
// TODO(@eyhn): change this to use mode link
|
||||
doc?.setPrimaryMode(mode);
|
||||
}
|
||||
|
||||
blockId
|
||||
? jumpToPageBlock(workspace.id, docId, blockId)
|
||||
: workbench.openDoc(docId);
|
||||
if (mode) {
|
||||
doc?.setMode(mode);
|
||||
}
|
||||
|
||||
peekView.close('none');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import type { Doc } from '@toeverything/infra';
|
||||
import type { Doc, DocMode } from '@toeverything/infra';
|
||||
import {
|
||||
DocsService,
|
||||
useLiveData,
|
||||
useService,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
export const useDoc = (pageId: string) => {
|
||||
import { type Editor, EditorsService } from '../../editor';
|
||||
|
||||
export const useEditor = (pageId: string, preferMode?: DocMode) => {
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
const docsService = useService(DocsService);
|
||||
const docRecordList = docsService.list;
|
||||
const docListReady = useLiveData(docRecordList.isReady$);
|
||||
const docRecord = docRecordList.doc$(pageId).value;
|
||||
const preferModeRef = useRef(preferMode);
|
||||
|
||||
const [doc, setDoc] = useState<Doc | null>(null);
|
||||
const [editor, setEditor] = useState<Editor | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!docRecord) {
|
||||
@@ -27,6 +31,19 @@ export const useDoc = (pageId: string) => {
|
||||
};
|
||||
}, [docRecord, docsService, pageId]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
const editor = doc.scope
|
||||
.get(EditorsService)
|
||||
.createEditor(preferModeRef.current || doc.primaryMode$.value);
|
||||
setEditor(editor);
|
||||
return () => {
|
||||
editor.dispose();
|
||||
};
|
||||
}, [doc]);
|
||||
|
||||
// set sync engine priority target
|
||||
useEffect(() => {
|
||||
currentWorkspace.engine.doc.setPriority(pageId, 10);
|
||||
@@ -35,5 +52,5 @@ export const useDoc = (pageId: string) => {
|
||||
};
|
||||
}, [currentWorkspace, pageId]);
|
||||
|
||||
return { doc, workspace: currentWorkspace, loading: !docListReady };
|
||||
return { doc, editor, workspace: currentWorkspace, loading: !docListReady };
|
||||
};
|
||||
|
||||
@@ -63,13 +63,13 @@ export class CMDKQuickSearchService extends Service {
|
||||
} else if (result.source === 'creation') {
|
||||
if (result.id === 'creation:create-page') {
|
||||
const newDoc = this.docsService.createDoc({
|
||||
mode: 'page',
|
||||
primaryMode: 'page',
|
||||
title: result.payload.title,
|
||||
});
|
||||
this.workbenchService.workbench.openDoc(newDoc.id);
|
||||
} else if (result.id === 'creation:create-edgeless') {
|
||||
const newDoc = this.docsService.createDoc({
|
||||
mode: 'edgeless',
|
||||
primaryMode: 'edgeless',
|
||||
title: result.payload.title,
|
||||
});
|
||||
this.workbenchService.workbench.openDoc(newDoc.id);
|
||||
|
||||
@@ -16,7 +16,7 @@ export class DocDisplayMetaService extends Service {
|
||||
);
|
||||
const icon = journalDateString
|
||||
? TodayIcon
|
||||
: docRecord.mode$.value === 'edgeless'
|
||||
: docRecord.primaryMode$.value === 'edgeless'
|
||||
? EdgelessIcon
|
||||
: PageIcon;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user