feat: init new plugin system (#3323)

This commit is contained in:
Alex Yang
2023-07-20 18:52:29 +08:00
committed by GitHub
parent 604b53d9a4
commit 19055baa49
45 changed files with 768 additions and 389 deletions

View 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;
};
}

View File

@@ -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);
});
}
}

View File

@@ -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;
};