feat(editor): replace slot with rxjs subject (#10768)

This commit is contained in:
Mirone
2025-03-12 11:29:24 +09:00
committed by GitHub
parent 19f978d9aa
commit cd63e0ed8b
302 changed files with 1405 additions and 1251 deletions

View File

@@ -441,7 +441,7 @@ const CREATE_AS_DOC = {
newDoc.addBlock('affine:surface', {}, rootId);
const noteId = newDoc.addBlock('affine:note', {}, rootId);
host.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({
host.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
pageId: newDoc.id,
host,
});

View File

@@ -341,7 +341,7 @@ const OthersAIGroup: AIItemGroupConfig = {
icon: CommentIcon(),
handler: host => {
const panel = getAIPanelWidget(host);
AIProvider.slots.requestOpenWithChat.emit({
AIProvider.slots.requestOpenWithChat.next({
host,
autoSelect: true,
});

View File

@@ -565,7 +565,7 @@ export function actionToResponse<T extends keyof BlockSuitePresets.AIActions>(
handler: () => {
reportResponse('result:continue-in-chat');
const panel = getAIPanelWidget(host);
AIProvider.slots.requestOpenWithChat.emit({ host });
AIProvider.slots.requestOpenWithChat.next({ host });
panel.hide();
},
},
@@ -604,11 +604,11 @@ export function actionToErrorResponse<
): ErrorConfig {
return {
upgrade: () => {
AIProvider.slots.requestUpgradePlan.emit({ host: panel.host });
AIProvider.slots.requestUpgradePlan.next({ host: panel.host });
panel.hide();
},
login: () => {
AIProvider.slots.requestLogin.emit({ host: panel.host });
AIProvider.slots.requestLogin.next({ host: panel.host });
panel.hide();
},
cancel: () => {

View File

@@ -193,7 +193,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
icon: ChatWithAiIcon(),
handler: () => {
reportResponse('result:continue-in-chat');
AIProvider.slots.requestOpenWithChat.emit({ host });
AIProvider.slots.requestOpenWithChat.next({ host });
panel.hide();
},
},
@@ -258,11 +258,11 @@ export function buildFinishConfig<T extends keyof BlockSuitePresets.AIActions>(
export function buildErrorConfig(panel: AffineAIPanelWidget) {
return {
upgrade: () => {
AIProvider.slots.requestUpgradePlan.emit({ host: panel.host });
AIProvider.slots.requestUpgradePlan.next({ host: panel.host });
panel.hide();
},
login: () => {
AIProvider.slots.requestLogin.emit({ host: panel.host });
AIProvider.slots.requestLogin.next({ host: panel.host });
panel.hide();
},
cancel: () => {

View File

@@ -273,12 +273,16 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
super.connectedCallback();
this._disposables.add(
AIProvider.slots.requestSendWithChat.on(
async ({ input, context, host }) => {
AIProvider.slots.requestSendWithChat.subscribe(
({ input, context, host }) => {
if (this.host === host) {
context && this.updateContext(context);
await this.updateComplete;
await this.send(input);
const { updateComplete, send } = this;
updateComplete
.then(() => {
return send(input);
})
.catch(console.error);
}
}
)

View File

@@ -316,7 +316,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
.catch(console.error);
disposables.add(
AIProvider.slots.userInfo.on(userInfo => {
AIProvider.slots.userInfo.subscribe(userInfo => {
const { status, error } = this.chatContextValue;
this.avatarUrl = userInfo?.avatarUrl ?? '';
if (
@@ -329,7 +329,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
})
);
disposables.add(
this.host.selection.slots.changed.on(() => {
this.host.selection.slots.changed.subscribe(() => {
this._selectionValue = this.host.selection.value;
})
);

View File

@@ -54,7 +54,7 @@ export class ChatPanelDocChip extends SignalWatcher(
const doc = this.docDisplayConfig.getDoc(this.chip.docId);
if (doc) {
this.disposables.add(
doc.slots.blockUpdated.on(
doc.slots.blockUpdated.subscribe(
throttle(this.autoUpdateChip, EXTRACT_DOC_THROTTLE)
)
);

View File

@@ -392,7 +392,7 @@ export class ChatPanel extends SignalWatcher(
if (!this.doc) throw new Error('doc is required');
this._disposables.add(
AIProvider.slots.actions.on(({ event }) => {
AIProvider.slots.actions.subscribe(({ event }) => {
const { status } = this.chatContextValue;
if (
event === 'finished' &&
@@ -403,16 +403,19 @@ export class ChatPanel extends SignalWatcher(
})
);
this._disposables.add(
AIProvider.slots.userInfo.on(async () => {
await this._initPanel();
AIProvider.slots.userInfo.subscribe(() => {
this._initPanel().catch(console.error);
})
);
this._disposables.add(
AIProvider.slots.requestOpenWithChat.on(async ({ host }) => {
AIProvider.slots.requestOpenWithChat.subscribe(({ host }) => {
if (this.host === host) {
const context = await extractSelectedContent(host);
if (!context) return;
this.updateContext(context);
extractSelectedContent(host)
.then(context => {
if (!context) return;
this.updateContext(context);
})
.catch(console.error);
}
})
);

View File

@@ -18,7 +18,7 @@ export const AIPreloadConfig = [
icon: LanguageIcon(),
text: 'Read a foreign language article with AI',
handler: () => {
AIProvider.slots.requestInsertTemplate.emit({
AIProvider.slots.requestInsertTemplate.next({
template: readAforeign,
mode: 'edgeless',
});
@@ -28,7 +28,7 @@ export const AIPreloadConfig = [
icon: MindmapIcon(),
text: 'Tidy an article with AI MindMap Action',
handler: () => {
AIProvider.slots.requestInsertTemplate.emit({
AIProvider.slots.requestInsertTemplate.next({
template: TidyMindMapV3,
mode: 'edgeless',
});
@@ -38,7 +38,7 @@ export const AIPreloadConfig = [
icon: ImageIcon(),
text: 'Add illustrations to the article',
handler: () => {
AIProvider.slots.requestInsertTemplate.emit({
AIProvider.slots.requestInsertTemplate.next({
template: redHat,
mode: 'edgeless',
});
@@ -48,7 +48,7 @@ export const AIPreloadConfig = [
icon: PenIcon(),
text: 'Complete writing with AI',
handler: () => {
AIProvider.slots.requestInsertTemplate.emit({
AIProvider.slots.requestInsertTemplate.next({
template: completeWritingWithAI,
mode: 'edgeless',
});
@@ -58,7 +58,7 @@ export const AIPreloadConfig = [
icon: SendIcon(),
text: 'Freely communicate with AI',
handler: () => {
AIProvider.slots.requestInsertTemplate.emit({
AIProvider.slots.requestInsertTemplate.next({
template: freelyCommunicateWithAI,
mode: 'edgeless',
});

View File

@@ -75,7 +75,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) {
aiPanel.hide();
extractSelectedContent(this.host)
.then(context => {
AIProvider.slots.requestSendWithChat.emit({
AIProvider.slots.requestSendWithChat.next({
input,
context,
host: this.host,

View File

@@ -107,7 +107,7 @@ const othersGroup: AIItemGroupConfig = {
showWhen: () => true,
handler: host => {
const panel = getAIPanelWidget(host);
AIProvider.slots.requestOpenWithChat.emit({
AIProvider.slots.requestOpenWithChat.next({
host,
mode: 'edgeless',
autoSelect: true,

View File

@@ -50,7 +50,7 @@ export function setupEdgelessElementToolbarAIEntry(
aiPanel.hide();
extractSelectedContent(edgeless.host)
.then(context => {
AIProvider.slots.requestSendWithChat.emit({
AIProvider.slots.requestSendWithChat.next({
input,
context,
host: edgeless.host,

View File

@@ -13,7 +13,7 @@ class AICodeBlockWatcher extends LifeCycleWatcher {
override mounted() {
super.mounted();
const { view } = this.std;
view.viewUpdated.on(payload => {
view.viewUpdated.subscribe(payload => {
if (payload.type !== 'widget' || payload.method !== 'add') {
return;
}

View File

@@ -52,7 +52,7 @@ function getAIEdgelessRootWatcher(framework: FrameworkProvider) {
override mounted() {
super.mounted();
const { view } = this.std;
view.viewUpdated.on(payload => {
view.viewUpdated.subscribe(payload => {
if (payload.type !== 'widget' || payload.method !== 'add') {
return;
}

View File

@@ -11,7 +11,7 @@ class AIImageBlockWatcher extends LifeCycleWatcher {
override mounted() {
super.mounted();
const { view } = this.std;
view.viewUpdated.on(payload => {
view.viewUpdated.subscribe(payload => {
if (payload.type !== 'widget' || payload.method !== 'add') {
return;
}

View File

@@ -23,7 +23,7 @@ function getAIPageRootWatcher(framework: FrameworkProvider) {
override mounted() {
super.mounted();
const { view } = this.std;
view.viewUpdated.on(payload => {
view.viewUpdated.subscribe(payload => {
if (payload.type !== 'widget' || payload.method !== 'add') {
return;
}

View File

@@ -188,7 +188,7 @@ const PaymentRequiredErrorRenderer = (host: EditorHost) => html`
<ai-error-wrapper
.text=${"You've reached the current usage cap for AFFiNE AI. You can subscribe to AFFiNE AI(with free 7-day-trial) to continue the AI experience!"}
.actionText=${'Upgrade'}
.onClick=${() => AIProvider.slots.requestUpgradePlan.emit({ host })}
.onClick=${() => AIProvider.slots.requestUpgradePlan.next({ host })}
></ai-error-wrapper>
`;
@@ -196,7 +196,7 @@ const LoginRequiredErrorRenderer = (host: EditorHost) => html`
<ai-error-wrapper
.text=${'You need to login to AFFiNE Cloud to continue using AFFiNE AI.'}
.actionText=${'Login'}
.onClick=${() => AIProvider.slots.requestLogin.emit({ host })}
.onClick=${() => AIProvider.slots.requestLogin.next({ host })}
></ai-error-wrapper>
`;

View File

@@ -1,14 +1,15 @@
import { BlockService } from '@blocksuite/affine/block-std';
import { Slot } from '@blocksuite/affine/global/slot';
import { RootBlockSchema } from '@blocksuite/affine/model';
import { Subject } from 'rxjs';
export class MindmapService extends BlockService {
static override readonly flavour = RootBlockSchema.model.flavour;
requestCenter = new Slot();
// eslint-disable-next-line rxjs/finnish
requestCenter = new Subject<void>();
center() {
this.requestCenter.emit();
this.requestCenter.next();
}
override mounted(): void {}

View File

@@ -65,7 +65,7 @@ export class MindmapSurfaceBlock extends BlockComponent<SurfaceBlockModel> {
private _setupCenterEffect() {
this._disposables.add(
this.mindmapService.requestCenter.on(() => {
this.mindmapService.requestCenter.subscribe(() => {
let bound: Bound;
this.model.elementModels.forEach(el => {
@@ -85,7 +85,7 @@ export class MindmapSurfaceBlock extends BlockComponent<SurfaceBlockModel> {
private _setupRenderer() {
this._disposables.add(
this.model.elementUpdated.on(() => {
this.model.elementUpdated.subscribe(() => {
this.mindmapService.center();
})
);

View File

@@ -1,6 +1,6 @@
import type { EditorHost } from '@blocksuite/affine/block-std';
import { Slot } from '@blocksuite/affine/global/slot';
import { captureException } from '@sentry/react';
import { Subject } from 'rxjs';
import type { ChatContextValue } from '../chat-panel/chat-context';
import {
@@ -123,22 +123,24 @@ export class AIProvider {
private readonly slots = {
// use case: when user selects "continue in chat" in an ask ai result panel
// do we need to pass the context to the chat panel?
requestOpenWithChat: new Slot<AIChatParams>(),
requestSendWithChat: new Slot<AISendParams>(),
requestInsertTemplate: new Slot<{
/* eslint-disable rxjs/finnish */
requestOpenWithChat: new Subject<AIChatParams>(),
requestSendWithChat: new Subject<AISendParams>(),
requestInsertTemplate: new Subject<{
template: string;
mode: 'page' | 'edgeless';
}>(),
requestLogin: new Slot<{ host: EditorHost }>(),
requestUpgradePlan: new Slot<{ host: EditorHost }>(),
requestLogin: new Subject<{ host: EditorHost }>(),
requestUpgradePlan: new Subject<{ host: EditorHost }>(),
// stream of AI actions triggered by users
actions: new Slot<{
actions: new Subject<{
action: keyof BlockSuitePresets.AIActions;
options: BlockSuitePresets.AITextActionOptions;
event: ActionEventType;
}>(),
// downstream can emit this slot to notify ai presets that user info has been updated
userInfo: new Slot<AIUserInfo | null>(),
userInfo: new Subject<AIUserInfo | null>(),
/* eslint-enable rxjs/finnish */
};
// track the history of triggered actions (in memory only)
@@ -162,7 +164,7 @@ export class AIProvider {
) => {
const options = args[0];
const slots = this.slots;
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'started',
@@ -186,31 +188,31 @@ export class AIProvider {
try {
user = await AIProvider.userInfo;
yield* result;
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'finished',
});
} catch (err) {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'error',
});
if (err instanceof PaymentRequiredError) {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'aborted:paywall',
});
} else if (err instanceof UnauthorizedError) {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'aborted:login-required',
});
} else {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'aborted:server-error',
@@ -232,7 +234,7 @@ export class AIProvider {
return result
.then(async result => {
user = await AIProvider.userInfo;
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'finished',
@@ -240,13 +242,13 @@ export class AIProvider {
return result;
})
.catch(err => {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'error',
});
if (err instanceof PaymentRequiredError) {
slots.actions.emit({
slots.actions.next({
action: id,
options,
event: 'aborted:paywall',

View File

@@ -527,13 +527,15 @@ Could you make a new website based on these notes and send back just the html fi
return client.forkSession(options);
});
const disposeRequestLoginHandler = AIProvider.slots.requestLogin.on(() => {
globalDialogService.open('sign-in', {});
});
const disposeRequestLoginHandler = AIProvider.slots.requestLogin.subscribe(
() => {
globalDialogService.open('sign-in', {});
}
);
setupTracker();
return () => {
disposeRequestLoginHandler.dispose();
disposeRequestLoginHandler.unsubscribe();
};
}

View File

@@ -3,6 +3,7 @@ import type { EditorHost } from '@blocksuite/affine/block-std';
import type { GfxPrimitiveElementModel } from '@blocksuite/affine/block-std/gfx';
import type { BlockModel } from '@blocksuite/affine/store';
import { lowerCase, omit } from 'lodash-es';
import type { Subject } from 'rxjs';
import { AIProvider } from './ai-provider';
@@ -60,9 +61,9 @@ type AIActionEventProperties = {
workspaceId: string;
};
type BlocksuiteActionEvent = Parameters<
Parameters<typeof AIProvider.slots.actions.on>[0]
>[0];
type SubjectValue<T> = T extends Subject<infer U> ? U : never;
type BlocksuiteActionEvent = SubjectValue<typeof AIProvider.slots.actions>;
const trackAction = ({
eventName,
@@ -252,15 +253,15 @@ const toTrackedOptions = (
};
export function setupTracker() {
AIProvider.slots.requestUpgradePlan.on(() => {
AIProvider.slots.requestUpgradePlan.subscribe(() => {
track.$.paywall.aiAction.viewPlans();
});
AIProvider.slots.requestLogin.on(() => {
AIProvider.slots.requestLogin.subscribe(() => {
track.doc.editor.aiActions.requestSignIn();
});
AIProvider.slots.actions.on(event => {
AIProvider.slots.actions.subscribe(event => {
const properties = toTrackedOptions(event);
if (properties) {
trackAction(properties);

View File

@@ -10,7 +10,7 @@ import {
Bound,
getCommonBoundWithRotation,
} from '@blocksuite/affine/global/gfx';
import { Slot } from '@blocksuite/affine/global/slot';
import { Subject } from 'rxjs';
import {
AFFINE_AI_PANEL_WIDGET,
@@ -22,7 +22,8 @@ export class CopilotTool extends BaseTool {
private _dragging = false;
draggingAreaUpdated = new Slot<boolean | void>();
// eslint-disable-next-line rxjs/finnish
draggingAreaUpdated = new Subject<boolean | void>();
dragLastPoint: [number, number] = [0, 0];
@@ -85,7 +86,7 @@ export class CopilotTool extends BaseTool {
if (!this._dragging) return;
this._dragging = false;
this.draggingAreaUpdated.emit(true);
this.draggingAreaUpdated.next(true);
}
override dragMove(e: PointerEventState): void {
@@ -108,7 +109,7 @@ export class CopilotTool extends BaseTool {
});
}
this.draggingAreaUpdated.emit();
this.draggingAreaUpdated.next();
}
override dragStart(e: PointerEventState): void {
@@ -116,7 +117,7 @@ export class CopilotTool extends BaseTool {
this._initDragState(e);
this._dragging = true;
this.draggingAreaUpdated.emit();
this.draggingAreaUpdated.next();
}
override mounted(): void {
@@ -167,7 +168,7 @@ export class CopilotTool extends BaseTool {
inoperable: true,
});
this.draggingAreaUpdated.emit(true);
this.draggingAreaUpdated.next(true);
}
}

View File

@@ -4,7 +4,7 @@ export function reportResponse(event: ActionEventType) {
const lastAction = AIProvider.actionHistory.at(-1);
if (!lastAction) return;
AIProvider.slots.actions.emit({
AIProvider.slots.actions.next({
action: lastAction.action,
options: lastAction.options,
event,

View File

@@ -200,7 +200,7 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
const CopilotSelectionTool = this.gfx.tool.get('copilot');
this._disposables.add(
CopilotSelectionTool.draggingAreaUpdated.on(shouldShowPanel => {
CopilotSelectionTool.draggingAreaUpdated.subscribe(shouldShowPanel => {
this._visible = true;
this._updateSelection(CopilotSelectionTool.area);
if (shouldShowPanel) {
@@ -213,7 +213,7 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
);
this._disposables.add(
this.gfx.viewport.viewportUpdated.on(() => {
this.gfx.viewport.viewportUpdated.subscribe(() => {
if (!this._visible) return;
this._updateSelection(CopilotSelectionTool.area);
@@ -258,7 +258,7 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
lockToolbar(disabled: boolean) {
const legacySlot = this.std.get(EdgelessLegacySlotIdentifier);
legacySlot.toolbarLocked.emit(disabled);
legacySlot.toolbarLocked.next(disabled);
}
override render() {

View File

@@ -9,7 +9,7 @@ import {
customImageProxyMiddleware,
ImageProxyService,
} from '@blocksuite/affine/blocks/image';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import type { DocMode } from '@blocksuite/affine/model';
import { LinkPreviewerService } from '@blocksuite/affine/shared/services';
import type { Store } from '@blocksuite/affine/store';
@@ -49,13 +49,14 @@ const BlockSuiteEditorImpl = ({
defaultOpenProperty,
}: EditorProps) => {
useEffect(() => {
const disposable = page.slots.blockUpdated.once(() => {
const disposable = page.slots.blockUpdated.subscribe(() => {
disposable.unsubscribe();
page.workspace.meta.setDocMeta(page.id, {
updatedDate: Date.now(),
});
});
return () => {
disposable.dispose();
disposable.unsubscribe();
};
}, [page]);
@@ -154,15 +155,16 @@ export const BlockSuiteEditor = (props: EditorProps) => {
return;
}
const timer = setTimeout(() => {
disposable.dispose();
disposable.unsubscribe();
setError(new NoPageRootError(props.page));
}, 20 * 1000);
const disposable = props.page.slots.rootAdded.once(() => {
const disposable = props.page.slots.rootAdded.subscribe(() => {
disposable.unsubscribe();
setIsLoading(false);
clearTimeout(timer);
});
return () => {
disposable.dispose();
disposable.unsubscribe();
clearTimeout(timer);
};
}, [props.page]);

View File

@@ -104,8 +104,11 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
const { id, created } = rootComponent.focusFirstParagraph();
if (created) {
std.view.viewUpdated.once(v => {
if (v.id === id) handleInlineAskAIAction(std.host, pageAIGroups);
const subscription = std.view.viewUpdated.subscribe(v => {
if (v.id === id) {
subscription.unsubscribe();
handleInlineAskAIAction(std.host, pageAIGroups);
}
});
} else {
handleInlineAskAIAction(std.host, pageAIGroups);

View File

@@ -47,7 +47,7 @@ export class EdgelessEditor extends SignalWatcher(
override connectedCallback() {
super.connectedCallback();
this._disposables.add(
this.doc.slots.rootAdded.on(() => this.requestUpdate())
this.doc.slots.rootAdded.subscribe(() => this.requestUpdate())
);
this.std = new BlockStdScope({
store: this.doc,

View File

@@ -59,7 +59,7 @@ export class PageEditor extends SignalWatcher(
override connectedCallback() {
super.connectedCallback();
this._disposables.add(
this.doc.slots.rootAdded.on(() => this.requestUpdate())
this.doc.slots.rootAdded.subscribe(() => this.requestUpdate())
);
this.std = new BlockStdScope({
store: this.doc,

View File

@@ -44,9 +44,7 @@ export function patchDocModeService(
? docsService.list.primaryMode$(id)
: docService.doc.primaryMode$;
const sub = mode$.subscribe(m => handler((m || DEFAULT_MODE) as DocMode));
return {
dispose: sub.unsubscribe,
};
return sub;
};
}

View File

@@ -9,7 +9,7 @@ export class EdgelessClipboardWatcher extends LifeCycleWatcher {
override mounted() {
super.mounted();
const { view } = this.std;
view.viewUpdated.on(payload => {
view.viewUpdated.subscribe(payload => {
if (payload.type !== 'block' || payload.method !== 'add') {
return;
}

View File

@@ -14,7 +14,7 @@ import type {
Container,
ServiceIdentifier,
} from '@blocksuite/affine/global/di';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import {
type ReferenceNodeConfig,
ReferenceNodeConfigIdentifier,

View File

@@ -57,7 +57,7 @@ const EdgelessNoteToggleButton = ({ note }: { note: NoteBlockModel }) => {
const { selection } = gfx;
const dispose = selection.slots.updated.on(() => {
const dispose = selection.slots.updated.subscribe(() => {
if (selection.has(note.id) && selection.editing) {
note.doc.transact(() => {
note.edgeless.collapse = false;
@@ -65,7 +65,7 @@ const EdgelessNoteToggleButton = ({ note }: { note: NoteBlockModel }) => {
}
});
return () => dispose.dispose();
return () => dispose.unsubscribe();
}, [gfx, note]);
const toggle = useCallback(() => {

View File

@@ -11,11 +11,11 @@ export function useAllBlockSuiteDocMeta(docCollection: Workspace): DocMeta[] {
weakMap.set(docCollection, baseAtom);
baseAtom.onMount = set => {
set([...docCollection.meta.docMetas]);
const dispose = docCollection.slots.docListUpdated.on(() => {
const dispose = docCollection.slots.docListUpdated.subscribe(() => {
set([...docCollection.meta.docMetas]);
});
return () => {
dispose.dispose();
dispose.unsubscribe();
};
};
}

View File

@@ -1,5 +1,5 @@
import { DebugLogger } from '@affine/debug';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import type { Store, Workspace } from '@blocksuite/affine/store';
import { useEffect, useState } from 'react';
@@ -16,14 +16,14 @@ export function useDocCollectionPage(
useEffect(() => {
const group = new DisposableGroup();
group.add(
docCollection.slots.docCreated.on(id => {
docCollection.slots.docCreated.subscribe(id => {
if (pageId === id) {
setPage(docCollection.getDoc(id));
}
})
);
group.add(
docCollection.slots.docRemoved.on(id => {
docCollection.slots.docRemoved.subscribe(id => {
if (pageId === id) {
setPage(null);
}

View File

@@ -69,16 +69,16 @@ export function useBlockSuitePagePreview(page: Store | null): Atom<string> {
const baseAtom = atom<string>('');
baseAtom.onMount = set => {
const disposables = [
page.slots.ready.on(() => {
page.slots.ready.subscribe(() => {
set(getPagePreviewText(page));
}),
page.slots.blockUpdated.on(() => {
page.slots.blockUpdated.subscribe(() => {
set(getPagePreviewText(page));
}),
];
set(getPagePreviewText(page));
return () => {
disposables.forEach(disposable => disposable.dispose());
disposables.forEach(disposable => disposable.unsubscribe());
};
};
weakMap.set(page, baseAtom);

View File

@@ -1,5 +1,5 @@
import { DebugLogger } from '@affine/debug';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import type { Store, Workspace } from '@blocksuite/affine/store';
import { useEffect, useState } from 'react';
@@ -16,14 +16,14 @@ export function useDocCollectionPage(
useEffect(() => {
const group = new DisposableGroup();
group.add(
docCollection.slots.docCreated.on(id => {
docCollection.slots.docCreated.subscribe(id => {
if (pageId === id) {
setPage(docCollection.getDoc(id));
}
})
);
group.add(
docCollection.slots.docRemoved.on(id => {
docCollection.slots.docRemoved.subscribe(id => {
if (pageId === id) {
setPage(null);
}

View File

@@ -111,14 +111,14 @@ export const WorkspaceSideEffects = () => {
})
);
const disposable = AIProvider.slots.requestInsertTemplate.on(
const disposable = AIProvider.slots.requestInsertTemplate.subscribe(
({ template, mode }) => {
insertTemplate({ template, mode });
}
);
return () => {
disposable.dispose();
disposable.unsubscribe();
insertTemplate.unsubscribe();
};
}, [
@@ -134,14 +134,14 @@ export const WorkspaceSideEffects = () => {
const globalDialogService = useService(GlobalDialogService);
useEffect(() => {
const disposable = AIProvider.slots.requestUpgradePlan.on(() => {
const disposable = AIProvider.slots.requestUpgradePlan.subscribe(() => {
workspaceDialogService.open('setting', {
activeTab: 'billing',
});
track.$.paywall.aiAction.viewPlans();
});
return () => {
disposable.dispose();
disposable.unsubscribe();
};
}, [workspaceDialogService]);

View File

@@ -92,7 +92,7 @@ export const EdgelessSnapshot = (props: Props) => {
// refresh viewport
const gfx = editorHost.std.get(GfxControllerIdentifier);
const disposable = editorHost.std.view.viewUpdated.on(payload => {
const disposable = editorHost.std.view.viewUpdated.subscribe(payload => {
if (
payload.type !== 'block' ||
payload.method !== 'add' ||
@@ -110,7 +110,7 @@ export const EdgelessSnapshot = (props: Props) => {
const bound = boundMap.get(docName);
bound && gfx.viewport.setViewportByBound(bound);
doc.readonly = true;
disposable.dispose();
disposable.unsubscribe();
});
// append to dom node

View File

@@ -42,13 +42,12 @@ export const ProfilePanel = () => {
useEffect(() => {
if (workspace?.docCollection) {
setName(workspace.docCollection.meta.name ?? UNTITLED_WORKSPACE_NAME);
const dispose = workspace.docCollection.meta.commonFieldsUpdated.on(
() => {
const dispose =
workspace.docCollection.meta.commonFieldsUpdated.subscribe(() => {
setName(workspace.docCollection.meta.name ?? UNTITLED_WORKSPACE_NAME);
}
);
});
return () => {
dispose.dispose();
dispose.unsubscribe();
};
} else {
setName(UNTITLED_WORKSPACE_NAME);

View File

@@ -18,10 +18,7 @@ import { ViewService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { isNewTabTrigger } from '@affine/core/utils';
import track from '@affine/track';
import {
type Disposable,
DisposableGroup,
} from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
import {
AiIcon,
@@ -39,6 +36,7 @@ import {
import clsx from 'clsx';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import type { Subscription } from 'rxjs';
import { AffineErrorBoundary } from '../../../../components/affine/affine-error-boundary';
import { GlobalPageHistoryModal } from '../../../../components/affine/page-history-modal';
@@ -113,14 +111,18 @@ const DetailPageImpl = memo(function DetailPageImpl() {
}, [editorContainer, isActiveView, setActiveBlockSuiteEditor]);
useEffect(() => {
const disposables: Disposable[] = [];
const disposables: Subscription[] = [];
const openHandler = () => {
workbench.openSidebar();
view.activeSidebarTab('chat');
};
disposables.push(AIProvider.slots.requestOpenWithChat.on(openHandler));
disposables.push(AIProvider.slots.requestSendWithChat.on(openHandler));
return () => disposables.forEach(d => d.dispose());
disposables.push(
AIProvider.slots.requestOpenWithChat.subscribe(openHandler)
);
disposables.push(
AIProvider.slots.requestSendWithChat.subscribe(openHandler)
);
return () => disposables.forEach(d => d.unsubscribe());
}, [activeSidebarTab, view, workbench]);
useEffect(() => {
@@ -169,7 +171,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
if (refNodeSlots) {
disposable.add(
// the event should not be emitted by AffineReference
refNodeSlots.docLinkClicked.on(
refNodeSlots.docLinkClicked.subscribe(
({ pageId, params, openMode, event, host }) => {
if (host !== editorContainer.host) {
return;

View File

@@ -109,7 +109,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
const docModeService = editor.host.std.get(DocModeProvider);
const refNodeService = editor.host.std.getOptional(RefNodeSlotsProvider);
const disposable = [
refNodeService?.docLinkClicked.on(({ host }) => {
refNodeService?.docLinkClicked.subscribe(({ host }) => {
if (host === editor.host) {
(chatPanelRef.current as ChatPanel).doc = editor.doc;
}
@@ -120,7 +120,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
}, editor.doc.id),
];
return () => disposable.forEach(d => d?.dispose());
return () => disposable.forEach(d => d?.unsubscribe());
}, [editor, framework]);
return <div className={styles.root} ref={containerRef} />;

View File

@@ -20,7 +20,7 @@ import {
WorkspacesService,
} from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import { type DocMode, DocModes } from '@blocksuite/affine/model';
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
import { Logo1Icon } from '@blocksuite/icons/rc';
@@ -201,7 +201,7 @@ const SharePageInner = ({
editorContainer.host?.std.getOptional(RefNodeSlotsProvider);
if (refNodeSlots) {
disposable.add(
refNodeSlots.docLinkClicked.on(({ pageId, params }) => {
refNodeSlots.docLinkClicked.subscribe(({ pageId, params }) => {
if (params) {
const { mode, blockIds, elementIds } = params;
jumpToPageBlock(workspaceId, pageId, mode, blockIds, elementIds);

View File

@@ -25,7 +25,7 @@ import {
customImageProxyMiddleware,
ImageProxyService,
} from '@blocksuite/affine/blocks/image';
import { DisposableGroup } from '@blocksuite/affine/global/slot';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
import { LinkPreviewerService } from '@blocksuite/affine/shared/services';
import {
@@ -162,7 +162,7 @@ const DetailPageImpl = () => {
const disposable = new DisposableGroup();
if (refNodeService) {
disposable.add(
refNodeService.docLinkClicked.on(({ pageId, params }) => {
refNodeService.docLinkClicked.subscribe(({ pageId, params }) => {
if (params) {
const { mode, blockIds, elementIds } = params;
return jumpToPageBlock(

View File

@@ -52,7 +52,7 @@ export class AuthService extends Service {
skip(1) // skip the initial value
)
.subscribe(({ account }) => {
AIProvider.slots.userInfo.emit(toAIUserInfo(account));
AIProvider.slots.userInfo.next(toAIUserInfo(account));
if (account === null) {
this.eventBus.emit(AccountLoggedOut, account);

View File

@@ -17,7 +17,7 @@ export class EditorSettingService extends Service {
onWorkspaceInitialized(workspace: Workspace) {
// set default mode for new doc
workspace.docCollection.slots.docCreated.on(docId => {
workspace.docCollection.slots.docCreated.subscribe(docId => {
const preferMode = this.editorSetting.settings$.value.newDocDefaultMode;
const docsService = workspace.scope.get(DocsService);
const mode = preferMode === 'ask' ? 'page' : preferMode;

View File

@@ -261,7 +261,9 @@ export class Editor extends Entity {
scrollViewport?.removeEventListener('scroll', saveScrollPosition);
});
if (gfx) {
unsubs.push(gfx.viewport.viewportUpdated.on(saveScrollPosition).dispose);
const subscription =
gfx.viewport.viewportUpdated.subscribe(saveScrollPosition);
unsubs.push(subscription.unsubscribe.bind(subscription));
}
// update selection when focusAt$ changed

View File

@@ -9,11 +9,8 @@ import { EditorService } from '@affine/core/modules/editor';
import { GuardService } from '@affine/core/modules/permissions';
import { DebugLogger } from '@affine/debug';
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
import { Bound } from '@blocksuite/affine/global/gfx';
import {
type Disposable,
DisposableGroup,
} from '@blocksuite/affine/global/slot';
import { RefNodeSlotsProvider } from '@blocksuite/affine/rich-text';
import {
FrameworkScope,
@@ -23,6 +20,7 @@ import {
} from '@toeverything/infra';
import clsx from 'clsx';
import { lazy, Suspense, useCallback, useEffect } from 'react';
import type { Subscription } from 'rxjs';
import { WorkbenchService } from '../../../workbench';
import type { DocReferenceInfo } from '../../entities/peek-view';
@@ -101,7 +99,7 @@ function DocPeekPreviewEditor({
// doc change event inside peek view should be handled by peek view
disposableGroup.add(
// todo(@pengx17): seems not working
refNodeSlots.docLinkClicked.on(options => {
refNodeSlots.docLinkClicked.subscribe(options => {
if (options.host !== editorContainer.host) {
return;
}
@@ -129,7 +127,7 @@ function DocPeekPreviewEditor({
);
useEffect(() => {
const disposables: Disposable[] = [];
const disposables: Subscription[] = [];
const openHandler = () => {
if (doc) {
workbench.openDoc(doc.id);
@@ -137,9 +135,13 @@ function DocPeekPreviewEditor({
// chat panel open is already handled in <DetailPageImpl />
}
};
disposables.push(AIProvider.slots.requestOpenWithChat.on(openHandler));
disposables.push(AIProvider.slots.requestSendWithChat.on(openHandler));
return () => disposables.forEach(d => d.dispose());
disposables.push(
AIProvider.slots.requestOpenWithChat.subscribe(openHandler)
);
disposables.push(
AIProvider.slots.requestSendWithChat.subscribe(openHandler)
);
return () => disposables.forEach(d => d.unsubscribe());
}, [doc, peekView, workbench, workspace.id]);
const openOutlinePanel = useCallback(() => {

View File

@@ -23,8 +23,10 @@ export class TagStore extends Store {
subscribe(cb: () => void) {
const disposable =
this.workspaceService.workspace.docCollection.slots.docListUpdated.on(cb);
return disposable.dispose;
this.workspaceService.workspace.docCollection.slots.docListUpdated.subscribe(
cb
);
return disposable.unsubscribe.bind(disposable);
}
constructor(private readonly workspaceService: WorkspaceService) {

View File

@@ -73,9 +73,11 @@ export class Workspace extends Entity {
name$ = LiveData.from<string | undefined>(
new Observable(subscriber => {
subscriber.next(this.docCollection.meta.name);
return this.docCollection.meta.commonFieldsUpdated.on(() => {
subscriber.next(this.docCollection.meta.name);
}).dispose;
const subscription =
this.docCollection.meta.commonFieldsUpdated.subscribe(() => {
subscriber.next(this.docCollection.meta.name);
});
return subscription.unsubscribe.bind(subscription);
}),
undefined
);
@@ -83,9 +85,11 @@ export class Workspace extends Entity {
avatar$ = LiveData.from<string | undefined>(
new Observable(subscriber => {
subscriber.next(this.docCollection.meta.avatar);
return this.docCollection.meta.commonFieldsUpdated.on(() => {
subscriber.next(this.docCollection.meta.avatar);
}).dispose;
const subscription =
this.docCollection.meta.commonFieldsUpdated.subscribe(() => {
subscriber.next(this.docCollection.meta.avatar);
});
return subscription.unsubscribe.bind(subscription);
}),
undefined
);

View File

@@ -1,4 +1,3 @@
import { Slot } from '@blocksuite/affine/global/slot';
import { SpecProvider } from '@blocksuite/affine/shared/utils';
import {
AwarenessStore,
@@ -10,6 +9,7 @@ import {
type YBlock,
} from '@blocksuite/affine/store';
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs';
@@ -37,7 +37,7 @@ export class DocImpl implements Doc {
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.emit();
this.slots.historyUpdated.next();
};
private readonly _initSubDoc = () => {
@@ -65,7 +65,8 @@ export class DocImpl implements Doc {
private _loaded!: boolean;
private readonly _onLoadSlot = new Slot();
// eslint-disable-next-line rxjs/finnish
private readonly _onLoadSlot = new Subject();
/** Indicate whether the block tree is ready */
private _ready = false;
@@ -98,8 +99,10 @@ export class DocImpl implements Doc {
readonly rootDoc: Y.Doc;
readonly slots = {
historyUpdated: new Slot(),
yBlockUpdated: new Slot<
// eslint-disable-next-line rxjs/finnish
historyUpdated: new Subject<void>(),
// eslint-disable-next-line rxjs/finnish
yBlockUpdated: new Subject<
| {
type: 'add';
id: string;
@@ -170,11 +173,11 @@ export class DocImpl implements Doc {
}
private _handleYBlockAdd(id: string) {
this.slots.yBlockUpdated.emit({ type: 'add', id });
this.slots.yBlockUpdated.next({ type: 'add', id });
}
private _handleYBlockDelete(id: string) {
this.slots.yBlockUpdated.emit({ type: 'delete', id });
this.slots.yBlockUpdated.next({ type: 'delete', id });
}
private _handleYEvent(event: Y.YEvent<YBlock | Y.Text | Y.Array<unknown>>) {
@@ -229,13 +232,13 @@ export class DocImpl implements Doc {
private _destroy() {
this.awarenessStore.destroy();
this._ySpaceDoc.destroy();
this._onLoadSlot.dispose();
this._onLoadSlot.unsubscribe();
this._loaded = false;
}
dispose() {
this._destroy();
this.slots.historyUpdated.dispose();
this.slots.historyUpdated.unsubscribe();
if (this.ready) {
this._yBlocks.unobserveDeep(this._handleYEvents);

View File

@@ -1,10 +1,10 @@
import { Slot } from '@blocksuite/affine/global/slot';
import {
createYProxy,
type DocMeta,
type DocsPropertiesMeta,
type WorkspaceMeta,
} from '@blocksuite/affine/store';
import { Subject } from 'rxjs';
import type * as Y from 'yjs';
type MetaState = {
@@ -15,10 +15,12 @@ type MetaState = {
};
export class WorkspaceMetaImpl implements WorkspaceMeta {
commonFieldsUpdated = new Slot();
docMetaAdded = new Slot<string>();
docMetaRemoved = new Slot<string>();
docMetaUpdated = new Slot();
/* eslint-disable rxjs/finnish */
commonFieldsUpdated = new Subject<void>();
docMetaAdded = new Subject<string>();
docMetaRemoved = new Subject<string>();
docMetaUpdated = new Subject<void>();
/* eslint-enable rxjs/finnish */
private readonly _handleDocCollectionMetaEvents = (
events: Y.YEvent<Y.Array<unknown> | Y.Text | Y.Map<unknown>>[]
@@ -81,7 +83,7 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
setProperties(meta: DocsPropertiesMeta) {
this._proxy.properties = meta;
this.docMetaUpdated.emit();
this.docMetaUpdated.next();
}
get docMetas() {
@@ -108,7 +110,7 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
}
private _handleCommonFieldsEvent() {
this.commonFieldsUpdated.emit();
this.commonFieldsUpdated.next();
}
private _handleDocMetaEvent() {
@@ -118,7 +120,7 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
docMetas.forEach(docMeta => {
if (!_prevDocs.has(docMeta.id)) {
this.docMetaAdded.emit(docMeta.id);
this.docMetaAdded.next(docMeta.id);
}
newDocs.add(docMeta.id);
});
@@ -126,13 +128,13 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
_prevDocs.forEach(prevDocId => {
const isRemoved = newDocs.has(prevDocId) === false;
if (isRemoved) {
this.docMetaRemoved.emit(prevDocId);
this.docMetaRemoved.next(prevDocId);
}
});
this._prevDocs = newDocs;
this.docMetaUpdated.emit();
this.docMetaUpdated.next();
}
addDocMeta(doc: DocMeta, index?: number) {

View File

@@ -2,7 +2,6 @@ import {
BlockSuiteError,
ErrorCode,
} from '@blocksuite/affine/global/exceptions';
import { Slot } from '@blocksuite/affine/global/slot';
import { NoopLogger } from '@blocksuite/affine/global/utils';
import {
type CreateBlocksOptions,
@@ -19,6 +18,7 @@ import {
type BlobSource,
MemoryBlobSource,
} from '@blocksuite/affine/sync';
import { Subject } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs';
@@ -46,9 +46,11 @@ export class WorkspaceImpl implements Workspace {
meta: WorkspaceMeta;
slots = {
docListUpdated: new Slot(),
docRemoved: new Slot<string>(),
docCreated: new Slot<string>(),
/* eslint-disable rxjs/finnish */
docListUpdated: new Subject<void>(),
docRemoved: new Subject<string>(),
docCreated: new Subject<string>(),
/* eslint-enable rxjs/finnish */
};
get docs() {
@@ -82,7 +84,7 @@ export class WorkspaceImpl implements Workspace {
}
private _bindDocMetaEvents() {
this.meta.docMetaAdded.on(docId => {
this.meta.docMetaAdded.subscribe(docId => {
const doc = new DocImpl({
id: docId,
collection: this,
@@ -91,14 +93,14 @@ export class WorkspaceImpl implements Workspace {
this.blockCollections.set(doc.id, doc);
});
this.meta.docMetaUpdated.on(() => this.slots.docListUpdated.emit());
this.meta.docMetaUpdated.subscribe(() => this.slots.docListUpdated.next());
this.meta.docMetaRemoved.on(id => {
this.meta.docMetaRemoved.subscribe(id => {
const doc = this._getDoc(id);
if (!doc) return;
this.blockCollections.delete(id);
doc.remove();
this.slots.docRemoved.emit(id);
this.slots.docRemoved.next(id);
});
}
@@ -126,7 +128,7 @@ export class WorkspaceImpl implements Workspace {
createDate: Date.now(),
tags: [],
});
this.slots.docCreated.emit(docId);
this.slots.docCreated.next(docId);
return this.getDoc(docId, {
id: docId,
query,