mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 23:56:36 +08:00
feat(core): add user list service for blocksuite (#10627)
This commit is contained in:
@@ -5,8 +5,8 @@ export type RemovedUserInfo = {
|
||||
|
||||
export type ExistedUserInfo = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
name?: string | null;
|
||||
avatar?: string | null;
|
||||
removed?: false;
|
||||
};
|
||||
|
||||
|
||||
@@ -255,6 +255,8 @@ export default tseslint.config(
|
||||
|
||||
types: {
|
||||
'^LiveData$': true,
|
||||
'^Signal$': true,
|
||||
'^ReadonlySignal$': true,
|
||||
'^Doc$': false,
|
||||
'^Awareness$': false,
|
||||
'^UndoManager$': false,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@datastructures-js/binary-search-tree": "^5.3.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"eventemitter2": "^6.4.9",
|
||||
"foxact": "^0.2.43",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { type ReadonlySignal, type Signal, signal } from '@preact/signals-core';
|
||||
import type {
|
||||
InteropObservable,
|
||||
Observer,
|
||||
@@ -140,6 +141,17 @@ export class LiveData<T = unknown>
|
||||
return data$;
|
||||
}
|
||||
|
||||
static fromSignal<T>(signal: ReadonlySignal<T>): LiveData<T> {
|
||||
return LiveData.from(
|
||||
new Observable(subscriber => {
|
||||
signal.subscribe(value => {
|
||||
subscriber.next(value);
|
||||
});
|
||||
}),
|
||||
signal.value
|
||||
);
|
||||
}
|
||||
|
||||
private static GLOBAL_COMPUTED_RECURSIVE_COUNT = 0;
|
||||
|
||||
/**
|
||||
@@ -286,6 +298,19 @@ export class LiveData<T = unknown>
|
||||
this.next(v);
|
||||
}
|
||||
|
||||
private _signal: Signal<T> | undefined;
|
||||
|
||||
get signal(): ReadonlySignal<T> {
|
||||
if (!this._signal) {
|
||||
this._signal = signal(this.value);
|
||||
this.subscribe(v => {
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
this._signal!.value = v;
|
||||
});
|
||||
}
|
||||
return this._signal;
|
||||
}
|
||||
|
||||
next = (v: T) => {
|
||||
if (this.isPoisoned) {
|
||||
throw this.poisonedError;
|
||||
@@ -379,7 +404,6 @@ export class LiveData<T = unknown>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
asObservable(): Observable<T> {
|
||||
return new Observable<T>(subscriber => {
|
||||
return this.subscribe(subscriber);
|
||||
@@ -421,7 +445,7 @@ export class LiveData<T = unknown>
|
||||
override pipe(...args: any[]) {
|
||||
return new Observable(subscriber => {
|
||||
this.ops$.next('watch');
|
||||
// eslint-disable-next-line prefer-spread
|
||||
|
||||
const subscription = this.raw$.pipe
|
||||
.apply(this.raw$, args as any)
|
||||
.subscribe(subscriber);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { JournalService } from '@affine/core/modules/journal';
|
||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
|
||||
import { MemberSearchService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import track from '@affine/track';
|
||||
import {
|
||||
@@ -69,6 +70,7 @@ import {
|
||||
type ReferenceReactRenderer,
|
||||
} from '../extensions/reference-renderer';
|
||||
import { patchSideBarService } from '../extensions/side-bar-service';
|
||||
import { patchUserListExtensions } from '../extensions/user-list';
|
||||
import { BiDirectionalLinkPanel } from './bi-directional-link-panel';
|
||||
import { BlocksuiteEditorJournalDocTitle } from './journal-doc-title';
|
||||
import { StarterBar } from './starter-bar';
|
||||
@@ -90,6 +92,7 @@ const usePatchSpecs = (mode: DocMode) => {
|
||||
editorService,
|
||||
workspaceService,
|
||||
featureFlagService,
|
||||
memberSearchService,
|
||||
} = useServices({
|
||||
PeekViewService,
|
||||
DocService,
|
||||
@@ -97,6 +100,7 @@ const usePatchSpecs = (mode: DocMode) => {
|
||||
WorkspaceService,
|
||||
EditorService,
|
||||
FeatureFlagService,
|
||||
MemberSearchService,
|
||||
});
|
||||
const framework = useFramework();
|
||||
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
|
||||
@@ -151,6 +155,7 @@ const usePatchSpecs = (mode: DocMode) => {
|
||||
patchPeekViewService(peekViewService),
|
||||
patchOpenDocExtension(),
|
||||
EdgelessClipboardWatcher,
|
||||
patchUserListExtensions(memberSearchService),
|
||||
patchDocUrlExtensions(framework),
|
||||
patchQuickSearchService(framework),
|
||||
patchSideBarService(framework),
|
||||
@@ -173,18 +178,19 @@ const usePatchSpecs = (mode: DocMode) => {
|
||||
|
||||
return builder.value;
|
||||
}, [
|
||||
framework,
|
||||
mode,
|
||||
enableAI,
|
||||
reactToLit,
|
||||
referenceRenderer,
|
||||
confirmModal,
|
||||
peekViewService,
|
||||
memberSearchService,
|
||||
docService,
|
||||
docsService,
|
||||
editorService,
|
||||
framework,
|
||||
peekViewService,
|
||||
reactToLit,
|
||||
referenceRenderer,
|
||||
featureFlagService,
|
||||
enableAI,
|
||||
enableTurboRenderer,
|
||||
featureFlagService.flags.enable_pdf_embed_preview.value,
|
||||
]);
|
||||
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { MemberSearchService } from '@affine/core/modules/permissions';
|
||||
import { UserListServiceExtension } from '@blocksuite/affine/blocks';
|
||||
|
||||
export function patchUserListExtensions(memberSearch: MemberSearchService) {
|
||||
return UserListServiceExtension({
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
hasMore$: memberSearch.hasMore$.signal,
|
||||
loadMore() {
|
||||
memberSearch.loadMore();
|
||||
},
|
||||
search(keyword) {
|
||||
memberSearch.search(keyword);
|
||||
},
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
users$: memberSearch.result$.map(users =>
|
||||
users.map(u => ({
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
avatar: u.avatarUrl,
|
||||
}))
|
||||
).signal,
|
||||
});
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import {
|
||||
DatabaseBlockDataSource,
|
||||
type DatabaseBlockModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { combineLatest, distinctUntilChanged, map, Observable } from 'rxjs';
|
||||
|
||||
import type { DocsService } from '../../doc';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
import type { DatabaseRow, DatabaseValueCell } from '../types';
|
||||
import { signalToLiveData, signalToObservable } from '../utils';
|
||||
import { signalToObservable } from '../utils';
|
||||
|
||||
const equalComparator = <T>(a: T, b: T) => {
|
||||
return isEqual(a, b);
|
||||
@@ -50,14 +50,14 @@ export class DocDatabaseBacklinksService extends Service {
|
||||
.map<DatabaseValueCell>(id => {
|
||||
return {
|
||||
id,
|
||||
value$: signalToLiveData(
|
||||
value$: LiveData.fromSignal(
|
||||
dataSource.cellValueGet$(rowId, id)
|
||||
).distinctUntilChanged(equalComparator),
|
||||
property: {
|
||||
id,
|
||||
type$: signalToLiveData(dataSource.propertyTypeGet$(id)),
|
||||
name$: signalToLiveData(dataSource.propertyNameGet$(id)),
|
||||
data$: signalToLiveData(dataSource.propertyDataGet$(id)),
|
||||
type$: LiveData.fromSignal(dataSource.propertyTypeGet$(id)),
|
||||
name$: LiveData.fromSignal(dataSource.propertyNameGet$(id)),
|
||||
data$: LiveData.fromSignal(dataSource.propertyDataGet$(id)),
|
||||
},
|
||||
};
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { DebugLogger } from '@affine/debug';
|
||||
import { BlockStdScope } from '@blocksuite/affine/block-std';
|
||||
import { PageEditorBlockSpecs } from '@blocksuite/affine/blocks';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { LiveData } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@@ -25,22 +24,6 @@ export function signalToObservable<T>(
|
||||
});
|
||||
}
|
||||
|
||||
export function signalToLiveData<T>(
|
||||
signal: ReadonlySignal<T>,
|
||||
defaultValue: T
|
||||
): LiveData<T>;
|
||||
|
||||
export function signalToLiveData<T>(
|
||||
signal: ReadonlySignal<T>
|
||||
): LiveData<T | undefined>;
|
||||
|
||||
export function signalToLiveData<T>(
|
||||
signal: ReadonlySignal<T>,
|
||||
defaultValue?: T
|
||||
) {
|
||||
return LiveData.from(signalToObservable(signal), defaultValue);
|
||||
}
|
||||
|
||||
// todo(pengx17): use rc pool?
|
||||
export function createBlockStdScope(doc: Store) {
|
||||
logger.debug('createBlockStdScope', doc.id);
|
||||
|
||||
@@ -13431,6 +13431,7 @@ __metadata:
|
||||
"@affine/templates": "workspace:*"
|
||||
"@datastructures-js/binary-search-tree": "npm:^5.3.2"
|
||||
"@emotion/react": "npm:^11.14.0"
|
||||
"@preact/signals-core": "npm:^1.8.0"
|
||||
"@swc/core": "npm:^1.10.1"
|
||||
"@testing-library/dom": "npm:^10.4.0"
|
||||
"@testing-library/react": "npm:^16.1.0"
|
||||
|
||||
Reference in New Issue
Block a user