优化
This commit is contained in:
7
client/web/plugins/com.msgbyte.webview/README.md
Normal file
7
client/web/plugins/com.msgbyte.webview/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## com.msgbyte.webview
|
||||
|
||||
为 `Tailchat` 增加 `Webview` 能力
|
||||
|
||||
### Usage
|
||||
|
||||
在 群组设置 -> 创建面板 可以添加网页面板。
|
||||
12
client/web/plugins/com.msgbyte.webview/manifest.json
Normal file
12
client/web/plugins/com.msgbyte.webview/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"label": "Web Panel Plugin",
|
||||
"label.zh-CN": "网页面板插件",
|
||||
"name": "com.msgbyte.webview",
|
||||
"url": "/plugins/com.msgbyte.webview/index.js",
|
||||
"version": "0.0.0",
|
||||
"author": "msgbyte",
|
||||
"description": "Provides groups with the ability to create web panels",
|
||||
"description.zh-CN": "为群组提供创建网页面板的功能",
|
||||
"documentUrl": "/plugins/com.msgbyte.webview/README.md",
|
||||
"requireRestart": false
|
||||
}
|
||||
10
client/web/plugins/com.msgbyte.webview/package.json
Normal file
10
client/web/plugins/com.msgbyte.webview/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@plugins/com.msgbyte.webview",
|
||||
"main": "src/index.tsx",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"url-regex": "^5.0.0",
|
||||
"xss": "^1.0.14"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Translate } from '../translate';
|
||||
import { FilterXSS, getDefaultWhiteList } from 'xss';
|
||||
import { useWatch } from '@capital/common';
|
||||
import { GroupExtraDataPanel, NoData, TextArea } from '@capital/component';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const EditModalContent = styled.div`
|
||||
padding: 10px;
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
> textarea {
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const xss = new FilterXSS({
|
||||
css: false,
|
||||
whiteList: { ...getDefaultWhiteList(), iframe: ['src', 'style', 'class'] },
|
||||
onIgnoreTag: function (tag, html, options) {
|
||||
if (['html', 'body', 'head', 'meta', 'style', 'div'].includes(tag)) {
|
||||
// 不对其属性列表进行过滤
|
||||
return html;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function getInjectedStyle() {
|
||||
try {
|
||||
// 当前面板文本颜色
|
||||
const currentTextColor = document.defaultView.getComputedStyle(
|
||||
document.querySelector('.tc-content-background')
|
||||
).color;
|
||||
|
||||
return `<style>body { color: ${currentTextColor} }</style>`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const GroupCustomWebPanelRender: React.FC<{ html: string }> = (props) => {
|
||||
const ref = useRef<HTMLIFrameElement>(null);
|
||||
const html = props.html;
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current || !html) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = ref.current.contentWindow.document;
|
||||
doc.open();
|
||||
doc.writeln(getInjectedStyle(), xss.process(html));
|
||||
doc.close();
|
||||
}, [html]);
|
||||
|
||||
if (!html) {
|
||||
return <NoData />;
|
||||
}
|
||||
|
||||
return <iframe ref={ref} className="w-full h-full" />;
|
||||
};
|
||||
GroupCustomWebPanelRender.displayName = 'GroupCustomWebPanelRender';
|
||||
|
||||
const GroupCustomWebPanelEditor: React.FC<{
|
||||
initValue: string;
|
||||
onChange: (html: string) => void;
|
||||
}> = React.memo((props) => {
|
||||
const [html, setHtml] = useState(() => props.initValue ?? '');
|
||||
|
||||
useWatch([html], () => {
|
||||
props.onChange(html);
|
||||
});
|
||||
|
||||
return <TextArea value={html} onChange={(e) => setHtml(e.target.value)} />;
|
||||
});
|
||||
GroupCustomWebPanelEditor.displayName = 'GroupCustomWebPanelEditor';
|
||||
|
||||
const GroupCustomWebPanel: React.FC<{ panelInfo: any }> = (props) => {
|
||||
return (
|
||||
<GroupExtraDataPanel
|
||||
names={['html']}
|
||||
render={(dataMap: Record<string, string>) => {
|
||||
return (
|
||||
<GroupCustomWebPanelRender
|
||||
html={dataMap['html'] ?? props.panelInfo?.meta?.html ?? ''}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderEdit={(dataMap: Record<string, string>) => {
|
||||
return (
|
||||
<EditModalContent>
|
||||
<div>{Translate.editTip}</div>
|
||||
|
||||
<div className="main">
|
||||
<GroupCustomWebPanelEditor
|
||||
initValue={dataMap['html'] ?? props.panelInfo?.meta?.html ?? ''}
|
||||
onChange={(html) => (dataMap['html'] = html)}
|
||||
/>
|
||||
</div>
|
||||
</EditModalContent>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
GroupCustomWebPanel.displayName = 'GroupCustomWebPanel';
|
||||
|
||||
export default GroupCustomWebPanel;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Translate } from '../translate';
|
||||
import { GroupPanelContainer, WebviewKeepAlive } from '@capital/component';
|
||||
import urlRegex from 'url-regex';
|
||||
import { useGroupPanelContext } from '@capital/common';
|
||||
|
||||
const GroupWebPanelRender: React.FC<{ panelInfo: any }> = (props) => {
|
||||
const { groupId, panelId } = useGroupPanelContext();
|
||||
const panelInfo = props.panelInfo;
|
||||
|
||||
if (!panelInfo) {
|
||||
return <div>{Translate.notfound}</div>;
|
||||
}
|
||||
|
||||
let url = String(panelInfo?.meta?.url);
|
||||
if (
|
||||
!url.includes('://') &&
|
||||
urlRegex({ exact: true, strict: false }).test(url)
|
||||
) {
|
||||
// 不包含协议, 但是是个网址
|
||||
url = 'https://' + url;
|
||||
}
|
||||
const background = panelInfo?.meta?.background ?? false;
|
||||
|
||||
return (
|
||||
<GroupPanelContainer groupId={groupId} panelId={panelId}>
|
||||
<WebviewKeepAlive
|
||||
key={String(url)}
|
||||
className={`w-full h-full ${background ? 'bg-white' : ''}`}
|
||||
url={url}
|
||||
/>
|
||||
</GroupPanelContainer>
|
||||
);
|
||||
};
|
||||
GroupWebPanelRender.displayName = 'GroupWebPanelRender';
|
||||
|
||||
export default GroupWebPanelRender;
|
||||
44
client/web/plugins/com.msgbyte.webview/src/index.tsx
Normal file
44
client/web/plugins/com.msgbyte.webview/src/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Loadable, regGroupPanel } from '@capital/common';
|
||||
import { Translate } from './translate';
|
||||
|
||||
const PLUGIN_NAME = 'com.msgbyte.webview';
|
||||
|
||||
regGroupPanel({
|
||||
name: `${PLUGIN_NAME}/grouppanel`,
|
||||
label: Translate.webpanel,
|
||||
provider: PLUGIN_NAME,
|
||||
extraFormMeta: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'url',
|
||||
label: Translate.website,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'background',
|
||||
label: Translate.addBackground,
|
||||
},
|
||||
],
|
||||
render: Loadable(() => import('./group/GroupWebPanelRender')),
|
||||
menus: [
|
||||
{
|
||||
name: 'openInNewWindow',
|
||||
label: Translate.openInExtra,
|
||||
icon: 'mdi:web',
|
||||
onClick: (panelInfo) => {
|
||||
if (panelInfo.meta?.url) {
|
||||
window.open(String(panelInfo.meta?.url));
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
regGroupPanel({
|
||||
name: `${PLUGIN_NAME}/customwebpanel`,
|
||||
label: Translate.customwebpanel,
|
||||
provider: PLUGIN_NAME,
|
||||
render: Loadable(() => import('./group/GroupCustomWebPanelRender'), {
|
||||
componentName: `${PLUGIN_NAME}:GroupCustomWebPanelRender`,
|
||||
}),
|
||||
});
|
||||
33
client/web/plugins/com.msgbyte.webview/src/translate.ts
Normal file
33
client/web/plugins/com.msgbyte.webview/src/translate.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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',
|
||||
}),
|
||||
website: localTrans({
|
||||
'zh-CN': '网址',
|
||||
'en-US': 'Website',
|
||||
}),
|
||||
addBackground: localTrans({
|
||||
'zh-CN': '添加背景色',
|
||||
'en-US': 'Add Background Color',
|
||||
}),
|
||||
htmlcode: localTrans({
|
||||
'zh-CN': 'HTML代码',
|
||||
'en-US': 'HTML Code',
|
||||
}),
|
||||
openInExtra: localTrans({
|
||||
'zh-CN': '在外部打开',
|
||||
'en-US': 'Open in extra',
|
||||
}),
|
||||
editTip: localTrans({
|
||||
'zh-CN': '使用html语法编辑, 关闭窗口自动保存',
|
||||
'en-US': 'Edit with html syntax, close the window and save automatically',
|
||||
}),
|
||||
};
|
||||
9
client/web/plugins/com.msgbyte.webview/tsconfig.json
Normal file
9
client/web/plugins/com.msgbyte.webview/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"@capital/*": ["../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user