mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
refactor(editor): move workspace meta to affine (#9524)
This commit is contained in:
252
packages/frontend/core/src/modules/workspace/impls/meta.ts
Normal file
252
packages/frontend/core/src/modules/workspace/impls/meta.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { Slot } from '@blocksuite/affine/global/utils';
|
||||
import {
|
||||
createYProxy,
|
||||
type DocMeta,
|
||||
type DocsPropertiesMeta,
|
||||
type Workspace,
|
||||
type WorkspaceMeta,
|
||||
} from '@blocksuite/affine/store';
|
||||
import type * as Y from 'yjs';
|
||||
|
||||
const COLLECTION_VERSION = 2;
|
||||
const PAGE_VERSION = 2;
|
||||
|
||||
type MetaState = {
|
||||
pages?: unknown[];
|
||||
properties?: DocsPropertiesMeta;
|
||||
workspaceVersion?: number;
|
||||
pageVersion?: number;
|
||||
blockVersions?: Record<string, number>;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
export class WorkspaceMetaImpl implements WorkspaceMeta {
|
||||
commonFieldsUpdated = new Slot();
|
||||
docMetaAdded = new Slot<string>();
|
||||
docMetaRemoved = new Slot<string>();
|
||||
docMetaUpdated = new Slot();
|
||||
|
||||
private readonly _handleDocCollectionMetaEvents = (
|
||||
events: Y.YEvent<Y.Array<unknown> | Y.Text | Y.Map<unknown>>[]
|
||||
) => {
|
||||
events.forEach(e => {
|
||||
const hasKey = (k: string) =>
|
||||
e.target === this._yMap && e.changes.keys.has(k);
|
||||
|
||||
if (
|
||||
e.target === this.yDocs ||
|
||||
e.target.parent === this.yDocs ||
|
||||
hasKey('pages')
|
||||
) {
|
||||
this._handleDocMetaEvent();
|
||||
}
|
||||
|
||||
if (hasKey('name') || hasKey('avatar')) {
|
||||
this._handleCommonFieldsEvent();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _id: string = 'meta';
|
||||
private readonly _doc: Y.Doc;
|
||||
private readonly _proxy: MetaState;
|
||||
private readonly _yMap: Y.Map<MetaState[keyof MetaState]>;
|
||||
private _prevDocs = new Set<string>();
|
||||
|
||||
get avatar() {
|
||||
return this._proxy.avatar;
|
||||
}
|
||||
|
||||
setAvatar(avatar: string) {
|
||||
this._doc.transact(() => {
|
||||
this._proxy.avatar = avatar;
|
||||
}, this._doc.clientID);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._proxy.name;
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this._doc.transact(() => {
|
||||
this._proxy.name = name;
|
||||
}, this._doc.clientID);
|
||||
}
|
||||
|
||||
get properties(): DocsPropertiesMeta {
|
||||
const meta = this._proxy.properties;
|
||||
if (!meta) {
|
||||
return {
|
||||
tags: {
|
||||
options: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
setProperties(meta: DocsPropertiesMeta) {
|
||||
this._proxy.properties = meta;
|
||||
this.docMetaUpdated.emit();
|
||||
}
|
||||
|
||||
get docMetas() {
|
||||
if (!this._proxy.pages) {
|
||||
return [] as DocMeta[];
|
||||
}
|
||||
return this._proxy.pages as DocMeta[];
|
||||
}
|
||||
|
||||
get docs() {
|
||||
return this._proxy.pages;
|
||||
}
|
||||
|
||||
get hasVersion() {
|
||||
if (!this._blockVersions || !this._pageVersion || !this._workspaceVersion) {
|
||||
return false;
|
||||
}
|
||||
return Object.keys(this._blockVersions).length > 0;
|
||||
}
|
||||
|
||||
private get _blockVersions() {
|
||||
return this._proxy.blockVersions;
|
||||
}
|
||||
|
||||
private get _pageVersion() {
|
||||
return this._proxy.pageVersion;
|
||||
}
|
||||
|
||||
private get _workspaceVersion() {
|
||||
return this._proxy.workspaceVersion;
|
||||
}
|
||||
|
||||
get yDocs() {
|
||||
return this._yMap.get('pages') as unknown as Y.Array<unknown>;
|
||||
}
|
||||
|
||||
constructor(doc: Y.Doc) {
|
||||
this._doc = doc;
|
||||
const map = doc.getMap(this._id) as Y.Map<MetaState[keyof MetaState]>;
|
||||
this._yMap = map;
|
||||
this._proxy = createYProxy(map);
|
||||
this._yMap.observeDeep(this._handleDocCollectionMetaEvents);
|
||||
}
|
||||
|
||||
private _handleCommonFieldsEvent() {
|
||||
this.commonFieldsUpdated.emit();
|
||||
}
|
||||
|
||||
private _handleDocMetaEvent() {
|
||||
const { docMetas, _prevDocs } = this;
|
||||
|
||||
const newDocs = new Set<string>();
|
||||
|
||||
docMetas.forEach(docMeta => {
|
||||
if (!_prevDocs.has(docMeta.id)) {
|
||||
this.docMetaAdded.emit(docMeta.id);
|
||||
}
|
||||
newDocs.add(docMeta.id);
|
||||
});
|
||||
|
||||
_prevDocs.forEach(prevDocId => {
|
||||
const isRemoved = newDocs.has(prevDocId) === false;
|
||||
if (isRemoved) {
|
||||
this.docMetaRemoved.emit(prevDocId);
|
||||
}
|
||||
});
|
||||
|
||||
this._prevDocs = newDocs;
|
||||
|
||||
this.docMetaUpdated.emit();
|
||||
}
|
||||
|
||||
addDocMeta(doc: DocMeta, index?: number) {
|
||||
this._doc.transact(() => {
|
||||
if (!this.docs) {
|
||||
return;
|
||||
}
|
||||
const docs = this.docs as unknown[];
|
||||
if (index === undefined) {
|
||||
docs.push(doc);
|
||||
} else {
|
||||
docs.splice(index, 0, doc);
|
||||
}
|
||||
}, this._doc.clientID);
|
||||
}
|
||||
|
||||
getDocMeta(id: string) {
|
||||
return this.docMetas.find(doc => doc.id === id);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (!this._proxy.pages) {
|
||||
this._proxy.pages = [];
|
||||
}
|
||||
}
|
||||
|
||||
removeDocMeta(id: string) {
|
||||
// you cannot delete a doc if there's no doc
|
||||
if (!this.docs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docMeta = this.docMetas;
|
||||
const index = docMeta.findIndex((doc: DocMeta) => id === doc.id);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
this._doc.transact(() => {
|
||||
if (!this.docs) {
|
||||
return;
|
||||
}
|
||||
this.docs.splice(index, 1);
|
||||
}, this._doc.clientID);
|
||||
}
|
||||
|
||||
setDocMeta(id: string, props: Partial<DocMeta>) {
|
||||
const docs = (this.docs as DocMeta[]) ?? [];
|
||||
const index = docs.findIndex((doc: DocMeta) => id === doc.id);
|
||||
|
||||
this._doc.transact(() => {
|
||||
if (!this.docs) {
|
||||
return;
|
||||
}
|
||||
if (index === -1) return;
|
||||
|
||||
const doc = this.docs[index] as Record<string, unknown>;
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
doc[key] = value;
|
||||
});
|
||||
}, this._doc.clientID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Only for doc initialization
|
||||
*/
|
||||
writeVersion(collection: Workspace) {
|
||||
const { blockVersions, pageVersion, workspaceVersion } = this._proxy;
|
||||
|
||||
if (!workspaceVersion) {
|
||||
this._proxy.workspaceVersion = COLLECTION_VERSION;
|
||||
} else {
|
||||
console.error('Workspace version is already set');
|
||||
}
|
||||
|
||||
if (!pageVersion) {
|
||||
this._proxy.pageVersion = PAGE_VERSION;
|
||||
} else {
|
||||
console.error('Doc version is already set');
|
||||
}
|
||||
|
||||
if (!blockVersions) {
|
||||
const _versions: Record<string, number> = {};
|
||||
collection.schema.flavourSchemaMap.forEach((schema, flavour) => {
|
||||
_versions[flavour] = schema.version;
|
||||
});
|
||||
this._proxy.blockVersions = _versions;
|
||||
} else {
|
||||
console.error('Block versions is already set');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
type Blocks,
|
||||
type CreateBlocksOptions,
|
||||
type Doc,
|
||||
DocCollectionMeta,
|
||||
type GetBlocksOptions,
|
||||
type IdGenerator,
|
||||
nanoid,
|
||||
@@ -29,6 +28,7 @@ import { Awareness } from 'y-protocols/awareness.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { DocImpl } from './doc';
|
||||
import { WorkspaceMetaImpl } from './meta';
|
||||
|
||||
type WorkspaceOptions = {
|
||||
id?: string;
|
||||
@@ -111,7 +111,7 @@ export class WorkspaceImpl implements Workspace {
|
||||
|
||||
this.idGenerator = nanoid;
|
||||
|
||||
this.meta = new DocCollectionMeta(this.doc);
|
||||
this.meta = new WorkspaceMetaImpl(this.doc);
|
||||
this._bindDocMetaEvents();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user