feat: 增加自定义网页面板

release/desktop
moonrailgun 3 years ago
parent 3816daf174
commit ebce488a0e

@ -379,7 +379,12 @@ importers:
specifiers: {}
web/plugins/com.msgbyte.webview:
specifiers: {}
specifiers:
js-base64: ^3.7.2
xss: ^1.0.11
dependencies:
js-base64: 3.7.2
xss: 1.0.11
packages:
@ -3897,7 +3902,6 @@ packages:
/commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
/commander/6.2.1:
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/commander/download/commander-6.2.1.tgz}
@ -4134,6 +4138,10 @@ packages:
engines: {node: '>= 6'}
dev: true
/cssfilter/0.0.10:
resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}
dev: false
/cssnano-preset-default/4.0.8:
resolution: {integrity: sha1-kgYisfwelaNOiDggPxOXpQTy0/8=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/cssnano-preset-default/download/cssnano-preset-default-4.0.8.tgz}
engines: {node: '>=6.9.0'}
@ -6102,7 +6110,7 @@ packages:
engines: {node: '>=10'}
/js-base64/3.7.2:
resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==, registry: https://registry.npm.taobao.org/, tarball: js-base64/download/js-base64-3.7.2.tgz}
resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==}
dev: false
/js-sha3/0.8.0:
@ -10166,6 +10174,15 @@ packages:
engines: {node: '>=0.4.0'}
dev: false
/xss/1.0.11:
resolution: {integrity: sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==}
engines: {node: '>= 0.10.0'}
hasBin: true
dependencies:
commander: 2.20.3
cssfilter: 0.0.10
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}

@ -3,5 +3,8 @@
"main": "src/index.tsx",
"version": "0.0.0",
"private": true,
"dependencies": {}
"dependencies": {
"js-base64": "^3.7.2",
"xss": "^1.0.11"
}
}

@ -0,0 +1,43 @@
import React, { useMemo } from 'react';
import { encode } from 'js-base64';
import { isValidStr } from '@capital/common';
import { Translate } from '../translate';
import { FilterXSS, getDefaultWhiteList } from 'xss';
import _mapValues from 'lodash/mapValues';
const xss = new FilterXSS({
// 允许style存在
whiteList: {
..._mapValues(getDefaultWhiteList(), (v) => [...v, 'style']),
style: [],
},
css: false,
});
const GroupCustomWebPanelRender: React.FC<{ panelInfo: any }> = (props) => {
const panelInfo = props.panelInfo;
if (!panelInfo) {
return <div>{Translate.notfound}</div>;
}
const html = panelInfo?.meta?.html;
const src = useMemo(() => {
if (isValidStr(html)) {
try {
return `data:text/html;charset=utf8;base64,${encode(
xss.process(html)
)}`;
} catch (e) {
return undefined;
}
} else {
return undefined;
}
}, [html]);
return <iframe className="w-full h-full" src={src} />;
};
GroupCustomWebPanelRender.displayName = 'GroupCustomWebPanelRender';
export default GroupCustomWebPanelRender;

@ -0,0 +1,19 @@
import React from 'react';
import { Translate } from '../translate';
const GroupWebPanelRender: React.FC<{ panelInfo: any }> = (props) => {
const panelInfo = props.panelInfo;
if (!panelInfo) {
return <div>{Translate.notfound}</div>;
}
const url = panelInfo?.meta?.url;
return (
<iframe key={String(url)} className="w-full h-full bg-white" src={url} />
);
};
GroupWebPanelRender.displayName = 'GroupWebPanelRender';
export default GroupWebPanelRender;

@ -1,27 +1,24 @@
import React from 'react';
import { regGroupPanel } from '@capital/common';
import { Loadable, regGroupPanel } from '@capital/common';
import { Translate } from './translate';
const PLUGIN_NAME = 'com.msgbyte.webview';
const GroupWebPanelRender: React.FC<{ panelInfo: any }> = (props) => {
const panelInfo = props.panelInfo;
if (!panelInfo) {
return <div>{Translate.notfound}</div>;
}
const url = panelInfo?.meta?.url;
return (
<iframe key={String(url)} className="w-full h-full bg-white" src={url} />
);
};
regGroupPanel({
name: `${PLUGIN_NAME}/grouppanel`,
label: Translate.webpanel,
provider: PLUGIN_NAME,
extraFormMeta: [{ type: 'text', name: 'url', label: Translate.website }],
render: GroupWebPanelRender,
render: Loadable(() => import('./group/GroupWebPanelRender')),
});
regGroupPanel({
name: `${PLUGIN_NAME}/customwebpanel`,
label: Translate.customwebpanel,
provider: PLUGIN_NAME,
extraFormMeta: [
{ type: 'textarea', name: 'html', label: Translate.htmlcode },
],
render: Loadable(() => import('./group/GroupCustomWebPanelRender'), {
componentName: 'com.msgbyte.webview:GroupCustomWebPanelRender',
}),
});

@ -2,6 +2,10 @@ import { localTrans } from '@capital/common';
export const Translate = {
webpanel: localTrans({ 'zh-CN': '网页面板', 'en-US': 'Webview Panel' }),
customwebpanel: localTrans({
'zh-CN': '自定义网页面板',
'en-US': 'Custom Webview Panel',
}),
notfound: localTrans({
'zh-CN': '加载失败, 面板信息不存在',
'en-US': 'Loading failed, panel info does not exist',
@ -10,4 +14,8 @@ export const Translate = {
'zh-CN': '网址',
'en-US': 'Website',
}),
htmlcode: localTrans({
'zh-CN': 'HTML代码',
'en-US': 'HTML Code',
}),
};

@ -6,6 +6,27 @@ import loadable, {
} from '@loadable/component';
import pMinDelay from 'p-min-delay';
import { LoadingSpinner } from './LoadingSpinner';
import { isValidStr } from 'tailchat-shared';
function promiseUsage<T>(p: Promise<T>, name: string): Promise<T> {
const start = new Date().valueOf();
return p.then((r) => {
const end = new Date().valueOf();
console.debug(`[Loadable] load ${name} usage: ${end - start}ms`);
return r;
});
}
interface LoadableOptions<P> extends OptionsWithoutResolver<P> {
/**
* ,
*
*/
componentName?: string;
}
/**
* : Loadable(() => import('xxxxxx'))
@ -13,10 +34,22 @@ import { LoadingSpinner } from './LoadingSpinner';
*/
export function Loadable<Props>(
loadFn: (props: Props) => Promise<DefaultComponent<Props>>,
options?: OptionsWithoutResolver<Props>
options?: LoadableOptions<Props>
): LoadableComponent<Props> {
return loadable((props) => pMinDelay(loadFn(props), 200), {
fallback: <LoadingSpinner />,
...options,
});
return loadable(
(props) => {
let p = loadFn(props);
if (isValidStr(options?.componentName)) {
// 增加promise加载用时统计
p = promiseUsage(p, String(options?.componentName));
}
return pMinDelay(p, 200);
},
{
fallback: <LoadingSpinner />,
...options,
}
);
}

Loading…
Cancel
Save