feat(core): add blocksuite writer info service (#10754)
This commit is contained in:
@@ -2,7 +2,7 @@ import type { BlockMeta } from '@blocksuite/affine-model';
|
|||||||
import { type BlockModel, StoreExtension } from '@blocksuite/store';
|
import { type BlockModel, StoreExtension } from '@blocksuite/store';
|
||||||
|
|
||||||
import { FeatureFlagService } from './feature-flag-service';
|
import { FeatureFlagService } from './feature-flag-service';
|
||||||
import { UserProvider } from './user-service';
|
import { WriterInfoProvider } from './user-service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service is used to add following info to the block.
|
* The service is used to add following info to the block.
|
||||||
@@ -43,20 +43,20 @@ export class BlockMetaService extends StoreExtension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = this._getCurrentUser();
|
const writer = this._getWriterInfo();
|
||||||
if (!currentUser) return;
|
if (!writer) return;
|
||||||
const now = getNow();
|
const now = getNow();
|
||||||
|
|
||||||
this.store.withoutTransact(() => {
|
this.store.withoutTransact(() => {
|
||||||
const isFlatModel = model.schema.model.isFlatData;
|
const isFlatModel = model.schema.model.isFlatData;
|
||||||
if (!isFlatModel) {
|
if (!isFlatModel) {
|
||||||
model['meta:createdAt'] = now;
|
model['meta:createdAt'] = now;
|
||||||
model['meta:createdBy'] = currentUser.id;
|
model['meta:createdBy'] = writer.id;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.props['meta:createdAt'] = now;
|
model.props['meta:createdAt'] = now;
|
||||||
model.props['meta:createdBy'] = currentUser.id;
|
model.props['meta:createdBy'] = writer.id;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,33 +65,33 @@ export class BlockMetaService extends StoreExtension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = this._getCurrentUser();
|
const writer = this._getWriterInfo();
|
||||||
if (!currentUser) return;
|
if (!writer) return;
|
||||||
const now = getNow();
|
const now = getNow();
|
||||||
|
|
||||||
this.store.withoutTransact(() => {
|
this.store.withoutTransact(() => {
|
||||||
const isFlatModel = model.schema.model.isFlatData;
|
const isFlatModel = model.schema.model.isFlatData;
|
||||||
if (!isFlatModel) {
|
if (!isFlatModel) {
|
||||||
model['meta:updatedAt'] = now;
|
model['meta:updatedAt'] = now;
|
||||||
model['meta:updatedBy'] = currentUser.id;
|
model['meta:updatedBy'] = writer.id;
|
||||||
if (!model['meta:createdAt']) {
|
if (!model['meta:createdAt']) {
|
||||||
model['meta:createdAt'] = now;
|
model['meta:createdAt'] = now;
|
||||||
model['meta:createdBy'] = currentUser.id;
|
model['meta:createdBy'] = writer.id;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.props['meta:updatedAt'] = now;
|
model.props['meta:updatedAt'] = now;
|
||||||
model.props['meta:updatedBy'] = currentUser.id;
|
model.props['meta:updatedBy'] = writer.id;
|
||||||
if (!model.props['meta:createdAt']) {
|
if (!model.props['meta:createdAt']) {
|
||||||
model.props['meta:createdAt'] = now;
|
model.props['meta:createdAt'] = now;
|
||||||
model.props['meta:createdBy'] = currentUser.id;
|
model.props['meta:createdBy'] = writer.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _getCurrentUser = () => {
|
private readonly _getWriterInfo = () => {
|
||||||
return this.store.getOptional(UserProvider)?.getCurrentUser();
|
return this.store.getOptional(WriterInfoProvider)?.getWriterInfo();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './user-list-service';
|
export * from './user-list-service';
|
||||||
export * from './user-service';
|
export * from './user-service';
|
||||||
|
export * from './writer-info-service';
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { Signal } from '@preact/signals-core';
|
|||||||
import type { AffineUserInfo } from './types';
|
import type { AffineUserInfo } from './types';
|
||||||
|
|
||||||
export interface UserService {
|
export interface UserService {
|
||||||
getCurrentUser(): AffineUserInfo | null;
|
|
||||||
userInfo$(id: string): Signal<AffineUserInfo | null>;
|
userInfo$(id: string): Signal<AffineUserInfo | null>;
|
||||||
revalidateUserInfo(id: string): void;
|
revalidateUserInfo(id: string): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createIdentifier } from '@blocksuite/global/di';
|
||||||
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import type { AffineUserInfo } from './types';
|
||||||
|
|
||||||
|
export interface WriterInfoService {
|
||||||
|
getWriterInfo(): AffineUserInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WriterInfoProvider = createIdentifier<WriterInfoService>(
|
||||||
|
'affine-writer-info-service'
|
||||||
|
);
|
||||||
|
|
||||||
|
export function WriterInfoServiceExtension(
|
||||||
|
service: WriterInfoService
|
||||||
|
): ExtensionType {
|
||||||
|
return {
|
||||||
|
setup(di) {
|
||||||
|
di.addImpl(WriterInfoProvider, () => service);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
type PageEditor,
|
type PageEditor,
|
||||||
} from '@affine/core/blocksuite/editors';
|
} from '@affine/core/blocksuite/editors';
|
||||||
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
|
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
|
||||||
import { AuthService, PublicUserService } from '@affine/core/modules/cloud';
|
import { PublicUserService } from '@affine/core/modules/cloud';
|
||||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||||
import { DocService, DocsService } from '@affine/core/modules/doc';
|
import { DocService, DocsService } from '@affine/core/modules/doc';
|
||||||
import type {
|
import type {
|
||||||
@@ -94,7 +94,6 @@ const usePatchSpecs = (mode: DocMode) => {
|
|||||||
featureFlagService,
|
featureFlagService,
|
||||||
memberSearchService,
|
memberSearchService,
|
||||||
publicUserService,
|
publicUserService,
|
||||||
authService,
|
|
||||||
} = useServices({
|
} = useServices({
|
||||||
PeekViewService,
|
PeekViewService,
|
||||||
DocService,
|
DocService,
|
||||||
@@ -104,7 +103,6 @@ const usePatchSpecs = (mode: DocMode) => {
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
MemberSearchService,
|
MemberSearchService,
|
||||||
PublicUserService,
|
PublicUserService,
|
||||||
AuthService,
|
|
||||||
});
|
});
|
||||||
const isCloud = workspaceService.workspace.flavour !== 'local';
|
const isCloud = workspaceService.workspace.flavour !== 'local';
|
||||||
const framework = useFramework();
|
const framework = useFramework();
|
||||||
@@ -167,7 +165,7 @@ const usePatchSpecs = (mode: DocMode) => {
|
|||||||
isCloud
|
isCloud
|
||||||
? [
|
? [
|
||||||
patchUserListExtensions(memberSearchService),
|
patchUserListExtensions(memberSearchService),
|
||||||
patchUserExtensions(publicUserService, authService),
|
patchUserExtensions(publicUserService),
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
mode === 'edgeless' && enableTurboRenderer
|
mode === 'edgeless' && enableTurboRenderer
|
||||||
@@ -201,7 +199,6 @@ const usePatchSpecs = (mode: DocMode) => {
|
|||||||
isCloud,
|
isCloud,
|
||||||
memberSearchService,
|
memberSearchService,
|
||||||
publicUserService,
|
publicUserService,
|
||||||
authService,
|
|
||||||
enableTurboRenderer,
|
enableTurboRenderer,
|
||||||
featureFlagService.flags.enable_pdf_embed_preview.value,
|
featureFlagService.flags.enable_pdf_embed_preview.value,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,24 +1,8 @@
|
|||||||
import type {
|
import type { PublicUserService } from '@affine/core/modules/cloud';
|
||||||
AuthService,
|
|
||||||
PublicUserService,
|
|
||||||
} from '@affine/core/modules/cloud';
|
|
||||||
import { UserServiceExtension } from '@blocksuite/affine/shared/services';
|
import { UserServiceExtension } from '@blocksuite/affine/shared/services';
|
||||||
|
|
||||||
export function patchUserExtensions(
|
export function patchUserExtensions(publicUserService: PublicUserService) {
|
||||||
publicUserService: PublicUserService,
|
|
||||||
authService: AuthService
|
|
||||||
) {
|
|
||||||
return UserServiceExtension({
|
return UserServiceExtension({
|
||||||
getCurrentUser() {
|
|
||||||
const account = authService.session.account$.value;
|
|
||||||
return account
|
|
||||||
? {
|
|
||||||
id: account.id,
|
|
||||||
avatar: account.avatar,
|
|
||||||
name: account.label,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line rxjs/finnish
|
// eslint-disable-next-line rxjs/finnish
|
||||||
userInfo$(id) {
|
userInfo$(id) {
|
||||||
return publicUserService.publicUser$(id).signal;
|
return publicUserService.publicUser$(id).signal;
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import { type Framework } from '@toeverything/infra';
|
|||||||
|
|
||||||
import { DocScope } from '../doc/scopes/doc';
|
import { DocScope } from '../doc/scopes/doc';
|
||||||
import { DocService } from '../doc/services/doc';
|
import { DocService } from '../doc/services/doc';
|
||||||
import { EditorScope } from '../editor';
|
|
||||||
import { GlobalCache, GlobalState } from '../storage/providers/global';
|
import { GlobalCache, GlobalState } from '../storage/providers/global';
|
||||||
import { GlobalStateService } from '../storage/services/global';
|
import { GlobalStateService } from '../storage/services/global';
|
||||||
import { UrlService } from '../url';
|
import { UrlService } from '../url';
|
||||||
@@ -55,10 +54,10 @@ import { ValidatorProvider } from './provider/validator';
|
|||||||
import { ServerScope } from './scopes/server';
|
import { ServerScope } from './scopes/server';
|
||||||
import { AcceptInviteService } from './services/accept-invite';
|
import { AcceptInviteService } from './services/accept-invite';
|
||||||
import { AuthService } from './services/auth';
|
import { AuthService } from './services/auth';
|
||||||
|
import { BlocksuiteWriterInfoService } from './services/blocksuite-writer-info';
|
||||||
import { CaptchaService } from './services/captcha';
|
import { CaptchaService } from './services/captcha';
|
||||||
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
||||||
import { DefaultServerService } from './services/default-server';
|
import { DefaultServerService } from './services/default-server';
|
||||||
import { EditorUserCursorLabelService } from './services/editor-user-cursor-label';
|
|
||||||
import { EventSourceService } from './services/eventsource';
|
import { EventSourceService } from './services/eventsource';
|
||||||
import { FetchService } from './services/fetch';
|
import { FetchService } from './services/fetch';
|
||||||
import { GraphQLService } from './services/graphql';
|
import { GraphQLService } from './services/graphql';
|
||||||
@@ -167,11 +166,6 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.service(WorkspaceInvoicesService)
|
.service(WorkspaceInvoicesService)
|
||||||
.entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService])
|
.entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService])
|
||||||
.service(SelfhostLicenseService, [SelfhostLicenseStore, WorkspaceService])
|
.service(SelfhostLicenseService, [SelfhostLicenseStore, WorkspaceService])
|
||||||
.store(SelfhostLicenseStore, [WorkspaceServerService]);
|
.store(SelfhostLicenseStore, [WorkspaceServerService])
|
||||||
|
.service(BlocksuiteWriterInfoService, [WorkspaceServerService]);
|
||||||
framework
|
|
||||||
.scope(WorkspaceScope)
|
|
||||||
.scope(DocScope)
|
|
||||||
.scope(EditorScope)
|
|
||||||
.service(EditorUserCursorLabelService, [WorkspaceServerService]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { WriterInfoServiceExtension } from '@blocksuite/affine/shared/services';
|
||||||
|
import { OnEvent, Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { type Workspace, WorkspaceInitialized } from '../../workspace';
|
||||||
|
import type { DocImpl } from '../../workspace/impls/doc';
|
||||||
|
import type { WorkspaceServerService } from './workspace-server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service is used to set the writer info for the blocksuite editor.
|
||||||
|
*/
|
||||||
|
@OnEvent(WorkspaceInitialized, i => i.onWorkspaceInitialized)
|
||||||
|
export class BlocksuiteWriterInfoService extends Service {
|
||||||
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
onWorkspaceInitialized(workspace: Workspace) {
|
||||||
|
const setWriterInfo = (doc: DocImpl) => {
|
||||||
|
const account = this.workspaceServerService.server?.account$.value;
|
||||||
|
doc.awarenessStore.awareness.setLocalStateField('user', {
|
||||||
|
name: account?.label,
|
||||||
|
});
|
||||||
|
doc.storeExtensions.push(
|
||||||
|
WriterInfoServiceExtension({
|
||||||
|
getWriterInfo: () => {
|
||||||
|
if (!account) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: account.id,
|
||||||
|
name: account.label,
|
||||||
|
avatar: account.avatar,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const subscription = workspace.docCollection.meta.docMetaAdded.subscribe(
|
||||||
|
docId => {
|
||||||
|
const doc = workspace.docCollection.docs.get(docId) as DocImpl;
|
||||||
|
setWriterInfo(doc);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.disposables.push(() => subscription.unsubscribe.bind(subscription));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { OnEvent, Service } from '@toeverything/infra';
|
|
||||||
|
|
||||||
import type { Editor } from '../../editor';
|
|
||||||
import { EditorInitialized } from '../../editor/events';
|
|
||||||
import type { WorkspaceServerService } from './workspace-server';
|
|
||||||
|
|
||||||
@OnEvent(EditorInitialized, i => i.onEditorInitialized)
|
|
||||||
export class EditorUserCursorLabelService extends Service {
|
|
||||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditorInitialized(editor: Editor) {
|
|
||||||
if (this.workspaceServerService.server) {
|
|
||||||
const subscription =
|
|
||||||
this.workspaceServerService.server.account$.subscribe(account => {
|
|
||||||
editor.doc.blockSuiteDoc.awarenessStore.awareness.setLocalStateField(
|
|
||||||
'user',
|
|
||||||
{
|
|
||||||
name: account?.label,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this.disposables.push(() => subscription.unsubscribe());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import { SpecProvider } from '@blocksuite/affine/shared/utils';
|
|||||||
import {
|
import {
|
||||||
AwarenessStore,
|
AwarenessStore,
|
||||||
type Doc,
|
type Doc,
|
||||||
|
type ExtensionType,
|
||||||
type GetBlocksOptions,
|
type GetBlocksOptions,
|
||||||
type Query,
|
type Query,
|
||||||
Store,
|
Store,
|
||||||
@@ -92,6 +93,8 @@ export class DocImpl implements Doc {
|
|||||||
*/
|
*/
|
||||||
protected readonly _ySpaceDoc: Y.Doc;
|
protected readonly _ySpaceDoc: Y.Doc;
|
||||||
|
|
||||||
|
readonly storeExtensions: ExtensionType[] = [];
|
||||||
|
|
||||||
readonly awarenessStore: AwarenessStore;
|
readonly awarenessStore: AwarenessStore;
|
||||||
|
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@@ -284,7 +287,9 @@ export class DocImpl implements Doc {
|
|||||||
|
|
||||||
const storeExtensions = SpecProvider._.getSpec('store');
|
const storeExtensions = SpecProvider._.getSpec('store');
|
||||||
const extensionSet = new Set(
|
const extensionSet = new Set(
|
||||||
storeExtensions.value.concat(extensions ?? [])
|
storeExtensions.value
|
||||||
|
.concat(extensions ?? [])
|
||||||
|
.concat(this.storeExtensions)
|
||||||
);
|
);
|
||||||
|
|
||||||
const doc = new Store({
|
const doc = new Store({
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export class WorkspaceRepositoryService extends Service {
|
|||||||
|
|
||||||
workspace.engine.start();
|
workspace.engine.start();
|
||||||
|
|
||||||
this.framework.emitEvent(WorkspaceInitialized, workspace);
|
workspaceScope.emitEvent(WorkspaceInitialized, workspace);
|
||||||
|
|
||||||
flavourProvider?.onWorkspaceInitialized?.(workspace);
|
flavourProvider?.onWorkspaceInitialized?.(workspace);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user