mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-01 23:38:37 +00:00
parent
91a520f13e
commit
0d94fcd070
71
frontend/src/css/umind.css
Normal file
71
frontend/src/css/umind.css
Normal file
@ -0,0 +1,71 @@
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umind-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.umind-container .umind-header {
|
||||
padding: 8px;
|
||||
border: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-container .umind-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.toolbar-container .custom-toolbar {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.toolbar-container .common-toolbar {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.umind-body .umind-editor-content,
|
||||
.umind-body .umind-editor-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.umind-editor-content .umind-editor {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar:first-child {
|
||||
border-right: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar:last-child {
|
||||
border-left: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar .detail-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.detail-panel .node-detail {
|
||||
flex: 1;
|
||||
background: #FAFAFA;
|
||||
}
|
76
frontend/src/umind/index.js
Normal file
76
frontend/src/umind/index.js
Normal file
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col } from 'antd';
|
||||
import GGEditor, { Mind } from 'gg-editor';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import UMindToolbar from './umind-toolbar/umind-toolbar';
|
||||
import UMindDetailPanel from './umind-detail-panel';
|
||||
import UMindEditorMinimap from './umind-editor/umind-editor-minimap';
|
||||
import UMindContextMenu from './umind-editor/umind-context-menu';
|
||||
import Loading from '../components/loading';
|
||||
|
||||
// import data from './mock.js';
|
||||
import 'antd/dist/antd.css';
|
||||
import './theme/iconfont.css'
|
||||
import '../css/umind.css';
|
||||
|
||||
const propTypes = {
|
||||
|
||||
};
|
||||
|
||||
const { repoID, fileName, filePath } = window.app.pageOptions;
|
||||
|
||||
class UMind extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
data: ''
|
||||
};
|
||||
this.umindContent = '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
|
||||
let url = res.data;
|
||||
seafileAPI.getFileContent(url).then(res => {
|
||||
let data = res.data;
|
||||
this.umindContent = data;
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<GGEditor className="umind-container">
|
||||
<Row type="flex" className="umind-header">
|
||||
<Col span={24} className="toolbar-container">
|
||||
<UMindToolbar />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type="flex" className="umind-body">
|
||||
<Col span={20} className="umind-editor-content">
|
||||
{this.state.isLoading && <Loading />}
|
||||
{!this.state.isLoading && (
|
||||
<Mind data={this.umindContent} className="umind-editor" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4} className="umind-editor-sidebar">
|
||||
<UMindDetailPanel />
|
||||
<UMindEditorMinimap />
|
||||
</Col>
|
||||
</Row>
|
||||
<UMindContextMenu />
|
||||
</GGEditor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UMind.propTypes = propTypes;
|
||||
|
||||
export default UMind;
|
114
frontend/src/umind/theme/iconfont.css
Normal file
114
frontend/src/umind/theme/iconfont.css
Normal file
@ -0,0 +1,114 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.eot?t=1522149591264');
|
||||
src: url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.eot?t=1522149591264#iefix') format('embedded-opentype'),
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
|
||||
url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.ttf?t=1522149591264') format('truetype'),
|
||||
url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.svg?t=1522149591264#iconfont') format('svg');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "bi-icon";
|
||||
src: url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.eot');
|
||||
src: url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.eot?#iefix') format('embedded-opentype'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.woff') format('woff'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.ttf') format('truetype'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.svg#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family:"iconfont" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bi-icon {
|
||||
font-family:"bi-icon" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-save:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.icon-undo:before {
|
||||
content: "\e8ab";
|
||||
}
|
||||
|
||||
.icon-redo:before {
|
||||
content: "\e8a9";
|
||||
}
|
||||
|
||||
.icon-cut:before {
|
||||
content: "\e8a2";
|
||||
}
|
||||
|
||||
.icon-copy-o:before {
|
||||
content: "\e8a4";
|
||||
}
|
||||
|
||||
.icon-paster-o:before {
|
||||
content: "\e8a8";
|
||||
}
|
||||
|
||||
.icon-delete-o:before {
|
||||
content: "\e8a5";
|
||||
}
|
||||
|
||||
.icon-zoom-in-o:before {
|
||||
content: "\e8ac";
|
||||
}
|
||||
|
||||
.icon-zoom-out-o:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.icon-fit:before {
|
||||
content: "\e8a6";
|
||||
}
|
||||
|
||||
.icon-actual-size-o:before {
|
||||
content: "\e8a3";
|
||||
}
|
||||
|
||||
.icon-to-back:before {
|
||||
content: "\e8b0";
|
||||
}
|
||||
|
||||
.icon-to-front:before {
|
||||
content: "\e8aa";
|
||||
}
|
||||
|
||||
.icon-select:before {
|
||||
content: "\e8a7";
|
||||
}
|
||||
|
||||
.icon-group:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.icon-ungroup:before {
|
||||
content: "\e8ad";
|
||||
}
|
||||
|
||||
.icon-insert-sibling:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.icon-insert-child:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.icon-collapse-subtree:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
|
||||
.icon-expand-subtree:before {
|
||||
content: "\e8b4";
|
||||
}
|
22
frontend/src/umind/umind-detail-panel/index.js
Normal file
22
frontend/src/umind/umind-detail-panel/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import { NodePanel, CanvasPanel, DetailPanel } from 'gg-editor';
|
||||
import NodeDetail from './node-detail';
|
||||
|
||||
class UMindDetails extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DetailPanel className="detail-panel">
|
||||
<NodePanel className="node-detail">
|
||||
<NodeDetail />
|
||||
</NodePanel>
|
||||
<CanvasPanel className="node-detail">
|
||||
<Card type="inner" title="画布属性" bordered={true} />
|
||||
</CanvasPanel>
|
||||
</DetailPanel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindDetails;
|
74
frontend/src/umind/umind-detail-panel/node-detail.js
Normal file
74
frontend/src/umind/umind-detail-panel/node-detail.js
Normal file
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import { withPropsAPI } from 'gg-editor';
|
||||
|
||||
const { Item } = Form;
|
||||
|
||||
const inlineFormItemLayout = {
|
||||
labelCol: {
|
||||
sm: { span: 6 },
|
||||
},
|
||||
wrapperCol: {
|
||||
sm: { span: 18 },
|
||||
},
|
||||
};
|
||||
|
||||
class NodeDetail extends React.Component {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { form, propsAPI } = this.props;
|
||||
const { getSelected, executeCommand, update } = propsAPI;
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = getSelected()[0];
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeCommand(() => {
|
||||
update(item, {
|
||||
...values,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { form, propsAPI } = this.props;
|
||||
const { getFieldDecorator } = form;
|
||||
const { getSelected } = propsAPI;
|
||||
|
||||
const item = getSelected()[0];
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { label } = item.getModel();
|
||||
|
||||
return (
|
||||
<Card type="inner" title="节点属性" bordered={false}>
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<Item
|
||||
label="标签"
|
||||
{...inlineFormItemLayout}
|
||||
>
|
||||
{
|
||||
getFieldDecorator('label', {
|
||||
initialValue: label,
|
||||
})(<Input onBlur={this.handleSubmit} />)
|
||||
}
|
||||
</Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form.create()(withPropsAPI(NodeDetail));
|
33
frontend/src/umind/umind-editor/context-menu.css
Normal file
33
frontend/src/umind/umind-editor/context-menu.css
Normal file
@ -0,0 +1,33 @@
|
||||
.context-menu {
|
||||
display: none;
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.context-menu .item {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 12px;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.context-menu .item:hover {
|
||||
background: #E6F7FF;
|
||||
}
|
||||
|
||||
.context-menu .item i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.context-menu .disable .item {
|
||||
cursor: auto;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.context-menu .disable .item:hover {
|
||||
background: #FFFFFF;
|
||||
}
|
61
frontend/src/umind/umind-editor/umind-context-menu.js
Normal file
61
frontend/src/umind/umind-editor/umind-context-menu.js
Normal file
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Command, NodeMenu, CanvasMenu, ContextMenu } from 'gg-editor';
|
||||
|
||||
import './context-menu.css';
|
||||
|
||||
class UMindContextMenu extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu className="context-menu">
|
||||
<NodeMenu>
|
||||
<Command name="append">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-insert-sibling" />
|
||||
<span>插入同级</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="appendChild">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-insert-child" />
|
||||
<span>插入子级</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="collapse">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-collapse-subtree" />
|
||||
<span>折叠</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="expand">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-expand-subtree" />
|
||||
<span>展开</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="delete">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-delete-o" />
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</Command>
|
||||
</NodeMenu>
|
||||
<CanvasMenu>
|
||||
<Command name="undo">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-undo" />
|
||||
<span>撤销</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="redo">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-redo" />
|
||||
<span>重做</span>
|
||||
</div>
|
||||
</Command>
|
||||
</CanvasMenu>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindContextMenu;
|
15
frontend/src/umind/umind-editor/umind-editor-minimap.js
Normal file
15
frontend/src/umind/umind-editor/umind-editor-minimap.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import { Minimap } from 'gg-editor';
|
||||
|
||||
class UMindEditorMinimap extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Card type="inner" title="缩略图" bordered={false}>
|
||||
<Minimap height={200} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindEditorMinimap;
|
38
frontend/src/umind/umind-toolbar/toolbar.css
Normal file
38
frontend/src/umind/umind-toolbar/toolbar.css
Normal file
@ -0,0 +1,38 @@
|
||||
.common-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.common-toolbar .command i {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 6px;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
text-align: center;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.common-toolbar .command i:hover {
|
||||
border: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.common-toolbar .disable i {
|
||||
cursor: auto;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.common-toolbar .disable i:hover {
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.tooltip .ant-tooltip-inner {
|
||||
font-size: 12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.common-toolbar .anticon.custom-save {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
102
frontend/src/umind/umind-toolbar/umind-toolbar.js
Normal file
102
frontend/src/umind/umind-toolbar/umind-toolbar.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Tooltip, Divider, Icon } from 'antd';
|
||||
import { Toolbar, Command } from 'gg-editor';
|
||||
import withGGEditorContext from 'gg-editor/es/common/context/GGEditorContext/withGGEditorContext';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
|
||||
import './toolbar.css';
|
||||
|
||||
const { repoID, filePath, fileName } = window.app.pageOptions;
|
||||
class UMindToolbar extends React.Component {
|
||||
|
||||
onSaveClick = (e) => {
|
||||
e.preventDefault();
|
||||
let { editor } = this.props;
|
||||
let page = editor.getCurrentPage();
|
||||
let { data, defaultData } = page._cfg;
|
||||
let dirPath = Utils.getDirName(filePath);
|
||||
seafileAPI.getUpdateLink(repoID, dirPath).then(res => {
|
||||
let updateLink = res.data;
|
||||
// need optimized
|
||||
let updateData = data ? JSON.stringify(data) : JSON.stringify(defaultData);
|
||||
seafileAPI.updateFile(updateLink, filePath, fileName, updateData).then(res => {
|
||||
toaster.success(gettext('File saved.'));
|
||||
}).catch(() => {
|
||||
toaster.success(gettext('File save failed.'));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="custom-toolbar" onClick={this.onSaveClick}>
|
||||
<Tooltip title="保存" placement="bottom" overlayClassName="tooltip">
|
||||
<Icon type="save" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Toolbar className="common-toolbar">
|
||||
<Divider type="vertical" />
|
||||
<Command name="undo">
|
||||
<Tooltip title="撤销" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-undo" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="redo">
|
||||
<Tooltip title="重做" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-redo" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="zoomIn">
|
||||
<Tooltip title="放大" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-zoom-in-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="zoomOut">
|
||||
<Tooltip title="缩小" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-zoom-out-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="autoZoom">
|
||||
<Tooltip title="适应画布" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-fit" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="resetZoom">
|
||||
<Tooltip title="实际尺寸" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-actual-size-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="append">
|
||||
<Tooltip title="插入同级" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-insert-sibling" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="appendChild">
|
||||
<Tooltip title="插入子级" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-insert-child" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="collapse">
|
||||
<Tooltip title="折叠" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-collapse-subtree" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="expand">
|
||||
<Tooltip title="展开" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-expand-subtree" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
</Toolbar>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withGGEditorContext(UMindToolbar);
|
@ -1,90 +1,17 @@
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import { Utils } from './utils/utils';
|
||||
import { gettext, lang } from './utils/constants';
|
||||
import toaster from './components/toast';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import UMind from './umind';
|
||||
|
||||
const { UMind, React, ReactDOM } = window;
|
||||
const { repoID, filePath, fileName, username } = window.app.pageOptions;
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
roots: [{
|
||||
label: '中心主题',
|
||||
children: [{
|
||||
label: '分支主题 1',
|
||||
}, {
|
||||
label: '分支主题 2',
|
||||
}, {
|
||||
label: '分支主题 3',
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
class ViewFileUmind extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: DEFAULT_DATA,
|
||||
isDataLoading: true
|
||||
};
|
||||
this.locale = 'zh-CN';
|
||||
this.config = {
|
||||
file: {
|
||||
id: repoID + filePath,
|
||||
},
|
||||
user: {
|
||||
id: username,
|
||||
},
|
||||
socket: {
|
||||
url: 'https://umind.alibaba-inc.com',
|
||||
events: {
|
||||
user: 'user',
|
||||
operation: 'operation',
|
||||
},
|
||||
onUserListChange: () => {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
|
||||
let url = res.data;
|
||||
seafileAPI.getFileContent(url).then(res => {
|
||||
if (res.data) {
|
||||
this.setState({
|
||||
isDataLoading: false,
|
||||
data: res.data
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
isDataLoading: false,
|
||||
data: DEFAULT_DATA
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
// toaster.success(gettext('file loading error'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleSave = (data) => {
|
||||
let dirPath = Utils.getDirName(filePath);
|
||||
seafileAPI.getUpdateLink(repoID, dirPath).then(res => {
|
||||
let updateLink = res.data;
|
||||
let updateData = JSON.stringify(data);
|
||||
seafileAPI.updateFile(updateLink, filePath, fileName, updateData).then(res => {
|
||||
// toaster.success(gettext('File saved.'));
|
||||
}).catch(() => {
|
||||
// toaster.success(gettext('File saved failed.'));
|
||||
});
|
||||
});
|
||||
}
|
||||
class ViewFileUMind extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UMind.default locale={this.locale} config={this.config} data={this.state.data} save={this.handleSave} />
|
||||
<UMind />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<ViewFileUmind />, document.getElementById('root'));
|
||||
ReactDOM.render(
|
||||
<ViewFileUMind />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
@ -1,111 +0,0 @@
|
||||
{% load seahub_tags i18n staticfiles %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="keywords" content="{% trans "File Collaboration Team Organization" %}" />
|
||||
{% block viewport %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
{% endblock %}
|
||||
<title>{% block sub_title %}{% endblock %}{{ site_title }}</title>
|
||||
<link rel="stylesheet" href="https://g.alicdn.com/code/lib/antd/3.10.7/antd.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="https://g.alicdn.com/code/lib/react/16.6.1/umd/react.production.min.js"></script>
|
||||
<script src="https://g.alicdn.com/code/lib/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://g.alicdn.com/code/lib/moment.js/2.22.2/moment.min.js"></script>
|
||||
<script src="https://g.alicdn.com/code/lib/antd/3.10.7/antd.min.js"></script>
|
||||
<script src="https://g.alicdn.com/code/lib/socket.io/2.1.1/socket.io.js"></script>
|
||||
<script src="https://g.alicdn.com/UMind/UMind/1.0.0/bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
// overwrite the one in base_for_react.html
|
||||
window.app = {
|
||||
config: {
|
||||
mediaUrl: '{{ MEDIA_URL }}',
|
||||
logoPath: '{{ logo_path }}',
|
||||
logoWidth: '{{ logo_width }}',
|
||||
logoHeight: '{{ logo_height }}',
|
||||
siteTitle: '{{ site_title }}',
|
||||
siteName: '{{ site_name }}',
|
||||
siteRoot: '{{ SITE_ROOT }}',
|
||||
loginUrl: '{{ LOGIN_URL }}',
|
||||
isPro: '{{ is_pro }}',
|
||||
lang: '{{ LANGUAGE_CODE }}',
|
||||
fileServerRoot: '{{ FILE_SERVER_ROOT }}',
|
||||
serviceURL: '{{ service_url }}',
|
||||
seafileVersion: '{{ seafile_version }}',
|
||||
},
|
||||
pageOptions: {
|
||||
seafileCollabServer: '{{seafile_collab_server}}',
|
||||
name: "{{request.user.username|email2nickname|escapejs}}",
|
||||
contactEmail: "{{request.user.username|email2contact_email|escapejs}}",
|
||||
username: "{{request.user.username|escapejs}}",
|
||||
canAddRepo: {% if user.permissions.can_add_repo %} true {% else %} false {% endif %},
|
||||
canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %},
|
||||
canGenerateUploadLink: {% if user.permissions.can_generate_upload_link %} true {% else %} false {% endif %},
|
||||
canViewOrg:'{{ user.permissions.can_view_org }}',
|
||||
fileAuditEnabled: '{{ file_audit_enabled }}',
|
||||
enableFileComment: '{{ enable_file_comment }}',
|
||||
folderPermEnabled: '{{ folder_perm_enabled }}',
|
||||
enableResetEncryptedRepoPassword: '{{ enable_reset_encrypted_repo_password }}',
|
||||
isEmailConfigured: '{{ is_email_configured }}',
|
||||
enableUploadFolder: '{{ enable_upload_folder }}',
|
||||
enableResumableFileUpload: '{{ enable_resumable_fileupload }}',
|
||||
// storage backends
|
||||
storages: (function () {
|
||||
// for 'create repo' & 'storage backend' column in 'my libs'
|
||||
var storages = [];
|
||||
{% for storage in storages %}
|
||||
storages.push({
|
||||
'id': '{{storage.storage_id|escapejs}}',
|
||||
'name': '{{storage.storage_name|escapejs}}'
|
||||
});
|
||||
{% endfor %}
|
||||
return storages;
|
||||
})(),
|
||||
enableRepoSnapshotLabel: {% if enable_repo_snapshot_label %} true {% else %} false {% endif %},
|
||||
shareLinkExpireDaysMin: "{{ share_link_expire_days_min }}",
|
||||
shareLinkExpireDaysMax: "{{ share_link_expire_days_max }}",
|
||||
maxFileName: "{{ max_file_name }}",
|
||||
enableWiki: {% if user.permissions.can_use_wiki %} true {% else %} false {% endif %},
|
||||
enableEncryptedLibrary: '{{ enable_encrypted_library }}',
|
||||
enableRepoHistorySetting: '{{ enable_repo_history_setting }}',
|
||||
isSystemStaff: {% if request.user.is_staff %} true {% else %} false {% endif %},
|
||||
thumbnailSizeForOriginal: {{ thumbnail_size_for_original }},
|
||||
repoPasswordMinLength: {{repo_password_min_length}},
|
||||
}
|
||||
};
|
||||
window.app.pageOptions = {
|
||||
username: '{{ user.username|escapejs }}',
|
||||
userNickName: '{{request.user.username|email2nickname|escapejs}}',
|
||||
|
||||
// for all types of files
|
||||
fileName: '{{ filename|escapejs }}',
|
||||
isStarred: {% if is_starred %}true{% else %}false{% endif %},
|
||||
isLocked: {% if file_locked %}true{% else %}false{% endif %},
|
||||
latestContributor: '{{ latest_contributor|escapejs }}',
|
||||
latestContributorName: '{{ latest_contributor|email2nickname|escapejs }}',
|
||||
lastModificationTime: '{{ last_modified }}',
|
||||
repoID: '{{ repo.id }}',
|
||||
repoName: '{{ repo.name|escapejs }}',
|
||||
filePath: '{{ path|escapejs }}',
|
||||
filePerm: '{{ file_perm }}',
|
||||
parentDir: '{{ parent_dir|escapejs }}',
|
||||
err: '{{ err }}',
|
||||
lockedByMe: {% if locked_by_me %}true{% else %}false{% endif %},
|
||||
canLockUnlockFile: {% if can_lock_unlock_file %}true{% else %}false{% endif %},
|
||||
canEditFile: {% if can_edit_file %}true{% else %}false{% endif %}, // only for some file types
|
||||
canDownloadFile: {% if can_download_file %}true{% else %}false{% endif %},
|
||||
enableComment: {% if enable_file_comment %}true{% else %}false{% endif %},
|
||||
enableWatermark: {% if enable_watermark %}true{% else %}false{% endif %},
|
||||
};
|
||||
</script>
|
||||
{% block render_bundle %}
|
||||
{% render_bundle 'viewFileUMind' 'js'%}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
10
seahub/templates/view_file_umind.html
Normal file
10
seahub/templates/view_file_umind.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% extends 'file_view_react.html' %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
{% block extra_style %}
|
||||
{% render_bundle 'viewFileUMind' 'css' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block render_bundle %}
|
||||
{% render_bundle 'viewFileUMind' 'js'%}
|
||||
{% endblock %}
|
@ -728,7 +728,7 @@ def view_lib_file(request, repo_id, path):
|
||||
return render(request, template, return_dict)
|
||||
|
||||
elif filetype == UMIND:
|
||||
return render(request, 'umind_file_view_react.html', return_dict)
|
||||
return render(request, 'view_file_umind.html', return_dict)
|
||||
|
||||
elif filetype == IMAGE:
|
||||
template = '%s_file_view_react.html' % filetype.lower()
|
||||
|
Loading…
Reference in New Issue
Block a user