fix(core): fix duplicate in all page (#11229)
This commit is contained in:
@@ -1,20 +1,15 @@
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
|
||||||
import { useDocCollectionHelper } from '@affine/core/components/hooks/use-block-suite-workspace-helper';
|
|
||||||
import { DocsService } from '@affine/core/modules/doc';
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import type { DocMode } from '@blocksuite/affine/model';
|
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
|
||||||
|
|
||||||
import { useNavigateHelper } from '../use-navigate-helper';
|
import { useNavigateHelper } from '../use-navigate-helper';
|
||||||
|
|
||||||
export function useBlockSuiteMetaHelper() {
|
export function useBlockSuiteMetaHelper() {
|
||||||
const workspace = useService(WorkspaceService).workspace;
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
const { setDocMeta, getDocMeta, setDocTitle } = useDocMetaHelper();
|
|
||||||
const { createDoc } = useDocCollectionHelper(workspace.docCollection);
|
|
||||||
const { openPage } = useNavigateHelper();
|
const { openPage } = useNavigateHelper();
|
||||||
|
const docsService = useService(DocsService);
|
||||||
const docRecordList = useService(DocsService).list;
|
const docRecordList = useService(DocsService).list;
|
||||||
|
|
||||||
// TODO-Doma
|
// TODO-Doma
|
||||||
@@ -48,47 +43,11 @@ export function useBlockSuiteMetaHelper() {
|
|||||||
|
|
||||||
const duplicate = useAsyncCallback(
|
const duplicate = useAsyncCallback(
|
||||||
async (pageId: string, openPageAfterDuplication: boolean = true) => {
|
async (pageId: string, openPageAfterDuplication: boolean = true) => {
|
||||||
const currentPagePrimaryMode =
|
const newPageId = await docsService.duplicate(pageId);
|
||||||
docRecordList.doc$(pageId).value?.primaryMode$.value;
|
|
||||||
const currentPageMeta = getDocMeta(pageId);
|
|
||||||
const newPage = createDoc();
|
|
||||||
const currentPage = workspace.docCollection.getDoc(pageId);
|
|
||||||
|
|
||||||
newPage.load();
|
|
||||||
if (!currentPageMeta || !currentPage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = encodeStateAsUpdate(currentPage.spaceDoc);
|
|
||||||
applyUpdate(newPage.spaceDoc, update);
|
|
||||||
|
|
||||||
setDocMeta(newPage.id, {
|
|
||||||
tags: currentPageMeta.tags,
|
|
||||||
});
|
|
||||||
|
|
||||||
const lastDigitRegex = /\((\d+)\)$/;
|
|
||||||
const match = currentPageMeta?.title?.match(lastDigitRegex);
|
|
||||||
const newNumber = match ? parseInt(match[1], 10) + 1 : 1;
|
|
||||||
|
|
||||||
const newPageTitle =
|
|
||||||
currentPageMeta?.title?.replace(lastDigitRegex, '') + `(${newNumber})`;
|
|
||||||
|
|
||||||
docRecordList
|
|
||||||
.doc$(newPage.id)
|
|
||||||
.value?.setPrimaryMode(currentPagePrimaryMode || ('page' as DocMode));
|
|
||||||
setDocTitle(newPage.id, newPageTitle);
|
|
||||||
openPageAfterDuplication &&
|
openPageAfterDuplication &&
|
||||||
openPage(workspace.docCollection.id, newPage.id);
|
openPage(workspace.docCollection.id, newPageId);
|
||||||
},
|
},
|
||||||
[
|
[docsService, openPage, workspace.docCollection.id]
|
||||||
docRecordList,
|
|
||||||
getDocMeta,
|
|
||||||
createDoc,
|
|
||||||
workspace.docCollection,
|
|
||||||
setDocMeta,
|
|
||||||
setDocTitle,
|
|
||||||
openPage,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -168,6 +168,93 @@ export class DocsService extends Service {
|
|||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async duplicate(sourceDocId: string, _targetDocId?: string) {
|
||||||
|
const targetDocId = _targetDocId ?? this.createDoc().id;
|
||||||
|
|
||||||
|
// check if source doc is removed
|
||||||
|
if (this.list.doc$(sourceDocId).value?.trash$.value) {
|
||||||
|
console.warn(
|
||||||
|
`Template doc(id: ${sourceDocId}) is removed, skip duplicate`
|
||||||
|
);
|
||||||
|
return targetDocId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { release: sourceRelease, doc: sourceDoc } = this.open(sourceDocId);
|
||||||
|
const { release: targetRelease, doc: targetDoc } = this.open(targetDocId);
|
||||||
|
await sourceDoc.waitForSyncReady();
|
||||||
|
|
||||||
|
// duplicate doc content
|
||||||
|
try {
|
||||||
|
const sourceBsDoc = this.store.getBlockSuiteDoc(sourceDocId);
|
||||||
|
const targetBsDoc = this.store.getBlockSuiteDoc(targetDocId);
|
||||||
|
if (!sourceBsDoc) throw new Error('Source doc not found');
|
||||||
|
if (!targetBsDoc) throw new Error('Target doc not found');
|
||||||
|
|
||||||
|
// clear the target doc (both surface and note)
|
||||||
|
targetBsDoc.root?.children.forEach(child =>
|
||||||
|
targetBsDoc.deleteBlock(child)
|
||||||
|
);
|
||||||
|
|
||||||
|
const collection = this.store.getBlocksuiteCollection();
|
||||||
|
const transformer = new Transformer({
|
||||||
|
schema: getAFFiNEWorkspaceSchema(),
|
||||||
|
blobCRUD: collection.blobSync,
|
||||||
|
docCRUD: {
|
||||||
|
create: (id: string) => collection.createDoc(id).getStore({ id }),
|
||||||
|
get: (id: string) => collection.getDoc(id)?.getStore({ id }) ?? null,
|
||||||
|
delete: (id: string) => collection.removeDoc(id),
|
||||||
|
},
|
||||||
|
middlewares: [replaceIdMiddleware(collection.idGenerator)],
|
||||||
|
});
|
||||||
|
const slice = Slice.fromModels(sourceBsDoc, [
|
||||||
|
...(sourceBsDoc.root?.children ?? []),
|
||||||
|
]);
|
||||||
|
const snapshot = transformer.sliceToSnapshot(slice);
|
||||||
|
if (!snapshot) {
|
||||||
|
throw new Error('Failed to create snapshot');
|
||||||
|
}
|
||||||
|
await transformer.snapshotToSlice(
|
||||||
|
snapshot,
|
||||||
|
targetBsDoc,
|
||||||
|
targetBsDoc.root?.id
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Failed to duplicate doc', {
|
||||||
|
sourceDocId,
|
||||||
|
targetDocId,
|
||||||
|
originalTargetDocId: _targetDocId,
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sourceRelease();
|
||||||
|
targetRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
// duplicate doc meta
|
||||||
|
targetDoc.record.setMeta({
|
||||||
|
tags: sourceDoc.meta$.value.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
// duplicate doc title
|
||||||
|
const originalTitle = sourceDoc.title$.value;
|
||||||
|
const lastDigitRegex = /\((\d+)\)$/;
|
||||||
|
const match = originalTitle.match(lastDigitRegex);
|
||||||
|
const newNumber = match ? parseInt(match[1], 10) + 1 : 1;
|
||||||
|
const newPageTitle =
|
||||||
|
originalTitle.replace(lastDigitRegex, '') + `(${newNumber})`;
|
||||||
|
targetDoc.changeDocTitle(newPageTitle);
|
||||||
|
|
||||||
|
// duplicate doc properties
|
||||||
|
const properties = sourceDoc.getProperties();
|
||||||
|
const removedProperties = ['id', 'isTemplate', 'journal'];
|
||||||
|
removedProperties.forEach(key => {
|
||||||
|
delete properties[key];
|
||||||
|
});
|
||||||
|
targetDoc.updateProperties(properties);
|
||||||
|
|
||||||
|
return targetDocId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicate a doc from template
|
* Duplicate a doc from template
|
||||||
* @param sourceDocId - the id of the source doc to be duplicated
|
* @param sourceDocId - the id of the source doc to be duplicated
|
||||||
|
|||||||
Reference in New Issue
Block a user