fork from bc4552c5a8
This commit is contained in:
67
exts/yapi-plugin-wiki/wikiPage/Editor.js
Normal file
67
exts/yapi-plugin-wiki/wikiPage/Editor.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Checkbox } from 'antd';
|
||||
import Editor from 'common/tui-editor/dist/tui-editor-Editor-all.min.js';
|
||||
require('common/tui-editor/dist/tui-editor.min.css'); // editor ui
|
||||
require('common/tui-editor/dist/tui-editor-contents.min.css'); // editor content
|
||||
class WikiEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
isConflict: PropTypes.bool,
|
||||
onUpload: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
notice: PropTypes.bool,
|
||||
onEmailNotice: PropTypes.func,
|
||||
desc: PropTypes.string
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.editor = new Editor({
|
||||
el: document.querySelector('#desc'),
|
||||
initialEditType: 'wysiwyg',
|
||||
height: '500px',
|
||||
initialValue: this.props.desc
|
||||
});
|
||||
}
|
||||
|
||||
onUpload = () => {
|
||||
let desc = this.editor.getHtml();
|
||||
let markdown = this.editor.getMarkdown();
|
||||
this.props.onUpload(desc, markdown);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isConflict, onCancel, notice, onEmailNotice } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
id="desc"
|
||||
className="wiki-editor"
|
||||
style={{ display: !isConflict ? 'block' : 'none' }}
|
||||
/>
|
||||
<div className="wiki-title wiki-up">
|
||||
<Button
|
||||
icon="upload"
|
||||
type="primary"
|
||||
className="upload-btn"
|
||||
disabled={isConflict}
|
||||
onClick={this.onUpload}
|
||||
>
|
||||
更新
|
||||
</Button>
|
||||
<Button onClick={onCancel} className="upload-btn">
|
||||
取消
|
||||
</Button>
|
||||
<Checkbox checked={notice} onChange={onEmailNotice}>
|
||||
通知相关人员
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WikiEditor;
|
||||
41
exts/yapi-plugin-wiki/wikiPage/View.js
Normal file
41
exts/yapi-plugin-wiki/wikiPage/View.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const WikiView = props => {
|
||||
const { editorEable, onEditor, uid, username, editorTime, desc } = props;
|
||||
return (
|
||||
<div className="wiki-view-content">
|
||||
<div className="wiki-title">
|
||||
<Button icon="edit" onClick={onEditor} disabled={!editorEable}>
|
||||
编辑
|
||||
</Button>
|
||||
{username && (
|
||||
<div className="wiki-user">
|
||||
由{' '}
|
||||
<Link className="user-name" to={`/user/profile/${uid || 11}`}>
|
||||
{username}
|
||||
</Link>{' '}
|
||||
修改于 {editorTime}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="tui-editor-contents"
|
||||
dangerouslySetInnerHTML={{ __html: desc }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WikiView.propTypes = {
|
||||
editorEable: PropTypes.bool,
|
||||
onEditor: PropTypes.func,
|
||||
uid: PropTypes.number,
|
||||
username: PropTypes.string,
|
||||
editorTime: PropTypes.string,
|
||||
desc: PropTypes.string
|
||||
};
|
||||
|
||||
export default WikiView;
|
||||
255
exts/yapi-plugin-wiki/wikiPage/index.js
Normal file
255
exts/yapi-plugin-wiki/wikiPage/index.js
Normal file
@@ -0,0 +1,255 @@
|
||||
import React, { Component } from 'react';
|
||||
import { message } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import axios from 'axios';
|
||||
import PropTypes from 'prop-types';
|
||||
import './index.scss';
|
||||
import { timeago } from '../../../common/utils';
|
||||
import { Link } from 'react-router-dom';
|
||||
import WikiView from './View.js';
|
||||
import WikiEditor from './Editor.js';
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
return {
|
||||
projectMsg: state.project.currProject
|
||||
};
|
||||
},
|
||||
{}
|
||||
)
|
||||
class WikiPage extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isEditor: false,
|
||||
isUpload: true,
|
||||
desc: '',
|
||||
markdown: '',
|
||||
notice: props.projectMsg.switch_notice,
|
||||
status: 'INIT',
|
||||
editUid: '',
|
||||
editName: '',
|
||||
curdata: null
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
match: PropTypes.object,
|
||||
projectMsg: PropTypes.object
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const currProjectId = this.props.match.params.id;
|
||||
await this.handleData({ project_id: currProjectId });
|
||||
this.handleConflict();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// willUnmount
|
||||
try {
|
||||
if (this.state.status === 'CLOSE') {
|
||||
this.WebSocket.send('end');
|
||||
this.WebSocket.close();
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 结束编辑websocket
|
||||
endWebSocket = () => {
|
||||
try {
|
||||
if (this.state.status === 'CLOSE') {
|
||||
const sendEnd = () => {
|
||||
this.WebSocket.send('end');
|
||||
};
|
||||
this.handleWebsocketAccidentClose(sendEnd);
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理多人编辑冲突问题
|
||||
handleConflict = () => {
|
||||
// console.log(location)
|
||||
let domain = location.hostname + (location.port !== '' ? ':' + location.port : '');
|
||||
let s;
|
||||
//因后端 node 仅支持 ws, 暂不支持 wss
|
||||
let wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
s = new WebSocket(
|
||||
wsProtocol +
|
||||
'://' +
|
||||
domain +
|
||||
'/api/ws_plugin/wiki_desc/solve_conflict?id=' +
|
||||
this.props.match.params.id
|
||||
);
|
||||
s.onopen = () => {
|
||||
this.WebSocket = s;
|
||||
s.send('start');
|
||||
};
|
||||
|
||||
s.onmessage = e => {
|
||||
let result = JSON.parse(e.data);
|
||||
if (result.errno === 0) {
|
||||
// 更新
|
||||
if (result.data) {
|
||||
this.setState({
|
||||
// curdata: result.data,
|
||||
desc: result.data.desc,
|
||||
username: result.data.username,
|
||||
uid: result.data.uid,
|
||||
editorTime: timeago(result.data.up_time)
|
||||
});
|
||||
}
|
||||
// 新建
|
||||
this.setState({
|
||||
isEditor: !this.state.isEditor,
|
||||
status: 'CLOSE'
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
editUid: result.data.uid,
|
||||
editName: result.data.username,
|
||||
status: 'EDITOR'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
s.onerror = () => {
|
||||
this.setState({
|
||||
status: 'CLOSE'
|
||||
});
|
||||
console.warn('websocket 连接失败,将导致多人编辑同一个接口冲突。');
|
||||
};
|
||||
};
|
||||
|
||||
// 点击编辑按钮 发送 websocket 获取数据
|
||||
onEditor = () => {
|
||||
// this.WebSocket.send('editor');
|
||||
const sendEditor = () => {
|
||||
this.WebSocket.send('editor');
|
||||
};
|
||||
this.handleWebsocketAccidentClose(sendEditor, status => {
|
||||
// 如果websocket 启动不成功用户依旧可以对wiki 进行编辑
|
||||
if (!status) {
|
||||
this.setState({
|
||||
isEditor: !this.state.isEditor
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 处理websocket 意外断开问题
|
||||
handleWebsocketAccidentClose = (fn, callback) => {
|
||||
// websocket 是否启动
|
||||
if (this.WebSocket) {
|
||||
// websocket 断开
|
||||
if (this.WebSocket.readyState !== 1) {
|
||||
message.error('websocket 链接失败,请重新刷新页面');
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取数据
|
||||
handleData = async params => {
|
||||
let result = await axios.get('/api/plugin/wiki_desc/get', { params });
|
||||
if (result.data.errcode === 0) {
|
||||
const data = result.data.data;
|
||||
if (data) {
|
||||
this.setState({
|
||||
desc: data.desc,
|
||||
markdown: data.markdown,
|
||||
username: data.username,
|
||||
uid: data.uid,
|
||||
editorTime: timeago(data.up_time)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
message.error(`请求数据失败: ${result.data.errmsg}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 数据上传
|
||||
onUpload = async (desc, markdown) => {
|
||||
const currProjectId = this.props.match.params.id;
|
||||
let option = {
|
||||
project_id: currProjectId,
|
||||
desc,
|
||||
markdown,
|
||||
email_notice: this.state.notice
|
||||
};
|
||||
let result = await axios.post('/api/plugin/wiki_desc/up', option);
|
||||
if (result.data.errcode === 0) {
|
||||
await this.handleData({ project_id: currProjectId });
|
||||
this.setState({ isEditor: false });
|
||||
} else {
|
||||
message.error(`更新失败: ${result.data.errmsg}`);
|
||||
}
|
||||
this.endWebSocket();
|
||||
// this.WebSocket.send('end');
|
||||
};
|
||||
// 取消编辑
|
||||
onCancel = () => {
|
||||
this.setState({ isEditor: false });
|
||||
this.endWebSocket();
|
||||
};
|
||||
|
||||
// 邮件通知
|
||||
onEmailNotice = e => {
|
||||
this.setState({
|
||||
notice: e.target.checked
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isEditor, username, editorTime, notice, uid, status, editUid, editName } = this.state;
|
||||
const editorEable =
|
||||
this.props.projectMsg.role === 'admin' ||
|
||||
this.props.projectMsg.role === 'owner' ||
|
||||
this.props.projectMsg.role === 'dev';
|
||||
const isConflict = status === 'EDITOR';
|
||||
|
||||
return (
|
||||
<div className="g-row">
|
||||
<div className="m-panel wiki-content">
|
||||
<div className="wiki-content">
|
||||
{isConflict && (
|
||||
<div className="wiki-conflict">
|
||||
<Link to={`/user/profile/${editUid || uid}`}>
|
||||
<b>{editName || username}</b>
|
||||
</Link>
|
||||
<span>正在编辑该wiki,请稍后再试...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isEditor ? (
|
||||
<WikiView
|
||||
editorEable={editorEable}
|
||||
onEditor={this.onEditor}
|
||||
uid={uid}
|
||||
username={username}
|
||||
editorTime={editorTime}
|
||||
desc={this.state.desc}
|
||||
/>
|
||||
) : (
|
||||
<WikiEditor
|
||||
isConflict={isConflict}
|
||||
onUpload={this.onUpload}
|
||||
onCancel={this.onCancel}
|
||||
notice={notice}
|
||||
onEmailNotice={this.onEmailNotice}
|
||||
desc={this.state.desc}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WikiPage;
|
||||
27
exts/yapi-plugin-wiki/wikiPage/index.scss
Normal file
27
exts/yapi-plugin-wiki/wikiPage/index.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
.wiki-content {
|
||||
.wiki-user {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.wiki-editor {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.wiki-conflict {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.wiki-up {
|
||||
text-align: right;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.wiki-title {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user