Files
AFFiNE-Mirror/blocksuite/framework/std/src/scope/std-scope.ts
T
Wu Yue d10e5ee92f feat(core): completely remove the dependence on EditorHost (#13110)
Close [AI-260](https://linear.app/affine-design/issue/AI-260)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added theme support to AI chat and message components, enabling
dynamic theming based on the current app theme.
* Introduced a reactive theme signal to the theme service for improved
theme handling.
* Integrated notification and theme services across various AI chat,
playground, and message components for consistent user experience.

* **Refactor**
* Simplified component APIs by removing dependencies on editor host and
related properties across AI chat, message, and tool components.
* Centralized and streamlined clipboard and markdown conversion
utilities, reducing external dependencies.
* Standardized the interface for context file addition and improved type
usage for better consistency.
* Reworked notification service to a class-based implementation for
improved encapsulation.
* Updated AI chat components to use injected notification and theme
services instead of host-based retrieval.

* **Bug Fixes**
* Improved reliability of copy and notification actions by decoupling
them from editor host dependencies.

* **Chores**
* Updated and cleaned up internal imports and removed unused properties
to enhance maintainability.
  * Added test IDs for sidebar close button to improve test reliability.
  * Updated test prompts in end-to-end tests for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 10:16:55 +00:00

170 lines
3.8 KiB
TypeScript

import { Container, type ServiceProvider } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import {
type ExtensionType,
type Store,
StoreSelectionExtension,
} from '@blocksuite/store';
import { Clipboard } from '../clipboard/index.js';
import { CommandManager } from '../command/index.js';
import { UIEventDispatcher } from '../event/index.js';
import { DndController } from '../extension/dnd/index.js';
import { EditorLifeCycleExtension } from '../extension/editor-life-cycle.js';
import { ServiceManager } from '../extension/service-manager.js';
import { GfxController } from '../gfx/controller.js';
import { GridManager, LayerManager } from '../gfx/index.js';
import { GfxSelectionManager } from '../gfx/selection.js';
import { SurfaceMiddlewareExtension } from '../gfx/surface-middleware.js';
import { ViewManager } from '../gfx/view/view-manager.js';
import {
BlockViewIdentifier,
LifeCycleWatcherIdentifier,
StdIdentifier,
} from '../identifier.js';
import { RangeManager } from '../inline/index.js';
import { EditorHost } from '../view/element/index.js';
import { ViewStore } from '../view/view-store.js';
export interface BlockStdOptions {
store: Store;
extensions: ExtensionType[];
}
export const internalExtensions = [
ServiceManager,
CommandManager,
UIEventDispatcher,
RangeManager,
ViewStore,
Clipboard,
GfxController,
GfxSelectionManager,
GridManager,
LayerManager,
SurfaceMiddlewareExtension,
ViewManager,
DndController,
EditorLifeCycleExtension,
];
export class BlockStdScope {
static internalExtensions = internalExtensions;
readonly container: Container;
readonly store: Store;
readonly provider: ServiceProvider;
readonly userExtensions: ExtensionType[];
private get _lifeCycleWatchers() {
return this.provider.getAll(LifeCycleWatcherIdentifier);
}
private _host!: EditorHost;
get dnd() {
return this.get(DndController);
}
get clipboard() {
return this.get(Clipboard);
}
get workspace() {
return this.store.workspace;
}
get command() {
return this.get(CommandManager);
}
get event() {
return this.get(UIEventDispatcher);
}
get get() {
return this.provider.get.bind(this.provider);
}
get getOptional() {
return this.provider.getOptional.bind(this.provider);
}
get host() {
if (!this._host) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Host is not ready to use, the `render` method should be called first'
);
}
return this._host;
}
get range() {
return this.get(RangeManager);
}
get selection() {
return this.get(StoreSelectionExtension);
}
get view() {
return this.get(ViewStore);
}
constructor(options: BlockStdOptions) {
this.store = options.store;
this.userExtensions = options.extensions;
this.container = new Container();
this.container.addImpl(StdIdentifier, () => this);
internalExtensions.forEach(ext => {
const container = this.container;
ext.setup(container);
});
this.userExtensions.forEach(ext => {
const container = this.container;
ext.setup(container);
});
this.provider = this.container.provider(undefined, this.store.provider);
this._lifeCycleWatchers.forEach(watcher => {
watcher.created();
});
}
getView(flavour: string) {
return this.getOptional(BlockViewIdentifier(flavour));
}
mount() {
this._lifeCycleWatchers.forEach(watcher => {
watcher.mounted();
});
}
render() {
const element = new EditorHost();
element.std = this;
element.store = this.store;
this._host = element;
this._lifeCycleWatchers.forEach(watcher => {
watcher.rendered();
});
return element;
}
unmount() {
this._lifeCycleWatchers.forEach(watcher => {
watcher.unmounted();
});
}
}