feat(core): new template doc property (#9538)

close AF-2045, AF-2047, AF-2065
This commit is contained in:
CatsJuice
2025-01-14 02:10:33 +00:00
parent 777eea124d
commit 10196f6785
26 changed files with 719 additions and 8 deletions

View File

@@ -5,7 +5,7 @@ import type { DocCustomPropertyInfo } from '../db';
*
* 'id' and 'type' is request, 'index' is a manually maintained incremental key.
*/
export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
export const BUILT_IN_CUSTOM_PROPERTY_TYPE: DocCustomPropertyInfo[] = [
{
id: 'tags',
type: 'tags',
@@ -23,6 +23,12 @@ export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
show: 'always-hide',
index: 'a0000003',
},
{
id: 'template',
type: 'template',
index: 'a00000031',
show: 'always-hide',
},
{
id: 'createdAt',
type: 'createdAt',
@@ -51,4 +57,4 @@ export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
show: 'always-hide',
index: 'a0000008',
},
] as DocCustomPropertyInfo[];
];

View File

@@ -1,6 +1,7 @@
import type { DocMode, RootBlockModel } from '@blocksuite/affine/blocks';
import { Entity } from '@toeverything/infra';
import type { DocProperties } from '../../db';
import type { WorkspaceService } from '../../workspace';
import type { DocScope } from '../scopes/doc';
import type { DocsStore } from '../stores/docs';
@@ -38,6 +39,18 @@ export class Doc extends Entity {
return this.record.customProperty$(propertyId);
}
setProperty(propertyId: string, value: string) {
return this.record.setProperty(propertyId, value);
}
updateProperties(properties: Partial<DocProperties>) {
return this.record.updateProperties(properties);
}
getProperties() {
return this.record.getProperties();
}
setCustomProperty(propertyId: string, value: string) {
return this.record.setCustomProperty(propertyId, value);
}

View File

@@ -42,7 +42,18 @@ export class DocRecord extends Entity<{ id: string }> {
});
}
setProperty(propertyId: string, value: string) {
getProperties() {
return this.docPropertiesStore.getDocProperties(this.id);
}
updateProperties(properties: Partial<DocProperties>) {
this.docPropertiesStore.updateDocProperties(this.id, properties);
}
setProperty<Key extends keyof DocProperties>(
propertyId: Key,
value: DocProperties[Key]
) {
this.docPropertiesStore.updateDocProperties(this.id, {
[propertyId]: value,
});

View File

@@ -1,8 +1,8 @@
import { DebugLogger } from '@affine/debug';
import { Unreachable } from '@affine/env/constant';
import type { DocMode } from '@blocksuite/affine/blocks';
import { type DocMode, replaceIdMiddleware } from '@blocksuite/affine/blocks';
import type { DeltaInsert } from '@blocksuite/affine/inline';
import { Text } from '@blocksuite/affine/store';
import { Slice, Text, Transformer } from '@blocksuite/affine/store';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { LiveData, ObjectPool, Service } from '@toeverything/infra';
import { omitBy } from 'lodash-es';
@@ -150,4 +150,83 @@ export class DocsService extends Service {
doc.changeDocTitle(newTitle);
release();
}
/**
* Duplicate a doc from template
* @param sourceDocId - the id of the source doc to be duplicated
* @param _targetDocId - the id of the target doc to be duplicated, if not provided, a new doc will be created
* @returns the id of the new doc
*/
async duplicateFromTemplate(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: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
get: (id: string) => collection.getDoc(id),
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 properties
const properties = sourceDoc.getProperties();
const removedProperties = ['id', 'isTemplate', 'journal'];
removedProperties.forEach(key => {
delete properties[key];
});
targetDoc.updateProperties(properties);
return targetDocId;
}
}

View File

@@ -24,6 +24,10 @@ export class DocsStore extends Store {
return this.workspaceService.workspace.docCollection.getDoc(id);
}
getBlocksuiteCollection() {
return this.workspaceService.workspace.docCollection;
}
createBlockSuiteDoc() {
return this.workspaceService.workspace.docCollection.createDoc();
}