refactor(plugin): Categorize plugins and rename some plugins

This commit is contained in:
robin 2023-09-15 18:45:02 +08:00
parent a8c37f4dab
commit 079de85f35
26 changed files with 209 additions and 85 deletions

View File

@ -1,6 +1,8 @@
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import './i18n/init';
import '@/utils/pluginKit';
import routes from '@/router';
function App() {

View File

@ -9,12 +9,12 @@ import {
import classNames from 'classnames';
import PluginRender from '../PluginRender';
import {
BlockQuote,
Bold,
Chart,
Code,
Formula,
Heading,
Help,
Hr,
@ -127,7 +127,9 @@ const MDEditor: ForwardRefRenderFunction<EditorRef, Props> = (
<div className={classNames('md-editor-wrap rounded', className)}>
<EditorContext.Provider value={context}>
{context && (
<div className="toolbar-wrap px-3 d-flex align-items-center flex-wrap">
<PluginRender
type="editor"
className="toolbar-wrap px-3 d-flex align-items-center flex-wrap">
<Heading {...context} />
<Bold {...context} />
<Italice {...context} />
@ -136,10 +138,7 @@ const MDEditor: ForwardRefRenderFunction<EditorRef, Props> = (
<Link {...context} />
<BlockQuote {...context} />
<Image {...context} />
<div className="toolbar-divider" />
<Table {...context} />
<Formula {...context} />
<Chart {...context} />
<div className="toolbar-divider" />
<OL {...context} />
<UL {...context} />
@ -148,7 +147,7 @@ const MDEditor: ForwardRefRenderFunction<EditorRef, Props> = (
<Hr {...context} />
<div className="toolbar-divider" />
<Help />
</div>
</PluginRender>
)}
</EditorContext.Provider>

View File

@ -1,8 +1,6 @@
import { FC, ReactNode, memo } from 'react';
import React, { FC, ReactNode, memo } from 'react';
import builtin from '@/plugins/builtin';
import * as plugins from '@/plugins';
import { Plugin, PluginType } from '@/utils/pluginKit';
import PluginKite, { Plugin, PluginType } from '@/utils/pluginKit';
/**
* NotePlease set at least either of the `slug_name` and `type` attributes, otherwise no plugins will be rendered.
@ -22,47 +20,60 @@ interface Props {
[prop: string]: any;
}
const findPlugin: (s, k: 'slug_name' | 'type', v) => Plugin[] = (
source,
k,
v,
) => {
const ret: Plugin[] = [];
if (source) {
Object.keys(source).forEach((i) => {
const p = source[i];
if (p && p.component && p.info && p.info[k] === v) {
ret.push(p);
}
});
}
return ret;
};
const Index: FC<Props> = ({ slug_name, type, children, ...props }) => {
const fk = slug_name ? 'slug_name' : 'type';
const fv = fk === 'slug_name' ? slug_name : type;
const bp = findPlugin(builtin, fk, fv);
const vp = findPlugin(plugins, fk, fv);
const pluginSlice = [...bp, ...vp];
const pluginSlice: Plugin[] = [];
const plugins = PluginKite.getPlugins();
plugins.forEach((plugin) => {
if (type && slug_name) {
if (plugin.info.slug_name === slug_name && plugin.info.type === type) {
pluginSlice.push(plugin);
}
} else if (type) {
if (plugin.info.type === type) {
pluginSlice.push(plugin);
}
} else if (slug_name) {
if (plugin.info.slug_name === slug_name) {
pluginSlice.push(plugin);
}
}
});
if (!pluginSlice.length) {
return null;
}
/**
* TODO: Rendering control for non-builtin plug-ins
* ps: Logic such as version compatibility determination can be placed here
*/
if (type === 'editor') {
const nodes = React.Children.map(children, (child, index) => {
if (index === 0) {
return (
<>
{child}
{pluginSlice.map((ps) => {
const PluginFC = ps.component;
return (
// @ts-ignore
<PluginFC key={ps.info.slug_name} />
);
})}
</>
);
}
return child;
});
return <div {...props}>{nodes}</div>;
}
return (
<>
{pluginSlice.map((ps) => {
const PluginFC = ps.component;
return (
// @ts-ignore
<PluginFC key={ps.info.slug_name} {...props}>
{children}
</PluginFC>
<PluginFC key={ps.info.slug_name} {...props} />
);
})}
</>

View File

@ -38,7 +38,7 @@ const Index: FC<Props> = ({ data }) => {
<div className="mb-5">
<div className="mb-3 d-flex align-items-center justify-content-between">
<h3 className="mb-0">{t('title')}</h3>
<PluginRender slug_name="algolia" />
<PluginRender type="search" slug_name="algolia" />
</div>
<p>
<span className="text-secondary me-1">{t('keywords')}</span>

View File

@ -144,9 +144,17 @@ const Index: React.FC = () => {
{step === 1 ? (
<Col className="mx-auto" md={6} lg={4} xl={3}>
{ucAgentInfo ? (
<PluginRender slug_name="uc_login" className="mb-5" />
<PluginRender
type="connector"
slug_name="hosting_connector"
className="mb-5"
/>
) : (
<PluginRender type="Connector" className="mb-5" />
<PluginRender
type="connector"
slug_name="third_party_connector"
className="mb-5"
/>
)}
{canOriginalLogin ? (
<>

View File

@ -35,7 +35,11 @@ const Index: React.FC = () => {
{showForm ? (
<Col className="mx-auto" md={6} lg={4} xl={3}>
<PluginRender type="Connector" className="mb-5" />
<PluginRender
type="connector"
slug_name="third_party_connector"
className="mb-5"
/>
{showSignupForm ? <SignUpForm callback={onStep} /> : null}
<div className="text-center mt-5">
<Trans i18nKey="login.info_login" ns="translation">

View File

@ -1,9 +1,9 @@
import pluginKit from '@/utils/pluginKit';
import { initI18nResource } from '@/utils/pluginKit/utils';
import en_US from './en_US.yaml';
import zh_CN from './zh_CN.yaml';
pluginKit.initI18nResource({
initI18nResource({
en_US,
zh_CN,
});

View File

@ -2,18 +2,21 @@ import { memo, FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert } from 'react-bootstrap';
import pluginKit, { PluginInfo } from '@/utils/pluginKit';
import { PluginInfo } from '@/utils/pluginKit';
import { getTransNs, getTransKeyPrefix } from '@/utils/pluginKit/utils';
import './i18n';
import info from './info.yaml';
const pluginInfo: PluginInfo = {
slug_name: info.slug_name,
type: info.type,
};
const Index: FC = () => {
const { t } = useTranslation(pluginKit.getTransNs(), {
keyPrefix: pluginKit.getTransKeyPrefix(pluginInfo),
const { t } = useTranslation(getTransNs(), {
keyPrefix: getTransKeyPrefix(pluginInfo),
});
return <Alert variant="info">{t('msg')}</Alert>;

View File

@ -1,9 +1,9 @@
import pluginKit from '@/utils/pluginKit';
import { initI18nResource } from '@/utils/pluginKit/utils';
import en_US from './en_US.yaml';
import zh_CN from './zh_CN.yaml';
pluginKit.initI18nResource({
initI18nResource({
en_US,
zh_CN,
});

View File

@ -1,7 +1,8 @@
import { memo, FC } from 'react';
import { useTranslation } from 'react-i18next';
import pluginKit, { PluginInfo } from '@/utils/pluginKit';
import { PluginInfo } from '@/utils/pluginKit';
import { getTransNs, getTransKeyPrefix } from '@/utils/pluginKit/utils';
import { SvgIcon } from '@/components';
import info from './info.yaml';
@ -10,11 +11,12 @@ import './i18n';
const pluginInfo: PluginInfo = {
slug_name: info.slug_name,
type: info.type,
};
const Index: FC = () => {
const { t } = useTranslation(pluginKit.getTransNs(), {
keyPrefix: pluginKit.getTransKeyPrefix(pluginInfo),
const { t } = useTranslation(getTransNs(), {
keyPrefix: getTransKeyPrefix(pluginInfo),
});
const { data } = useGetAlgoliaInfo();

View File

@ -1,3 +1,4 @@
slug_name: algolia
type: search
version: 0.0.1
author: Answer.dev

View File

@ -1,5 +1,5 @@
plugin:
uc_login:
hosting_connector:
ui:
connect: Connect with {{ auth_name }}
login: Login

View File

@ -1,9 +1,9 @@
import pluginKit from '@/utils/pluginKit';
import { initI18nResource } from '@/utils/pluginKit/utils';
import en_US from './en_US.yaml';
import zh_CN from './zh_CN.yaml';
pluginKit.initI18nResource({
initI18nResource({
en_US,
zh_CN,
});

View File

@ -1,5 +1,5 @@
plugin:
uc_login:
hosting_connector:
ui:
connect: 连接到 {{ auth_name }}
login: 登录

View File

@ -4,7 +4,8 @@ import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import pluginKit, { PluginInfo } from '@/utils/pluginKit';
import { PluginInfo } from '@/utils/pluginKit';
import { getTransNs, getTransKeyPrefix } from '@/utils/pluginKit/utils';
import { SvgIcon } from '@/components';
import { userCenterStore } from '@/stores';
import './i18n';
@ -17,11 +18,12 @@ interface Props {
const pluginInfo: PluginInfo = {
slug_name: info.slug_name,
type: info.type,
};
const Index: FC<Props> = ({ className }) => {
const { t } = useTranslation(pluginKit.getTransNs(), {
keyPrefix: pluginKit.getTransKeyPrefix(pluginInfo),
const { t } = useTranslation(getTransNs(), {
keyPrefix: getTransKeyPrefix(pluginInfo),
});
const ucAgent = userCenterStore().agent;
const ucLoginRedirect =

View File

@ -0,0 +1,5 @@
slug_name: hosting_connector
type: connector
version: 0.0.1
author: Answer.dev

View File

@ -1,5 +1,5 @@
plugin:
connector:
third_party_connector:
ui:
connect: Connect with {{ auth_name }}
remove: Remove {{ auth_name }}

View File

@ -1,9 +1,9 @@
import pluginKit from '@/utils/pluginKit';
import { initI18nResource } from '@/utils/pluginKit/utils';
import en_US from './en_US.yaml';
import zh_CN from './zh_CN.yaml';
pluginKit.initI18nResource({
initI18nResource({
en_US,
zh_CN,
});

View File

@ -1,5 +1,5 @@
plugin:
connector:
third_party_connector:
ui:
connect: 连接到 {{ auth_name }}
remove: 解绑 {{ auth_name }}

View File

@ -4,7 +4,8 @@ import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import pluginKit, { PluginInfo } from '@/utils/pluginKit';
import { PluginInfo } from '@/utils/pluginKit';
import { getTransNs, getTransKeyPrefix } from '@/utils/pluginKit/utils';
import { SvgIcon } from '@/components';
import info from './info.yaml';
@ -19,8 +20,8 @@ interface Props {
className?: string;
}
const Index: FC<Props> = ({ className }) => {
const { t } = useTranslation(pluginKit.getTransNs(), {
keyPrefix: pluginKit.getTransKeyPrefix(pluginInfo),
const { t } = useTranslation(getTransNs(), {
keyPrefix: getTransKeyPrefix(pluginInfo),
});
const { data } = useGetStartUseOauthConnector();

View File

@ -1,5 +1,5 @@
slug_name: connector
type: Connector
slug_name: third_party_connector
type: connector
version: 0.0.1
link: https://github.com/answerdev/plugins/tree/main/connector/
author: Answer.dev

View File

@ -1,4 +0,0 @@
slug_name: uc_login
version: 0.0.1
author: Answer.dev

View File

@ -1,9 +1,9 @@
import Connector from './Connector';
import UcLogin from './UcLogin';
import ThirdPartyConnector from './ThirdPartyConnector';
import HostingConnector from './HostingConnector';
import Algolia from './Algolia';
export default {
Connector,
UcLogin,
ThirdPartyConnector,
HostingConnector,
Algolia,
};

View File

@ -0,0 +1,94 @@
import { NamedExoticComponent, FC } from 'react';
import builtin from '@/plugins/builtin';
import * as allPlugins from '@/plugins';
/**
* This information is to be defined for all components.
* It may be used for feature upgrades or version compatibility processing.
*
* @field slug_name: Unique identity string for the plugin, usually configured in `info.yaml`
* @field type: The type of plugin is defined and a single type of plugin can have multiple implementations.
* For example, a plugin of type `connector` can have a `google` implementation and a `github` implementation.
* `PluginRender` automatically renders the plug-in types already included in `PluginType`.
* @field name: Plugin name, optionally configurable. Usually read from the `i18n` file
* @field description: Plugin description, optionally configurable. Usually read from the `i18n` file
*/
export type PluginType = 'connector' | 'search' | 'editor';
export interface PluginInfo {
slug_name: string;
type: PluginType;
name?: string;
description?: string;
}
export interface Plugin {
info: PluginInfo;
component: NamedExoticComponent | FC;
}
class Plugins {
plugins: Plugin[] = [];
constructor() {
this.registerBuiltin();
this.registerPlugins();
}
validate(plugin: Plugin) {
if (!plugin) {
return false;
}
const { info } = plugin;
const { slug_name, type } = info;
if (!slug_name) {
return false;
}
if (!type) {
return false;
}
return true;
}
registerBuiltin() {
Object.keys(builtin).forEach((key) => {
const plugin = builtin[key];
this.register(plugin);
});
}
registerPlugins() {
Object.keys(allPlugins).forEach((key) => {
const plugin = allPlugins[key];
this.register(plugin);
});
}
register(plugin: Plugin) {
const bool = this.validate(plugin);
if (!bool) {
return;
}
this.plugins.push(plugin);
}
getPlugin(slug_name: string) {
return this.plugins.find((p) => p.info.slug_name === slug_name);
}
getPluginsByType(type: PluginType) {
return this.plugins.filter((p) => p.info.type === type);
}
getPlugins() {
return this.plugins;
}
}
const plugins = new Plugins();
export default plugins;

View File

@ -8,7 +8,7 @@ import i18next from 'i18next';
*
* @field slug_name: Unique identity string for the plugin, usually configured in `info.yaml`
* @field type: The type of plugin is defined and a single type of plugin can have multiple implementations.
* For example, a plugin of type `Connector` can have a `google` implementation and a `github` implementation.
* For example, a plugin of type `connector` can have a `google` implementation and a `github` implementation.
* `PluginRender` automatically renders the plug-in types already included in `PluginType`.
* @field name: Plugin name, optionally configurable. Usually read from the `i18n` file
* @field description: Plugin description, optionally configurable. Usually read from the `i18n` file
@ -16,10 +16,10 @@ import i18next from 'i18next';
const I18N_NS = 'plugin';
export type PluginType = 'Connector';
export type PluginType = 'connector' | 'search' | 'editor';
export interface PluginInfo {
slug_name: string;
type?: PluginType;
type: PluginType;
name?: string;
description?: string;
}
@ -71,8 +71,4 @@ const getTransKeyPrefix = (info: PluginInfo) => {
return kp;
};
export default {
initI18nResource,
getTransNs,
getTransKeyPrefix,
};
export { initI18nResource, getTransNs, getTransKeyPrefix };