From 89537e6892068d384b58dabb58c53375aef7a16a Mon Sep 17 00:00:00 2001 From: EYHN Date: Wed, 14 Aug 2024 11:43:03 +0000 Subject: [PATCH] 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') ``` --- .../infra/src/modules/doc/entities/doc.ts | 34 ++++--- .../src/modules/doc/entities/record-list.ts | 23 +++-- .../infra/src/modules/doc/entities/record.ts | 22 ++--- .../infra/src/modules/doc/services/docs.ts | 17 +--- .../infra/src/modules/doc/stores/docs.ts | 6 +- .../frontend/component/src/ui/radio/types.ts | 2 +- .../core/src/bootstrap/first-app-data.ts | 2 +- .../affine/ai-onboarding/edgeless.dialog.tsx | 16 +-- .../page-history-modal/history-modal.tsx | 6 +- .../page-properties/info-modal/info-modal.tsx | 13 +-- .../affine/reference-link/index.tsx | 13 ++- .../share-menu/share-export.tsx | 7 +- .../share-menu/share-page.tsx | 2 +- .../specs/custom/linked-widget.ts | 2 +- .../specs/custom/spec-patchers.tsx | 18 ++-- .../block-suite-header/menu/index.tsx | 26 +++-- .../block-suite-header/title/index.tsx | 45 +++++---- .../block-suite-mode-switch/index.tsx | 49 ++++----- .../block-suite-page-list/utils.tsx | 19 +--- .../src/components/page-detail-editor.tsx | 23 ++--- .../page-list/docs/virtualized-page-list.tsx | 3 - .../src/components/page-list/page-group.tsx | 14 +-- .../core/src/components/page-list/types.ts | 1 - .../view/edit-collection/edit-collection.tsx | 1 - .../view/edit-collection/rules-mode.tsx | 8 +- .../view/edit-collection/select-page.tsx | 26 ++--- .../page-list/virtualized-trash-list.tsx | 4 - .../src/components/pure/help-island/index.tsx | 16 ++- .../hooks/affine/use-all-page-list-config.tsx | 5 - .../affine/use-block-suite-meta-helper.ts | 7 +- ...se-register-blocksuite-editor-commands.tsx | 8 +- .../core/src/layouts/workspace-layout.tsx | 2 +- .../src/modules/editor/entities/editor.ts | 31 ++++++ .../frontend/core/src/modules/editor/index.ts | 27 +++++ .../core/src/modules/editor/scopes/editor.ts | 7 ++ .../src/modules/editor/services/editor.ts | 11 +++ .../src/modules/editor/services/editors.ts | 10 ++ .../explorer/views/nodes/doc/index.tsx | 8 +- packages/frontend/core/src/modules/index.ts | 2 + .../view/doc-preview/doc-peek-view.tsx | 32 +++--- .../peek-view/view/image-preview/index.tsx | 4 +- .../peek-view/view/peek-view-controls.tsx | 13 ++- .../core/src/modules/peek-view/view/utils.ts | 25 ++++- .../src/modules/quicksearch/services/cmdk.ts | 4 +- .../quicksearch/services/doc-display-meta.ts | 2 +- .../src/pages/share/share-detail-page.tsx | 99 ++++++++++--------- .../core/src/pages/share/share-header.tsx | 8 +- .../detail-page/detail-page-header.tsx | 24 ++--- .../workspace/detail-page/detail-page.tsx | 78 +++++++++++---- .../workspace/detail-page/tabs/journal.tsx | 2 +- .../detail-page/use-header-responsive.ts | 5 +- 51 files changed, 453 insertions(+), 379 deletions(-) create mode 100644 packages/frontend/core/src/modules/editor/entities/editor.ts create mode 100644 packages/frontend/core/src/modules/editor/index.ts create mode 100644 packages/frontend/core/src/modules/editor/scopes/editor.ts create mode 100644 packages/frontend/core/src/modules/editor/services/editor.ts create mode 100644 packages/frontend/core/src/modules/editor/services/editors.ts diff --git a/packages/common/infra/src/modules/doc/entities/doc.ts b/packages/common/infra/src/modules/doc/entities/doc.ts index 4e4fb8b555..a22e6e467f 100644 --- a/packages/common/infra/src/modules/doc/entities/doc.ts +++ b/packages/common/infra/src/modules/doc/entities/doc.ts @@ -1,3 +1,5 @@ +import type { RootBlockModel } from '@blocksuite/blocks'; + import { Entity } from '../../../framework'; import type { DocScope } from '../scopes/doc'; import type { DocsStore } from '../stores/docs'; @@ -19,24 +21,22 @@ export class Doc extends Entity { public readonly record = this.scope.props.record; readonly meta$ = this.record.meta$; - readonly mode$ = this.record.mode$; + readonly primaryMode$ = this.record.primaryMode$; readonly title$ = this.record.title$; readonly trash$ = this.record.trash$; - setMode(mode: DocMode) { - return this.record.setMode(mode); + setPrimaryMode(mode: DocMode) { + return this.record.setPrimaryMode(mode); } - getMode() { - return this.record.getMode(); + getPrimaryMode() { + return this.record.getPrimaryMode(); } - toggleMode() { - return this.record.toggleMode(); - } - - observeMode() { - return this.record.observeMode(); + togglePrimaryMode() { + this.setPrimaryMode( + this.getPrimaryMode() === 'edgeless' ? 'page' : 'edgeless' + ); } moveToTrash() { @@ -54,4 +54,16 @@ export class Doc extends Entity { setPriorityLoad(priority: number) { return this.store.setPriorityLoad(this.id, priority); } + + changeDocTitle(newTitle: string) { + const pageBlock = this.blockSuiteDoc.getBlocksByFlavour('affine:page').at(0) + ?.model as RootBlockModel | undefined; + if (pageBlock) { + this.blockSuiteDoc.transact(() => { + pageBlock.title.delete(0, pageBlock.title.length); + pageBlock.title.insert(newTitle, 0); + }); + this.record.setMeta({ title: newTitle }); + } + } } diff --git a/packages/common/infra/src/modules/doc/entities/record-list.ts b/packages/common/infra/src/modules/doc/entities/record-list.ts index e2db0963bc..06ac29b984 100644 --- a/packages/common/infra/src/modules/doc/entities/record-list.ts +++ b/packages/common/infra/src/modules/doc/entities/record-list.ts @@ -55,21 +55,24 @@ export class DocRecordList extends Entity { return this.docs$.map(record => record.find(record => record.id === id)); } - public setMode(id: string, mode: DocMode) { - return this.store.setDocModeSetting(id, mode); + public setPrimaryMode(id: string, mode: DocMode) { + return this.store.setDocPrimaryModeSetting(id, mode); } - public getMode(id: string) { - return this.store.getDocModeSetting(id); + public getPrimaryMode(id: string) { + return this.store.getDocPrimaryModeSetting(id); } - public toggleMode(id: string) { - const mode = this.getMode(id) === 'edgeless' ? 'page' : 'edgeless'; - this.setMode(id, mode); - return this.getMode(id); + public togglePrimaryMode(id: string) { + const mode = this.getPrimaryMode(id) === 'edgeless' ? 'page' : 'edgeless'; + this.setPrimaryMode(id, mode); + return this.getPrimaryMode(id); } - public observeMode(id: string) { - return this.store.watchDocModeSetting(id); + public primaryMode$(id: string) { + return LiveData.from( + this.store.watchDocPrimaryModeSetting(id), + this.getPrimaryMode(id) + ); } } diff --git a/packages/common/infra/src/modules/doc/entities/record.ts b/packages/common/infra/src/modules/doc/entities/record.ts index 589f02e44a..991d396b39 100644 --- a/packages/common/infra/src/modules/doc/entities/record.ts +++ b/packages/common/infra/src/modules/doc/entities/record.ts @@ -26,27 +26,17 @@ export class DocRecord extends Entity<{ id: string }> { this.docsStore.setDocMeta(this.id, meta); } - mode$: LiveData = LiveData.from( - this.docsStore.watchDocModeSetting(this.id), + primaryMode$: LiveData = LiveData.from( + this.docsStore.watchDocPrimaryModeSetting(this.id), 'page' ).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page')); - setMode(mode: DocMode) { - return this.docsStore.setDocModeSetting(this.id, mode); + setPrimaryMode(mode: DocMode) { + return this.docsStore.setDocPrimaryModeSetting(this.id, mode); } - getMode() { - return this.docsStore.getDocModeSetting(this.id); - } - - toggleMode() { - const mode = this.getMode() === 'edgeless' ? 'page' : 'edgeless'; - this.setMode(mode); - return this.getMode(); - } - - observeMode() { - return this.docsStore.watchDocModeSetting(this.id); + getPrimaryMode() { + return this.docsStore.getDocPrimaryModeSetting(this.id); } moveToTrash() { diff --git a/packages/common/infra/src/modules/doc/services/docs.ts b/packages/common/infra/src/modules/doc/services/docs.ts index 7e81218517..3b2f23eb82 100644 --- a/packages/common/infra/src/modules/doc/services/docs.ts +++ b/packages/common/infra/src/modules/doc/services/docs.ts @@ -1,5 +1,4 @@ import { Unreachable } from '@affine/env/constant'; -import type { RootBlockModel } from '@blocksuite/blocks'; import { Service } from '../../../framework'; import { initEmptyPage } from '../../../initialization'; @@ -54,7 +53,7 @@ export class DocsService extends Service { createDoc( options: { - mode?: DocMode; + primaryMode?: DocMode; title?: string; } = {} ) { @@ -65,8 +64,8 @@ export class DocsService extends Service { if (!docRecord) { throw new Unreachable(); } - if (options.mode) { - docRecord.setMode(options.mode); + if (options.primaryMode) { + docRecord.setPrimaryMode(options.primaryMode); } return docRecord; } @@ -100,15 +99,7 @@ export class DocsService extends Service { const { doc, release } = this.open(docId); doc.setPriorityLoad(10); await doc.waitForSyncReady(); - const pageBlock = doc.blockSuiteDoc.getBlocksByFlavour('affine:page').at(0) - ?.model as RootBlockModel | undefined; - if (pageBlock) { - doc.blockSuiteDoc.transact(() => { - pageBlock.title.delete(0, pageBlock.title.length); - pageBlock.title.insert(newTitle, 0); - }); - doc.record.setMeta({ title: newTitle }); - } + doc.changeDocTitle(newTitle); release(); } } diff --git a/packages/common/infra/src/modules/doc/stores/docs.ts b/packages/common/infra/src/modules/doc/stores/docs.ts index 2e828144e2..8d7f2f2bb7 100644 --- a/packages/common/infra/src/modules/doc/stores/docs.ts +++ b/packages/common/infra/src/modules/doc/stores/docs.ts @@ -101,15 +101,15 @@ export class DocsStore extends Store { this.workspaceService.workspace.docCollection.setDocMeta(id, meta); } - setDocModeSetting(id: string, mode: DocMode) { + setDocPrimaryModeSetting(id: string, mode: DocMode) { return this.localState.set(`page:${id}:mode`, mode); } - getDocModeSetting(id: string) { + getDocPrimaryModeSetting(id: string) { return this.localState.get(`page:${id}:mode`); } - watchDocModeSetting(id: string) { + watchDocPrimaryModeSetting(id: string) { return this.localState.watch(`page:${id}:mode`); } diff --git a/packages/frontend/component/src/ui/radio/types.ts b/packages/frontend/component/src/ui/radio/types.ts index 4722d4ba77..1d30f63220 100644 --- a/packages/frontend/component/src/ui/radio/types.ts +++ b/packages/frontend/component/src/ui/radio/types.ts @@ -6,7 +6,7 @@ type SimpleRadioItem = string; export interface RadioProps extends RadioGroupItemProps { items: RadioItem[] | SimpleRadioItem[]; value: any; - onChange: (value: any) => void; + onChange?: (value: any) => void; /** * Total width of the radio group, items will be evenly distributed diff --git a/packages/frontend/core/src/bootstrap/first-app-data.ts b/packages/frontend/core/src/bootstrap/first-app-data.ts index 336a0ba70f..8ea7248e54 100644 --- a/packages/frontend/core/src/bootstrap/first-app-data.ts +++ b/packages/frontend/core/src/bootstrap/first-app-data.ts @@ -31,7 +31,7 @@ export async function buildShowcaseWorkspace( ); if (defaultDoc) { - defaultDoc.setMode('edgeless'); + defaultDoc.setPrimaryMode('edgeless'); } dispose(); diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx index 5689e2875b..08b70baedd 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx @@ -2,14 +2,10 @@ import { Button, FlexWrapper, notify } from '@affine/component'; import { openSettingModalAtom } from '@affine/core/atoms'; import { track } from '@affine/core/mixpanel'; import { SubscriptionService } from '@affine/core/modules/cloud'; +import { EditorService } from '@affine/core/modules/editor'; import { useI18n } from '@affine/i18n'; import { AiIcon } from '@blocksuite/icons/rc'; -import { - DocService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useAtomValue, useSetAtom } from 'jotai'; import Lottie from 'lottie-react'; @@ -46,10 +42,9 @@ const EdgelessOnboardingAnimation = () => { }; export const AIOnboardingEdgeless = () => { - const { docService, subscriptionService } = useServices({ - WorkspaceService, - DocService, + const { subscriptionService, editorService } = useServices({ SubscriptionService, + EditorService, }); const t = useI18n(); @@ -61,8 +56,7 @@ export const AIOnboardingEdgeless = () => { const setSettingModal = useSetAtom(openSettingModalAtom); - const doc = docService.doc; - const mode = useLiveData(doc.mode$); + const mode = useLiveData(editorService.editor.mode$); const goToPricingPlans = useCallback(() => { track.$.aiOnboarding.dialog.viewPlans(); diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index c18badec95..f939ca240b 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -5,6 +5,7 @@ import { Modal, useConfirmModal } from '@affine/component/ui/modal'; import { openSettingModalAtom } from '@affine/core/atoms'; import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title'; import { track } from '@affine/core/mixpanel'; +import { EditorService } from '@affine/core/modules/editor'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { i18nTime, Trans, useI18n } from '@affine/i18n'; @@ -14,7 +15,6 @@ import * as Collapsible from '@radix-ui/react-collapsible'; import type { DialogContentProps } from '@radix-ui/react-dialog'; import { type DocMode, - DocService, useLiveData, useService, WorkspaceService, @@ -433,8 +433,8 @@ const PageHistoryManager = ({ [activeVersion, onClose, onRestore, snapshotPage] ); - const doc = useService(DocService).doc; - const [mode, setMode] = useState(doc.mode$.value); + const editor = useService(EditorService).editor; + const [mode, setMode] = useState(editor.mode$.value); const title = useDocCollectionPageTitle(docCollection, pageId); diff --git a/packages/frontend/core/src/components/affine/page-properties/info-modal/info-modal.tsx b/packages/frontend/core/src/components/affine/page-properties/info-modal/info-modal.tsx index c7cba63371..93e167408a 100644 --- a/packages/frontend/core/src/components/affine/page-properties/info-modal/info-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/info-modal/info-modal.tsx @@ -5,12 +5,7 @@ import { Scrollable, } from '@affine/component'; import { DocsSearchService } from '@affine/core/modules/docs-search'; -import { - LiveData, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import { Suspense, useCallback, useContext, useMemo, useRef } from 'react'; import { BlocksuiteHeaderTitle } from '../../../blocksuite/block-suite-header/title'; @@ -35,9 +30,8 @@ export const InfoModal = ({ onOpenChange: (open: boolean) => void; docId: string; }) => { - const { docsSearchService, workspaceService } = useServices({ + const { docsSearchService } = useServices({ DocsSearchService, - WorkspaceService, }); const titleInputHandleRef = useRef(null); const manager = usePagePropertiesManager(docId); @@ -72,10 +66,9 @@ export const InfoModal = ({ >
diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx index 66921a0a0c..8ccd70e49b 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -84,10 +84,17 @@ export function AffinePageReference({ const t = useI18n(); const docsService = useService(DocsService); - const mode$ = LiveData.from(docsService.list.observeMode(pageId), undefined); - const docMode = useLiveData(mode$) ?? null; + const docPrimaryMode = useLiveData( + LiveData.computed(get => { + const primaryMode$ = get(docsService.list.doc$(pageId))?.primaryMode$; + if (!primaryMode$) { + return null; + } + return get(primaryMode$); + }) + ); const el = pageReferenceRenderer({ - docMode, + docMode: docPrimaryMode, pageId, pageMetaHelper, journalHelper, diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx index bbb8c62c7d..89a1cf1583 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx @@ -3,10 +3,11 @@ import { Divider } from '@affine/component/ui/divider'; import { ExportMenuItems } from '@affine/core/components/page-list'; import { useExportPage } from '@affine/core/hooks/affine/use-export-page'; import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url'; +import { EditorService } from '@affine/core/modules/editor'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; import { CopyIcon } from '@blocksuite/icons/rc'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import * as styles from './index.css'; import type { ShareMenuProps } from './share-menu'; @@ -16,7 +17,7 @@ export const ShareExport = ({ currentPage, }: ShareMenuProps) => { const t = useI18n(); - const doc = useService(DocService).doc; + const editor = useService(EditorService).editor; const workspaceId = workspace.id; const pageId = currentPage.id; const { sharingUrl, onClickCopyLink } = useSharingUrl({ @@ -25,7 +26,7 @@ export const ShareExport = ({ urlType: 'workspace', }); const exportHandler = useExportPage(currentPage); - const currentMode = useLiveData(doc.mode$); + const currentMode = useLiveData(editor.mode$); const isMac = environment.isBrowser && environment.isMacOs; return ( diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx index 60c053f216..953f2523b7 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx @@ -71,7 +71,7 @@ export const AffineSharePage = (props: ShareMenuProps) => { isSharedPage === null || sharedMode === null || baseUrl === null; const [showDisable, setShowDisable] = useState(false); - const currentDocMode = useLiveData(doc.mode$); + const currentDocMode = useLiveData(doc.primaryMode$); const mode = useMemo(() => { if (isSharedPage && sharedMode) { diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts index 0f030a3017..b439151cd9 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts @@ -57,7 +57,7 @@ export function createLinkedWidgetConfig(framework: FrameworkProvider) { const MAX_DOCS = 6; const docsService = framework.get(DocsService); const isEdgeless = (d: DocMeta) => - docsService.list.getMode(d.id) === 'edgeless'; + docsService.list.getPrimaryMode(d.id) === 'edgeless'; return Promise.resolve([ { name: 'Link to Doc', diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index e0e17f6054..2af9e9e273 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -279,28 +279,28 @@ export function patchDocModeService( pageService.docModeService = { setMode: (mode: DocMode, id?: string) => { if (id) { - docsService.list.setMode(id, mode); + docsService.list.setPrimaryMode(id, mode); } else { - docService.doc.setMode(mode); + docService.doc.setPrimaryMode(mode); } }, getMode: (id?: string) => { const mode = id - ? docsService.list.getMode(id) - : docService.doc.getMode(); + ? docsService.list.getPrimaryMode(id) + : docService.doc.getPrimaryMode(); return mode || DEFAULT_MODE; }, toggleMode: (id?: string) => { const mode = id - ? docsService.list.toggleMode(id) - : docService.doc.toggleMode(); + ? docsService.list.togglePrimaryMode(id) + : docService.doc.togglePrimaryMode(); return mode || DEFAULT_MODE; }, onModeChange: (handler: (mode: DocMode) => void, id?: string) => { // eslint-disable-next-line rxjs/finnish const mode$ = id - ? docsService.list.observeMode(id) - : docService.doc.observeMode(); + ? docsService.list.primaryMode$(id) + : docService.doc.primaryMode$; const sub = mode$.subscribe(m => handler(m || DEFAULT_MODE)); return { dispose: sub.unsubscribe, @@ -412,7 +412,7 @@ export function patchQuickSearchService( ? 'edgeless' : 'page'; const newDoc = docsService.createDoc({ - mode, + primaryMode: mode, title: result.payload.title, }); resolve({ diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx index e70d772ad7..164b790212 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx @@ -19,6 +19,7 @@ import { useExportPage } from '@affine/core/hooks/affine/use-export-page'; import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { track } from '@affine/core/mixpanel'; +import { EditorService } from '@affine/core/modules/editor'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench/services/view'; import { useDetailPageHeaderResponsive } from '@affine/core/pages/workspace/detail-page/use-header-responsive'; @@ -41,12 +42,7 @@ import { TocIcon, } from '@blocksuite/icons/rc'; import type { Doc } from '@blocksuite/store'; -import { - DocService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; import { useCallback, useState } from 'react'; @@ -75,9 +71,11 @@ export const PageHeaderMenuButton = ({ const workspace = useService(WorkspaceService).workspace; const docCollection = workspace.docCollection; - const doc = useService(DocService).doc; - const isInTrash = useLiveData(doc.meta$.map(m => m.trash)); - const currentMode = useLiveData(doc.mode$); + const editorService = useService(EditorService); + const isInTrash = useLiveData( + editorService.editor.doc.meta$.map(meta => meta.trash) + ); + const currentMode = useLiveData(editorService.editor.mode$); const workbench = useService(WorkbenchService).workbench; @@ -139,9 +137,9 @@ export const PageHeaderMenuButton = ({ setTrashModal({ open: true, pageIds: [pageId], - pageTitles: [doc.meta$.value.title ?? ''], + pageTitles: [editorService.editor.doc.meta$.value.title ?? ''], }); - }, [doc.meta$.value.title, pageId, setTrashModal]); + }, [editorService, pageId, setTrashModal]); const handleRename = useCallback(() => { rename?.(); @@ -149,7 +147,7 @@ export const PageHeaderMenuButton = ({ }, [rename]); const handleSwitchMode = useCallback(() => { - doc.toggleMode(); + editorService.editor.toggleMode(); track.$.header.docOptions.switchPageMode({ mode: currentMode === 'page' ? 'edgeless' : 'page', }); @@ -158,7 +156,7 @@ export const PageHeaderMenuButton = ({ ? t['com.affine.toastMessage.edgelessMode']() : t['com.affine.toastMessage.pageMode']() ); - }, [currentMode, doc, t]); + }, [currentMode, editorService, t]); const menuItemStyle = { padding: '4px 12px', transition: 'all 0.3s', @@ -170,7 +168,7 @@ export const PageHeaderMenuButton = ({ } }, []); - const exportHandler = useExportPage(doc.blockSuiteDoc); + const exportHandler = useExportPage(editorService.editor.doc.blockSuiteDoc); const handleDuplicate = useCallback(() => { duplicate(pageId); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx index b4cf76358c..0ee3660860 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx @@ -1,53 +1,52 @@ import type { InlineEditProps } from '@affine/component'; import { InlineEdit } from '@affine/component'; +import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; +import { track } from '@affine/core/mixpanel'; import { - useBlockSuiteDocMeta, - useDocMetaHelper, -} from '@affine/core/hooks/use-block-suite-page-meta'; -import type { DocCollection } from '@affine/core/shared'; + DocsService, + useLiveData, + useService, + WorkspaceService, +} from '@toeverything/infra'; import clsx from 'clsx'; import type { HTMLAttributes } from 'react'; -import { useCallback } from 'react'; import * as styles from './style.css'; export interface BlockSuiteHeaderTitleProps { - docCollection: DocCollection; - pageId: string; + docId: string; /** if set, title cannot be edited */ - isPublic?: boolean; inputHandleRef?: InlineEditProps['handleRef']; className?: string; - onEditSave?: () => void; } const inputAttrs = { 'data-testid': 'title-content', } as HTMLAttributes; export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => { - const { docCollection, pageId, isPublic, inputHandleRef, onEditSave } = props; - const currentPage = docCollection.getDoc(pageId); - const pageMeta = useBlockSuiteDocMeta(docCollection).find( - meta => meta.id === currentPage?.id - ); - const title = pageMeta?.title; - const { setDocTitle } = useDocMetaHelper(docCollection); + const { inputHandleRef, docId } = props; + const workspaceService = useService(WorkspaceService); + const isSharedMode = workspaceService.workspace.openOptions.isSharedMode; + const docsService = useService(DocsService); - const onChange = useCallback( - (v: string) => { - onEditSave?.(); - setDocTitle(currentPage?.id || '', v); + const docRecord = useLiveData(docsService.list.doc$(docId)); + const docTitle = useLiveData(docRecord?.title$); + + const onChange = useAsyncCallback( + async (v: string) => { + await docsService.changeDocTitle(docId, v); + track.$.header.actions.renameDoc(); }, - [currentPage?.id, onEditSave, setDocTitle] + [docId, docsService] ); return ( { +export const EditorModeSwitch = () => { const t = useI18n(); - const docsService = useService(DocsService); - const doc = useLiveData(docsService.list.doc$(pageId)); - const trash = useLiveData(doc?.trash$); - const currentMode = useLiveData(doc?.mode$); + const editor = useService(EditorService).editor; + const trash = useLiveData(editor.doc.trash$); + const isSharedMode = editor.isSharedMode; + const currentMode = useLiveData(editor.mode$); const togglePage = useCallback(() => { - if (currentMode === 'page' || isPublic || trash) return; - doc?.setMode('page'); + if (currentMode === 'page' || isSharedMode || trash) return; + editor.setMode('page'); + editor.doc.setPrimaryMode('page'); toast(t['com.affine.toastMessage.pageMode']()); track.$.header.actions.switchPageMode({ mode: 'page' }); - }, [currentMode, doc, isPublic, t, trash]); + }, [currentMode, editor, isSharedMode, t, trash]); const toggleEdgeless = useCallback(() => { - if (currentMode === 'edgeless' || isPublic || trash) return; - doc?.setMode('edgeless'); + if (currentMode === 'edgeless' || isSharedMode || trash) return; + editor.setMode('edgeless'); + editor.doc.setPrimaryMode('edgeless'); toast(t['com.affine.toastMessage.edgelessMode']()); track.$.header.actions.switchPageMode({ mode: 'edgeless' }); - }, [currentMode, doc, isPublic, t, trash]); + }, [currentMode, editor, isSharedMode, t, trash]); const onModeChange = useCallback( (mode: DocMode) => { @@ -66,13 +60,12 @@ export const EditorModeSwitch = ({ ); const shouldHide = useCallback( - (mode: DocMode) => - (trash && currentMode !== mode) || (isPublic && publicMode !== mode), - [currentMode, isPublic, publicMode, trash] + (mode: DocMode) => (trash || isSharedMode) && currentMode !== mode, + [currentMode, isSharedMode, trash] ); useEffect(() => { - if (trash || isPublic || currentMode === undefined) return; + if (trash || isSharedMode || currentMode === undefined) return; return registerAffineCommand({ id: 'affine:doc-mode-switch', category: 'editor:page', @@ -87,14 +80,14 @@ export const EditorModeSwitch = ({ }, run: () => onModeChange(currentMode === 'edgeless' ? 'page' : 'edgeless'), }); - }, [currentMode, isPublic, onModeChange, t, trash]); + }, [currentMode, isSharedMode, onModeChange, t, trash]); return (
void; + setMode?: (mode: DocMode) => void; hidePage?: boolean; hideEdgeless?: boolean; } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index 6b7a62899d..c1c4de0078 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -9,19 +9,14 @@ import type { DocCollection } from '../../../shared'; export const usePageHelper = (docCollection: DocCollection) => { const workbench = useService(WorkbenchService).workbench; const { createDoc } = useDocCollectionHelper(docCollection); - const docRecordList = useService(DocsService).list; - - const isPreferredEdgeless = useCallback( - (pageId: string) => - docRecordList.doc$(pageId).value?.mode$.value === 'edgeless', - [docRecordList] - ); + const docsService = useService(DocsService); + const docRecordList = docsService.list; const createPageAndOpen = useCallback( (mode?: 'page' | 'edgeless', open?: boolean | 'new-tab') => { const page = createDoc(); initEmptyPage(page); - docRecordList.doc$(page.id).value?.setMode(mode || 'page'); + docRecordList.doc$(page.id).value?.setPrimaryMode(mode || 'page'); if (open !== false) workbench.openDoc(page.id, { at: open === 'new-tab' ? 'new-tab' : 'active', @@ -82,16 +77,10 @@ export const usePageHelper = (docCollection: DocCollection) => { return useMemo(() => { return { - isPreferredEdgeless, createPage: (open?: boolean | 'new-tab') => createPageAndOpen('page', open), createEdgeless: createEdgelessAndOpen, importFile: importFileAndOpen, }; - }, [ - isPreferredEdgeless, - createEdgelessAndOpen, - createPageAndOpen, - importFileAndOpen, - ]); + }, [createEdgelessAndOpen, createPageAndOpen, importFileAndOpen]); }; diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index e8b1797e88..34fbe5849e 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -6,7 +6,6 @@ import type { AffineEditorContainer } from '@blocksuite/presets'; import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store'; import { type DocMode, - DocService, fontStyleOptions, useLiveData, useService, @@ -17,6 +16,7 @@ import { memo, Suspense, useCallback, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper'; +import { EditorService } from '../modules/editor'; import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor'; import * as styles from './page-detail-editor.css'; @@ -45,19 +45,10 @@ function useRouterHash() { const PageDetailEditorMain = memo(function PageDetailEditorMain({ page, onLoad, - isPublic, - publishMode, }: PageDetailEditorProps & { page: BlockSuiteDoc }) { - const currentMode = useLiveData(useService(DocService).doc.mode$); - const mode = useMemo(() => { - const shareMode = publishMode || currentMode; - - if (isPublic) { - return shareMode; - } - return currentMode; - }, [isPublic, publishMode, currentMode]); - + const editor = useService(EditorService).editor; + const mode = useLiveData(editor.mode$); + const isSharedMode = editor.isSharedMode; const { appSettings } = useAppSettingHelper(); const value = useMemo(() => { @@ -97,8 +88,8 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({ return ( diff --git a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx index 9a04b7fd94..fe93156d05 100644 --- a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx +++ b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx @@ -9,7 +9,6 @@ import type { DocMeta } from '@blocksuite/store'; import { useService, WorkspaceService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; -import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; import { usePageItemGroupDefinitions } from '../group-definitions'; import { usePageHeaderColsDef } from '../header-col-def'; @@ -69,7 +68,6 @@ export const VirtualizedPageList = ({ const currentWorkspace = useService(WorkspaceService).workspace; const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection); const pageOperations = usePageOperationsRenderer(); - const { isPreferredEdgeless } = usePageHelper(currentWorkspace.docCollection); const pageHeaderColsDef = usePageHeaderColsDef(); const filteredPageMetas = useFilteredPageMetas(pageMetas, { @@ -162,7 +160,6 @@ export const VirtualizedPageList = ({ onSelectedIdsChange={setSelectedPageIds} items={pageMetasToRender} rowAsLink - isPreferredEdgeless={isPreferredEdgeless} docCollection={currentWorkspace.docCollection} operationsRenderer={pageOperationRenderer} itemRenderer={pageItemRenderer} diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 5f039e37da..42e16fe7ef 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -201,7 +201,6 @@ export const ItemGroup = ({ const requiredPropNames = [ 'docCollection', 'rowAsLink', - 'isPreferredEdgeless', 'operationsRenderer', 'selectedIds', 'onSelectedIdsChange', @@ -294,13 +293,12 @@ const PageTitle = ({ id }: { id: string }) => { const UnifiedPageIcon = ({ id, docCollection, - isPreferredEdgeless, }: { id: string; docCollection: DocCollection; - isPreferredEdgeless?: (id: string) => boolean; }) => { - const isEdgeless = isPreferredEdgeless ? isPreferredEdgeless(id) : false; + const list = useService(DocsService).list; + const isEdgeless = useLiveData(list.primaryMode$(id)) === 'edgeless'; const { isJournal } = useJournalInfoHelper(docCollection, id); if (isJournal) { return ; @@ -340,13 +338,7 @@ function pageMetaToListItemProp( updatedDate: item.updatedDate ? new Date(item.updatedDate) : undefined, to: props.rowAsLink && !props.selectable ? `/${item.id}` : undefined, onClick: toggleSelection, - icon: ( - - ), + icon: , tags: item.tags ?.map(id => tagIdToTagOption(id, props.docCollection)) diff --git a/packages/frontend/core/src/components/page-list/types.ts b/packages/frontend/core/src/components/page-list/types.ts index e0bcad3027..9a3e451163 100644 --- a/packages/frontend/core/src/components/page-list/types.ts +++ b/packages/frontend/core/src/components/page-list/types.ts @@ -95,7 +95,6 @@ export interface ListProps { className?: string; hideHeader?: boolean; // whether or not to hide the header. default is false (showing header) groupBy?: ItemGroupDefinition[]; - isPreferredEdgeless?: (pageId: string) => boolean; // determines the icon used for each row rowAsLink?: boolean; selectable?: 'toggle' | boolean; // show selection checkbox. toggle means showing a toggle selection in header on click; boolean == true means showing a selection checkbox for each item selectedIds?: string[]; // selected page ids diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx b/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx index e341e15147..1caf4c5cf2 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx +++ b/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx @@ -192,7 +192,6 @@ export const EditCollection = ({ export type AllPageListConfig = { allPages: DocMeta[]; docCollection: DocCollection; - isEdgeless: (id: string) => boolean; /** * Return `undefined` if the page is not public */ diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx b/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx index 3c3c2710e4..35f051794b 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx +++ b/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx @@ -9,7 +9,7 @@ import { ToggleCollapseIcon, } from '@blocksuite/icons/rc'; import type { DocMeta } from '@blocksuite/store'; -import { useLiveData, useService } from '@toeverything/infra'; +import { DocsService, useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import clsx from 'clsx'; import type { ReactNode } from 'react'; @@ -42,6 +42,7 @@ export const RulesMode = ({ const [showPreview, setShowPreview] = useState(true); const allowListPages: DocMeta[] = []; const rulesPages: DocMeta[] = []; + const docsService = useService(DocsService); const favAdapter = useService(CompatibleFavoriteItemsAdapter); const favorites = useLiveData(favAdapter.favorites$); allPageListConfig.allPages.forEach(meta => { @@ -158,7 +159,8 @@ export const RulesMode = ({ alignItems: 'center', }} > - {allPageListConfig.isEdgeless(id) ? ( + {docsService.list.getPrimaryMode(id) === + 'edgeless' ? ( ) : ( @@ -211,7 +213,6 @@ export const RulesMode = ({ className={styles.resultPages} items={rulesPages} docCollection={allPageListConfig.docCollection} - isPreferredEdgeless={allPageListConfig.isEdgeless} operationsRenderer={operationsRenderer} > ) : ( @@ -230,7 +231,6 @@ export const RulesMode = ({ className={styles.resultPages} items={allowListPages} docCollection={allPageListConfig.docCollection} - isPreferredEdgeless={allPageListConfig.isEdgeless} operationsRenderer={operationsRenderer} >
diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx b/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx index c8a7b8cbb5..00b45abdeb 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx +++ b/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx @@ -7,7 +7,6 @@ import { Trans, useI18n } from '@affine/i18n'; import { FilterIcon } from '@blocksuite/icons/rc'; import type { DocMeta } from '@blocksuite/store'; import { - DocsService, useLiveData, useServices, WorkspaceService, @@ -57,17 +56,12 @@ export const SelectPage = ({ const clearSelected = useCallback(() => { onChange([]); }, [onChange]); - const { - workspaceService, - compatibleFavoriteItemsAdapter, - shareDocsService, - docsService, - } = useServices({ - DocsService, - ShareDocsService, - WorkspaceService, - CompatibleFavoriteItemsAdapter, - }); + const { workspaceService, compatibleFavoriteItemsAdapter, shareDocsService } = + useServices({ + ShareDocsService, + WorkspaceService, + CompatibleFavoriteItemsAdapter, + }); const shareDocs = useLiveData(shareDocsService.shareDocs?.list$); const workspace = workspaceService.workspace; const docCollection = workspace.docCollection; @@ -97,13 +91,6 @@ export const SelectPage = ({ [favourites] ); - const isEdgeless = useCallback( - (id: string) => { - return docsService.list.doc$(id).value?.mode$.value === 'edgeless'; - }, - [docsService.list] - ); - const onToggleFavoritePage = useCallback( (page: DocMeta) => { const status = isFavorite(page); @@ -207,7 +194,6 @@ export const SelectPage = ({ selectable onSelectedIdsChange={onChange} selectedIds={value} - isPreferredEdgeless={isEdgeless} operationsRenderer={operationsRenderer} itemRenderer={pageItemRenderer} headerRenderer={pageHeaderRenderer} diff --git a/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx b/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx index 0713e9db5f..35e9f63c1e 100644 --- a/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx +++ b/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx @@ -6,7 +6,6 @@ import type { DocMeta } from '@blocksuite/store'; import { useService, WorkspaceService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; -import { usePageHelper } from '../blocksuite/block-suite-page-list/utils'; import { ListFloatingToolbar } from './components/list-floating-toolbar'; import { usePageHeaderColsDef } from './header-col-def'; import { TrashOperationCell } from './operation-cell'; @@ -26,8 +25,6 @@ export const VirtualizedTrashList = () => { trash: true, }); - const { isPreferredEdgeless } = usePageHelper(docCollection); - const listRef = useRef(null); const [showFloatingToolbar, setShowFloatingToolbar] = useState(false); const [selectedPageIds, setSelectedPageIds] = useState([]); @@ -121,7 +118,6 @@ export const VirtualizedTrashList = () => { selectable="toggle" items={filteredPageMetas} rowAsLink - isPreferredEdgeless={isPreferredEdgeless} onSelectionActiveChange={setShowFloatingToolbar} docCollection={currentWorkspace.docCollection} operationsRenderer={pageOperationsRenderer} diff --git a/packages/frontend/core/src/components/pure/help-island/index.tsx b/packages/frontend/core/src/components/pure/help-island/index.tsx index a0b5502e22..aa8ecc5a68 100644 --- a/packages/frontend/core/src/components/pure/help-island/index.tsx +++ b/packages/frontend/core/src/components/pure/help-island/index.tsx @@ -3,10 +3,9 @@ import { popupWindow } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { CloseIcon, NewIcon } from '@blocksuite/icons/rc'; import { - DocsService, GlobalContextService, useLiveData, - useService, + useServices, } from '@toeverything/infra'; import { useSetAtom } from 'jotai/react'; import { useCallback, useState } from 'react'; @@ -33,12 +32,11 @@ type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts'; const showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST; export const HelpIsland = () => { - const docId = useLiveData( - useService(GlobalContextService).globalContext.docId.$ - ); - const docRecordList = useService(DocsService).list; - const doc = useLiveData(docId ? docRecordList.doc$(docId) : undefined); - const mode = useLiveData(doc?.mode$); + const { globalContextService } = useServices({ + GlobalContextService, + }); + const docId = useLiveData(globalContextService.globalContext.docId.$); + const docMode = useLiveData(globalContextService.globalContext.docMode.$); const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const [spread, setShowSpread] = useState(false); const t = useI18n(); @@ -69,7 +67,7 @@ export const HelpIsland = () => { onClick={() => { setShowSpread(!spread); }} - inEdgelessPage={!!docId && mode === 'edgeless'} + inEdgelessPage={!!docId && docMode === 'edgeless'} > { const workspace = currentWorkspace.docCollection; const pageMetas = useBlockSuiteDocMeta(workspace); - const { isPreferredEdgeless } = usePageHelper(workspace); const pageMap = useMemo( () => Object.fromEntries(pageMetas.map(page => [page.id, page])), [pageMetas] @@ -58,7 +55,6 @@ export const useAllPageListConfig = () => { return useMemo(() => { return { allPages: pageMetas, - isEdgeless: isPreferredEdgeless, getPublicMode(id) { const mode = shareDocs?.find(shareDoc => shareDoc.id === id)?.mode; if (mode === PublicPageMode.Edgeless) { @@ -83,7 +79,6 @@ export const useAllPageListConfig = () => { }; }, [ pageMetas, - isPreferredEdgeless, currentWorkspace.docCollection, shareDocs, pageMap, diff --git a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts index 709f1924c3..12066322c8 100644 --- a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts +++ b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts @@ -75,7 +75,8 @@ export function useBlockSuiteMetaHelper(docCollection: DocCollection) { const duplicate = useAsyncCallback( async (pageId: string, openPageAfterDuplication: boolean = true) => { - const currentPageMode = pageRecordList.doc$(pageId).value?.mode$.value; + const currentPagePrimaryMode = + pageRecordList.doc$(pageId).value?.primaryMode$.value; const currentPageMeta = getDocMeta(pageId); const newPage = createDoc(); const currentPage = docCollection.getDoc(pageId); @@ -99,7 +100,9 @@ export function useBlockSuiteMetaHelper(docCollection: DocCollection) { const newPageTitle = currentPageMeta.title.replace(lastDigitRegex, '') + `(${newNumber})`; - pageRecordList.doc$(newPage.id).value?.setMode(currentPageMode || 'page'); + pageRecordList + .doc$(newPage.id) + .value?.setPrimaryMode(currentPagePrimaryMode || 'page'); setDocTitle(newPage.id, newPageTitle); openPageAfterDuplication && openPage(docCollection.id, newPage.id); }, diff --git a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx index 058b457553..f6468301ca 100644 --- a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -5,6 +5,7 @@ import { registerAffineCommand, } from '@affine/core/commands'; import { track } from '@affine/core/mixpanel'; +import type { Editor } from '@affine/core/modules/editor'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; @@ -23,10 +24,10 @@ import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper'; import { useExportPage } from './use-export-page'; import { useTrashModalHelper } from './use-trash-modal-helper'; -export function useRegisterBlocksuiteEditorCommands() { +export function useRegisterBlocksuiteEditorCommands(editor: Editor) { const doc = useService(DocService).doc; const docId = doc.id; - const mode = useLiveData(doc.mode$); + const mode = useLiveData(editor.mode$); const t = useI18n(); const workspace = useService(WorkspaceService).workspace; const docCollection = workspace.docCollection; @@ -149,7 +150,7 @@ export function useRegisterBlocksuiteEditorCommands() { mode: mode === 'page' ? 'edgeless' : 'page', }); - doc.toggleMode(); + editor.toggleMode(); toast( mode === 'page' ? t['com.affine.toastMessage.edgelessMode']() @@ -311,6 +312,7 @@ export function useRegisterBlocksuiteEditorCommands() { unsubs.forEach(unsub => unsub()); }; }, [ + editor, favorite, mode, onClickDelete, diff --git a/packages/frontend/core/src/layouts/workspace-layout.tsx b/packages/frontend/core/src/layouts/workspace-layout.tsx index 085a251110..540c5000c0 100644 --- a/packages/frontend/core/src/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/layouts/workspace-layout.tsx @@ -101,7 +101,7 @@ const WorkspaceLayoutProviders = ({ children }: PropsWithChildren) => { timeout(10000 /* 10s */), mergeMap(({ mode, doc }) => { if (doc) { - docsList.setMode(doc.id, mode as DocMode); + docsList.setPrimaryMode(doc.id, mode as DocMode); workbench.openDoc(doc.id); } return EMPTY; diff --git a/packages/frontend/core/src/modules/editor/entities/editor.ts b/packages/frontend/core/src/modules/editor/entities/editor.ts new file mode 100644 index 0000000000..3cc26759ca --- /dev/null +++ b/packages/frontend/core/src/modules/editor/entities/editor.ts @@ -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(); + } +} diff --git a/packages/frontend/core/src/modules/editor/index.ts b/packages/frontend/core/src/modules/editor/index.ts new file mode 100644 index 0000000000..3ee383b183 --- /dev/null +++ b/packages/frontend/core/src/modules/editor/index.ts @@ -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]); +} diff --git a/packages/frontend/core/src/modules/editor/scopes/editor.ts b/packages/frontend/core/src/modules/editor/scopes/editor.ts new file mode 100644 index 0000000000..250dc9cb8e --- /dev/null +++ b/packages/frontend/core/src/modules/editor/scopes/editor.ts @@ -0,0 +1,7 @@ +import { Scope } from '@toeverything/infra'; + +import type { Editor } from '../entities/editor'; + +export class EditorScope extends Scope<{ + editor: Editor; +}> {} diff --git a/packages/frontend/core/src/modules/editor/services/editor.ts b/packages/frontend/core/src/modules/editor/services/editor.ts new file mode 100644 index 0000000000..f4780a64e7 --- /dev/null +++ b/packages/frontend/core/src/modules/editor/services/editor.ts @@ -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(); + } +} diff --git a/packages/frontend/core/src/modules/editor/services/editors.ts b/packages/frontend/core/src/modules/editor/services/editors.ts new file mode 100644 index 0000000000..c60dca7853 --- /dev/null +++ b/packages/frontend/core/src/modules/editor/services/editors.ts @@ -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 }); + } +} diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx index 0a111b4153..cf8f4a6d79 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx @@ -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' ? ( ) : ( ) - ) : docMode === 'edgeless' ? ( + ) : docPrimaryMode === 'edgeless' ? ( ) : ( ); }, - [docMode, isLinked] + [docPrimaryMode, isLinked] ); const children = useLiveData( diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index 351f34093a..f7076a3291 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -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); } diff --git a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx index 71852c9213..36fb67150e 100644 --- a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx @@ -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(null); + const [editorElement, setEditorElement] = + useState(null); const onRef = (editor: AffineEditorContainer) => { - setEditor(editor); + setEditorElement(editor); }; const docs = useService(DocsService); const [resolvedMode, setResolvedMode] = useState(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({ /> diff --git a/packages/frontend/core/src/modules/peek-view/view/image-preview/index.tsx b/packages/frontend/core/src/modules/peek-view/view/image-preview/index.tsx index 72e99a0889..5b0779db20 100644 --- a/packages/frontend/core/src/modules/peek-view/view/image-preview/index.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/image-preview/index.tsx @@ -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(() => { diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx index 0c2c04ab69..279fc2f88f 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx @@ -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'); }, }, diff --git a/packages/frontend/core/src/modules/peek-view/view/utils.ts b/packages/frontend/core/src/modules/peek-view/view/utils.ts index 71c15568e5..d88e17d130 100644 --- a/packages/frontend/core/src/modules/peek-view/view/utils.ts +++ b/packages/frontend/core/src/modules/peek-view/view/utils.ts @@ -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(null); + const [editor, setEditor] = useState(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 }; }; diff --git a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts index 2c7097088c..6634f3a8da 100644 --- a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts +++ b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts @@ -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); diff --git a/packages/frontend/core/src/modules/quicksearch/services/doc-display-meta.ts b/packages/frontend/core/src/modules/quicksearch/services/doc-display-meta.ts index 814d8b3834..73d3139a23 100644 --- a/packages/frontend/core/src/modules/quicksearch/services/doc-display-meta.ts +++ b/packages/frontend/core/src/modules/quicksearch/services/doc-display-meta.ts @@ -16,7 +16,7 @@ export class DocDisplayMetaService extends Service { ); const icon = journalDateString ? TodayIcon - : docRecord.mode$.value === 'edgeless' + : docRecord.primaryMode$.value === 'edgeless' ? EdgelessIcon : PageIcon; diff --git a/packages/frontend/core/src/pages/share/share-detail-page.tsx b/packages/frontend/core/src/pages/share/share-detail-page.tsx index c9bd0fd1e2..6eccf5cf20 100644 --- a/packages/frontend/core/src/pages/share/share-detail-page.tsx +++ b/packages/frontend/core/src/pages/share/share-detail-page.tsx @@ -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(null); const [page, setPage] = useState(null); + const [editor, setEditor] = useState(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 ( - - -
-
- - - - - {publishMode === 'page' ? : null} - - - - {loginStatus !== 'authenticated' ? ( - - - {t['com.affine.share-page.footer.built-with']()} - - - - ) : null} + + + +
+
+ + + + + {publishMode === 'page' ? : null} + + + + {loginStatus !== 'authenticated' ? ( + + + {t['com.affine.share-page.footer.built-with']()} + + + + ) : null} +
-
- - - + + + + ); diff --git a/packages/frontend/core/src/pages/share/share-header.tsx b/packages/frontend/core/src/pages/share/share-header.tsx index f4eccc41b2..ac772b3406 100644 --- a/packages/frontend/core/src/pages/share/share-header.tsx +++ b/packages/frontend/core/src/pages/share/share-header.tsx @@ -17,12 +17,8 @@ export function ShareHeader({ }) { return (
- - + +
- +
{ - track.$.header.actions.renameDoc(); - }, []); + const editor = useService(EditorService).editor; + const currentMode = useLiveData(editor.mode$); return (
- +
{hideCollect ? null : ( diff --git a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx index 8c8eb7c352..539f2f6ca3 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx @@ -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(null); - const workspace = useService(WorkspaceService).workspace; - const globalContext = useService(GlobalContextService).globalContext; - const docCollection = workspace.docCollection; - const mode = useLiveData(doc.mode$); + const [editorContainer, setEditorContainer] = + useState(null); + const isSideBarOpen = useLiveData(workbench.sidebarOpen$); const { appSettings } = useAppSettingHelper(); const chatPanelRef = useRef(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 ( - <> + @@ -271,7 +293,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { /> @@ -281,7 +303,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { } unmountOnInactive={false}> - + }> @@ -289,16 +311,16 @@ const DetailPageImpl = memo(function DetailPageImpl() { }> - + }> - + - + ); }); @@ -310,6 +332,7 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => { const docRecord = useLiveData(docRecordList.doc$(pageId)); const [doc, setDoc] = useState(null); + const [editor, setEditor] = useState(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 ; } - if (!doc) { + if (!doc || !editor) { return ; } return ( - + + + ); }; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/tabs/journal.tsx b/packages/frontend/core/src/pages/workspace/detail-page/tabs/journal.tsx index 848346d7db..8b8dffc60f 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/tabs/journal.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/tabs/journal.tsx @@ -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, diff --git a/packages/frontend/core/src/pages/workspace/detail-page/use-header-responsive.ts b/packages/frontend/core/src/pages/workspace/detail-page/use-header-responsive.ts index 3ee4951a36..9d37064efb 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/use-header-responsive.ts +++ b/packages/frontend/core/src/pages/workspace/detail-page/use-header-responsive.ts @@ -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();