From 2cba8a4ccd0ff5bc479067ae98f0392ff9938c1e Mon Sep 17 00:00:00 2001 From: JimmFly Date: Thu, 12 Sep 2024 07:55:22 +0000 Subject: [PATCH] fix(core): title could not be changed when creating a new doc (#8203) Before change, the title could not be modified from outside the editor without refreshing: https://github.com/user-attachments/assets/536acba1-4e31-418a-bc1a-8578e3128bba after: https://github.com/user-attachments/assets/30a4b270-b8b1-4787-acef-0ab2a72a8f74 --- .../infra/src/modules/doc/entities/record.ts | 4 +- .../modules/workspace/entities/workspace.ts | 3 + .../workspace/detail/mobile-detail-page.tsx | 2 +- .../mobile/src/provider/model-provider.tsx | 4 +- .../affine/create-workspace-modal/index.tsx | 8 +- .../core/src/components/affine/empty/docs.tsx | 1 - .../affine/page-history-modal/data.ts | 2 +- .../affine/reference-link/index.tsx | 2 +- .../general-setting/appearance/index.tsx | 2 +- .../general-setting/editor/general.tsx | 2 +- .../block-suite-header/menu/index.tsx | 6 +- .../block-suite-page-list/utils.tsx | 9 +- .../page-list/docs/page-list-header.tsx | 6 +- .../page-list/docs/virtualized-page-list.tsx | 2 +- .../components/page-list/operation-cell.tsx | 6 +- .../page-list/tags/tag-list-item.tsx | 2 +- .../page-list/virtualized-trash-list.tsx | 3 +- .../pure/trash-page-footer/index.tsx | 2 +- .../src/components/root-app-sidebar/index.tsx | 6 +- .../affine/use-block-suite-meta-helper.ts | 97 +++++++------------ ...se-register-blocksuite-editor-commands.tsx | 5 +- .../hooks/affine/use-trash-modal-helper.ts | 6 +- .../src/hooks/use-block-suite-page-meta.ts | 78 +++++++++------ .../explorer/views/nodes/doc/operations.tsx | 16 +++ .../workspace/detail-page/detail-page.tsx | 2 +- .../workspace/detail-page/tabs/journal.tsx | 3 +- .../core/src/providers/modal-provider.tsx | 4 +- 27 files changed, 138 insertions(+), 145 deletions(-) diff --git a/packages/common/infra/src/modules/doc/entities/record.ts b/packages/common/infra/src/modules/doc/entities/record.ts index ae582f7185..c07c6cc636 100644 --- a/packages/common/infra/src/modules/doc/entities/record.ts +++ b/packages/common/infra/src/modules/doc/entities/record.ts @@ -39,11 +39,11 @@ export class DocRecord extends Entity<{ id: string }> { } moveToTrash() { - return this.setMeta({ trash: true }); + return this.setMeta({ trash: true, trashDate: Date.now() }); } restoreFromTrash() { - return this.setMeta({ trash: false }); + return this.setMeta({ trash: false, trashDate: undefined }); } title$ = this.meta$.map(meta => meta.title ?? ''); diff --git a/packages/common/infra/src/modules/workspace/entities/workspace.ts b/packages/common/infra/src/modules/workspace/entities/workspace.ts index bf4120f382..a2ebfa7884 100644 --- a/packages/common/infra/src/modules/workspace/entities/workspace.ts +++ b/packages/common/infra/src/modules/workspace/entities/workspace.ts @@ -35,6 +35,9 @@ export class Workspace extends Entity { idGenerator: () => nanoid(), schema: globalBlockSuiteSchema, }); + this._docCollection.slots.docCreated.on(id => { + this.engine.doc.markAsReady(id); + }); } return this._docCollection; } diff --git a/packages/frontend/apps/mobile/src/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/apps/mobile/src/pages/workspace/detail/mobile-detail-page.tsx index 899df02a8c..9dcad9e54b 100644 --- a/packages/frontend/apps/mobile/src/pages/workspace/detail/mobile-detail-page.tsx +++ b/packages/frontend/apps/mobile/src/pages/workspace/detail/mobile-detail-page.tsx @@ -62,7 +62,7 @@ const DetailPageImpl = () => { const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper(); const editorContainer = useLiveData(editor.editorContainer$); - const { setDocReadonly } = useDocMetaHelper(workspace.docCollection); + const { setDocReadonly } = useDocMetaHelper(); // TODO(@eyhn): remove jotai here const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor(); diff --git a/packages/frontend/apps/mobile/src/provider/model-provider.tsx b/packages/frontend/apps/mobile/src/provider/model-provider.tsx index ad247186be..060e74d4e0 100644 --- a/packages/frontend/apps/mobile/src/provider/model-provider.tsx +++ b/packages/frontend/apps/mobile/src/provider/model-provider.tsx @@ -22,9 +22,7 @@ import { MobileSignInModal } from '../views/sign-in/modal'; export function MobileCurrentWorkspaceModals() { const currentWorkspace = useService(WorkspaceService).workspace; - const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper( - currentWorkspace.docCollection - ); + const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper(); const deletePageTitles = trashModal.pageTitles; const trashConfirmOpen = trashModal.open; const onTrashConfirmOpenChange = useCallback( diff --git a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx index b4ee0157f0..fe7cdaca07 100644 --- a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx @@ -9,7 +9,7 @@ import { apis } from '@affine/electron-api'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; import { - initEmptyPage, + DocsService, useLiveData, useService, WorkspacesService, @@ -179,6 +179,7 @@ export const CreateWorkspaceModal = ({ const [step, setStep] = useState(); const t = useI18n(); const workspacesService = useService(WorkspacesService); + const docsService = useService(DocsService); const [loading, setLoading] = useState(false); // TODO(@Peng): maybe refactor using xstate? @@ -242,9 +243,8 @@ export const CreateWorkspaceModal = ({ async workspace => { workspace.meta.initialize(); workspace.meta.setName(name); - const page = workspace.createDoc(); + const page = docsService.createDoc(); defaultDocId = page.id; - initEmptyPage(page); } ); onCreate(id, defaultDocId); @@ -252,7 +252,7 @@ export const CreateWorkspaceModal = ({ setLoading(false); }, - [loading, onCreate, workspacesService] + [docsService, loading, onCreate, workspacesService] ); const onOpenChange = useCallback( diff --git a/packages/frontend/core/src/components/affine/empty/docs.tsx b/packages/frontend/core/src/components/affine/empty/docs.tsx index fa489acec1..c76bb774c1 100644 --- a/packages/frontend/core/src/components/affine/empty/docs.tsx +++ b/packages/frontend/core/src/components/affine/empty/docs.tsx @@ -39,7 +39,6 @@ export const EmptyDocs = ({ undefined, isNewTabTrigger(e) ? 'new-tab' : true ); - doc.load(); if (tag) tag.tag(doc.id); }, diff --git a/packages/frontend/core/src/components/affine/page-history-modal/data.ts b/packages/frontend/core/src/components/affine/page-history-modal/data.ts index 2df0f71c41..feb6e40beb 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/data.ts +++ b/packages/frontend/core/src/components/affine/page-history-modal/data.ts @@ -239,7 +239,7 @@ export const useRestorePage = ( const { trigger: recover, isMutating } = useMutation({ mutation: recoverDocMutation, }); - const { getDocMeta, setDocTitle } = useDocMetaHelper(docCollection); + const { getDocMeta, setDocTitle } = useDocMetaHelper(); const onRestore = useMemo(() => { return async (version: string, update: Uint8Array) => { 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 0a6eac7cf3..2ebc2ff3c9 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -95,7 +95,7 @@ export function AffinePageReference({ mode?: DocMode; params?: URLSearchParams; }) { - const pageMetaHelper = useDocMetaHelper(docCollection); + const pageMetaHelper = useDocMetaHelper(); const journalHelper = useJournalHelper(docCollection); const t = useI18n(); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx index 90a76964fa..639f4b84b7 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx @@ -120,7 +120,7 @@ export const AppearanceSettings = () => { ) : null} {runtimeConfig.enableThemeEditor ? : null} - {/* //TODO(@JimmFly): remove Page component when stable release */} + {/* // TODO(@JimmFly): remove Page component when stable release */} {runtimeConfig.enableNewSettingUnstableApi ? ( diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx index 88fecdf40b..6ef478b931 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx @@ -459,7 +459,7 @@ export const General = () => { - {/* //TODO(@akumatus): implement these settings + {/* // TODO(@akumatus): implement these settings */} 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 e2bb4017ed..7e07dc26e5 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 @@ -81,12 +81,12 @@ export const PageHeaderMenuButton = ({ const { favorite, toggleFavorite } = useFavorite(pageId); - const { duplicate } = useBlockSuiteMetaHelper(docCollection); + const { duplicate } = useBlockSuiteMetaHelper(); const { importFile } = usePageHelper(docCollection); - const { setTrashModal } = useTrashModalHelper(docCollection); + const { setTrashModal } = useTrashModalHelper(); const [isEditing, setEditing] = useState(!page.readonly); - const { setDocReadonly } = useDocMetaHelper(docCollection); + const { setDocReadonly } = useDocMetaHelper(); const view = useService(ViewService).view; 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 195b7d8b20..941d7942cd 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 @@ -1,8 +1,7 @@ import { toast } from '@affine/component'; -import { useDocCollectionHelper } from '@affine/core/hooks/use-block-suite-workspace-helper'; import { WorkbenchService } from '@affine/core/modules/workbench'; import type { DocMode } from '@blocksuite/blocks'; -import { DocsService, initEmptyPage, useServices } from '@toeverything/infra'; +import { DocsService, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import type { DocCollection } from '../../../shared'; @@ -13,13 +12,11 @@ export const usePageHelper = (docCollection: DocCollection) => { WorkbenchService, }); const workbench = workbenchService.workbench; - const { createDoc } = useDocCollectionHelper(docCollection); const docRecordList = docsService.list; const createPageAndOpen = useCallback( (mode?: DocMode, open?: boolean | 'new-tab') => { - const page = createDoc(); - initEmptyPage(page); + const page = docsService.createDoc(); if (mode) { docRecordList.doc$(page.id).value?.setPrimaryMode(mode); } @@ -30,7 +27,7 @@ export const usePageHelper = (docCollection: DocCollection) => { }); return page; }, - [createDoc, docRecordList, workbench] + [docRecordList, docsService, workbench] ); const createEdgelessAndOpen = useCallback( diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx index baa63d11b2..0c7df917fd 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx @@ -19,7 +19,7 @@ import { SearchIcon, ViewLayersIcon, } from '@blocksuite/icons/rc'; -import type { Doc as BlockSuiteDoc } from '@blocksuite/store'; +import type { DocRecord } from '@toeverything/infra'; import { useLiveData, useService, @@ -122,7 +122,7 @@ export const CollectionPageListHeader = ({ const { openConfirmModal } = useConfirmModal(); const createAndAddDocument = useCallback( - (createDocumentFn: () => BlockSuiteDoc) => { + (createDocumentFn: () => DocRecord) => { const newDoc = createDocumentFn(); collectionService.addPageToCollection(collection.id, newDoc.id); }, @@ -130,7 +130,7 @@ export const CollectionPageListHeader = ({ ); const onConfirmAddDocument = useCallback( - (createDocumentFn: () => BlockSuiteDoc) => { + (createDocumentFn: () => DocRecord) => { openConfirmModal({ title: t['com.affine.collection.add-doc.confirm.title'](), description: t['com.affine.collection.add-doc.confirm.description'](), 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 fe93156d05..c075e72a3b 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 @@ -122,7 +122,7 @@ export const VirtualizedPageList = ({ return ; }, [collection, currentWorkspace.id, tag]); - const { setTrashModal } = useTrashModalHelper(currentWorkspace.docCollection); + const { setTrashModal } = useTrashModalHelper(); const handleMultiDelete = useCallback(() => { if (filteredSelectedPageIds.length === 0) { diff --git a/packages/frontend/core/src/components/page-list/operation-cell.tsx b/packages/frontend/core/src/components/page-list/operation-cell.tsx index 49687992ab..05ce681c8b 100644 --- a/packages/frontend/core/src/components/page-list/operation-cell.tsx +++ b/packages/frontend/core/src/components/page-list/operation-cell.tsx @@ -80,10 +80,10 @@ export const PageOperationCell = ({ featureFlagService.flags.enable_multi_view.$ ); const currentWorkspace = workspaceService.workspace; - const { setTrashModal } = useTrashModalHelper(currentWorkspace.docCollection); + const { setTrashModal } = useTrashModalHelper(); const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc')); const workbench = workbenchService.workbench; - const { duplicate } = useBlockSuiteMetaHelper(currentWorkspace.docCollection); + const { duplicate } = useBlockSuiteMetaHelper(); const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id); const [openInfoModal, setOpenInfoModal] = useState(false); @@ -93,7 +93,7 @@ export const PageOperationCell = ({ }, []); const onDisablePublicSharing = useCallback(() => { - //TODO(@EYHN): implement disable public sharing + // TODO(@EYHN): implement disable public sharing toast('Successfully disabled', { portal: document.body, }); diff --git a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx index d3be08402d..da54dc177c 100644 --- a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx +++ b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx @@ -23,7 +23,7 @@ const TagListTitleCell = ({ > {title || t['Untitled']()} - {/* //TODO(@EYHN): when indexer is ready, add this back + {/* // TODO(@EYHN): when indexer is ready, add this back
{ const currentWorkspace = useService(WorkspaceService).workspace; const docCollection = currentWorkspace.docCollection; - const { restoreFromTrash, permanentlyDeletePage } = - useBlockSuiteMetaHelper(docCollection); + const { restoreFromTrash, permanentlyDeletePage } = useBlockSuiteMetaHelper(); const pageMetas = useBlockSuiteDocMeta(docCollection); const filteredPageMetas = useFilteredPageMetas(pageMetas, { trash: true, diff --git a/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx b/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx index 55d20f742f..1f34e56631 100644 --- a/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx +++ b/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx @@ -19,7 +19,7 @@ export const TrashPageFooter = () => { const t = useI18n(); const { appSettings } = useAppSettingHelper(); const { jumpToSubPath } = useNavigateHelper(); - const { restoreFromTrash } = useBlockSuiteMetaHelper(docCollection); + const { restoreFromTrash } = useBlockSuiteMetaHelper(); const [open, setOpen] = useState(false); const hintText = t['com.affine.cmdk.affine.editor.trash-footer-hint'](); diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index cedc6e6d52..efd38bbe69 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -99,11 +99,7 @@ export const RootAppSidebar = (): ReactElement => { const onClickNewPage = useAsyncCallback( async (e?: MouseEvent) => { - const page = pageHelper.createPage( - undefined, - isNewTabTrigger(e) ? 'new-tab' : true - ); - page.load(); + pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true); track.$.navigationPanel.$.createDoc(); }, [pageHelper] 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 143df51170..dd0369634a 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 @@ -1,86 +1,59 @@ import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta'; import { useDocCollectionHelper } from '@affine/core/hooks/use-block-suite-workspace-helper'; -import { CollectionService } from '@affine/core/modules/collection'; import type { DocMode } from '@blocksuite/blocks'; -import { DocsService, useService } from '@toeverything/infra'; +import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; import { useCallback } from 'react'; import { applyUpdate, encodeStateAsUpdate } from 'yjs'; -import type { DocCollection } from '../../shared'; import { useNavigateHelper } from '../use-navigate-helper'; -export function useBlockSuiteMetaHelper(docCollection: DocCollection) { - const { setDocMeta, getDocMeta, setDocReadonly, setDocTitle } = - useDocMetaHelper(docCollection); - const { createDoc } = useDocCollectionHelper(docCollection); +export function useBlockSuiteMetaHelper() { + const workspace = useService(WorkspaceService).workspace; + const { setDocMeta, getDocMeta, setDocTitle, setDocReadonly } = + useDocMetaHelper(); + const { createDoc } = useDocCollectionHelper(workspace.docCollection); const { openPage } = useNavigateHelper(); - const collectionService = useService(CollectionService); - const pageRecordList = useService(DocsService).list; + const docRecordList = useService(DocsService).list; // TODO-Doma // "Remove" may cause ambiguity here. Consider renaming as "moveToTrash". const removeToTrash = useCallback( - (pageId: string) => { - setDocMeta(pageId, { - trash: true, - trashDate: Date.now(), - }); - setDocReadonly(pageId, true); - collectionService.deletePagesFromCollections([pageId]); + (docId: string) => { + const docRecord = docRecordList.doc$(docId).value; + if (docRecord) { + docRecord.moveToTrash(); + setDocReadonly(docId, true); + } }, - [collectionService, setDocMeta, setDocReadonly] + [docRecordList, setDocReadonly] ); const restoreFromTrash = useCallback( - (pageId: string) => { - setDocMeta(pageId, { - trash: false, - trashDate: undefined, - }); - setDocReadonly(pageId, false); + (docId: string) => { + const docRecord = docRecordList.doc$(docId).value; + if (docRecord) { + docRecord.restoreFromTrash(); + setDocReadonly(docId, false); + } }, - [setDocMeta, setDocReadonly] + [docRecordList, setDocReadonly] ); const permanentlyDeletePage = useCallback( (pageId: string) => { - docCollection.removeDoc(pageId); + workspace.docCollection.removeDoc(pageId); }, - [docCollection] - ); - - /** - * see {@link useBlockSuiteWorkspacePageIsPublic} - */ - const publicPage = useCallback( - (pageId: string) => { - setDocMeta(pageId, { - isPublic: true, - }); - }, - [setDocMeta] - ); - - /** - * see {@link useBlockSuiteWorkspacePageIsPublic} - */ - const cancelPublicPage = useCallback( - (pageId: string) => { - setDocMeta(pageId, { - isPublic: false, - }); - }, - [setDocMeta] + [workspace] ); const duplicate = useAsyncCallback( async (pageId: string, openPageAfterDuplication: boolean = true) => { const currentPagePrimaryMode = - pageRecordList.doc$(pageId).value?.primaryMode$.value; + docRecordList.doc$(pageId).value?.primaryMode$.value; const currentPageMeta = getDocMeta(pageId); const newPage = createDoc(); - const currentPage = docCollection.getDoc(pageId); + const currentPage = workspace.docCollection.getDoc(pageId); newPage.load(); if (!currentPageMeta || !currentPage) { @@ -95,33 +68,31 @@ export function useBlockSuiteMetaHelper(docCollection: DocCollection) { }); const lastDigitRegex = /\((\d+)\)$/; - const match = currentPageMeta.title.match(lastDigitRegex); + const match = currentPageMeta?.title?.match(lastDigitRegex); const newNumber = match ? parseInt(match[1], 10) + 1 : 1; const newPageTitle = - currentPageMeta.title.replace(lastDigitRegex, '') + `(${newNumber})`; + currentPageMeta?.title?.replace(lastDigitRegex, '') + `(${newNumber})`; - pageRecordList + docRecordList .doc$(newPage.id) .value?.setPrimaryMode(currentPagePrimaryMode || ('page' as DocMode)); setDocTitle(newPage.id, newPageTitle); - openPageAfterDuplication && openPage(docCollection.id, newPage.id); + openPageAfterDuplication && + openPage(workspace.docCollection.id, newPage.id); }, [ - docCollection, - createDoc, + docRecordList, getDocMeta, - openPage, - pageRecordList, + createDoc, + workspace.docCollection, setDocMeta, setDocTitle, + openPage, ] ); return { - publicPage, - cancelPublicPage, - removeToTrash, restoreFromTrash, permanentlyDeletePage, 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 b0b1df85e4..a9c6983e98 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 @@ -30,7 +30,6 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { const mode = useLiveData(editor.mode$); const t = useI18n(); const workspace = useService(WorkspaceService).workspace; - const docCollection = workspace.docCollection; const favAdapter = useService(CompatibleFavoriteItemsAdapter); const favorite = useLiveData(favAdapter.isFavorite$(docId, 'doc')); @@ -50,9 +49,9 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { setInfoModalState(true); }, [setInfoModalState]); - const { duplicate } = useBlockSuiteMetaHelper(docCollection); + const { duplicate } = useBlockSuiteMetaHelper(); const exportHandler = useExportPage(); - const { setTrashModal } = useTrashModalHelper(docCollection); + const { setTrashModal } = useTrashModalHelper(); const onClickDelete = useCallback( (title: string) => { setTrashModal({ diff --git a/packages/frontend/core/src/hooks/affine/use-trash-modal-helper.ts b/packages/frontend/core/src/hooks/affine/use-trash-modal-helper.ts index f300294c26..dd78e282a7 100644 --- a/packages/frontend/core/src/hooks/affine/use-trash-modal-helper.ts +++ b/packages/frontend/core/src/hooks/affine/use-trash-modal-helper.ts @@ -1,18 +1,16 @@ import { toast } from '@affine/component'; import { useI18n } from '@affine/i18n'; -import type { DocCollection } from '@blocksuite/store'; import { useAtom } from 'jotai'; import { useCallback } from 'react'; import { trashModalAtom } from '../../atoms/trash-modal'; import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper'; -export function useTrashModalHelper(docCollection: DocCollection) { +export function useTrashModalHelper() { const t = useI18n(); const [trashModal, setTrashModal] = useAtom(trashModalAtom); const { pageIds } = trashModal; - const { removeToTrash } = useBlockSuiteMetaHelper(docCollection); - + const { removeToTrash } = useBlockSuiteMetaHelper(); const handleOnConfirm = useCallback(() => { pageIds.forEach(pageId => { removeToTrash(pageId); diff --git a/packages/frontend/core/src/hooks/use-block-suite-page-meta.ts b/packages/frontend/core/src/hooks/use-block-suite-page-meta.ts index b0007f14e6..77ef800a02 100644 --- a/packages/frontend/core/src/hooks/use-block-suite-page-meta.ts +++ b/packages/frontend/core/src/hooks/use-block-suite-page-meta.ts @@ -1,8 +1,8 @@ -import type { RootBlockModel } from '@blocksuite/blocks'; -import { assertExists } from '@blocksuite/global/utils'; import type { DocCollection, DocMeta } from '@blocksuite/store'; -import { useMemo } from 'react'; +import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; +import { useCallback, useMemo } from 'react'; +import { useAsyncCallback } from './affine-async-hooks'; import { useAllBlockSuiteDocMeta } from './use-all-block-suite-page-meta'; import { useJournalHelper } from './use-journal'; @@ -23,34 +23,54 @@ export function useBlockSuiteDocMeta(docCollection: DocCollection) { ); } -export function useDocMetaHelper(docCollection: DocCollection) { +export function useDocMetaHelper() { + const workspaceService = useService(WorkspaceService); + const docsService = useService(DocsService); + + const setDocTitle = useAsyncCallback( + async (docId: string, newTitle: string) => { + await docsService.changeDocTitle(docId, newTitle); + }, + [docsService] + ); + + const setDocMeta = useCallback( + (docId: string, docMeta: Partial) => { + const doc = docsService.list.doc$(docId).value; + if (doc) { + doc.setMeta(docMeta); + } + }, + [docsService] + ); + + const getDocMeta = useCallback( + (docId: string) => { + const doc = docsService.list.doc$(docId).value; + return doc?.meta$.value; + }, + [docsService] + ); + const setDocReadonly = useCallback( + (docId: string, readonly: boolean) => { + const doc = workspaceService.workspace.docCollection.getDoc(docId); + if (doc?.blockCollection) { + workspaceService.workspace.docCollection.awarenessStore.setReadonly( + doc.blockCollection, + readonly + ); + } + }, + [workspaceService] + ); + return useMemo( () => ({ - setDocTitle: (docId: string, newTitle: string) => { - const page = docCollection.getDoc(docId); - assertExists(page); - const pageBlock = page - .getBlockByFlavour('affine:page') - .at(0) as RootBlockModel; - assertExists(pageBlock); - page.transact(() => { - pageBlock.title.delete(0, pageBlock.title.length); - pageBlock.title.insert(newTitle, 0); - }); - docCollection.meta.setDocMeta(docId, { title: newTitle }); - }, - setDocReadonly: (docId: string, readonly: boolean) => { - const page = docCollection.getDoc(docId); - assertExists(page); - page.awarenessStore.setReadonly(page.blockCollection, readonly); - }, - setDocMeta: (docId: string, docMeta: Partial) => { - docCollection.meta.setDocMeta(docId, docMeta); - }, - getDocMeta: (docId: string) => { - return docCollection.meta.getDocMeta(docId); - }, + setDocTitle, + setDocMeta, + getDocMeta, + setDocReadonly, }), - [docCollection] + [getDocMeta, setDocMeta, setDocReadonly, setDocTitle] ); } diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx index a99e62c258..461aeed03d 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx @@ -7,6 +7,7 @@ import { } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; +import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { track } from '@affine/core/mixpanel'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; @@ -14,6 +15,7 @@ import { WorkbenchService } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { DeleteIcon, + DuplicateIcon, InformationIcon, LinkedPageIcon, OpenInNewIcon, @@ -69,6 +71,11 @@ export const useExplorerDocNodeOperations = ( }, [docId, compatibleFavoriteItemsAdapter]) ); + const { duplicate } = useBlockSuiteMetaHelper(); + const handleDuplicate = useCallback(() => { + duplicate(docId, true); + track.$.navigationPanel.docs.createDoc(); + }, [docId, duplicate]); const handleOpenInfoModal = useCallback(() => { track.$.docInfoPanel.$.open(); options.openInfoModal(); @@ -173,6 +180,14 @@ export const useExplorerDocNodeOperations = ( ), }, + { + index: 99, + view: ( + } onClick={handleDuplicate}> + {t['com.affine.header.option.duplicate']()} + + ), + }, { index: 99, view: ( @@ -230,6 +245,7 @@ export const useExplorerDocNodeOperations = ( enableMultiView, favorite, handleAddLinkedPage, + handleDuplicate, handleMoveToTrash, handleOpenInNewTab, handleOpenInSplitView, 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 a7cb31740e..dd754c900d 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 @@ -89,7 +89,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { const isSideBarOpen = useLiveData(workbench.sidebarOpen$); const { appSettings } = useAppSettingHelper(); const chatPanelRef = useRef(null); - const { setDocReadonly } = useDocMetaHelper(workspace.docCollection); + const { setDocReadonly } = useDocMetaHelper(); const isActiveView = useIsActiveView(); // TODO(@eyhn): remove jotai here 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 2a6396505f..22ed324c87 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 @@ -301,9 +301,8 @@ const ConflictList = ({ className, ...attrs }: ConflictListProps) => { - const workspace = useService(WorkspaceService).workspace; const currentDoc = useService(DocService).doc; - const { setTrashModal } = useTrashModalHelper(workspace.docCollection); + const { setTrashModal } = useTrashModalHelper(); const handleOpenTrashModal = useCallback( (docRecord: DocRecord) => { diff --git a/packages/frontend/core/src/providers/modal-provider.tsx b/packages/frontend/core/src/providers/modal-provider.tsx index 9de670958b..3739e5eb04 100644 --- a/packages/frontend/core/src/providers/modal-provider.tsx +++ b/packages/frontend/core/src/providers/modal-provider.tsx @@ -91,9 +91,7 @@ export const Setting = () => { export function CurrentWorkspaceModals() { const currentWorkspace = useService(WorkspaceService).workspace; - const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper( - currentWorkspace.docCollection - ); + const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper(); const deletePageTitles = trashModal.pageTitles; const trashConfirmOpen = trashModal.open; const onTrashConfirmOpenChange = useCallback(