mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
feat: init new plugin system (#3323)
This commit is contained in:
17
packages/plugin-infra/src/entry.ts
Normal file
17
packages/plugin-infra/src/entry.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { FC } from 'react';
|
||||
|
||||
export type Part = 'headerItem' | 'editor' | 'window';
|
||||
|
||||
export type CallbackMap = {
|
||||
headerItem: (root: HTMLElement) => () => void;
|
||||
window: (root: HTMLElement) => () => void;
|
||||
editor: (root: HTMLElement, editor: EditorContainer) => () => void;
|
||||
};
|
||||
|
||||
export interface PluginContext {
|
||||
register: <T extends Part>(part: T, callback: CallbackMap[T]) => void;
|
||||
utils: {
|
||||
PluginProvider: FC;
|
||||
};
|
||||
}
|
||||
@@ -3,17 +3,30 @@ import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { atom, createStore } from 'jotai/vanilla';
|
||||
|
||||
import { getWorkspace, waitForWorkspace } from './__internal__/workspace';
|
||||
import type { AffinePlugin, Definition, ServerAdapter } from './type';
|
||||
import type { CallbackMap } from './entry';
|
||||
import type {
|
||||
AffinePlugin,
|
||||
Definition,
|
||||
ExpectedLayout,
|
||||
ServerAdapter,
|
||||
} from './type';
|
||||
import type { Loader, PluginUIAdapter } from './type';
|
||||
import type { PluginBlockSuiteAdapter } from './type';
|
||||
|
||||
const isServer = typeof window === 'undefined';
|
||||
const isClient = typeof window !== 'undefined';
|
||||
|
||||
// global store
|
||||
export const rootStore = createStore();
|
||||
|
||||
// todo: for now every plugin is enabled by default
|
||||
// id -> HTML element
|
||||
export const headerItemsAtom = atom<Record<string, CallbackMap['headerItem']>>(
|
||||
{}
|
||||
);
|
||||
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);
|
||||
@@ -39,6 +52,38 @@ export const currentPageAtom = atom<Promise<Page>>(async get => {
|
||||
return page;
|
||||
});
|
||||
|
||||
const contentLayoutBaseAtom = atom<ExpectedLayout>('editor');
|
||||
|
||||
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
||||
export const contentLayoutAtom = atom<
|
||||
ExpectedLayout,
|
||||
[SetStateAction<ExpectedLayout>],
|
||||
void
|
||||
>(
|
||||
get => get(contentLayoutBaseAtom),
|
||||
(get, set, layout) => {
|
||||
set(contentLayoutBaseAtom, prev => {
|
||||
let setV: (prev: ExpectedLayout) => ExpectedLayout;
|
||||
if (typeof layout !== 'function') {
|
||||
setV = () => layout;
|
||||
} else {
|
||||
setV = layout;
|
||||
}
|
||||
const nextValue = setV(prev);
|
||||
if (nextValue === 'editor') {
|
||||
return nextValue;
|
||||
}
|
||||
if (nextValue.first !== 'editor') {
|
||||
throw new Error('The first element of the layout should be editor.');
|
||||
}
|
||||
if (nextValue.splitPercentage && nextValue.splitPercentage < 70) {
|
||||
throw new Error('The split percentage should be greater than 70.');
|
||||
}
|
||||
return nextValue;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export function definePlugin<ID extends string>(
|
||||
definition: Definition<ID>,
|
||||
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
|
||||
@@ -47,8 +92,8 @@ export function definePlugin<ID extends string>(
|
||||
) {
|
||||
const basePlugin = {
|
||||
definition,
|
||||
uiAdapter: {},
|
||||
blockSuiteAdapter: {},
|
||||
uiAdapter: undefined,
|
||||
blockSuiteAdapter: undefined,
|
||||
};
|
||||
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
@@ -56,76 +101,21 @@ export function definePlugin<ID extends string>(
|
||||
[definition.id]: basePlugin,
|
||||
}));
|
||||
|
||||
if (isServer) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
} else if (isClient) {
|
||||
if (blockSuiteAdapter) {
|
||||
const updateAdapter = (adapter: Partial<PluginBlockSuiteAdapter>) => {
|
||||
if (serverAdapter) {
|
||||
console.log('register server adapter');
|
||||
serverAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
blockSuiteAdapter: adapter,
|
||||
serverAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
blockSuiteAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
console.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
blockSuiteAdapter.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
});
|
||||
}
|
||||
}
|
||||
if (uiAdapterLoader) {
|
||||
const updateAdapter = (adapter: Partial<PluginUIAdapter>) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
uiAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
uiAdapterLoader
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
console.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
uiAdapterLoader.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ export type ServerAdapter = (affine: AFFiNEServer) => () => void;
|
||||
|
||||
export type AffinePlugin<ID extends string> = {
|
||||
definition: Definition<ID>;
|
||||
uiAdapter: Partial<PluginUIAdapter>;
|
||||
blockSuiteAdapter: Partial<PluginBlockSuiteAdapter>;
|
||||
uiAdapter: undefined;
|
||||
blockSuiteAdapter: undefined;
|
||||
serverAdapter?: ServerAdapter;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user