mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 09:52:49 +08:00
refactor(electron): server side plugin (#3360)
This commit is contained in:
@@ -4,14 +4,7 @@ import { atom, createStore } from 'jotai/vanilla';
|
||||
|
||||
import { getWorkspace, waitForWorkspace } from './__internal__/workspace';
|
||||
import type { CallbackMap } from './entry';
|
||||
import type {
|
||||
AffinePlugin,
|
||||
Definition,
|
||||
ExpectedLayout,
|
||||
ServerAdapter,
|
||||
} from './type';
|
||||
import type { Loader, PluginUIAdapter } from './type';
|
||||
import type { PluginBlockSuiteAdapter } from './type';
|
||||
import type { ExpectedLayout } from './type';
|
||||
|
||||
// global store
|
||||
export const rootStore = createStore();
|
||||
@@ -24,10 +17,6 @@ export const editorItemsAtom = atom<Record<string, CallbackMap['editor']>>({});
|
||||
export const registeredPluginAtom = atom<string[]>([]);
|
||||
export const windowItemsAtom = atom<Record<string, CallbackMap['window']>>({});
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const affinePluginsAtom = atom<Record<string, AffinePlugin<string>>>({});
|
||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||
export const currentPageIdAtom = atom<string | null>(null);
|
||||
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
|
||||
@@ -83,39 +72,3 @@ export const contentLayoutAtom = atom<
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export function definePlugin<ID extends string>(
|
||||
definition: Definition<ID>,
|
||||
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
|
||||
blockSuiteAdapter?: Loader<Partial<PluginBlockSuiteAdapter>>,
|
||||
serverAdapter?: Loader<ServerAdapter>
|
||||
) {
|
||||
const basePlugin = {
|
||||
definition,
|
||||
uiAdapter: undefined,
|
||||
blockSuiteAdapter: undefined,
|
||||
};
|
||||
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: basePlugin,
|
||||
}));
|
||||
|
||||
if (serverAdapter) {
|
||||
console.log('register server adapter');
|
||||
serverAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
serverAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/plugin-infra/src/server.ts
Normal file
4
packages/plugin-infra/src/server.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface ServerContext {
|
||||
registerCommand: (command: string, fn: (...args: any[]) => any) => void;
|
||||
unregisterCommand: (command: string) => void;
|
||||
}
|
||||
@@ -1,84 +1,4 @@
|
||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||
/// <reference path="./webpack-hmr.d.ts" />
|
||||
|
||||
/**
|
||||
* AFFiNE Plugin System Types
|
||||
*/
|
||||
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import type { Page } from '@playwright/test';
|
||||
import type { WritableAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
/**
|
||||
* A code loader interface of the plugin API.
|
||||
*
|
||||
* Plugin should be lazy-loaded. If a plugin is not enabled, it will not be loaded into the Mask.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const loader = {
|
||||
* load: () => import("./code"),
|
||||
* hotModuleReload: hot => import.meta.webpackHot && import.meta.webpackHot.accept('./code', () => hot(import("./code")))
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The `./code` should use `export default` to export what loader expects.
|
||||
*/
|
||||
export interface Loader<DeferredModule> {
|
||||
/**
|
||||
* The `load()` function will be called on demand.
|
||||
*
|
||||
* It should not have side effects (e.g. start some daemon, start a new HTTP request or WebSocket client),
|
||||
* those work should be in the `.init()` function.
|
||||
* @returns the actual definition of this plugin
|
||||
* @example load: () => import('./path')
|
||||
*/
|
||||
load(): Promise<{
|
||||
default: DeferredModule;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* This provides the functionality for hot module reload on the plugin.
|
||||
* When the callback is called, the old instance of the plugin will be unloaded, then the new instance will be init.
|
||||
* @example hotModuleReload: hot => import.meta.webpackHot && import.meta.webpackHot.accept('./path', () => hot(import('./path')))
|
||||
*/
|
||||
hotModuleReload(
|
||||
onHot: (
|
||||
hot: Promise<{
|
||||
default: DeferredModule;
|
||||
}>
|
||||
) => void
|
||||
): void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars
|
||||
interface AFFiNEPlugin {
|
||||
// todo: add more fields
|
||||
}
|
||||
|
||||
export interface I18NStringField {
|
||||
/** The i18n key of the string content. */
|
||||
i18nKey?: string;
|
||||
/** The fallback content to display if there is no i18n string found. */
|
||||
fallback: string;
|
||||
}
|
||||
|
||||
/** The publisher of the plugin */
|
||||
export interface Publisher {
|
||||
/** The name of the publisher */
|
||||
name: I18NStringField;
|
||||
/** URL of the publisher */
|
||||
link: string;
|
||||
}
|
||||
|
||||
/** For what stage the plugin */
|
||||
export enum ReleaseStage {
|
||||
NIGHTLY = 'nightly',
|
||||
PROD = 'prod',
|
||||
DEV = 'dev',
|
||||
}
|
||||
|
||||
export type LayoutDirection = 'horizontal' | 'vertical';
|
||||
export type LayoutNode = LayoutParentNode | string;
|
||||
@@ -107,85 +27,3 @@ export type ContentLayoutAtom = WritableAtom<
|
||||
[SetStateAction<ExpectedLayout>],
|
||||
void
|
||||
>;
|
||||
|
||||
export type Definition<ID extends string> = {
|
||||
/**
|
||||
* ID of the plugin. It should be unique.
|
||||
* @example "com.affine.pro"
|
||||
*/
|
||||
id: ID;
|
||||
/**
|
||||
* The human-readable name of the plugin.
|
||||
* @example { i18nKey: "name", fallback: "Never gonna give you up" }
|
||||
*/
|
||||
name: I18NStringField;
|
||||
/**
|
||||
* A brief description of this plugin.
|
||||
* @example { i18nKey: "description", fallback: "This plugin is going to replace every link in the page to https://www.youtube.com/watch?v=dQw4w9WgXcQ" }
|
||||
*/
|
||||
description?: I18NStringField;
|
||||
/**
|
||||
* Publisher of this plugin.
|
||||
* @example { link: "https://affine.pro", name: { fallback: "AFFiNE", i18nKey: "org_name" } }
|
||||
*/
|
||||
publisher?: Publisher;
|
||||
|
||||
/**
|
||||
* The version of this plugin.
|
||||
* @example "1.0.0"
|
||||
*/
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* The loader of this plugin.
|
||||
* @example ReleaseStage.PROD
|
||||
*/
|
||||
stage: ReleaseStage;
|
||||
|
||||
/**
|
||||
* Registered commands
|
||||
*/
|
||||
commands: string[];
|
||||
};
|
||||
|
||||
// todo(himself65): support Vue.js
|
||||
export type Adapter<Props extends Record<string, unknown>> = (
|
||||
props: Props
|
||||
) => ReactElement;
|
||||
|
||||
export type AffinePluginContext = {
|
||||
toast: (text: string) => void;
|
||||
};
|
||||
|
||||
export type BaseProps = {
|
||||
contentLayoutAtom: ContentLayoutAtom;
|
||||
};
|
||||
|
||||
export type PluginUIAdapter = {
|
||||
sidebarItem: Adapter<BaseProps>;
|
||||
headerItem: Adapter<BaseProps>;
|
||||
detailContent: Adapter<BaseProps>;
|
||||
debugContent: Adapter<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
type Cleanup = () => void;
|
||||
|
||||
export type PluginBlockSuiteAdapter = {
|
||||
storeDecorator: (currentWorkspace: Workspace) => Promise<void>;
|
||||
pageDecorator: (currentPage: Page) => Cleanup;
|
||||
uiDecorator: (root: EditorContainer) => Cleanup;
|
||||
};
|
||||
|
||||
type AFFiNEServer = {
|
||||
registerCommand: (command: string, fn: (...args: any[]) => any) => void;
|
||||
unregisterCommand: (command: string) => void;
|
||||
};
|
||||
|
||||
export type ServerAdapter = (affine: AFFiNEServer) => () => void;
|
||||
|
||||
export type AffinePlugin<ID extends string> = {
|
||||
definition: Definition<ID>;
|
||||
uiAdapter: undefined;
|
||||
blockSuiteAdapter: undefined;
|
||||
serverAdapter?: ServerAdapter;
|
||||
};
|
||||
|
||||
224
packages/plugin-infra/src/webpack-hmr.d.ts
vendored
224
packages/plugin-infra/src/webpack-hmr.d.ts
vendored
@@ -1,224 +0,0 @@
|
||||
// Copied from @types/webpack-env
|
||||
/**
|
||||
* Webpack module API - variables and global functions available inside modules
|
||||
*/
|
||||
|
||||
declare namespace __WebpackModuleApi {
|
||||
type ModuleId = any;
|
||||
interface HotNotifierInfo {
|
||||
type:
|
||||
| 'self-declined'
|
||||
| 'declined'
|
||||
| 'unaccepted'
|
||||
| 'accepted'
|
||||
| 'disposed'
|
||||
| 'accept-errored'
|
||||
| 'self-accept-errored'
|
||||
| 'self-accept-error-handler-errored';
|
||||
/**
|
||||
* The module in question.
|
||||
*/
|
||||
moduleId: number;
|
||||
/**
|
||||
* For errors: the module id owning the accept handler.
|
||||
*/
|
||||
dependencyId?: number | undefined;
|
||||
/**
|
||||
* For declined/accepted/unaccepted: the chain from where the update was propagated.
|
||||
*/
|
||||
chain?: number[] | undefined;
|
||||
/**
|
||||
* For declined: the module id of the declining parent
|
||||
*/
|
||||
parentId?: number | undefined;
|
||||
/**
|
||||
* For accepted: the modules that are outdated and will be disposed
|
||||
*/
|
||||
outdatedModules?: number[] | undefined;
|
||||
/**
|
||||
* For accepted: The location of accept handlers that will handle the update
|
||||
*/
|
||||
outdatedDependencies?:
|
||||
| {
|
||||
[dependencyId: number]: number[];
|
||||
}
|
||||
| undefined;
|
||||
/**
|
||||
* For errors: the thrown error
|
||||
*/
|
||||
error?: Error | undefined;
|
||||
/**
|
||||
* For self-accept-error-handler-errored: the error thrown by the module
|
||||
* before the error handler tried to handle it.
|
||||
*/
|
||||
originalError?: Error | undefined;
|
||||
}
|
||||
|
||||
interface Hot {
|
||||
/**
|
||||
* Accept code updates for the specified dependencies. The callback is called when dependencies were replaced.
|
||||
* @param dependencies
|
||||
* @param callback
|
||||
* @param errorHandler
|
||||
*/
|
||||
accept(
|
||||
dependencies: string[],
|
||||
callback?: (updatedDependencies: ModuleId[]) => void,
|
||||
errorHandler?: (err: Error) => void
|
||||
): void;
|
||||
/**
|
||||
* Accept code updates for the specified dependencies. The callback is called when dependencies were replaced.
|
||||
* @param dependency
|
||||
* @param callback
|
||||
* @param errorHandler
|
||||
*/
|
||||
accept(
|
||||
dependency: string,
|
||||
callback?: () => void,
|
||||
errorHandler?: (err: Error) => void
|
||||
): void;
|
||||
/**
|
||||
* Accept code updates for this module without notification of parents.
|
||||
* This should only be used if the module doesn’t export anything.
|
||||
* The errHandler can be used to handle errors that occur while loading the updated module.
|
||||
* @param errHandler
|
||||
*/
|
||||
accept(errHandler?: (err: Error) => void): void;
|
||||
/**
|
||||
* Do not accept updates for the specified dependencies. If any dependencies is updated, the code update fails with code "decline".
|
||||
*/
|
||||
decline(dependencies: string[]): void;
|
||||
/**
|
||||
* Do not accept updates for the specified dependencies. If any dependencies is updated, the code update fails with code "decline".
|
||||
*/
|
||||
decline(dependency: string): void;
|
||||
/**
|
||||
* Flag the current module as not update-able. If updated the update code would fail with code "decline".
|
||||
*/
|
||||
decline(): void;
|
||||
/**
|
||||
* Add a one time handler, which is executed when the current module code is replaced.
|
||||
* Here you should destroy/remove any persistent resource you have claimed/created.
|
||||
* If you want to transfer state to the new module, add it to data object.
|
||||
* The data will be available at module.hot.data on the new module.
|
||||
* @param callback
|
||||
*/
|
||||
dispose(callback: (data: any) => void): void;
|
||||
dispose(callback: <T>(data: T) => void): void;
|
||||
/**
|
||||
* Add a one time handler, which is executed when the current module code is replaced.
|
||||
* Here you should destroy/remove any persistent resource you have claimed/created.
|
||||
* If you want to transfer state to the new module, add it to data object.
|
||||
* The data will be available at module.hot.data on the new module.
|
||||
* @param callback
|
||||
*/
|
||||
addDisposeHandler(callback: (data: any) => void): void;
|
||||
addDisposeHandler<T>(callback: (data: T) => void): void;
|
||||
/**
|
||||
* Remove a handler.
|
||||
* This can useful to add a temporary dispose handler. You could i. e. replace code while in the middle of a multi-step async function.
|
||||
* @param callback
|
||||
*/
|
||||
removeDisposeHandler(callback: (data: any) => void): void;
|
||||
removeDisposeHandler<T>(callback: (data: T) => void): void;
|
||||
/**
|
||||
* Throws an exceptions if status() is not idle.
|
||||
* Check all currently loaded modules for updates and apply updates if found.
|
||||
* If no update was found, the callback is called with null.
|
||||
* If autoApply is truthy the callback will be called with all modules that were disposed.
|
||||
* apply() is automatically called with autoApply as options parameter.
|
||||
* If autoApply is not set the callback will be called with all modules that will be disposed on apply().
|
||||
* @param autoApply
|
||||
* @param callback
|
||||
*/
|
||||
check(
|
||||
autoApply: boolean,
|
||||
callback: (err: Error, outdatedModules: ModuleId[]) => void
|
||||
): void;
|
||||
/**
|
||||
* Throws an exceptions if status() is not idle.
|
||||
* Check all currently loaded modules for updates and apply updates if found.
|
||||
* If no update was found, the callback is called with null.
|
||||
* The callback will be called with all modules that will be disposed on apply().
|
||||
* @param callback
|
||||
*/
|
||||
check(callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
|
||||
/**
|
||||
* If status() != "ready" it throws an error.
|
||||
* Continue the update process.
|
||||
* @param options
|
||||
* @param callback
|
||||
*/
|
||||
apply(
|
||||
options: AcceptOptions,
|
||||
callback: (err: Error, outdatedModules: ModuleId[]) => void
|
||||
): void;
|
||||
/**
|
||||
* If status() != "ready" it throws an error.
|
||||
* Continue the update process.
|
||||
* @param callback
|
||||
*/
|
||||
apply(callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
|
||||
/**
|
||||
* Return one of idle, check, watch, watch-delay, prepare, ready, dispose, apply, abort or fail.
|
||||
*/
|
||||
status(): string;
|
||||
/** Register a callback on status change. */
|
||||
status(callback: (status: string) => void): void;
|
||||
/** Register a callback on status change. */
|
||||
addStatusHandler(callback: (status: string) => void): void;
|
||||
/**
|
||||
* Remove a registered status change handler.
|
||||
* @param callback
|
||||
*/
|
||||
removeStatusHandler(callback: (status: string) => void): void;
|
||||
|
||||
active: boolean;
|
||||
data: any;
|
||||
}
|
||||
|
||||
interface AcceptOptions {
|
||||
/**
|
||||
* If true the update process continues even if some modules are not accepted (and would bubble to the entry point).
|
||||
*/
|
||||
ignoreUnaccepted?: boolean | undefined;
|
||||
/**
|
||||
* Ignore changes made to declined modules.
|
||||
*/
|
||||
ignoreDeclined?: boolean | undefined;
|
||||
/**
|
||||
* Ignore errors throw in accept handlers, error handlers and while reevaluating module.
|
||||
*/
|
||||
ignoreErrored?: boolean | undefined;
|
||||
/**
|
||||
* Notifier for declined modules.
|
||||
*/
|
||||
onDeclined?: ((info: HotNotifierInfo) => void) | undefined;
|
||||
/**
|
||||
* Notifier for unaccepted modules.
|
||||
*/
|
||||
onUnaccepted?: ((info: HotNotifierInfo) => void) | undefined;
|
||||
/**
|
||||
* Notifier for accepted modules.
|
||||
*/
|
||||
onAccepted?: ((info: HotNotifierInfo) => void) | undefined;
|
||||
/**
|
||||
* Notifier for disposed modules.
|
||||
*/
|
||||
onDisposed?: ((info: HotNotifierInfo) => void) | undefined;
|
||||
/**
|
||||
* Notifier for errors.
|
||||
*/
|
||||
onErrored?: ((info: HotNotifierInfo) => void) | undefined;
|
||||
/**
|
||||
* Indicates that apply() is automatically called by check function
|
||||
*/
|
||||
autoApply?: boolean | undefined;
|
||||
}
|
||||
}
|
||||
interface ImportMeta {
|
||||
/**
|
||||
* `import.meta.webpackHot` is an alias for` module.hot` which is also available in strict ESM
|
||||
*/
|
||||
webpackHot?: __WebpackModuleApi.Hot | undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user