Files
AFFiNE-Mirror/blocksuite/affine/ext-loader/src/manager.ts
T
Saul-Mirone cb550b7b21 refactor(editor): extract mobile extension builder (#12239)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new mobile view extension that activates mobile-specific UI features based on the runtime environment.
  - Automatically enables mobile keyboard toolbar and linked document menu features in mobile contexts.

- **Improvements**
  - Simplified code and paragraph block configurations on mobile, including disabling line numbers and adjusting placeholders.
  - Enhanced configuration chaining to include mobile-specific settings by default.
  - Improved extension registration flow with method chaining support.

- **Refactor**
  - Removed deprecated mobile patch classes and configurations, consolidating mobile logic into dedicated extensions.
  - Streamlined mobile-related code for better maintainability and performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-12 12:54:51 +00:00

147 lines
4.4 KiB
TypeScript

import { BlockSuiteError } from '@blocksuite/global/exceptions';
import type { ExtensionType } from '@blocksuite/store';
import type { BaseExtensionProvider, Context, Empty } from './base-provider';
/**
* A manager class that handles the registration and configuration of extensions
* for different scopes. It manages extension providers and their instances,
* allowing for dynamic configuration and extension loading.
*
* @typeParam Scope - The type of scope identifiers used for categorizing extensions
*/
export class ExtensionManager<Scope extends string> {
/** @internal */
protected _extensions: Map<string, Set<ExtensionType>> = new Map();
/** @internal */
private readonly _providers: Set<typeof BaseExtensionProvider<Scope>>;
/** @internal */
private readonly _providerOptions: Map<
typeof BaseExtensionProvider<Scope>,
object
> = new Map();
/** @internal */
private readonly _providerInstances: Map<
typeof BaseExtensionProvider<Scope>,
BaseExtensionProvider<Scope>
> = new Map();
/**
* Creates a new ExtensionManager instance with the specified providers.
*
* @param providers - Array of extension provider classes to be managed
*/
constructor(providers: Array<typeof BaseExtensionProvider<Scope>>) {
this._providers = new Set(providers);
}
/** @internal */
private readonly _build = (scope: Scope) => {
const context = this._getContextByScope(scope);
this._providers.forEach(Provider => {
let instance: BaseExtensionProvider<Scope>;
if (this._providerInstances.has(Provider)) {
instance = this._providerInstances.get(Provider)!;
} else {
instance = new Provider();
this._providerInstances.set(Provider, instance);
}
instance.setup(context, this._providerOptions.get(Provider));
});
};
/** @internal */
private readonly _registerToScope = (
scope: Scope,
extensions: ExtensionType[] | ExtensionType
) => {
let extSet: Set<ExtensionType>;
if (!this._extensions.has(scope)) {
extSet = new Set();
} else {
extSet = this._extensions.get(scope)!;
}
const extensionsArray = Array.isArray(extensions)
? extensions
: [extensions];
extensionsArray.forEach(extension => {
extSet.add(extension);
});
this._extensions.set(scope, extSet);
};
/** @internal */
private readonly _getContextByScope = (scope: Scope): Context<Scope> => {
const context: Context<Scope> = {
scope,
register: (extensions: ExtensionType[] | ExtensionType) => {
this._registerToScope(scope, extensions);
return context;
},
};
return context;
};
/**
* Retrieves all extensions registered for a specific scope.
* It triggers the build process.
*
* @param scope - The scope to retrieve extensions for
* @returns An array of extensions registered for the specified scope
* @throws {BlockSuiteError} If the scope is not found
*/
get(scope: Scope) {
if (this._extensions.has(scope)) {
this._extensions.delete(scope);
}
this._build(scope);
const extensionSet = this._extensions.get(scope);
if (!extensionSet) {
throw new BlockSuiteError(
BlockSuiteError.ErrorCode.ValueNotExists,
`Extension scope ${scope} not found`
);
}
return Array.from(extensionSet);
}
/**
* Configures a specific provider with new options.
* Can update existing configuration or remove it entirely.
* Triggers a rebuild of the provider instance when configuration changes.
*
* @typeParam T - The type of configuration options for the provider
* @param provider - The provider class to configure
* @param options - New configuration options or a function to update existing options
*/
configure<T extends Empty>(
provider: typeof BaseExtensionProvider<Scope, T>,
options: ((prev: T | undefined) => T | undefined) | T | undefined
) {
const prev = this._providerOptions.get(provider);
let config: T | undefined;
if (typeof options === 'function') {
config = (options as (prev: unknown) => T)(prev);
} else {
config = options;
}
if (prev === config) {
return;
}
if (config === undefined) {
this._providerOptions.delete(provider);
} else {
this._providerOptions.set(provider, config);
}
// If the config is changed, we need to rebuild the extension
this._providerInstances.delete(provider);
}
}