feat: init new plugin system (#3323)
This commit is contained in:
@@ -12,3 +12,5 @@ tests/affine-legacy/0.7.0-canary.18/static
|
|||||||
.github/helm
|
.github/helm
|
||||||
_next
|
_next
|
||||||
storybook-static
|
storybook-static
|
||||||
|
web-static
|
||||||
|
public
|
||||||
|
|||||||
@@ -276,12 +276,13 @@ export const createConfiguration: (
|
|||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
hot: 'only',
|
hot: 'only',
|
||||||
liveReload: false,
|
liveReload: true,
|
||||||
client: undefined,
|
client: undefined,
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
static: {
|
static: {
|
||||||
directory: resolve(rootPath, 'public'),
|
directory: resolve(rootPath, 'public'),
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
|
watch: true,
|
||||||
},
|
},
|
||||||
} as DevServerConfiguration,
|
} as DevServerConfiguration,
|
||||||
} satisfies webpack.Configuration;
|
} satisfies webpack.Configuration;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
rel="shortcut icon"
|
rel="shortcut icon"
|
||||||
href="https://affine.pro/favicon.ico"
|
href="https://affine.pro/favicon.ico"
|
||||||
/>
|
/>
|
||||||
|
<link rel="stylesheet" href="/plugins/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine-test/fixtures": "workspace:*",
|
"@affine-test/fixtures": "workspace:*",
|
||||||
"@affine/bookmark-block": "workspace:*",
|
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@affine/copilot": "workspace:*",
|
"@affine/copilot": "workspace:*",
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
@@ -53,6 +52,7 @@
|
|||||||
"react-resizable-panels": "^0.0.53",
|
"react-resizable-panels": "^0.0.53",
|
||||||
"react-router-dom": "^6.14.1",
|
"react-router-dom": "^6.14.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
"ses": "^0.18.5",
|
||||||
"swr": "^2.1.5",
|
"swr": "^2.1.5",
|
||||||
"y-protocols": "^1.0.5",
|
"y-protocols": "^1.0.5",
|
||||||
"yjs": "^13.6.6",
|
"yjs": "^13.6.6",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"script": "build"
|
"script": "build"
|
||||||
},
|
},
|
||||||
"outputs": ["{projectRoot}/dist"]
|
"outputs": ["{projectRoot}/dist", "{projectRoot}/public/plugins"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
apps/core/public/.gitignore
vendored
4
apps/core/public/.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
*.js
|
*.js
|
||||||
*.map
|
*.map
|
||||||
|
|
||||||
|
plugins
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ currentWorkspaceIdAtom.onMount = set => {
|
|||||||
if (value) {
|
if (value) {
|
||||||
set(value);
|
set(value);
|
||||||
localStorage.setItem('last_workspace_id', value);
|
localStorage.setItem('last_workspace_id', value);
|
||||||
} else {
|
|
||||||
set(null);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
callback(router.state);
|
callback(router.state);
|
||||||
@@ -65,8 +63,6 @@ currentPageIdAtom.onMount = set => {
|
|||||||
const value = state.location.pathname.split('/')[3];
|
const value = state.location.pathname.split('/')[3];
|
||||||
if (value) {
|
if (value) {
|
||||||
set(value);
|
set(value);
|
||||||
} else {
|
|
||||||
set(null);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
callback(router.state);
|
callback(router.state);
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { ExpectedLayout } from '@toeverything/plugin-infra/type';
|
|
||||||
import { atom } from 'jotai';
|
|
||||||
|
|
||||||
export 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,6 +1,181 @@
|
|||||||
import('@affine/bookmark-block');
|
/// <reference types="@types/webpack-env" />
|
||||||
if (runtimeConfig.enablePlugin) {
|
import 'ses';
|
||||||
import('@affine/copilot');
|
|
||||||
}
|
import * as AFFiNEComponent from '@affine/component';
|
||||||
|
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||||
|
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||||
|
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
|
||||||
|
import * as Icons from '@blocksuite/icons';
|
||||||
|
import type {
|
||||||
|
CallbackMap,
|
||||||
|
PluginContext,
|
||||||
|
} from '@toeverything/plugin-infra/entry';
|
||||||
|
import * as Manager from '@toeverything/plugin-infra/manager';
|
||||||
|
import {
|
||||||
|
editorItemsAtom,
|
||||||
|
headerItemsAtom,
|
||||||
|
registeredPluginAtom,
|
||||||
|
rootStore,
|
||||||
|
windowItemsAtom,
|
||||||
|
} from '@toeverything/plugin-infra/manager';
|
||||||
|
import * as Jotai from 'jotai';
|
||||||
|
import { Provider } from 'jotai/react';
|
||||||
|
import * as JotaiUtils from 'jotai/utils';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import * as ReactDomClient from 'react-dom/client';
|
||||||
|
|
||||||
|
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||||
|
React.createElement(
|
||||||
|
Provider,
|
||||||
|
{
|
||||||
|
store: rootStore,
|
||||||
|
},
|
||||||
|
children
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('JotaiUtils', JotaiUtils);
|
||||||
|
|
||||||
|
const customRequire = (id: string) => {
|
||||||
|
if (id === '@toeverything/plugin-infra/manager') {
|
||||||
|
return harden(Manager);
|
||||||
|
}
|
||||||
|
if (id === 'react') {
|
||||||
|
return React;
|
||||||
|
}
|
||||||
|
if (id === 'react/jsx-runtime') {
|
||||||
|
return ReactJSXRuntime;
|
||||||
|
}
|
||||||
|
if (id === 'react-dom') {
|
||||||
|
return ReactDom;
|
||||||
|
}
|
||||||
|
if (id === 'react-dom/client') {
|
||||||
|
return ReactDomClient;
|
||||||
|
}
|
||||||
|
if (id === '@blocksuite/icons') {
|
||||||
|
return harden(Icons);
|
||||||
|
}
|
||||||
|
if (id === '@affine/component') {
|
||||||
|
return harden(AFFiNEComponent);
|
||||||
|
}
|
||||||
|
if (id === '@blocksuite/blocks/std') {
|
||||||
|
return harden(BlockSuiteBlocksStd);
|
||||||
|
}
|
||||||
|
if (id === '@blocksuite/global/utils') {
|
||||||
|
return harden(BlockSuiteGlobalUtils);
|
||||||
|
}
|
||||||
|
if (id === 'jotai') {
|
||||||
|
return harden(Jotai);
|
||||||
|
}
|
||||||
|
if (id === 'jotai/utils') {
|
||||||
|
return harden(JotaiUtils);
|
||||||
|
}
|
||||||
|
if (id === '../plugin.js') {
|
||||||
|
return entryCompartment.evaluate('exports');
|
||||||
|
}
|
||||||
|
throw new Error(`Cannot find module '${id}'`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createGlobalThis = () => {
|
||||||
|
return {
|
||||||
|
process: harden({
|
||||||
|
env: {
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// UNSAFE: React will read `window` and `document`
|
||||||
|
window,
|
||||||
|
document,
|
||||||
|
navigator,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
|
||||||
|
// fixme: use our own db api
|
||||||
|
indexedDB: globalThis.indexedDB,
|
||||||
|
IDBRequest: globalThis.IDBRequest,
|
||||||
|
IDBDatabase: globalThis.IDBDatabase,
|
||||||
|
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||||
|
IDBFactory: globalThis.IDBFactory,
|
||||||
|
IDBKeyRange: globalThis.IDBKeyRange,
|
||||||
|
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||||
|
IDBTransaction: globalThis.IDBTransaction,
|
||||||
|
IDBObjectStore: globalThis.IDBObjectStore,
|
||||||
|
IDBIndex: globalThis.IDBIndex,
|
||||||
|
IDBCursor: globalThis.IDBCursor,
|
||||||
|
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||||
|
|
||||||
|
exports: {},
|
||||||
|
console: globalThis.console,
|
||||||
|
require: customRequire,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const group = new DisposableGroup();
|
||||||
|
const pluginList = await (
|
||||||
|
await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin))
|
||||||
|
).json();
|
||||||
|
const builtInPlugins: string[] = pluginList.map((plugin: any) => plugin.name);
|
||||||
|
const pluginGlobalThis = createGlobalThis();
|
||||||
|
const pluginEntry = await fetch('/plugins/plugin.js').then(res => res.text());
|
||||||
|
const entryCompartment = new Compartment(pluginGlobalThis, {});
|
||||||
|
entryCompartment.evaluate(pluginEntry, {
|
||||||
|
__evadeHtmlCommentTest__: true,
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
builtInPlugins.map(plugin => {
|
||||||
|
const pluginCompartment = new Compartment(createGlobalThis(), {});
|
||||||
|
const pluginGlobalThis = pluginCompartment.globalThis;
|
||||||
|
const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin);
|
||||||
|
const packageJsonURL = new URL('package.json', baseURL);
|
||||||
|
return fetch(packageJsonURL).then(async res => {
|
||||||
|
const packageJson = await res.json();
|
||||||
|
const pluginConfig = packageJson['affinePlugin'];
|
||||||
|
if (
|
||||||
|
pluginConfig.release === false &&
|
||||||
|
process.env.NODE_ENV !== 'development'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
|
||||||
|
const coreEntry = new URL(pluginConfig.entry.core, baseURL.toString());
|
||||||
|
const codeText = await fetch(coreEntry).then(res => res.text());
|
||||||
|
pluginCompartment.evaluate(codeText);
|
||||||
|
pluginGlobalThis.__INTERNAL__ENTRY = {
|
||||||
|
register: (part, callback) => {
|
||||||
|
if (part === 'headerItem') {
|
||||||
|
rootStore.set(headerItemsAtom, items => ({
|
||||||
|
...items,
|
||||||
|
[plugin]: callback as CallbackMap['headerItem'],
|
||||||
|
}));
|
||||||
|
} else if (part === 'editor') {
|
||||||
|
rootStore.set(editorItemsAtom, items => ({
|
||||||
|
...items,
|
||||||
|
[plugin]: callback as CallbackMap['editor'],
|
||||||
|
}));
|
||||||
|
} else if (part === 'window') {
|
||||||
|
rootStore.set(windowItemsAtom, items => ({
|
||||||
|
...items,
|
||||||
|
[plugin]: callback as CallbackMap['window'],
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown part: ${part}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
utils: {
|
||||||
|
PluginProvider,
|
||||||
|
},
|
||||||
|
} satisfies PluginContext;
|
||||||
|
const dispose = pluginCompartment.evaluate(
|
||||||
|
'exports.entry(__INTERNAL__ENTRY)'
|
||||||
|
);
|
||||||
|
if (typeof dispose !== 'function') {
|
||||||
|
throw new Error('Plugin entry must return a function');
|
||||||
|
}
|
||||||
|
pluginGlobalThis.__INTERNAL__ENTRY = undefined;
|
||||||
|
group.add(dispose);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
console.log('register plugins finished');
|
console.log('register plugins finished');
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import { SettingHeader } from '@affine/component/setting-components';
|
import {
|
||||||
|
SettingHeader,
|
||||||
|
SettingWrapper,
|
||||||
|
} from '@affine/component/setting-components';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
|
import { registeredPluginAtom } from '@toeverything/plugin-infra/manager';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
|
|
||||||
export const Plugins = () => {
|
export const Plugins = () => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const plugins = useAtomValue(affinePluginsAtom);
|
const allowedPlugins = useAtomValue(registeredPluginAtom);
|
||||||
|
console.log('allowedPlugins', allowedPlugins);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingHeader
|
<SettingHeader
|
||||||
title={'Plugins'}
|
title={'Plugins'}
|
||||||
subtitle={t['None yet']()}
|
subtitle={allowedPlugins.length === 0 && t['None yet']()}
|
||||||
data-testid="plugins-title"
|
data-testid="plugins-title"
|
||||||
/>
|
/>
|
||||||
{Object.values(plugins).map(({ definition, uiAdapter }) => {
|
{allowedPlugins.map(plugin => (
|
||||||
const Content = uiAdapter.debugContent;
|
<SettingWrapper key={plugin} title={plugin}></SettingWrapper>
|
||||||
return <div key={definition.id}>{Content && <Content />}</div>;
|
))}
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,20 +7,18 @@ import { SidebarSwitch } from '@affine/component/app-sidebar/sidebar-header';
|
|||||||
import { isDesktop } from '@affine/env/constant';
|
import { isDesktop } from '@affine/env/constant';
|
||||||
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
|
import { headerItemsAtom } from '@toeverything/plugin-infra/manager';
|
||||||
import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { contentLayoutAtom } from '../../../atoms/layout';
|
|
||||||
import { currentModeAtom } from '../../../atoms/mode';
|
import { currentModeAtom } from '../../../atoms/mode';
|
||||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||||
import DownloadClientTip from './download-tips';
|
import DownloadClientTip from './download-tips';
|
||||||
@@ -124,41 +122,37 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
|||||||
|
|
||||||
export type HeaderProps = BaseHeaderProps;
|
export type HeaderProps = BaseHeaderProps;
|
||||||
|
|
||||||
const PluginHeaderItemAdapter = memo<{
|
|
||||||
headerItem: PluginUIAdapter['headerItem'];
|
|
||||||
}>(function PluginHeaderItemAdapter({ headerItem }) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{headerItem({
|
|
||||||
contentLayoutAtom,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const PluginHeader = () => {
|
const PluginHeader = () => {
|
||||||
const affinePluginsMap = useAtomValue(affinePluginsAtom);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const plugins = useMemo(
|
const headerItems = useAtomValue(headerItemsAtom);
|
||||||
() => Object.values(affinePluginsMap),
|
useEffect(() => {
|
||||||
[affinePluginsMap]
|
const root = rootRef.current;
|
||||||
);
|
if (!root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let disposes: (() => void)[] = [];
|
||||||
|
const renderTimeout = setTimeout(() => {
|
||||||
|
disposes = Object.entries(headerItems).map(([id, headerItem]) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('plugin-id', id);
|
||||||
|
const cleanup = headerItem(div);
|
||||||
|
root.appendChild(div);
|
||||||
|
return () => {
|
||||||
|
cleanup();
|
||||||
|
root.removeChild(div);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return () => {
|
||||||
<div>
|
clearTimeout(renderTimeout);
|
||||||
{plugins
|
setTimeout(() => {
|
||||||
.filter(plugin => plugin.uiAdapter.headerItem != null)
|
disposes.forEach(dispose => dispose());
|
||||||
.map(plugin => {
|
});
|
||||||
const headerItem = plugin.uiAdapter
|
};
|
||||||
.headerItem as PluginUIAdapter['headerItem'];
|
}, [headerItems]);
|
||||||
return (
|
|
||||||
<PluginHeaderItemAdapter
|
return <div className={styles.pluginHeaderItems} ref={rootRef} />;
|
||||||
key={plugin.definition.id}
|
|
||||||
headerItem={headerItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header = forwardRef<
|
export const Header = forwardRef<
|
||||||
|
|||||||
@@ -238,3 +238,10 @@ export const windowAppControl = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ComplexStyleRule);
|
} as ComplexStyleRule);
|
||||||
|
|
||||||
|
export const pluginHeaderItems = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '12px',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,21 +7,22 @@ import { assertExists } from '@blocksuite/global/utils';
|
|||||||
import type { Page, Workspace } from '@blocksuite/store';
|
import type { Page, Workspace } from '@blocksuite/store';
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||||
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
|
import type { CallbackMap } from '@toeverything/plugin-infra/entry';
|
||||||
import type {
|
import {
|
||||||
AffinePlugin,
|
affinePluginsAtom,
|
||||||
LayoutNode,
|
contentLayoutAtom,
|
||||||
PluginUIAdapter,
|
editorItemsAtom,
|
||||||
} from '@toeverything/plugin-infra/type';
|
rootStore,
|
||||||
import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type';
|
windowItemsAtom,
|
||||||
|
} from '@toeverything/plugin-infra/manager';
|
||||||
|
import type { AffinePlugin, LayoutNode } from '@toeverything/plugin-infra/type';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import type { CSSProperties, FC, ReactElement } from 'react';
|
import type { CSSProperties, FC, ReactElement } from 'react';
|
||||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
import { memo, Suspense, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
import { pageSettingFamily } from '../atoms';
|
import { pageSettingFamily } from '../atoms';
|
||||||
import { contentLayoutAtom } from '../atoms/layout';
|
|
||||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||||
import * as styles from './page-detail-editor.css';
|
import * as styles from './page-detail-editor.css';
|
||||||
@@ -42,11 +43,6 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
onLoad,
|
onLoad,
|
||||||
isPublic,
|
isPublic,
|
||||||
}: PageDetailEditorProps) {
|
}: PageDetailEditorProps) {
|
||||||
const affinePluginsMap = useAtomValue(affinePluginsAtom);
|
|
||||||
const plugins = useMemo(
|
|
||||||
() => Object.values(affinePluginsMap),
|
|
||||||
[affinePluginsMap]
|
|
||||||
);
|
|
||||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new PageNotFoundError(workspace, pageId);
|
throw new PageNotFoundError(workspace, pageId);
|
||||||
@@ -100,33 +96,65 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
if (onLoad) {
|
if (onLoad) {
|
||||||
dispose = onLoad(page, editor);
|
dispose = onLoad(page, editor);
|
||||||
}
|
}
|
||||||
const uiDecorators = plugins
|
const editorItems = rootStore.get(editorItemsAtom);
|
||||||
.map(plugin => plugin.blockSuiteAdapter.uiDecorator)
|
let disposes: (() => void)[] = [];
|
||||||
.filter((ui): ui is PluginBlockSuiteAdapter['uiDecorator'] =>
|
const renderTimeout = setTimeout(() => {
|
||||||
Boolean(ui)
|
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
||||||
);
|
const div = document.createElement('div');
|
||||||
const disposes = uiDecorators.map(ui => ui(editor));
|
div.setAttribute('plugin-id', id);
|
||||||
|
const cleanup = editorItem(div, editor);
|
||||||
|
assertExists(parent);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
return () => {
|
||||||
|
cleanup();
|
||||||
|
document.body.removeChild(div);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposes.forEach(fn => fn());
|
|
||||||
dispose();
|
dispose();
|
||||||
|
clearTimeout(renderTimeout);
|
||||||
|
setTimeout(() => {
|
||||||
|
disposes.forEach(dispose => dispose());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[plugins, onLoad]
|
[onLoad]
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const PluginContentAdapter = memo<{
|
const PluginContentAdapter = memo<{
|
||||||
detailContent: PluginUIAdapter['detailContent'];
|
windowItem: CallbackMap['window'];
|
||||||
}>(function PluginContentAdapter({ detailContent }) {
|
}>(function PluginContentAdapter({ windowItem }) {
|
||||||
return (
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
<div className={pluginContainer}>
|
useEffect(() => {
|
||||||
{detailContent({
|
const root = ref.current;
|
||||||
contentLayoutAtom,
|
if (!root) {
|
||||||
})}
|
return;
|
||||||
</div>
|
}
|
||||||
);
|
let cleanup: () => void = () => {};
|
||||||
|
let childDiv: HTMLDivElement | null = null;
|
||||||
|
const renderTimeout = setTimeout(() => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
cleanup = windowItem(div);
|
||||||
|
root.appendChild(div);
|
||||||
|
childDiv = div;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(renderTimeout);
|
||||||
|
setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
if (childDiv) {
|
||||||
|
root.removeChild(childDiv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [windowItem]);
|
||||||
|
return <div className={pluginContainer} ref={ref} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
type LayoutPanelProps = {
|
type LayoutPanelProps = {
|
||||||
@@ -139,16 +167,13 @@ const LayoutPanel = memo(function LayoutPanel(
|
|||||||
props: LayoutPanelProps
|
props: LayoutPanelProps
|
||||||
): ReactElement {
|
): ReactElement {
|
||||||
const node = props.node;
|
const node = props.node;
|
||||||
|
const windowItems = useAtomValue(windowItemsAtom);
|
||||||
if (typeof node === 'string') {
|
if (typeof node === 'string') {
|
||||||
if (node === 'editor') {
|
if (node === 'editor') {
|
||||||
return <EditorWrapper {...props.editorProps} />;
|
return <EditorWrapper {...props.editorProps} />;
|
||||||
} else {
|
} else {
|
||||||
const plugin = props.plugins.find(
|
const windowItem = windowItems[node];
|
||||||
plugin => plugin.definition.id === node
|
return <PluginContentAdapter windowItem={windowItem} />;
|
||||||
);
|
|
||||||
const Content = plugin?.uiAdapter.detailContent;
|
|
||||||
assertExists(Content);
|
|
||||||
return <PluginContentAdapter detailContent={Content} />;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { EditorContainer } from '@blocksuite/editor';
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||||
import { useAtomValue } from 'jotai/index';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useAtom } from 'jotai/react';
|
import { useAtom } from 'jotai/react';
|
||||||
import { type ReactElement, useCallback, useEffect } from 'react';
|
import { type ReactElement, useCallback, useEffect } from 'react';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|||||||
@@ -46,9 +46,6 @@
|
|||||||
"path": "../../packages/workspace"
|
"path": "../../packages/workspace"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"path": "../../plugins/bookmark-block"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "../../plugins/copilot"
|
"path": "../../plugins/copilot"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,13 +19,14 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "dev-core",
|
"dev": "dev-core",
|
||||||
|
"dev:plugin": "vite build --watch",
|
||||||
"dev:electron": "yarn workspace @affine/electron dev:app",
|
"dev:electron": "yarn workspace @affine/electron dev:app",
|
||||||
"dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs",
|
"dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs",
|
||||||
"build": "yarn nx build @affine/core",
|
"build": "yarn nx build @affine/core",
|
||||||
"build:electron": "yarn nx build @affine/electron",
|
"build:electron": "yarn nx build @affine/electron",
|
||||||
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
||||||
"build:infra": "yarn nx run-many -t build -p plugin-infra infra",
|
"build:infra": "yarn nx run-many -t build -p plugin-infra infra",
|
||||||
"build:plugins": "yarn nx run-many -t build -p @affine/bookmark-block",
|
"build:plugins": "yarn workspace @affine/bookmark-block build && yarn vite build",
|
||||||
"build:storybook": "yarn nx build @affine/storybook",
|
"build:storybook": "yarn nx build @affine/storybook",
|
||||||
"start:web-static": "yarn workspace @affine/core static-server",
|
"start:web-static": "yarn workspace @affine/core static-server",
|
||||||
"start:storybook": "yarn exec serve apps/storybook/storybook-static -l 6006",
|
"start:storybook": "yarn exec serve apps/storybook/storybook-static -l 6006",
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.60.1",
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||||
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
||||||
"@vitejs/plugin-react": "^4.0.1",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
"@vitest/coverage-istanbul": "^0.32.2",
|
"@vitest/coverage-istanbul": "^0.32.2",
|
||||||
"@vitest/ui": "^0.32.2",
|
"@vitest/ui": "^0.32.2",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.44.0",
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
"vite-plugin-istanbul": "^4.1.0",
|
"vite-plugin-istanbul": "^4.1.0",
|
||||||
|
"vite-plugin-static-copy": "^0.17.0",
|
||||||
"vite-tsconfig-paths": "^4.2.0",
|
"vite-tsconfig-paths": "^4.2.0",
|
||||||
"vitest": "^0.32.2",
|
"vitest": "^0.32.2",
|
||||||
"vitest-fetch-mock": "^0.2.2",
|
"vitest-fetch-mock": "^0.2.2",
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ const flags = {
|
|||||||
coverage: process.env.COVERAGE === 'true',
|
coverage: process.env.COVERAGE === 'true',
|
||||||
} satisfies BuildFlags;
|
} satisfies BuildFlags;
|
||||||
|
|
||||||
|
spawn('vite', ['build'], {
|
||||||
|
cwd: projectRoot,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
|
||||||
spawn(
|
spawn(
|
||||||
'node',
|
'node',
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -91,6 +91,12 @@ flags.mode = buildFlags.mode as any;
|
|||||||
flags.channel = buildFlags.channel as any;
|
flags.channel = buildFlags.channel as any;
|
||||||
flags.coverage = buildFlags.coverage;
|
flags.coverage = buildFlags.coverage;
|
||||||
|
|
||||||
|
spawn('vite', ['build', '--watch'], {
|
||||||
|
cwd: projectRoot,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
|
||||||
spawn(
|
spawn(
|
||||||
'node',
|
'node',
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
|
"./entry": {
|
||||||
|
"type": "./dist/entry.d.ts",
|
||||||
|
"import": "./dist/entry.js",
|
||||||
|
"require": "./dist/entry.cjs"
|
||||||
|
},
|
||||||
"./manager": {
|
"./manager": {
|
||||||
"type": "./dist/manager.d.ts",
|
"type": "./dist/manager.d.ts",
|
||||||
"import": "./dist/manager.js",
|
"import": "./dist/manager.js",
|
||||||
|
|||||||
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 { atom, createStore } from 'jotai/vanilla';
|
||||||
|
|
||||||
import { getWorkspace, waitForWorkspace } from './__internal__/workspace';
|
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 { Loader, PluginUIAdapter } from './type';
|
||||||
import type { PluginBlockSuiteAdapter } from './type';
|
import type { PluginBlockSuiteAdapter } from './type';
|
||||||
|
|
||||||
const isServer = typeof window === 'undefined';
|
|
||||||
const isClient = typeof window !== 'undefined';
|
|
||||||
|
|
||||||
// global store
|
// global store
|
||||||
export const rootStore = createStore();
|
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 affinePluginsAtom = atom<Record<string, AffinePlugin<string>>>({});
|
||||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||||
export const currentPageIdAtom = 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;
|
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>(
|
export function definePlugin<ID extends string>(
|
||||||
definition: Definition<ID>,
|
definition: Definition<ID>,
|
||||||
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
|
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
|
||||||
@@ -47,8 +92,8 @@ export function definePlugin<ID extends string>(
|
|||||||
) {
|
) {
|
||||||
const basePlugin = {
|
const basePlugin = {
|
||||||
definition,
|
definition,
|
||||||
uiAdapter: {},
|
uiAdapter: undefined,
|
||||||
blockSuiteAdapter: {},
|
blockSuiteAdapter: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
rootStore.set(affinePluginsAtom, plugins => ({
|
rootStore.set(affinePluginsAtom, plugins => ({
|
||||||
@@ -56,76 +101,21 @@ export function definePlugin<ID extends string>(
|
|||||||
[definition.id]: basePlugin,
|
[definition.id]: basePlugin,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (isServer) {
|
if (serverAdapter) {
|
||||||
if (serverAdapter) {
|
console.log('register server adapter');
|
||||||
console.log('register server adapter');
|
serverAdapter
|
||||||
serverAdapter
|
.load()
|
||||||
.load()
|
.then(({ default: adapter }) => {
|
||||||
.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>) => {
|
|
||||||
rootStore.set(affinePluginsAtom, plugins => ({
|
rootStore.set(affinePluginsAtom, plugins => ({
|
||||||
...plugins,
|
...plugins,
|
||||||
[definition.id]: {
|
[definition.id]: {
|
||||||
...basePlugin,
|
...basePlugin,
|
||||||
blockSuiteAdapter: adapter,
|
serverAdapter: adapter,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
};
|
})
|
||||||
|
.catch(err => {
|
||||||
blockSuiteAdapter
|
console.error(err);
|
||||||
.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.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export type ServerAdapter = (affine: AFFiNEServer) => () => void;
|
|||||||
|
|
||||||
export type AffinePlugin<ID extends string> = {
|
export type AffinePlugin<ID extends string> = {
|
||||||
definition: Definition<ID>;
|
definition: Definition<ID>;
|
||||||
uiAdapter: Partial<PluginUIAdapter>;
|
uiAdapter: undefined;
|
||||||
blockSuiteAdapter: Partial<PluginBlockSuiteAdapter>;
|
blockSuiteAdapter: undefined;
|
||||||
serverAdapter?: ServerAdapter;
|
serverAdapter?: ServerAdapter;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
|||||||
minify: false,
|
minify: false,
|
||||||
lib: {
|
lib: {
|
||||||
entry: {
|
entry: {
|
||||||
|
entry: resolve(root, 'src/entry.ts'),
|
||||||
type: resolve(root, 'src/type.ts'),
|
type: resolve(root, 'src/type.ts'),
|
||||||
manager: resolve(root, 'src/manager.ts'),
|
manager: resolve(root, 'src/manager.ts'),
|
||||||
'__internal__/workspace': resolve(
|
'__internal__/workspace': resolve(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# `@affine/bookmark-block`
|
# `@affine/bookmark-block`
|
||||||
|
|
||||||
|
> Moved to [@affine/bookmark-plugin](../bookmark)
|
||||||
|
>
|
||||||
> A block for bookmarking a website
|
> A block for bookmarking a website
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -12,18 +12,8 @@
|
|||||||
"dev": "node ./scripts/dev.mjs"
|
"dev": "node ./scripts/dev.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/component": "workspace:*",
|
|
||||||
"@toeverything/plugin-infra": "workspace:*",
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
"foxact": "^0.2.11",
|
|
||||||
"link-preview-js": "^3.0.4"
|
"link-preview-js": "^3.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "*",
|
|
||||||
"react-dom": "*"
|
|
||||||
},
|
|
||||||
"version": "0.7.0-canary.47"
|
"version": "0.7.0-canary.47"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type';
|
|
||||||
import { noop } from 'foxact/noop';
|
|
||||||
import { StrictMode } from 'react';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
|
||||||
|
|
||||||
import { BookMarkUI } from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
uiDecorator: editor => {
|
|
||||||
if (
|
|
||||||
editor.parentElement &&
|
|
||||||
editor.page.awarenessStore.getFlag('enable_bookmark_operation')
|
|
||||||
) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
editor.parentElement.appendChild(div);
|
|
||||||
const root = createRoot(div);
|
|
||||||
root.render(
|
|
||||||
<StrictMode>
|
|
||||||
<BookMarkUI page={editor.page} />
|
|
||||||
</StrictMode>
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
root.unmount();
|
|
||||||
div.remove();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return noop;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} satisfies Partial<PluginBlockSuiteAdapter>;
|
|
||||||
@@ -22,26 +22,9 @@ definePlugin(
|
|||||||
commands: ['com.blocksuite.bookmark-block.get-bookmark-data-by-link'],
|
commands: ['com.blocksuite.bookmark-block.get-bookmark-data-by-link'],
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
{
|
{
|
||||||
load: () => import('./blocksuite/index'),
|
load: () => import('./server'),
|
||||||
hotModuleReload: onHot =>
|
hotModuleReload: onHot => onHot(import('./server')),
|
||||||
import.meta.webpackHot &&
|
|
||||||
import.meta.webpackHot.accept('./blocksuite', () =>
|
|
||||||
onHot(import('./blocksuite/index'))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
load: () =>
|
|
||||||
import(
|
|
||||||
/* webpackIgnore: true */
|
|
||||||
'./server'
|
|
||||||
),
|
|
||||||
hotModuleReload: onHot =>
|
|
||||||
onHot(
|
|
||||||
import(
|
|
||||||
/* webpackIgnore: true */
|
|
||||||
'./server'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
16
plugins/bookmark/package.json
Normal file
16
plugins/bookmark/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@affine/bookmark-plugin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"affinePlugin": {
|
||||||
|
"release": true,
|
||||||
|
"entry": {
|
||||||
|
"core": "./src/index.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@affine/component": "workspace:*",
|
||||||
|
"@blocksuite/icons": "^2.1.25",
|
||||||
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
|
"foxact": "^0.2.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
} from '@blocksuite/blocks/std';
|
} from '@blocksuite/blocks/std';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import type { FC } from 'react';
|
import type { FC, ReactElement } from 'react';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export type BookMarkProps = {
|
export type BookMarkProps = {
|
||||||
@@ -95,7 +96,8 @@ const shouldShowBookmarkMenu = (pastedBlocks: SerializedBlock[]) => {
|
|||||||
}
|
}
|
||||||
return !!firstBlock.text[0].attributes?.link;
|
return !!firstBlock.text[0].attributes?.link;
|
||||||
};
|
};
|
||||||
export const BookMarkUI: FC<BookMarkProps> = ({ page }) => {
|
|
||||||
|
const BookMarkUI: FC<BookMarkProps> = ({ page }) => {
|
||||||
const [anchor, setAnchor] = useState<Range | null>(null);
|
const [anchor, setAnchor] = useState<Range | null>(null);
|
||||||
const [selectedOption, setSelectedOption] = useState<string>(
|
const [selectedOption, setSelectedOption] = useState<string>(
|
||||||
menuOptions[0].id
|
menuOptions[0].id
|
||||||
@@ -218,3 +220,15 @@ export const BookMarkUI: FC<BookMarkProps> = ({ page }) => {
|
|||||||
</MuiClickAwayListener>
|
</MuiClickAwayListener>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AppProps = {
|
||||||
|
page: Page;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const App = (props: AppProps): ReactElement => {
|
||||||
|
return (
|
||||||
|
<StrictMode>
|
||||||
|
<BookMarkUI page={props.page} />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
};
|
||||||
21
plugins/bookmark/src/index.ts
Normal file
21
plugins/bookmark/src/index.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { PluginContext } from '@toeverything/plugin-infra/entry';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { App } from './app';
|
||||||
|
|
||||||
|
export const entry = (context: PluginContext) => {
|
||||||
|
console.log('register');
|
||||||
|
|
||||||
|
context.register('editor', (div, editor) => {
|
||||||
|
const root = createRoot(div);
|
||||||
|
root.render(createElement(App, { page: editor.page }));
|
||||||
|
return () => {
|
||||||
|
root.unmount();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log('unregister');
|
||||||
|
};
|
||||||
|
};
|
||||||
17
plugins/bookmark/tsconfig.json
Normal file
17
plugins/bookmark/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["./src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": false,
|
||||||
|
"outDir": "lib",
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../packages/plugin-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/component"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,28 +1,27 @@
|
|||||||
{
|
{
|
||||||
"name": "@affine/copilot",
|
"name": "@affine/copilot",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/index.ts",
|
"affinePlugin": {
|
||||||
"module": "./src/index.ts",
|
"release": false,
|
||||||
"exports": {
|
"entry": {
|
||||||
".": "./src/index.ts"
|
"core": "./src/index.ts"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@toeverything/plugin-infra": "workspace:*",
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
|
"idb": "^7.1.1",
|
||||||
"langchain": "^0.0.107",
|
"langchain": "^0.0.107",
|
||||||
"marked": "^5.1.0",
|
"marked": "^5.1.0",
|
||||||
"marked-gfm-heading-id": "^3.0.4",
|
"marked-gfm-heading-id": "^3.0.4",
|
||||||
"marked-mangle": "^1.1.0"
|
"marked-mangle": "^1.1.0",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/marked": "^5.0.0",
|
"@types/marked": "^5.0.0",
|
||||||
"@types/react": "^18.2.14",
|
|
||||||
"@types/react-dom": "^18.2.6",
|
|
||||||
"idb": "^7.1.1",
|
|
||||||
"jotai": "^2.2.2",
|
"jotai": "^2.2.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0"
|
||||||
"zod": "^3.21.4"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { SendIcon } from '@blocksuite/icons';
|
import { SendIcon } from '@blocksuite/icons';
|
||||||
import { rootStore } from '@toeverything/plugin-infra/manager';
|
import { contentLayoutAtom } from '@toeverything/plugin-infra/manager';
|
||||||
import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { Provider, useAtomValue, useSetAtom } from 'jotai';
|
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { StrictMode, Suspense, useCallback, useState } from 'react';
|
import { Suspense, useCallback, useState } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
|
||||||
|
|
||||||
import { ConversationList } from '../core/components/conversation-list';
|
import { ConversationList } from '../core/components/conversation-list';
|
||||||
import { FollowingUp } from '../core/components/following-up';
|
import { FollowingUp } from '../core/components/following-up';
|
||||||
@@ -17,51 +15,6 @@ import {
|
|||||||
textareaStyle,
|
textareaStyle,
|
||||||
} from './index.css';
|
} from './index.css';
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
import('@blocksuite/blocks')
|
|
||||||
.then(({ FormatQuickBar }) => {
|
|
||||||
FormatQuickBar.customElements.push((_page, getSelection) => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const root = createRoot(div);
|
|
||||||
|
|
||||||
const AskAI = (): ReactElement => {
|
|
||||||
const { conversationAtom } = useChatAtoms();
|
|
||||||
const call = useSetAtom(conversationAtom);
|
|
||||||
const onClickAskAI = useCallback(() => {
|
|
||||||
const selection = getSelection();
|
|
||||||
if (selection != null) {
|
|
||||||
const text = selection.models
|
|
||||||
.map(model => {
|
|
||||||
return model.text?.toString();
|
|
||||||
})
|
|
||||||
.filter((v): v is string => Boolean(v))
|
|
||||||
.join('\n');
|
|
||||||
console.log('selected text:', text);
|
|
||||||
call(
|
|
||||||
`I selected some text from the document: \n"${text}."`
|
|
||||||
).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [call]);
|
|
||||||
|
|
||||||
return <div onClick={onClickAskAI}>Ask AI</div>;
|
|
||||||
};
|
|
||||||
root.render(
|
|
||||||
<StrictMode>
|
|
||||||
<Provider store={rootStore}>
|
|
||||||
<AskAI />
|
|
||||||
</Provider>
|
|
||||||
</StrictMode>
|
|
||||||
);
|
|
||||||
return div;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Actions = () => {
|
const Actions = () => {
|
||||||
const { conversationAtom, followingUpAtoms } = useChatAtoms();
|
const { conversationAtom, followingUpAtoms } = useChatAtoms();
|
||||||
const call = useSetAtom(conversationAtom);
|
const call = useSetAtom(conversationAtom);
|
||||||
@@ -108,12 +61,10 @@ const DetailContentImpl = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DetailContent: PluginUIAdapter['detailContent'] = ({
|
export const DetailContent = (): ReactElement => {
|
||||||
contentLayoutAtom,
|
|
||||||
}): ReactElement => {
|
|
||||||
const layout = useAtomValue(contentLayoutAtom);
|
const layout = useAtomValue(contentLayoutAtom);
|
||||||
const key = useAtomValue(openAIApiKeyAtom);
|
const key = useAtomValue(openAIApiKeyAtom);
|
||||||
if (layout === 'editor' || layout.second !== 'com.affine.copilot') {
|
if (layout === 'editor' || layout.second !== 'copilot') {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
if (!key) {
|
if (!key) {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { IconButton, Tooltip } from '@affine/component';
|
import { IconButton, Tooltip } from '@affine/component';
|
||||||
import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
|
import { contentLayoutAtom } from '@toeverything/plugin-infra/manager';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const HeaderItem: PluginUIAdapter['headerItem'] = ({
|
export const HeaderItem = (): ReactElement => {
|
||||||
contentLayoutAtom,
|
|
||||||
}): ReactElement => {
|
|
||||||
const setLayout = useSetAtom(contentLayoutAtom);
|
const setLayout = useSetAtom(contentLayoutAtom);
|
||||||
return (
|
return (
|
||||||
<Tooltip content="Chat with AI" placement="bottom-end">
|
<Tooltip content="Chat with AI" placement="bottom-end">
|
||||||
@@ -18,7 +16,7 @@ export const HeaderItem: PluginUIAdapter['headerItem'] = ({
|
|||||||
return {
|
return {
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
first: 'editor',
|
first: 'editor',
|
||||||
second: 'com.affine.copilot',
|
second: 'copilot',
|
||||||
splitPercentage: 70,
|
splitPercentage: 70,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button } from '@affine/component';
|
import { Button } from '@affine/component';
|
||||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
|
||||||
import { PlusIcon, ResetIcon } from '@blocksuite/icons';
|
import { PlusIcon, ResetIcon } from '@blocksuite/icons';
|
||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import type { MessageType } from 'langchain/schema';
|
import type { MessageType } from 'langchain/schema';
|
||||||
@@ -31,7 +30,6 @@ export const Conversation = (props: ConversationProps): ReactElement => {
|
|||||||
[styles.avatarRightStyle]: props.type === 'human',
|
[styles.avatarRightStyle]: props.type === 'human',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<WorkspaceAvatar workspace={null} />
|
|
||||||
<div className={styles.conversationContainerStyle}>
|
<div className={styles.conversationContainerStyle}>
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.conversationStyle, {
|
className={clsx(styles.conversationStyle, {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { IndexedDBChatMessageHistory } from '@affine/copilot/core/langchain/message-history';
|
import type { IndexedDBChatMessageHistory } from '@affine/copilot/core/langchain/message-history';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import { atomWithDefault } from 'jotai/utils';
|
import { atomWithDefault, atomWithStorage } from 'jotai/utils';
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
|
||||||
import type { WritableAtom } from 'jotai/vanilla';
|
import type { WritableAtom } from 'jotai/vanilla';
|
||||||
import type { LLMChain } from 'langchain/chains';
|
import type { LLMChain } from 'langchain/chains';
|
||||||
import { type ConversationChain } from 'langchain/chains';
|
import { type ConversationChain } from 'langchain/chains';
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
import { definePlugin } from '@toeverything/plugin-infra/manager';
|
import type { PluginContext } from '@toeverything/plugin-infra/entry';
|
||||||
import { ReleaseStage } from '@toeverything/plugin-infra/type';
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
definePlugin(
|
import { DetailContent } from './UI/detail-content';
|
||||||
{
|
import { HeaderItem } from './UI/header-item';
|
||||||
id: 'com.affine.copilot',
|
|
||||||
name: {
|
export const entry = (context: PluginContext) => {
|
||||||
fallback: 'AFFiNE Copilot',
|
context.register('headerItem', div => {
|
||||||
i18nKey: 'com.affine.copilot.name',
|
const root = createRoot(div);
|
||||||
},
|
root.render(
|
||||||
description: {
|
createElement(context.utils.PluginProvider, {}, createElement(HeaderItem))
|
||||||
fallback:
|
);
|
||||||
'AFFiNE Copilot will help you with best writing experience on the World.',
|
return () => {
|
||||||
},
|
root.unmount();
|
||||||
publisher: {
|
};
|
||||||
name: {
|
});
|
||||||
fallback: 'AFFiNE',
|
|
||||||
},
|
context.register('window', div => {
|
||||||
link: 'https://affine.pro',
|
const root = createRoot(div);
|
||||||
},
|
root.render(
|
||||||
stage: ReleaseStage.NIGHTLY,
|
createElement(
|
||||||
version: '0.0.1',
|
context.utils.PluginProvider,
|
||||||
commands: [],
|
{},
|
||||||
},
|
createElement(DetailContent)
|
||||||
{
|
)
|
||||||
load: () => import('./UI/index'),
|
);
|
||||||
hotModuleReload: onHot =>
|
return () => {
|
||||||
import.meta.webpackHot &&
|
root.unmount();
|
||||||
import.meta.webpackHot.accept('./UI', () => onHot(import('./UI/index'))),
|
};
|
||||||
}
|
});
|
||||||
);
|
return () => {};
|
||||||
|
};
|
||||||
|
|||||||
16
plugins/hello-world/package.json
Normal file
16
plugins/hello-world/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@affine/hello-world-plugin",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"affinePlugin": {
|
||||||
|
"release": false,
|
||||||
|
"entry": {
|
||||||
|
"core": "./src/index.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@affine/component": "workspace:*",
|
||||||
|
"@blocksuite/icons": "^2.1.25",
|
||||||
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
plugins/hello-world/src/app.tsx
Normal file
17
plugins/hello-world/src/app.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { IconButton, Tooltip } from '@affine/component';
|
||||||
|
import { AffineLogoSBlue2_1Icon } from '@blocksuite/icons';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const HeaderItem = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip content="Plugin Enabled">
|
||||||
|
<IconButton
|
||||||
|
onClick={useCallback(() => {
|
||||||
|
console.log('clicked hello world!');
|
||||||
|
}, [])}
|
||||||
|
>
|
||||||
|
<AffineLogoSBlue2_1Icon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
26
plugins/hello-world/src/index.ts
Normal file
26
plugins/hello-world/src/index.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { PluginContext } from '@toeverything/plugin-infra/entry';
|
||||||
|
import {
|
||||||
|
currentWorkspaceIdAtom,
|
||||||
|
rootStore,
|
||||||
|
} from '@toeverything/plugin-infra/manager';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { HeaderItem } from './app';
|
||||||
|
|
||||||
|
export const entry = (context: PluginContext) => {
|
||||||
|
console.log('register');
|
||||||
|
console.log('hello, world!');
|
||||||
|
console.log(rootStore.get(currentWorkspaceIdAtom));
|
||||||
|
context.register('headerItem', div => {
|
||||||
|
const root = createRoot(div);
|
||||||
|
root.render(createElement(HeaderItem));
|
||||||
|
return () => {
|
||||||
|
root.unmount();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log('unregister');
|
||||||
|
};
|
||||||
|
};
|
||||||
17
plugins/hello-world/tsconfig.json
Normal file
17
plugins/hello-world/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["./src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": false,
|
||||||
|
"outDir": "lib",
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../packages/plugin-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/component"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"outDir": "lib"
|
"outDir": "lib"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
"vite.config.ts",
|
||||||
"vitest.config.ts",
|
"vitest.config.ts",
|
||||||
"scripts",
|
"scripts",
|
||||||
"apps/core/.webpack/runtime-config.ts"
|
"apps/core/.webpack/runtime-config.ts"
|
||||||
|
|||||||
92
vite.config.ts
Normal file
92
vite.config.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||||
|
import react from '@vitejs/plugin-react-swc';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const root = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
const builtInPlugins = ['hello-world', 'bookmark', 'copilot'];
|
||||||
|
|
||||||
|
const outputJson: [pluginName: string, output: string][] = [];
|
||||||
|
|
||||||
|
const entry = builtInPlugins.reduce(
|
||||||
|
(acc, plugin) => {
|
||||||
|
const packageJson = require(resolve(
|
||||||
|
root,
|
||||||
|
'plugins',
|
||||||
|
plugin,
|
||||||
|
'package.json'
|
||||||
|
));
|
||||||
|
const entry = packageJson.affinePlugin.entry.core;
|
||||||
|
acc[`${plugin}/index`] = resolve(root, 'plugins', plugin, entry);
|
||||||
|
packageJson.affinePlugin.entry.core = './index.js';
|
||||||
|
outputJson.push([plugin, JSON.stringify(packageJson, null, 2)]);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: resolve(root, 'apps', 'core', 'public', 'plugins'),
|
||||||
|
emptyOutDir: true,
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry,
|
||||||
|
formats: ['cjs'],
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: () => 'plugin',
|
||||||
|
entryFileNames: '[name].js',
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
},
|
||||||
|
external: [
|
||||||
|
// built-in packages
|
||||||
|
/^@affine/,
|
||||||
|
/^@blocksuite/,
|
||||||
|
/^@toeverything/,
|
||||||
|
|
||||||
|
// react
|
||||||
|
/^react/,
|
||||||
|
/^react-dom/,
|
||||||
|
|
||||||
|
// store
|
||||||
|
/^jotai/,
|
||||||
|
|
||||||
|
// css
|
||||||
|
/^@vanilla-extract/,
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
vanillaExtractPlugin(),
|
||||||
|
{
|
||||||
|
name: 'generate-manifest',
|
||||||
|
generateBundle() {
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
fileName: `plugin-list.json`,
|
||||||
|
source: JSON.stringify(
|
||||||
|
builtInPlugins.map(plugin => ({
|
||||||
|
name: plugin,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
});
|
||||||
|
outputJson.forEach(([name, json]) => {
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
fileName: `${name}/package.json`,
|
||||||
|
source: json,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import path, { resolve } from 'node:path';
|
|||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react-swc';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|||||||
84
yarn.lock
84
yarn.lock
@@ -64,19 +64,23 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@affine/bookmark-block@workspace:*, @affine/bookmark-block@workspace:plugins/bookmark-block":
|
"@affine/bookmark-block@workspace:plugins/bookmark-block":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
||||||
|
dependencies:
|
||||||
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
|
link-preview-js: ^3.0.4
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
|
"@affine/bookmark-plugin@workspace:plugins/bookmark":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@affine/bookmark-plugin@workspace:plugins/bookmark"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine/component": "workspace:*"
|
"@affine/component": "workspace:*"
|
||||||
|
"@blocksuite/icons": ^2.1.25
|
||||||
"@toeverything/plugin-infra": "workspace:*"
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
foxact: ^0.2.11
|
foxact: ^0.2.11
|
||||||
link-preview-js: ^3.0.4
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0
|
|
||||||
peerDependencies:
|
|
||||||
react: "*"
|
|
||||||
react-dom: "*"
|
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@@ -165,8 +169,6 @@ __metadata:
|
|||||||
"@affine/component": "workspace:*"
|
"@affine/component": "workspace:*"
|
||||||
"@toeverything/plugin-infra": "workspace:*"
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
"@types/marked": ^5.0.0
|
"@types/marked": ^5.0.0
|
||||||
"@types/react": ^18.2.14
|
|
||||||
"@types/react-dom": ^18.2.6
|
|
||||||
idb: ^7.1.1
|
idb: ^7.1.1
|
||||||
jotai: ^2.2.2
|
jotai: ^2.2.2
|
||||||
langchain: ^0.0.107
|
langchain: ^0.0.107
|
||||||
@@ -187,7 +189,6 @@ __metadata:
|
|||||||
resolution: "@affine/core@workspace:apps/core"
|
resolution: "@affine/core@workspace:apps/core"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine-test/fixtures": "workspace:*"
|
"@affine-test/fixtures": "workspace:*"
|
||||||
"@affine/bookmark-block": "workspace:*"
|
|
||||||
"@affine/component": "workspace:*"
|
"@affine/component": "workspace:*"
|
||||||
"@affine/copilot": "workspace:*"
|
"@affine/copilot": "workspace:*"
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
@@ -241,6 +242,7 @@ __metadata:
|
|||||||
react-resizable-panels: ^0.0.53
|
react-resizable-panels: ^0.0.53
|
||||||
react-router-dom: ^6.14.1
|
react-router-dom: ^6.14.1
|
||||||
rxjs: ^7.8.1
|
rxjs: ^7.8.1
|
||||||
|
ses: ^0.18.5
|
||||||
style-loader: ^3.3.3
|
style-loader: ^3.3.3
|
||||||
swc-loader: ^0.2.3
|
swc-loader: ^0.2.3
|
||||||
swc-plugin-coverage-instrument: ^0.0.19
|
swc-plugin-coverage-instrument: ^0.0.19
|
||||||
@@ -373,6 +375,16 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@affine/hello-world-plugin@workspace:plugins/hello-world":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@affine/hello-world-plugin@workspace:plugins/hello-world"
|
||||||
|
dependencies:
|
||||||
|
"@affine/component": "workspace:*"
|
||||||
|
"@blocksuite/icons": ^2.1.25
|
||||||
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@affine/i18n@workspace:*, @affine/i18n@workspace:packages/i18n":
|
"@affine/i18n@workspace:*, @affine/i18n@workspace:packages/i18n":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/i18n@workspace:packages/i18n"
|
resolution: "@affine/i18n@workspace:packages/i18n"
|
||||||
@@ -434,7 +446,7 @@ __metadata:
|
|||||||
"@typescript-eslint/parser": ^5.60.1
|
"@typescript-eslint/parser": ^5.60.1
|
||||||
"@vanilla-extract/vite-plugin": ^3.8.2
|
"@vanilla-extract/vite-plugin": ^3.8.2
|
||||||
"@vanilla-extract/webpack-plugin": ^2.2.0
|
"@vanilla-extract/webpack-plugin": ^2.2.0
|
||||||
"@vitejs/plugin-react": ^4.0.1
|
"@vitejs/plugin-react-swc": ^3.3.2
|
||||||
"@vitest/coverage-istanbul": ^0.32.2
|
"@vitest/coverage-istanbul": ^0.32.2
|
||||||
"@vitest/ui": ^0.32.2
|
"@vitest/ui": ^0.32.2
|
||||||
eslint: ^8.44.0
|
eslint: ^8.44.0
|
||||||
@@ -464,6 +476,7 @@ __metadata:
|
|||||||
typescript: ^5.1.6
|
typescript: ^5.1.6
|
||||||
vite: ^4.3.9
|
vite: ^4.3.9
|
||||||
vite-plugin-istanbul: ^4.1.0
|
vite-plugin-istanbul: ^4.1.0
|
||||||
|
vite-plugin-static-copy: ^0.17.0
|
||||||
vite-tsconfig-paths: ^4.2.0
|
vite-tsconfig-paths: ^4.2.0
|
||||||
vitest: ^0.32.2
|
vitest: ^0.32.2
|
||||||
vitest-fetch-mock: ^0.2.2
|
vitest-fetch-mock: ^0.2.2
|
||||||
@@ -4657,6 +4670,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@endo/env-options@npm:^0.1.1":
|
||||||
|
version: 0.1.1
|
||||||
|
resolution: "@endo/env-options@npm:0.1.1"
|
||||||
|
checksum: c721db3e2d3f9a52a5c391dc8e6d473ffc44cf2bb252fad3048551e35aac52484d612753211116ff9a7157a0768d7c61352c494f491e3463e78fef438beb1d42
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@esbuild/android-arm64@npm:0.17.19":
|
"@esbuild/android-arm64@npm:0.17.19":
|
||||||
version: 0.17.19
|
version: 0.17.19
|
||||||
resolution: "@esbuild/android-arm64@npm:0.17.19"
|
resolution: "@esbuild/android-arm64@npm:0.17.19"
|
||||||
@@ -11385,7 +11405,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core@npm:^1.3.70":
|
"@swc/core@npm:^1.3.61, @swc/core@npm:^1.3.70":
|
||||||
version: 1.3.70
|
version: 1.3.70
|
||||||
resolution: "@swc/core@npm:1.3.70"
|
resolution: "@swc/core@npm:1.3.70"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12841,6 +12861,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@vitejs/plugin-react-swc@npm:^3.3.2":
|
||||||
|
version: 3.3.2
|
||||||
|
resolution: "@vitejs/plugin-react-swc@npm:3.3.2"
|
||||||
|
dependencies:
|
||||||
|
"@swc/core": ^1.3.61
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^4
|
||||||
|
checksum: 1e44b1b7c50b6ae381e2ded9a7971af42286144bf257d7ed1a9dbf8acec0c4af78fe46982941566d31b80b7886649d9b1c06a873571d228e0b8fce68742b9ff4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@vitejs/plugin-react@npm:^3.0.1":
|
"@vitejs/plugin-react@npm:^3.0.1":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "@vitejs/plugin-react@npm:3.1.0"
|
resolution: "@vitejs/plugin-react@npm:3.1.0"
|
||||||
@@ -19460,8 +19491,8 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"foxact@npm:^0.2.11":
|
"foxact@npm:^0.2.11":
|
||||||
version: 0.2.11
|
version: 0.2.17
|
||||||
resolution: "foxact@npm:0.2.11"
|
resolution: "foxact@npm:0.2.17"
|
||||||
dependencies:
|
dependencies:
|
||||||
client-only: ^0.0.1
|
client-only: ^0.0.1
|
||||||
server-only: ^0.0.1
|
server-only: ^0.0.1
|
||||||
@@ -19470,7 +19501,7 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
react:
|
react:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 711a5d1e287c45eb258a9003910d13fc2c82f3c6cd0e4e9c673dbe64c6a7d95da7cc983b5f143270bc6304b9ceb0de2d25c01588e066e01e8eb0dbb55e55eaa6
|
checksum: 9229ac42032dbce52c95f5583601ba5dbfe8e4bd40b6ec3b372d91793b7b621e01eb45bb953dfe077e65a2ecd4dcdaa42c8f1c60efbf43048a3cd0038a754b60
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -29218,6 +29249,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ses@npm:^0.18.5":
|
||||||
|
version: 0.18.5
|
||||||
|
resolution: "ses@npm:0.18.5"
|
||||||
|
dependencies:
|
||||||
|
"@endo/env-options": ^0.1.1
|
||||||
|
checksum: b41faaf28efc97c09b0322de5c12603e015238e4b2cf5195c8fc3f5fb9bc66169dd394c63bcdb30d8fa36a55d03eeda3df64857ab724b2297fb6b60c4f5dc0e2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"set-blocking@npm:^2.0.0":
|
"set-blocking@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "set-blocking@npm:2.0.0"
|
resolution: "set-blocking@npm:2.0.0"
|
||||||
@@ -31924,6 +31964,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"vite-plugin-static-copy@npm:^0.17.0":
|
||||||
|
version: 0.17.0
|
||||||
|
resolution: "vite-plugin-static-copy@npm:0.17.0"
|
||||||
|
dependencies:
|
||||||
|
chokidar: ^3.5.3
|
||||||
|
fast-glob: ^3.2.11
|
||||||
|
fs-extra: ^11.1.0
|
||||||
|
picocolors: ^1.0.0
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^3.0.0 || ^4.0.0
|
||||||
|
checksum: b73f001f080517d7c2342ad5b0779c1e5ec8b5820e85a57280e8f979d095946a32ab717a59eaae60e3f715784a138785208619aa6d936cd9a80e6a03e364e4fc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"vite-tsconfig-paths@npm:^4.2.0":
|
"vite-tsconfig-paths@npm:^4.2.0":
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
resolution: "vite-tsconfig-paths@npm:4.2.0"
|
resolution: "vite-tsconfig-paths@npm:4.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user