195 lines
9.1 KiB
JavaScript
195 lines
9.1 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
||
import { Button, Space, Input, } from "antd";
|
||
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
|
||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||
import { SlateNode } from "@wangeditor/editor";
|
||
import { generateNewId } from "oak-domain/lib/utils/uuid";
|
||
import classNames from "classnames";
|
||
import Prompt from "../../../components/common/prompt";
|
||
import Style from "./web.module.less";
|
||
import { TocView } from '../toc/tocView';
|
||
import { EyeOutlined, } from "@ant-design/icons";
|
||
// 工具栏配置
|
||
const toolbarConfig = {
|
||
excludeKeys: ["fullScreen"],
|
||
}; // TS 语法
|
||
// 自定义校验图片
|
||
function customCheckImageFn(src, alt, url) {
|
||
// TS 语法
|
||
if (!src) {
|
||
return;
|
||
}
|
||
if (src.indexOf("http") !== 0) {
|
||
return "图片网址必须以 http/https 开头";
|
||
}
|
||
return true;
|
||
// 返回值有三种选择:
|
||
// 1. 返回 true ,说明检查通过,编辑器将正常插入图片
|
||
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
|
||
// 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
|
||
}
|
||
export default function Render(props) {
|
||
const { methods, data } = props;
|
||
const { t, setMessage, setEditor, check, uploadFile, update, setHtml, gotoPreview, } = methods;
|
||
const { oakId, oakFullpath, id, name, content, editor, origin, tocPosition = 'none', highlightBgColor, scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, activeColor, html, oakLoading, oakExecuting, } = data;
|
||
const [articleId, setArticleId] = useState('');
|
||
const [toc, setToc] = useState([]);
|
||
const [showToc, setShowToc] = useState(false);
|
||
const containerId = scrollId || 'article-upsert-editorContainer';
|
||
useEffect(() => {
|
||
if (id) {
|
||
setArticleId(id);
|
||
}
|
||
}, [id]);
|
||
useEffect(() => {
|
||
if (tocPosition !== 'none') {
|
||
setShowToc(true);
|
||
}
|
||
}, [tocPosition]);
|
||
return (<div className={Style.container}>
|
||
<Prompt when={!id || data.oakDirty} message={t('exitConfirm')}/>
|
||
<div className={Style.top}>
|
||
<div className={Style.header}>
|
||
<div className={Style.title}>{name}</div>
|
||
<Space>
|
||
<Button onClick={() => {
|
||
gotoPreview(html, data.name);
|
||
}} icon={<EyeOutlined />}>
|
||
{t('preview')}
|
||
</Button>
|
||
<Button disabled={oakLoading || oakExecuting || !(name && name.length > 0 && html && html.length > 0 && html !== '<p><br></p>')} type="primary" onClick={() => {
|
||
update({
|
||
content: html,
|
||
});
|
||
check();
|
||
}}>
|
||
{t('save')}
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
<div className={Style.toolbar}>
|
||
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default"/>
|
||
</div>
|
||
</div>
|
||
<div className={Style.contentContainer} id='article-upsert-editorContainer'>
|
||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc}
|
||
// highlightBgColor={highlightBgColor}
|
||
activeColor={activeColor} closed={tocClosed} scrollId={containerId} tocWidth={tocWidth} tocHeight={'calc(100% - 16px)'}/>) : null}
|
||
|
||
<div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
|
||
<div className={classNames(Style.editorContainer, {
|
||
[Style.editorExternalContainer]: !!scrollId,
|
||
})}>
|
||
<div className={Style.titleContainer}>
|
||
<Input onChange={(e) => {
|
||
if (e.target.value.trim() && e.target.value.trim() !== '') {
|
||
update({ name: e.target.value.trim() });
|
||
}
|
||
else {
|
||
update({ name: null });
|
||
}
|
||
}} value={data.name ?? ''} placeholder={t('placeholder.name')} size="large" maxLength={32} showCount className={Style.titleInput}/>
|
||
</div>
|
||
<div>
|
||
{!!articleId && <Editor defaultConfig={{
|
||
autoFocus: true,
|
||
placeholder: t('placeholder.content'),
|
||
MENU_CONF: {
|
||
checkImage: customCheckImageFn,
|
||
uploadImage: {
|
||
// 自定义上传
|
||
async customUpload(file, insertFn) {
|
||
// TS 语法
|
||
// file 即选中的文件
|
||
const { name, size, type } = file;
|
||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||
const filename = name.substring(0, name.lastIndexOf("."));
|
||
const extraFile = {
|
||
entity: "article",
|
||
entityId: oakId || articleId,
|
||
origin: origin,
|
||
type: "image",
|
||
tag1: "source",
|
||
objectId: generateNewId(),
|
||
filename,
|
||
size,
|
||
extension,
|
||
bucket: "",
|
||
id: generateNewId(),
|
||
fileType: type,
|
||
};
|
||
try {
|
||
// 自己实现上传,并得到图片 url alt href
|
||
const url = await uploadFile(extraFile, file);
|
||
// 最后插入图片
|
||
insertFn(url, extraFile.filename);
|
||
}
|
||
catch (err) {
|
||
setMessage({
|
||
type: "error",
|
||
content: err.message,
|
||
});
|
||
}
|
||
},
|
||
},
|
||
uploadVideo: {
|
||
// 自定义上传
|
||
async customUpload(file, insertFn) {
|
||
// TS 语法
|
||
// file 即选中的文件
|
||
const { name, size, type } = file;
|
||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||
const filename = name.substring(0, name.lastIndexOf("."));
|
||
const extraFile = {
|
||
entity: "article",
|
||
entityId: oakId || articleId,
|
||
origin: origin,
|
||
type: "video",
|
||
tag1: "source",
|
||
objectId: generateNewId(),
|
||
filename,
|
||
size,
|
||
extension,
|
||
bucket: "",
|
||
id: generateNewId(),
|
||
fileType: type,
|
||
};
|
||
try {
|
||
// 自己实现上传,并得到图片 url alt href
|
||
const url = await uploadFile(extraFile, file);
|
||
// 最后插入图片
|
||
insertFn(url, url + "?vframe/jpg/offset/0");
|
||
}
|
||
catch (err) {
|
||
setMessage({
|
||
type: "error",
|
||
content: err.message,
|
||
});
|
||
}
|
||
},
|
||
},
|
||
},
|
||
}} onCreated={setEditor} onChange={(editor) => {
|
||
setHtml(editor.getHtml());
|
||
const headers = editor.getElemsByTypePrefix("header");
|
||
const tocItems = headers.map((header) => {
|
||
const text = SlateNode.string(header);
|
||
const { id, type } = header;
|
||
return {
|
||
text,
|
||
level: parseInt(type.substring(6)),
|
||
id,
|
||
};
|
||
});
|
||
setToc([...tocItems]);
|
||
}} style={{
|
||
minHeight: '100%',
|
||
}} mode="default"/>}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||
</div>
|
||
</div>);
|
||
}
|