mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-22 08:47:10 +08:00
feat(editor): add block meta service (#10561)
This commit is contained in:
@@ -30,3 +30,10 @@ export type Connectable = Exclude<
|
||||
GfxModel,
|
||||
ConnectorElementModel | BrushElementModel | GroupElementModel
|
||||
>;
|
||||
|
||||
export type BlockMeta = {
|
||||
'meta:createdAt'?: number;
|
||||
'meta:createdBy'?: string;
|
||||
'meta:updatedAt'?: number;
|
||||
'meta:updatedBy'?: string;
|
||||
};
|
||||
|
||||
112
blocksuite/affine/shared/src/services/block-meta-service.ts
Normal file
112
blocksuite/affine/shared/src/services/block-meta-service.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { BlockMeta } from '@blocksuite/affine-model';
|
||||
import { type BlockModel, type Store, StoreExtension } from '@blocksuite/store';
|
||||
|
||||
import { FeatureFlagService } from './feature-flag-service';
|
||||
import { UserProvider } from './user-service';
|
||||
|
||||
/**
|
||||
* The service is used to add following info to the block.
|
||||
* - createdAt: The time when the block is created.
|
||||
* - createdBy: The user who created the block.
|
||||
* - updatedAt: The time when the block is updated.
|
||||
* - updatedBy: The user who updated the block.
|
||||
*/
|
||||
export class BlockMetaService extends StoreExtension {
|
||||
static override key = 'affine-block-meta-service';
|
||||
|
||||
get isBlockMetaEnabled() {
|
||||
return (
|
||||
this.store.get(FeatureFlagService).getFlag('enable_block_meta') === true
|
||||
);
|
||||
}
|
||||
|
||||
constructor(store: Store) {
|
||||
super(store);
|
||||
|
||||
if (!this.isBlockMetaEnabled) return;
|
||||
|
||||
this.store.slots.blockUpdated.on(({ type, id }) => {
|
||||
if (!this.isBlockMetaEnabled) return;
|
||||
|
||||
const model = this.store.getBlock(id)?.model;
|
||||
if (!model) return;
|
||||
|
||||
if (type === 'add') {
|
||||
return this._onBlockCreated(model);
|
||||
}
|
||||
|
||||
if (type === 'update') {
|
||||
return this._onBlockUpdated(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _onBlockCreated = (model: BlockModel<BlockMeta>): void => {
|
||||
if (!isBlockMetaSupported(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUser = this._getCurrentUser();
|
||||
if (!currentUser) return;
|
||||
const now = getNow();
|
||||
|
||||
this.store.withoutTransact(() => {
|
||||
const isFlatModel = model.schema.model.isFlatData;
|
||||
if (!isFlatModel) {
|
||||
model['meta:createdAt'] = now;
|
||||
model['meta:createdBy'] = currentUser.id;
|
||||
return;
|
||||
}
|
||||
|
||||
model.props['meta:createdAt'] = now;
|
||||
model.props['meta:createdBy'] = currentUser.id;
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _onBlockUpdated = (model: BlockModel<BlockMeta>): void => {
|
||||
if (!isBlockMetaSupported(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUser = this._getCurrentUser();
|
||||
if (!currentUser) return;
|
||||
const now = getNow();
|
||||
|
||||
this.store.withoutTransact(() => {
|
||||
const isFlatModel = model.schema.model.isFlatData;
|
||||
if (!isFlatModel) {
|
||||
model['meta:updatedAt'] = now;
|
||||
model['meta:updatedBy'] = currentUser.id;
|
||||
if (!model['meta:createdAt']) {
|
||||
model['meta:createdAt'] = now;
|
||||
model['meta:createdBy'] = currentUser.id;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
model.props['meta:updatedAt'] = now;
|
||||
model.props['meta:updatedBy'] = currentUser.id;
|
||||
if (!model.props['meta:createdAt']) {
|
||||
model.props['meta:createdAt'] = now;
|
||||
model.props['meta:createdBy'] = currentUser.id;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _getCurrentUser = () => {
|
||||
return this.store.getOptional(UserProvider)?.getCurrentUser();
|
||||
};
|
||||
}
|
||||
|
||||
function isBlockMetaSupported(model: BlockModel) {
|
||||
return [
|
||||
'meta:createdAt',
|
||||
'meta:createdBy',
|
||||
'meta:updatedAt',
|
||||
'meta:updatedBy',
|
||||
].every(key => model.keys.includes(key));
|
||||
}
|
||||
|
||||
function getNow() {
|
||||
return Date.now();
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './block-meta-service';
|
||||
export * from './doc-display-meta-service';
|
||||
export * from './doc-mode-service';
|
||||
export * from './drag-handle-config';
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ImageSelectionExtension,
|
||||
} from '@blocksuite/affine-shared/selection';
|
||||
import {
|
||||
BlockMetaService,
|
||||
FeatureFlagService,
|
||||
FileSizeLimitService,
|
||||
LinkPreviewerService,
|
||||
@@ -87,15 +88,15 @@ export const StoreExtensions: ExtensionType[] = [
|
||||
DatabaseSelectionExtension,
|
||||
TableSelectionExtension,
|
||||
|
||||
FeatureFlagService,
|
||||
LinkPreviewerService,
|
||||
FileSizeLimitService,
|
||||
ImageStoreSpec,
|
||||
|
||||
HtmlAdapterExtension,
|
||||
MarkdownAdapterExtension,
|
||||
NotionHtmlAdapterExtension,
|
||||
PlainTextAdapterExtension,
|
||||
|
||||
AdapterFactoryExtensions,
|
||||
|
||||
FeatureFlagService,
|
||||
LinkPreviewerService,
|
||||
FileSizeLimitService,
|
||||
ImageStoreSpec,
|
||||
BlockMetaService,
|
||||
].flat();
|
||||
|
||||
@@ -11,6 +11,8 @@ export const StoreExtensionIdentifier =
|
||||
export const storeExtensionSymbol = Symbol('StoreExtension');
|
||||
|
||||
export class StoreExtension extends Extension {
|
||||
static readonly key: string;
|
||||
|
||||
constructor(readonly store: Store) {
|
||||
super();
|
||||
}
|
||||
@@ -30,8 +32,6 @@ export class StoreExtension extends Extension {
|
||||
provider.get(this)
|
||||
);
|
||||
}
|
||||
|
||||
static readonly key: string;
|
||||
}
|
||||
|
||||
export function isStoreExtensionConstructor(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { computed, signal } from '@preact/signals-core';
|
||||
import type { ExtensionType } from '../../extension/extension.js';
|
||||
import {
|
||||
BlockSchemaIdentifier,
|
||||
StoreExtensionIdentifier,
|
||||
StoreSelectionExtension,
|
||||
} from '../../extension/index.js';
|
||||
import { Schema } from '../../schema/index.js';
|
||||
@@ -313,6 +314,17 @@ export class Store {
|
||||
}
|
||||
|
||||
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
|
||||
this._doc = doc;
|
||||
this.slots = {
|
||||
ready: new Slot(),
|
||||
rootAdded: new Slot(),
|
||||
rootDeleted: new Slot(),
|
||||
blockUpdated: new Slot(),
|
||||
historyUpdated: this._doc.slots.historyUpdated,
|
||||
yBlockUpdated: this._doc.slots.yBlockUpdated,
|
||||
};
|
||||
this._schema = new Schema();
|
||||
|
||||
const container = new Container();
|
||||
container.addImpl(StoreIdentifier, () => this);
|
||||
|
||||
@@ -327,18 +339,6 @@ export class Store {
|
||||
});
|
||||
|
||||
this._provider = container.provider(undefined, provider);
|
||||
this._doc = doc;
|
||||
|
||||
this.slots = {
|
||||
ready: new Slot(),
|
||||
rootAdded: new Slot(),
|
||||
rootDeleted: new Slot(),
|
||||
blockUpdated: new Slot(),
|
||||
historyUpdated: this._doc.slots.historyUpdated,
|
||||
yBlockUpdated: this._doc.slots.yBlockUpdated,
|
||||
};
|
||||
|
||||
this._schema = new Schema();
|
||||
this._provider.getAll(BlockSchemaIdentifier).forEach(schema => {
|
||||
this._schema.register([schema]);
|
||||
});
|
||||
@@ -698,6 +698,7 @@ export class Store {
|
||||
|
||||
load(initFn?: () => void) {
|
||||
this._doc.load(initFn);
|
||||
this._provider.getAll(StoreExtensionIdentifier);
|
||||
this.slots.ready.emit();
|
||||
return this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user