mirror of https://github.com/msgbyte/tailchat
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
3.0 KiB
TypeScript
119 lines
3.0 KiB
TypeScript
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;
|