mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
[Wiki] Support show folder (#2334)
This commit is contained in:
committed by
Daniel Pan
parent
b8662376a1
commit
681a4235a4
1
.gitignore
vendored
1
.gitignore
vendored
@@ -49,5 +49,6 @@ tags
|
|||||||
frontend/config/webpack.config.dev.js
|
frontend/config/webpack.config.dev.js
|
||||||
frontend/webpack-stats.dev.json
|
frontend/webpack-stats.dev.json
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
|
frontend/package-lock.json
|
||||||
|
|
||||||
/.idea
|
/.idea
|
@@ -41,7 +41,7 @@
|
|||||||
"remark-parse": "^5.0.0",
|
"remark-parse": "^5.0.0",
|
||||||
"remark-rehype": "^3.0.0",
|
"remark-rehype": "^3.0.0",
|
||||||
"remark-slug": "^5.0.0",
|
"remark-slug": "^5.0.0",
|
||||||
"seafile-js": "^0.2.12",
|
"seafile-js": "^0.2.13",
|
||||||
"seafile-ui": "^0.1.10",
|
"seafile-ui": "^0.1.10",
|
||||||
"sw-precache-webpack-plugin": "0.11.4",
|
"sw-precache-webpack-plugin": "0.11.4",
|
||||||
"unified": "^6.1.6",
|
"unified": "^6.1.6",
|
||||||
|
@@ -1,327 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import TreeView from './tree-view/tree-view';
|
|
||||||
import { siteRoot, logoPath, mediaUrl, siteTitle, logoWidth, logoHeight } from './constance';
|
|
||||||
import Tree from './tree-view/tree';
|
|
||||||
import { Node } from './tree-view/node'
|
|
||||||
import NodeMenu from './menu-component/node-menu';
|
|
||||||
import MenuControl from './menu-component/node-menu-control';
|
|
||||||
const gettext = window.gettext;
|
|
||||||
|
|
||||||
class SidePanel extends Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
tree_data: new Tree(),
|
|
||||||
currentNode: null,
|
|
||||||
isNodeItemFrezee: false,
|
|
||||||
isShowMenu: false,
|
|
||||||
menuPosition: {
|
|
||||||
left: 0,
|
|
||||||
top: 0
|
|
||||||
},
|
|
||||||
isLoadFailed: false,
|
|
||||||
isMenuIconShow: false
|
|
||||||
}
|
|
||||||
this.searchedPath = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeSide = () => {
|
|
||||||
this.props.onCloseSide();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseEnter = () => {
|
|
||||||
this.setState({
|
|
||||||
isMenuIconShow: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseLeave = () => {
|
|
||||||
this.setState({
|
|
||||||
isMenuIconShow: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onNodeClick = (e, node) => {
|
|
||||||
this.setState({
|
|
||||||
currentNode: node
|
|
||||||
})
|
|
||||||
this.props.onFileClick(e, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowContextMenu = (e, node) => {
|
|
||||||
let left = e.clientX - 8*16;
|
|
||||||
let top = e.clientY + 10;
|
|
||||||
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
|
||||||
this.setState({
|
|
||||||
isShowMenu: !this.state.isShowMenu,
|
|
||||||
currentNode: node,
|
|
||||||
menuPosition: position,
|
|
||||||
isNodeItemFrezee: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onHeadingMenuClick = (e) => {
|
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
|
||||||
let node = this.state.tree_data.root;
|
|
||||||
let left = e.clientX - 8*16;
|
|
||||||
let top = e.clientY + 10;
|
|
||||||
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
|
||||||
this.setState({
|
|
||||||
isShowMenu: !this.state.isShowMenu,
|
|
||||||
currentNode: node,
|
|
||||||
menuPosition: position
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onHideContextMenu = () => {
|
|
||||||
this.setState({
|
|
||||||
isShowMenu: false,
|
|
||||||
isNodeItemFrezee: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddFolderNode = (dirPath) => {
|
|
||||||
this.props.editorUtilities.createDir(dirPath).then(res => {
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
let index = dirPath.lastIndexOf("/");
|
|
||||||
let name = dirPath.substring(index+1);
|
|
||||||
let parentPath = dirPath.substring(0, index);
|
|
||||||
if (!parentPath) {
|
|
||||||
parentPath = "/";
|
|
||||||
}
|
|
||||||
let node = new Node({name : name, type: "dir", isExpanded: false, children: []});
|
|
||||||
let parentNode = tree.getNodeByPath(parentPath);
|
|
||||||
tree.addChildToNode(parentNode, node);
|
|
||||||
tree.setOneNodeToActived({node});
|
|
||||||
this.setState({
|
|
||||||
tree_data: tree
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddFileNode = (filePath) => {
|
|
||||||
this.props.editorUtilities.createFile(filePath).then(res => {
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
let index = filePath.lastIndexOf("/");
|
|
||||||
let name = filePath.substring(index+1);
|
|
||||||
let parentPath = filePath.substring(0, index);
|
|
||||||
if (!parentPath) {
|
|
||||||
parentPath = "/";
|
|
||||||
}
|
|
||||||
let node = new Node({name : name, type: "file", isExpanded: false, children: []});
|
|
||||||
let parentNode = tree.getNodeByPath(parentPath);
|
|
||||||
tree.addChildToNode(parentNode, node);
|
|
||||||
tree.setOneNodeToActived({node});
|
|
||||||
this.setState({
|
|
||||||
tree_data: tree
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onRenameNode = (newName) => {
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
let node = this.state.currentNode;
|
|
||||||
let type = node.type;
|
|
||||||
let filePath = node.path;
|
|
||||||
if (type === 'file') {
|
|
||||||
this.props.editorUtilities.renameFile(filePath, newName).then(res => {
|
|
||||||
if (this.isModifyCurrentFile()) {
|
|
||||||
tree.updateNodeParamValue(node, "name", newName);
|
|
||||||
node.name = newName; //repair current node
|
|
||||||
this.props.onFileClick(null, node);
|
|
||||||
tree.setOneNodeToActived({node});
|
|
||||||
this.setState({tree_data: tree});
|
|
||||||
} else {
|
|
||||||
tree.updateNodeParamValue(node, "name", newName);
|
|
||||||
this.setState({tree_data: tree});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'dir') {
|
|
||||||
let _this = this;
|
|
||||||
this.props.editorUtilities.renameDir(filePath, newName).then(res => {
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
let currentNode = this.state.currentNode;
|
|
||||||
if (this.isModifyContainsCurrentFile()) {
|
|
||||||
let nodePath = currentNode.path;
|
|
||||||
let filePath = _this.props.currentFilePath;
|
|
||||||
let start = filePath.indexOf(nodePath);
|
|
||||||
let node = currentNode.getNodeByPath(filePath.slice(start));
|
|
||||||
if (node) {
|
|
||||||
tree.updateNodeParamValue(currentNode, "name", newName);
|
|
||||||
currentNode.name = newName;
|
|
||||||
this.props.onFileClick(null, node);
|
|
||||||
tree.setOneNodeToActived({node});
|
|
||||||
this.setState({tree_data: tree});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tree.updateNodeParamValue(currentNode, "name", newName);
|
|
||||||
this.setState({tree_data: tree});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteNode = () => {
|
|
||||||
var currentNode = this.state.currentNode;
|
|
||||||
let filePath = currentNode.path;
|
|
||||||
let type = currentNode.type;
|
|
||||||
if (type === 'file') {
|
|
||||||
this.props.editorUtilities.deleteFile(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'dir') {
|
|
||||||
this.props.editorUtilities.deleteDir(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
let isCurrentFile = false;
|
|
||||||
if (this.state.currentNode.type === "dir") {
|
|
||||||
isCurrentFile = this.isModifyContainsCurrentFile();
|
|
||||||
} else {
|
|
||||||
isCurrentFile = this.isModifyCurrentFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
tree.removeNodeFromTree(currentNode);
|
|
||||||
|
|
||||||
if (isCurrentFile) {
|
|
||||||
let homeNode = this.getHomeNode();
|
|
||||||
this.props.onFileClick(null, homeNode);
|
|
||||||
tree.setNoneNodeActived();
|
|
||||||
this.setState({tree_data: tree})
|
|
||||||
} else {
|
|
||||||
this.setState({tree_data: tree})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isModifyCurrentFile() {
|
|
||||||
let nodeName = this.state.currentNode.name;
|
|
||||||
let filePath = this.props.currentFilePath;
|
|
||||||
let index = filePath.lastIndexOf("/");
|
|
||||||
let fileName = filePath.slice(index+1);
|
|
||||||
return nodeName === fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
isModifyContainsCurrentFile() {
|
|
||||||
let filePath = this.props.currentFilePath;
|
|
||||||
let nodePath = this.state.currentNode.path;
|
|
||||||
|
|
||||||
if (filePath.indexOf(nodePath) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeTreeData() {
|
|
||||||
this.props.editorUtilities.getFiles().then((files) => {
|
|
||||||
// construct the tree object
|
|
||||||
var rootObj = {
|
|
||||||
name: '/',
|
|
||||||
type: 'dir',
|
|
||||||
isExpanded: true
|
|
||||||
}
|
|
||||||
var treeData = new Tree();
|
|
||||||
treeData.parseFromList(rootObj, files);
|
|
||||||
let homeNode = this.getHomeNode(treeData);
|
|
||||||
this.setState({
|
|
||||||
tree_data: treeData,
|
|
||||||
currentNode: homeNode
|
|
||||||
})
|
|
||||||
}, () => {
|
|
||||||
console.log("failed to load files");
|
|
||||||
this.setState({
|
|
||||||
isLoadFailed: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getHomeNode(treeData) {
|
|
||||||
let root = null;
|
|
||||||
if (treeData) {
|
|
||||||
root = treeData.root;
|
|
||||||
} else {
|
|
||||||
root = this.state.tree_data.root;
|
|
||||||
}
|
|
||||||
let homeNode = root.getNodeByPath(decodeURI("/home.md"));
|
|
||||||
return homeNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
//init treeview data
|
|
||||||
this.initializeTreeData();
|
|
||||||
document.addEventListener('click', this.onHideContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
let path = nextProps.searchedPath; //handle search module
|
|
||||||
if (path !== this.searchedPath) {
|
|
||||||
let node = this.state.tree_data.getNodeByPath(path);
|
|
||||||
this.searchedPath = path;
|
|
||||||
this.props.onFileClick(null, node);
|
|
||||||
let tree = this.state.tree_data.copy();
|
|
||||||
tree.setOneNodeToActived({node});
|
|
||||||
this.setState({
|
|
||||||
tree_data: tree
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('click', this.onHideContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={`wiki-side-panel ${this.props.closeSideBar ? "": "left-zero"}`}>
|
|
||||||
<div className="side-panel-top panel-top">
|
|
||||||
<a href={siteRoot} id="logo">
|
|
||||||
<img src={mediaUrl + logoPath} title={siteTitle} alt="logo" width={logoWidth} height={logoHeight} />
|
|
||||||
</a>
|
|
||||||
<a title="Close" aria-label="Close" onClick={this.closeSide} className="sf2-icon-x1 sf-popover-close side-panel-close op-icon d-md-none "></a>
|
|
||||||
</div>
|
|
||||||
<div id="side-nav" className="wiki-side-nav" role="navigation">
|
|
||||||
<h3
|
|
||||||
className="wiki-pages-heading"
|
|
||||||
onMouseEnter={this.onMouseEnter}
|
|
||||||
onMouseLeave={this.onMouseLeave}
|
|
||||||
>
|
|
||||||
{gettext("Pages")}
|
|
||||||
<div className="heading-icon">
|
|
||||||
<MenuControl
|
|
||||||
isShow={this.state.isMenuIconShow}
|
|
||||||
onClick={this.onHeadingMenuClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<div className="wiki-pages-container">
|
|
||||||
{this.state.tree_data &&
|
|
||||||
<TreeView
|
|
||||||
permission={this.props.permission}
|
|
||||||
currentFilePath={this.props.currentFilePath}
|
|
||||||
treeData={this.state.tree_data}
|
|
||||||
currentNode={this.state.currentNode}
|
|
||||||
isNodeItemFrezee={this.state.isNodeItemFrezee}
|
|
||||||
onNodeClick={this.onNodeClick}
|
|
||||||
onShowContextMenu={this.onShowContextMenu}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<NodeMenu
|
|
||||||
isShowMenu={this.state.isShowMenu}
|
|
||||||
menuPosition={this.state.menuPosition}
|
|
||||||
currentNode={this.state.currentNode}
|
|
||||||
onHideContextMenu={this.onHideContextMenu}
|
|
||||||
onDeleteNode={this.onDeleteNode}
|
|
||||||
onAddFileNode={this.onAddFileNode}
|
|
||||||
onAddFolderNode={this.onAddFolderNode}
|
|
||||||
onRenameNode={this.onRenameNode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default SidePanel;
|
|
@@ -1,8 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import cookie from 'react-cookies';
|
|
||||||
import { keyCodes, bytesToSize } from './utils';
|
import { keyCodes, bytesToSize } from './utils';
|
||||||
import { siteRoot, avatarInfo, gettext } from './constance';
|
import { siteRoot, avatarInfo, gettext } from './constance';
|
||||||
|
import editorUtilities from '../utils/editor-utilties';
|
||||||
|
|
||||||
|
|
||||||
class Account extends Component {
|
class Account extends Component {
|
||||||
@@ -72,7 +72,7 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAccountInfo = () => {
|
getAccountInfo = () => {
|
||||||
this.props.seafileAPI.getAccountInfo().then(resp => {
|
editorUtilities.getAccountInfo().then(resp => {
|
||||||
this.setState({
|
this.setState({
|
||||||
userName: resp.data.name,
|
userName: resp.data.name,
|
||||||
contactEmail: resp.data.email,
|
contactEmail: resp.data.email,
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Search from './search';
|
|
||||||
import MarkdownViewer from './markdown-viewer';
|
|
||||||
import Account from './account';
|
|
||||||
import { gettext, repoID, serviceUrl, slug, siteRoot } from './constance';
|
import { gettext, repoID, serviceUrl, slug, siteRoot } from './constance';
|
||||||
|
import Search from './search';
|
||||||
|
import Account from './account';
|
||||||
|
import MarkdownViewer from './markdown-viewer';
|
||||||
|
import TreeDirView from './tree-dir-view/tree-dir-view';
|
||||||
|
|
||||||
class MainPanel extends Component {
|
class MainPanel extends Component {
|
||||||
|
|
||||||
@@ -16,15 +17,27 @@ class MainPanel extends Component {
|
|||||||
window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit';
|
window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMainNavBarClick = (e) => {
|
||||||
|
this.props.onMainNavBarClick(e.target.dataset.path);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var filePathList = this.props.filePath.split('/');
|
|
||||||
var pathElem = filePathList.map((item, index) => {
|
let filePathList = this.props.filePath.split('/');
|
||||||
if (item == "") {
|
let nodePath = "";
|
||||||
|
let pathElem = filePathList.map((item, index) => {
|
||||||
|
if (item === "") {
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
if (index === (filePathList.length - 1)) {
|
||||||
return (
|
return (
|
||||||
<span key={index}><span className="path-split">/</span>{item}</span>
|
<span key={index}><span className="path-split">/</span>{item}</span>
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
nodePath += "/" + item;
|
||||||
|
return (
|
||||||
|
<a key={index} className="custom-link" data-path={nodePath} onClick={this.onMainNavBarClick}><span className="path-split">/</span>{item}</a>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,8 +49,8 @@ class MainPanel extends Component {
|
|||||||
<a className="btn btn-secondary btn-topbar" onClick={this.onEditClick}>{gettext("Edit Page")}</a>
|
<a className="btn btn-secondary btn-topbar" onClick={this.onEditClick}>{gettext("Edit Page")}</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="common-toolbar">
|
<div className="common-toolbar">
|
||||||
<Search seafileAPI={this.props.seafileAPI} onSearchedClick={this.props.onSearchedClick}/>
|
<Search onSearchedClick={this.props.onSearchedClick}/>
|
||||||
<Account seafileAPI={this.props.seafileAPI} />
|
<Account />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-main">
|
<div className="cur-view-main">
|
||||||
@@ -50,13 +63,20 @@ class MainPanel extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<MarkdownViewer
|
{ this.props.isViewFileState && <MarkdownViewer
|
||||||
markdownContent={this.props.content}
|
markdownContent={this.props.content}
|
||||||
latestContributor={this.props.latestContributor}
|
latestContributor={this.props.latestContributor}
|
||||||
lastModified = {this.props.lastModified}
|
lastModified = {this.props.lastModified}
|
||||||
onLinkClick={this.props.onLinkClick}
|
onLinkClick={this.props.onLinkClick}
|
||||||
isFileLoading={this.props.isFileLoading}
|
isFileLoading={this.props.isFileLoading}
|
||||||
/>
|
/>}
|
||||||
|
{ !this.props.isViewFileState &&
|
||||||
|
<TreeDirView
|
||||||
|
node={this.props.changedNode}
|
||||||
|
onMainNodeClick={this.props.onMainNodeClick}
|
||||||
|
>
|
||||||
|
</TreeDirView>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -55,14 +55,6 @@ class CreateFileForder extends React.Component {
|
|||||||
this.newInput.setSelectionRange(0,0);
|
this.newInput.setSelectionRange(0,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeState(isFile) {
|
|
||||||
if (isFile) {
|
|
||||||
this.setState({childName: '.md'});
|
|
||||||
} else{
|
|
||||||
this.setState({childName: ""});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.changeState(nextProps.isFile);
|
this.changeState(nextProps.isFile);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { gettext, repoID } from './constance';
|
import { gettext, repoID } from './constance';
|
||||||
import SearchResultItem from './SearchResultItem';
|
import SearchResultItem from './search-result-item';
|
||||||
|
import editorUtilities from '../utils/editor-utilties';
|
||||||
|
|
||||||
class Search extends Component {
|
class Search extends Component {
|
||||||
|
|
||||||
@@ -78,13 +79,13 @@ class Search extends Component {
|
|||||||
isResultGetted: false
|
isResultGetted: false
|
||||||
})
|
})
|
||||||
|
|
||||||
this.source = this.props.seafileAPI.getSource();
|
this.source = editorUtilities.getSource();
|
||||||
this.sendRequest(queryData, this.source.token);
|
this.sendRequest(queryData, this.source.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRequest(queryData, cancelToken) {
|
sendRequest(queryData, cancelToken) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
this.props.seafileAPI.searchFiles(queryData,cancelToken).then(res => {
|
editorUtilities.searchFiles(queryData,cancelToken).then(res => {
|
||||||
if (!res.data.total) {
|
if (!res.data.total) {
|
||||||
_this.setState({
|
_this.setState({
|
||||||
resultItems: [],
|
resultItems: [],
|
||||||
|
164
frontend/src/components/side-panel.js
Normal file
164
frontend/src/components/side-panel.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import TreeView from './tree-view/tree-view';
|
||||||
|
import { siteRoot, logoPath, mediaUrl, siteTitle, logoWidth, logoHeight } from './constance';
|
||||||
|
import NodeMenu from './menu-component/node-menu';
|
||||||
|
import MenuControl from './menu-component/node-menu-control';
|
||||||
|
|
||||||
|
const gettext = window.gettext;
|
||||||
|
|
||||||
|
class SidePanel extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
currentNode: null,
|
||||||
|
isNodeItemFrezee: false,
|
||||||
|
isShowMenu: false,
|
||||||
|
menuPosition: {
|
||||||
|
left: 0,
|
||||||
|
top: 0
|
||||||
|
},
|
||||||
|
isLoadFailed: false,
|
||||||
|
isMenuIconShow: false
|
||||||
|
}
|
||||||
|
this.searchedPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSide = () => {
|
||||||
|
this.props.onCloseSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
this.setState({
|
||||||
|
isMenuIconShow: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({
|
||||||
|
isMenuIconShow: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeClick = (e, node) => {
|
||||||
|
this.setState({currentNode: node})
|
||||||
|
this.props.onNodeClick(e, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowContextMenu = (e, node) => {
|
||||||
|
let left = e.clientX - 8*16;
|
||||||
|
let top = e.clientY + 10;
|
||||||
|
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
||||||
|
this.setState({
|
||||||
|
isShowMenu: !this.state.isShowMenu,
|
||||||
|
currentNode: node,
|
||||||
|
menuPosition: position,
|
||||||
|
isNodeItemFrezee: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onHeadingMenuClick = (e) => {
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
let node = this.props.treeData.root;
|
||||||
|
let left = e.clientX - 8*16;
|
||||||
|
let top = e.clientY + 10;
|
||||||
|
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
||||||
|
this.setState({
|
||||||
|
isShowMenu: !this.state.isShowMenu,
|
||||||
|
currentNode: node,
|
||||||
|
menuPosition: position
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onHideContextMenu = () => {
|
||||||
|
this.setState({
|
||||||
|
isShowMenu: false,
|
||||||
|
isNodeItemFrezee: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddFolderNode = (dirPath) => {
|
||||||
|
this.props.onAddFolderNode(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddFileNode = (filePath) => {
|
||||||
|
this.props.onAddFileNode(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRenameNode = (newName) => {
|
||||||
|
let node = this.state.currentNode;
|
||||||
|
this.props.onRenameNode(node, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteNode = () => {
|
||||||
|
let node = this.state.currentNode;
|
||||||
|
this.props.onDeleteNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('click', this.onHideContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.setState({
|
||||||
|
currentNode: nextProps.changedNode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('click', this.onHideContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={`wiki-side-panel ${this.props.closeSideBar ? "": "left-zero"}`}>
|
||||||
|
<div className="side-panel-top panel-top">
|
||||||
|
<a href={siteRoot} id="logo">
|
||||||
|
<img src={mediaUrl + logoPath} title={siteTitle} alt="logo" width={logoWidth} height={logoHeight} />
|
||||||
|
</a>
|
||||||
|
<a title="Close" aria-label="Close" onClick={this.closeSide} className="sf2-icon-x1 sf-popover-close side-panel-close op-icon d-md-none "></a>
|
||||||
|
</div>
|
||||||
|
<div id="side-nav" className="wiki-side-nav" role="navigation">
|
||||||
|
<h3
|
||||||
|
className="wiki-pages-heading"
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
>
|
||||||
|
{gettext("Pages")}
|
||||||
|
<div className="heading-icon">
|
||||||
|
<MenuControl
|
||||||
|
isShow={this.state.isMenuIconShow}
|
||||||
|
onClick={this.onHeadingMenuClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
<div className="wiki-pages-container">
|
||||||
|
{this.props.treeData &&
|
||||||
|
<TreeView
|
||||||
|
permission={this.props.permission}
|
||||||
|
currentFilePath={this.props.currentFilePath}
|
||||||
|
treeData={this.props.treeData}
|
||||||
|
currentNode={this.state.currentNode}
|
||||||
|
isNodeItemFrezee={this.state.isNodeItemFrezee}
|
||||||
|
onNodeClick={this.onNodeClick}
|
||||||
|
onShowContextMenu={this.onShowContextMenu}
|
||||||
|
onDirCollapse={this.props.onDirCollapse}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<NodeMenu
|
||||||
|
isShowMenu={this.state.isShowMenu}
|
||||||
|
menuPosition={this.state.menuPosition}
|
||||||
|
currentNode={this.state.currentNode}
|
||||||
|
onHideContextMenu={this.onHideContextMenu}
|
||||||
|
onDeleteNode={this.onDeleteNode}
|
||||||
|
onAddFileNode={this.onAddFileNode}
|
||||||
|
onAddFolderNode={this.onAddFolderNode}
|
||||||
|
onRenameNode={this.onRenameNode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default SidePanel;
|
33
frontend/src/components/tree-dir-view/tree-dir-list.js
Normal file
33
frontend/src/components/tree-dir-view/tree-dir-list.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
class TreeDirList extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isMourseEnter: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMainNodeClick = () => {
|
||||||
|
this.props.onMainNodeClick(this.props.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let node = this.props.node;
|
||||||
|
return (
|
||||||
|
<tr className='row' onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
|
<td className="dirent-icon" style={{width: "5%"}}>
|
||||||
|
<img src={node.type === "dir" ? "/media/img/folder-192.png" : "/media/img/file/192/txt.png"}></img>
|
||||||
|
</td>
|
||||||
|
<td style={{width: "60%"}}>
|
||||||
|
<a className="custom-link" onClick={this.onMainNodeClick}>{node.name}</a>
|
||||||
|
</td>
|
||||||
|
<td style={{width: "15%"}}>{node.size}</td>
|
||||||
|
<td style={{width: "20%"}} title={node.last_update_time}>{node.last_update_time}</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TreeDirList;
|
33
frontend/src/components/tree-dir-view/tree-dir-view.js
Normal file
33
frontend/src/components/tree-dir-view/tree-dir-view.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import TreeDirList from './tree-dir-list'
|
||||||
|
import "../../css/tree-dir-view.css";
|
||||||
|
const gettext = window.gettext;
|
||||||
|
|
||||||
|
class TreeDirView extends React.Component {
|
||||||
|
render() {
|
||||||
|
let node = this.props.node;
|
||||||
|
let children = node.hasChildren() ? node.children : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className="doc-view-container">
|
||||||
|
<thead className="doc-view-header">
|
||||||
|
<tr className="row">
|
||||||
|
<th style={{width: "5%"}}></th>
|
||||||
|
<th style={{width: "60%"}}>{gettext('Name')}</th>
|
||||||
|
<th style={{width: "15%"}}>{gettext('Size')}</th>
|
||||||
|
<th style={{width: "20%"}}>{gettext('Last Update')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="doc-view-body">
|
||||||
|
{children && children.map((node, index) => {
|
||||||
|
return (
|
||||||
|
<TreeDirList key={index} node={node} onMainNodeClick={this.props.onMainNodeClick}></TreeDirList>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TreeDirView;
|
@@ -1,85 +1,61 @@
|
|||||||
|
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
|
|
||||||
static create(attrs = {}) {
|
static deserializefromJson(object) {
|
||||||
|
const {name, type, size, last_update_time, isExpanded = true, children = []} = object;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a `Node` from a JSON `object`.
|
|
||||||
*
|
|
||||||
* @param {Object} object
|
|
||||||
* @return {Node}
|
|
||||||
*/
|
|
||||||
static fromJSON(object) {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
isExpanded = true,
|
|
||||||
children = [],
|
|
||||||
} = object;
|
|
||||||
|
|
||||||
const node = new Node({
|
const node = new Node({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
size,
|
||||||
|
last_update_time,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
children: children.map(Node.fromJSON),
|
children: children.map(item => Node.deserializefromJson(item)),
|
||||||
});
|
});
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor({name, type, size, last_update_time, isExpanded, children}) {
|
||||||
constructor({ name, type, isExpanded, children }) {
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.children = children ? children : [];
|
this.size = size;
|
||||||
|
this.last_update_time = last_update_time;
|
||||||
this.isExpanded = isExpanded !== undefined ? isExpanded : true;
|
this.isExpanded = isExpanded !== undefined ? isExpanded : true;
|
||||||
|
this.children = children ? children : [];
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
var n = new Node({
|
||||||
|
name: this.name,
|
||||||
|
type: this.type,
|
||||||
|
size: this.size,
|
||||||
|
last_update_time: this.last_update_time,
|
||||||
|
isExpanded: this.isExpanded
|
||||||
|
});
|
||||||
|
n.children = this.children.map(child => {
|
||||||
|
var newChild = child.clone();
|
||||||
|
newChild.parent = n;
|
||||||
|
return newChild;
|
||||||
|
});
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
get path() {
|
get path() {
|
||||||
if (!this.parent) {
|
if (!this.parent) {
|
||||||
return this.name;
|
return this.name;
|
||||||
} else {
|
} else {
|
||||||
var p = this.parent.path;
|
let p = this.parent.path;
|
||||||
if (p === "/")
|
return p === "/" ? (p + this.name) : (p + "/" + this.name);
|
||||||
return p + this.name;
|
|
||||||
else
|
|
||||||
return p + "/" + this.name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copy() {
|
|
||||||
var n = new Node({
|
|
||||||
name: this.name,
|
|
||||||
type: this.type,
|
|
||||||
isExpanded: this.isExpanded
|
|
||||||
});
|
|
||||||
n.children = this.children.map(child => { var newChild = child.copy(); newChild.parent = n; return newChild; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
isRoot() {
|
|
||||||
return this.parent === undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children.length > 0;
|
return this.children.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isImage() {
|
isRoot() {
|
||||||
let index = this.name.lastIndexOf(".");
|
return this.parent === undefined;
|
||||||
if (index == -1) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
let type = this.name.substring(index).toLowerCase();
|
|
||||||
if (type == ".png" || type == ".jpg") {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isMarkdown() {
|
isMarkdown() {
|
||||||
@@ -100,39 +76,21 @@ class Node {
|
|||||||
return this.type == "dir";
|
return this.type == "dir";
|
||||||
}
|
}
|
||||||
|
|
||||||
getleafPaths() {
|
isImage() {
|
||||||
let paths = new Map();
|
let index = this.name.lastIndexOf(".");
|
||||||
function getleafPath(node){
|
if (index == -1) {
|
||||||
if (node.hasChildren()) {
|
return false;
|
||||||
let children = node.children;
|
} else {
|
||||||
children.forEach(child => {
|
let type = this.name.substring(index).toLowerCase();
|
||||||
if (child.hasChildren()) {
|
if (type == ".png" || type == ".jpg") {
|
||||||
getleafPath(child);
|
return true;
|
||||||
} else {
|
} else {
|
||||||
let path = child.path;
|
return false;
|
||||||
paths.set(path,child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getleafPath(this);
|
|
||||||
return paths;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeByPath(path) {
|
serializeToJson() {
|
||||||
let paths = this.getleafPaths();
|
|
||||||
if (paths.has(path)) {
|
|
||||||
return paths.get(path);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a JSON representation of the node.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
|
||||||
var children = []
|
var children = []
|
||||||
if (this.hasChildren()) {
|
if (this.hasChildren()) {
|
||||||
children = this.children.map(m => m.toJSON());
|
children = this.children.map(m => m.toJSON());
|
||||||
@@ -141,6 +99,8 @@ class Node {
|
|||||||
const object = {
|
const object = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
size: this.size,
|
||||||
|
last_update_time: this.last_update_time,
|
||||||
isExpanded: this.isExpanded,
|
isExpanded: this.isExpanded,
|
||||||
children: children
|
children: children
|
||||||
}
|
}
|
||||||
@@ -150,5 +110,4 @@ class Node {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Node;
|
||||||
export { Node }
|
|
@@ -44,10 +44,7 @@ class TreeNodeView extends React.Component {
|
|||||||
|
|
||||||
handleCollapse = (e) => {
|
handleCollapse = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const { node } = this.props;
|
this.props.onDirCollapse(e, this.props.node);
|
||||||
if (this.props.treeView.toggleCollapse) {
|
|
||||||
this.props.treeView.toggleCollapse(node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart = (e) => {
|
onDragStart = (e) => {
|
||||||
@@ -69,12 +66,12 @@ class TreeNodeView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('click', this.hideMenuIcon);
|
document.addEventListener('click', this.hideMenuIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('click', this.hideMenuIcon);
|
document.removeEventListener('click', this.hideMenuIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCollapse = () => {
|
renderCollapse = () => {
|
||||||
const { node } = this.props;
|
const { node } = this.props;
|
||||||
@@ -115,6 +112,7 @@ class TreeNodeView extends React.Component {
|
|||||||
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
||||||
permission={this.props.permission}
|
permission={this.props.permission}
|
||||||
currentFilePath={this.props.currentFilePath}
|
currentFilePath={this.props.currentFilePath}
|
||||||
|
onDirCollapse={this.props.onDirCollapse}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -127,10 +125,11 @@ class TreeNodeView extends React.Component {
|
|||||||
|
|
||||||
renderMenuController() {
|
renderMenuController() {
|
||||||
if (this.props.permission === "rw") {
|
if (this.props.permission === "rw") {
|
||||||
|
let isShow = (this.props.node.path === this.props.currentFilePath);
|
||||||
return (
|
return (
|
||||||
<div className="right-icon">
|
<div className="right-icon">
|
||||||
<MenuControl
|
<MenuControl
|
||||||
isShow={this.state.isMenuIconShow}
|
isShow={this.state.isMenuIconShow || isShow}
|
||||||
onClick={this.onMenuControlClick}
|
onClick={this.onMenuControlClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,16 +175,15 @@ class TreeNodeView extends React.Component {
|
|||||||
if (node.path === this.props.currentFilePath) {
|
if (node.path === this.props.currentFilePath) {
|
||||||
hlClass = "tree-node-hight-light";
|
hlClass = "tree-node-hight-light";
|
||||||
}
|
}
|
||||||
let customClass = "tree-node " + hlClass;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div type={type} className={customClass} style={styles}>
|
<div type={type} className="tree-node" style={styles}>
|
||||||
<div
|
<div
|
||||||
onMouseLeave={this.onMouseLeave}
|
onMouseLeave={this.onMouseLeave}
|
||||||
onMouseEnter={this.onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
type={type}
|
type={type}
|
||||||
className={`tree-node-inner text-nowrap ${node.name === '/'? 'hide': ''}`}
|
className={`tree-node-inner text-nowrap ${hlClass} ${node.name === '/'? 'hide': ''}`}
|
||||||
>
|
>
|
||||||
<div className="tree-node-text" type={type} draggable="true" onDragStart={this.onDragStart}>{node.name}</div>
|
<div className="tree-node-text" type={type} draggable="true" onDragStart={this.onDragStart}>{node.name}</div>
|
||||||
<div className="left-icon">
|
<div className="left-icon">
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TreeNodeView from './tree-node-view';
|
import TreeNodeView from './tree-node-view';
|
||||||
|
import editorUtilities from '../../utils/editor-utilties'
|
||||||
|
|
||||||
class TreeView extends React.PureComponent {
|
class TreeView extends React.PureComponent {
|
||||||
|
|
||||||
@@ -10,29 +11,17 @@ class TreeView extends React.PureComponent {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse = (node) => {
|
toggleCollapse = (e, node) => {
|
||||||
const tree = this.props.treeData;
|
this.props.onDirCollapse(e, node);
|
||||||
node.isExpanded = !node.isExpanded;
|
|
||||||
|
|
||||||
// copy the tree to make PureComponent work
|
|
||||||
this.setState({
|
|
||||||
tree: tree.copy()
|
|
||||||
});
|
|
||||||
|
|
||||||
this.change(tree);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart = (e, node) => {
|
onDragStart = (e, node) => {
|
||||||
const url = this.props.editorUtilities.getFileURL(node);
|
const url = editorUtilities.getFileURL(node);
|
||||||
e.dataTransfer.setData("text/uri-list", url);
|
e.dataTransfer.setData("text/uri-list", url);
|
||||||
e.dataTransfer.setData("text/plain", url);
|
e.dataTransfer.setData("text/plain", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
onNodeClick = (e, node) => {
|
onNodeClick = (e, node) => {
|
||||||
if (node.isDir()) {
|
|
||||||
this.toggleCollapse(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onNodeClick(e, node);
|
this.props.onNodeClick(e, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +37,14 @@ class TreeView extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className="tree-view tree">
|
<div className="tree-view tree">
|
||||||
<TreeNodeView
|
<TreeNodeView
|
||||||
paddingLeft={20}
|
paddingLeft={12}
|
||||||
treeView={this}
|
treeView={this}
|
||||||
node={this.props.treeData.root}
|
node={this.props.treeData.root}
|
||||||
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
||||||
permission={this.props.permission}
|
permission={this.props.permission}
|
||||||
currentFilePath={this.props.currentFilePath}
|
currentFilePath={this.props.currentFilePath}
|
||||||
onShowContextMenu={this.props.onShowContextMenu}
|
onShowContextMenu={this.props.onShowContextMenu}
|
||||||
|
onDirCollapse={this.props.onDirCollapse}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,102 +1,145 @@
|
|||||||
import { Node } from './node'
|
import Node from './node';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
class Tree {
|
class Tree {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = null;
|
this.root = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy() {
|
clone() {
|
||||||
var t = new Tree();
|
var t = new Tree();
|
||||||
if (this.root)
|
if (this.root)
|
||||||
t.root = this.root.copy();
|
t.root = this.root.clone();
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
setRoot(dir) {
|
setRoot(node) {
|
||||||
this.root = dir;
|
this.root = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChildToNode(node, child) {
|
addNodeToParent(node, parentNode) {
|
||||||
child.parent = node;
|
node.parent = parentNode;
|
||||||
node.children.push(child);
|
parentNode.children.push(node);
|
||||||
return child;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(node, child, insertIndex) {
|
removeNodeFromParent(node, parentNode) {
|
||||||
if (!(child instanceof Node)) {
|
let children = parentNode.children;
|
||||||
throw new TypeError('Child must be of type Node.');
|
|
||||||
}
|
|
||||||
if (insertIndex < 0 || insertIndex > node.children.length) {
|
|
||||||
throw new Error('Invalid index.');
|
|
||||||
}
|
|
||||||
|
|
||||||
child.parent = node;
|
|
||||||
node.children.splice(insertIndex, 0, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeChildNode(node, child) {
|
|
||||||
let children = node.children;
|
|
||||||
let removeNode = null;
|
let removeNode = null;
|
||||||
let index = null;
|
let index = null;
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
if (child.path === children[i].path) {
|
if (node.path === children[i].path) {
|
||||||
removeNode = children[i];
|
removeNode = children[i];
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child.parent = null;
|
node.parent = null;
|
||||||
node.children.splice(index, 1);
|
parentNode.children.splice(index, 1);
|
||||||
return removeNode ? removeNode : null;
|
return removeNode ? removeNode : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addNodeToTree(node) {
|
addNode(node) {
|
||||||
let parentNode = this.getNodeParentFromTree(node);
|
let treeNodeParent = this.findNodeParentFromTree(node);
|
||||||
this.addChildToNode(parentNode, node);
|
if (treeNodeParent) {
|
||||||
}
|
this.addNodeToParent(node, treeNodeParent);
|
||||||
|
|
||||||
removeNodeFromTree(node) {
|
|
||||||
let parentNode = this.getNodeParentFromTree(node);
|
|
||||||
this.removeChildNode(parentNode, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
getNodeParentFromTree(node) {
|
|
||||||
let parentNode = node.parent;
|
|
||||||
let findNode = null;
|
|
||||||
function cb(node) {
|
|
||||||
if(parentNode.path === node.path){
|
|
||||||
findNode = node;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.traverseBF(cb);
|
|
||||||
return findNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNodeByPath(path) {
|
|
||||||
let findNode = null;
|
|
||||||
function cb(node){
|
|
||||||
if (node.path === path) {
|
|
||||||
findNode = node;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.traverseBF(cb);
|
|
||||||
return findNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodeParamValue(node, param, value) {
|
|
||||||
let findNode = this.getNodeByPath(node.path);
|
|
||||||
if (findNode[param]) {
|
|
||||||
findNode[param] = value;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteNode(node) {
|
||||||
|
let treeNodeParent = this.findNodeParentFromTree(node);
|
||||||
|
if (treeNodeParent) {
|
||||||
|
this.removeNodeFromParent(node, treeNodeParent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNodeParam(node, param, newValue) {
|
||||||
|
let treeNode = this.findNodeFromTree(node);
|
||||||
|
if (treeNode && treeNode[param]) {
|
||||||
|
treeNode[param] = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
findNode(node) {
|
||||||
|
return this.findNodeFromTree(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
findNodeFromTree(node) {
|
||||||
|
let findNode = this.getNodeByPath(node.path);
|
||||||
|
return findNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
findNodeParentFromTree(node) {
|
||||||
|
let parentNode = node.parent;
|
||||||
|
let findNode = null;
|
||||||
|
function cb(treeNode) {
|
||||||
|
if (treeNode.path === parentNode.path) {
|
||||||
|
findNode = treeNode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.traverseDF(cb);
|
||||||
|
return findNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodeToActivated(node) {
|
||||||
|
this.setTreeToUnActivated();
|
||||||
|
let treeNode = this.findNodeFromTree(node);
|
||||||
|
if (treeNode) {
|
||||||
|
treeNode.isExpanded = true;
|
||||||
|
while (treeNode.parent) {
|
||||||
|
treeNode.parent.isExpanded = true;
|
||||||
|
treeNode = treeNode.parent;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTreeToUnActivated() {
|
||||||
|
function cb(treeNode) {
|
||||||
|
treeNode.isExpanded = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.traverseBF(cb);
|
||||||
|
this.root.isExpanded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeByPath(path) {
|
||||||
|
let findNode = null;
|
||||||
|
function cb(treeNode) {
|
||||||
|
if (treeNode.path === path) {
|
||||||
|
findNode = treeNode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.traverseBF(cb);
|
||||||
|
return findNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNodeChild(parentNode, node) {
|
||||||
|
let isChild = false;
|
||||||
|
while(node.parent){
|
||||||
|
if(node.parent.path === parentNode.path){
|
||||||
|
isChild = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
return isChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
traverseDF(callback) {
|
traverseDF(callback) {
|
||||||
let stack = [];
|
let stack = [];
|
||||||
let found = false;
|
let found = false;
|
||||||
@@ -125,105 +168,46 @@ class Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOneNodeToActived({node}) {
|
parseModelToTree(model) {
|
||||||
this.setNoneNodeActived();
|
|
||||||
let root = this.root;
|
|
||||||
root.isExpanded = true;
|
|
||||||
let layer2 = root.hasChildren() ? root.children : null; // direct to replace root child;
|
|
||||||
let isLayer2 = false;
|
|
||||||
for (let i = 0; i < layer2.length; i++) {
|
|
||||||
if (node.path === layer2[i].path) {
|
|
||||||
isLayer2 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isLayer2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let replaceNode = null;
|
|
||||||
let needReplacedNode = null;
|
|
||||||
while (node.parent) {
|
|
||||||
let flag = false;
|
|
||||||
node.parent.isExpanded = true;
|
|
||||||
for (let i = 0; i < layer2.length; i++) {
|
|
||||||
if (node.parent.path === layer2[i].path) {
|
|
||||||
replaceNode = node.parent;
|
|
||||||
needReplacedNode = layer2[i];
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (flag) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
node = node.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeChildNode(root, needReplacedNode);
|
|
||||||
this.addChildToNode(root, replaceNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
setNoneNodeActived() {
|
|
||||||
function setNodeToDeactived(node) {
|
|
||||||
if (node.isExpanded) {
|
|
||||||
node.isExpanded = false;
|
|
||||||
if (node.hasChildren()) {
|
|
||||||
let children = node.children;
|
|
||||||
children.forEach(function(child) {
|
|
||||||
setNodeToDeactived(child);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNodeToDeactived(this.root);
|
|
||||||
this.root.isExpanded = true; // default to show;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse tree from javascript object
|
|
||||||
*/
|
|
||||||
parse(model) {
|
|
||||||
var node = new Node({
|
var node = new Node({
|
||||||
name: model.name,
|
name: model.name,
|
||||||
type: model.type,
|
type: model.type,
|
||||||
isExpanded: model.isExpanded
|
size: model.size,
|
||||||
|
last_update_time: moment.unix(model.last_update_time).fromNow(),
|
||||||
|
isExpanded: false
|
||||||
});
|
});
|
||||||
this.root = node;
|
if (model.children instanceof Array) {
|
||||||
for (let child of model.children) {
|
for (let child of model.children) {
|
||||||
this.addChildToNode(node, this.parseNode(child));
|
this.addNodeToParent(this.parseNodeToTree(child), node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseFromList(rootObj, nodeList) {
|
parseListToTree(nodeList) {
|
||||||
var root = new Node({
|
|
||||||
name: rootObj.name,
|
function getNodePath(parentPath, nodeName) {
|
||||||
type: rootObj.type,
|
return parentPath === "/" ? (parentPath + nodeName) : (parentPath + "/" + nodeName);
|
||||||
isExpanded: rootObj.isExpanded
|
|
||||||
});
|
|
||||||
this.root = root;
|
|
||||||
|
|
||||||
var map = new Map();
|
|
||||||
map.set(root.name, root);
|
|
||||||
|
|
||||||
function joinPath(parent_path, name) {
|
|
||||||
if (parent_path === "/")
|
|
||||||
return parent_path + name;
|
|
||||||
else
|
|
||||||
return parent_path + "/" + name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var treeNodeList = []
|
let root = new Node({name: '/', type: 'dir', isExpanded: true});
|
||||||
|
this.root = root;
|
||||||
|
|
||||||
|
let map = new Map();
|
||||||
|
map.set(root.name, root);
|
||||||
|
let treeNodeList = [];
|
||||||
for (let nodeObj of nodeList) {
|
for (let nodeObj of nodeList) {
|
||||||
var node = new Node({
|
let node = new Node({
|
||||||
name: nodeObj.name,
|
name: nodeObj.name,
|
||||||
type: nodeObj.type,
|
type: nodeObj.type,
|
||||||
|
size: nodeObj.size,
|
||||||
|
last_update_time: moment.unix(nodeObj.last_update_time).fromNow(),
|
||||||
isExpanded: false
|
isExpanded: false
|
||||||
});
|
});
|
||||||
node.parent_path = nodeObj.parent_path;
|
node.parent_path = nodeObj.parent_path;
|
||||||
treeNodeList.push(node);
|
treeNodeList.push(node);
|
||||||
if (nodeObj.type === "dir") {
|
if (node.isDir()) {
|
||||||
map.set(joinPath(nodeObj.parent_path, nodeObj.name), node);
|
map.set(getNodePath(node.parent_path, node.name), node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,26 +216,28 @@ class Tree {
|
|||||||
if (p === undefined) {
|
if (p === undefined) {
|
||||||
console.log("warning: node " + node.parent_path + " not exist");
|
console.log("warning: node " + node.parent_path + " not exist");
|
||||||
} else {
|
} else {
|
||||||
this.addChildToNode(p, node);
|
this.addNodeToParent(node, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseNode(model) {
|
parseNodeToTree(node) {
|
||||||
var node = new Node({
|
var node = new Node({
|
||||||
name: model.name,
|
name: node.name,
|
||||||
type: model.type,
|
type: node.type,
|
||||||
isExpanded: model.isExpanded
|
size: node.size,
|
||||||
|
last_update_time: moment.unix(node.last_update_time).fromNow(),
|
||||||
|
isExpanded: false
|
||||||
});
|
});
|
||||||
if (model.children instanceof Array) {
|
if (node.children instanceof Array) {
|
||||||
for (let child of model.children) {
|
for (let child of node.children) {
|
||||||
this.addChildToNode(node, this.parseNode(child));
|
this.addNodeToParent(this.parseNodeToTree(child), node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Tree;
|
export default Tree;
|
||||||
|
@@ -102,13 +102,14 @@
|
|||||||
|
|
||||||
.tree-node-inner {
|
.tree-node-inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 12px;
|
height: 26px;
|
||||||
height: 24px;
|
cursor: pointer;
|
||||||
|
line-height: 1.625;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-inner .tree-node-text {
|
.tree-node-inner .tree-node-text {
|
||||||
padding-left: 1.2rem;
|
padding-left: 2.8rem;
|
||||||
width: calc(100% - 1.5rem);
|
width: calc(100% - 2.8rem);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -122,27 +123,32 @@
|
|||||||
align-items:center;
|
align-items:center;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding-left: 0.7rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-toggle-icon {
|
.folder-toggle-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
line-height: 1.5;
|
color: #c0c0c0;
|
||||||
|
line-height: 1.625;
|
||||||
|
width: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-icon {
|
.tree-node-icon {
|
||||||
margin-right: 0.4rem;
|
|
||||||
margin-left: 0.1rem;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: #b0b0b0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-inner .right-icon {
|
.tree-node-inner .right-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
color: #888;
|
color: #888;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@@ -155,11 +161,16 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
line-height: 1.625 !important;
|
line-height: 1.625rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-hight-light {
|
.tree-node-hight-light {
|
||||||
background-color: rgb(255,239,178);
|
color: #fff;
|
||||||
|
background-color: #feac74 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-hight-light i {
|
||||||
|
color:#fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
|
50
frontend/src/css/tree-dir-view.css
Normal file
50
frontend/src/css/tree-dir-view.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
.doc-view-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-header .row,
|
||||||
|
.doc-view-body .row {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-body tr:hover{
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-header tr .dirent-icon,
|
||||||
|
.doc-view-body tr .dirent-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-container td {
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-container th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #9c9c9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-view-container th, td {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.doc-view-container .img-placeholder,
|
||||||
|
.doc-view-container tr img {
|
||||||
|
display: block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
@@ -38,16 +38,7 @@
|
|||||||
.wiki-md-viewer-rendered-content {
|
.wiki-md-viewer-rendered-content {
|
||||||
padding: 30px 0 0;
|
padding: 30px 0 0;
|
||||||
}
|
}
|
||||||
.wiki-pages-container .tree-node-inner {
|
|
||||||
line-height: 1.625;
|
|
||||||
}
|
|
||||||
.wiki-pages-container .folder-toggle-icon {
|
|
||||||
color: #c0c0c0;
|
|
||||||
line-height: 1.625;
|
|
||||||
}
|
|
||||||
.wiki-pages-container .tree-node-icon {
|
|
||||||
color: #b0b0b0;
|
|
||||||
}
|
|
||||||
.wiki-main .cur-view-path {
|
.wiki-main .cur-view-path {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -198,4 +189,13 @@ img[src=""] {
|
|||||||
|
|
||||||
.wiki-md-viewer-rendered-content.article h1 {
|
.wiki-md-viewer-rendered-content.article h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-link {
|
||||||
|
color: #eb8205 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-link:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
}
|
}
|
71
frontend/src/utils/editor-utilties.js
Normal file
71
frontend/src/utils/editor-utilties.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { slug, repoID, siteRoot } from '../components/constance';
|
||||||
|
import { SeafileAPI } from 'seafile-js';
|
||||||
|
import cookie from 'react-cookies';
|
||||||
|
|
||||||
|
let seafileAPI = new SeafileAPI();
|
||||||
|
let xcsrfHeaders = cookie.load('sfcsrftoken');
|
||||||
|
seafileAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
|
||||||
|
|
||||||
|
class EditorUtilities {
|
||||||
|
|
||||||
|
getFiles() {
|
||||||
|
return seafileAPI.listWikiDir(slug, "/").then(items => {
|
||||||
|
const files = items.data.dir_file_list.map(item => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
type: item.type === 'dir' ? 'dir' : 'file',
|
||||||
|
isExpanded: item.type === 'dir' ? true : false,
|
||||||
|
parent_path: item.parent_dir,
|
||||||
|
last_update_time: item.last_update_time,
|
||||||
|
size: item.size
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return files;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createFile(filePath) {
|
||||||
|
return seafileAPI.createFile(repoID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFile(filePath) {
|
||||||
|
return seafileAPI.deleteFile(repoID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
renameFile(filePath, newFileName) {
|
||||||
|
return seafileAPI.renameFile(repoID, filePath, newFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
createDir(dirPath) {
|
||||||
|
return seafileAPI.createDir(repoID, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDir(dirPath) {
|
||||||
|
return seafileAPI.deleteDir(repoID, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
renameDir(dirPath, newDirName) {
|
||||||
|
return seafileAPI.renameDir(repoID, dirPath, newDirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
getWikiFileContent(slug, filePath) {
|
||||||
|
return seafileAPI.getWikiFileContent(slug, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSource() {
|
||||||
|
return seafileAPI.getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchFiles(queryData,cancelToken) {
|
||||||
|
return seafileAPI.searchFiles(queryData,cancelToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccountInfo() {
|
||||||
|
return seafileAPI.getAccountInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorUtilities = new EditorUtilities();
|
||||||
|
|
||||||
|
export default editorUtilities;
|
@@ -1,94 +1,396 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import SidePanel from './components/SidePanel';
|
|
||||||
import MainPanel from './components/MainPanel';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cookie from 'react-cookies';
|
import { slug, repoID, serviceUrl, initialFilePath } from './components/constance';
|
||||||
import { SeafileAPI } from 'seafile-js';
|
import editorUtilities from './utils/editor-utilties';
|
||||||
import { slug, repoID, serviceUrl, initialFilePath, siteRoot } from './components/constance';
|
import SidePanel from './components/side-panel';
|
||||||
|
import MainPanel from './components/main-panel';
|
||||||
|
import Node from './components/tree-view/node'
|
||||||
|
import Tree from './components/tree-view/tree'
|
||||||
|
import 'seafile-ui';
|
||||||
import './assets/css/fa-solid.css';
|
import './assets/css/fa-solid.css';
|
||||||
import './assets/css/fa-regular.css';
|
import './assets/css/fa-regular.css';
|
||||||
import './assets/css/fontawesome.css';
|
import './assets/css/fontawesome.css';
|
||||||
import 'seafile-ui';
|
|
||||||
import './css/side-panel.css';
|
import './css/side-panel.css';
|
||||||
import './css/wiki.css';
|
import './css/wiki.css';
|
||||||
import './css/search.css';
|
import './css/search.css';
|
||||||
|
|
||||||
// init seafileAPI
|
|
||||||
let seafileAPI = new SeafileAPI();
|
|
||||||
let xcsrfHeaders = cookie.load('sfcsrftoken');
|
|
||||||
seafileAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
|
|
||||||
|
|
||||||
class EditorUtilities {
|
|
||||||
getFiles() {
|
|
||||||
return seafileAPI.listWikiDir(slug).then(items => {
|
|
||||||
const files = items.data.dir_file_list.map(item => {
|
|
||||||
return {
|
|
||||||
name: item.name,
|
|
||||||
type: item.type === 'dir' ? 'dir' : 'file',
|
|
||||||
isExpanded: item.type === 'dir' ? true : false,
|
|
||||||
parent_path: item.parent_dir,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return files;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
createFile(filePath) {
|
|
||||||
return seafileAPI.createFile(repoID, filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteFile(filePath) {
|
|
||||||
return seafileAPI.deleteFile(repoID, filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
renameFile(filePath, newFileName) {
|
|
||||||
return seafileAPI.renameFile(repoID, filePath, newFileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
createDir(dirPath) {
|
|
||||||
return seafileAPI.createDir(repoID, dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteDir(dirPath) {
|
|
||||||
return seafileAPI.deleteDir(repoID, dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
renameDir(dirPath, newDirName) {
|
|
||||||
return seafileAPI.renameDir(repoID, dirPath, newDirName)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const editorUtilities = new EditorUtilities();
|
|
||||||
|
|
||||||
class Wiki extends Component {
|
class Wiki extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
content: '',
|
content: '',
|
||||||
|
tree_data: new Tree(),
|
||||||
closeSideBar: false,
|
closeSideBar: false,
|
||||||
fileName: '',
|
|
||||||
filePath: '',
|
filePath: '',
|
||||||
latestContributor: '',
|
latestContributor: '',
|
||||||
lastModified: '',
|
lastModified: '',
|
||||||
permission: '',
|
permission: '',
|
||||||
isFileLoading: false,
|
isFileLoading: false,
|
||||||
searchedPath: null,
|
changedNode: null,
|
||||||
|
isViewFileState: true
|
||||||
};
|
};
|
||||||
window.onpopstate = this.onpopstate;
|
window.onpopstate = this.onpopstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadFile(initialFilePath);
|
this.initSidePanelData();
|
||||||
|
this.initMainPanelData(initialFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileNameFromPath(filePath) {
|
initMainPanelData(filePath) {
|
||||||
let index = filePath.lastIndexOf("/");
|
if (!this.isMarkdownFile(filePath)) {
|
||||||
if (index == -1) {
|
filePath = "/home.md";
|
||||||
return "";
|
}
|
||||||
|
this.setState({isFileLoading: true});
|
||||||
|
editorUtilities.getWikiFileContent(slug, filePath)
|
||||||
|
.then(res => {
|
||||||
|
this.setState({
|
||||||
|
content: res.data.content,
|
||||||
|
latestContributor: res.data.latest_contributor,
|
||||||
|
lastModified: moment.unix(res.data.last_modified).fromNow(),
|
||||||
|
permission: res.data.permission,
|
||||||
|
filePath: filePath,
|
||||||
|
isFileLoading: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const hash = window.location.hash;
|
||||||
|
let fileUrl = serviceUrl + '/wikis/' + slug + filePath + hash;
|
||||||
|
window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
initSidePanelData() {
|
||||||
|
editorUtilities.getFiles().then((files) => {
|
||||||
|
// construct the tree object
|
||||||
|
var treeData = new Tree();
|
||||||
|
treeData.parseListToTree(files);
|
||||||
|
let homeNode = this.getHomeNode(treeData);
|
||||||
|
this.setState({
|
||||||
|
tree_data: treeData,
|
||||||
|
changedNode: homeNode
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
console.log("failed to load files");
|
||||||
|
this.setState({
|
||||||
|
isLoadFailed: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onLinkClick = (event) => {
|
||||||
|
const url = event.target.href;
|
||||||
|
if (this.isInternalMarkdownLink(url)) {
|
||||||
|
let path = this.getPathFromInternalMarkdownLink(url);
|
||||||
|
this.initMainPanelData(path);
|
||||||
} else {
|
} else {
|
||||||
return filePath.substring(index + 1);
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onpopstate = (event) => {
|
||||||
|
if (event.state && event.state.filePath) {
|
||||||
|
this.initMainPanelData(event.state.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchedClick = (path) => {
|
||||||
|
if (this.state.currentFilePath !== path) {
|
||||||
|
this.initMainPanelData(path);
|
||||||
|
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let node = tree.getNodeByPath(path);
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
|
||||||
|
let path = node.path; //node is file
|
||||||
|
this.enterViewFileState(tree, node, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMainNavBarClick = (nodePath) => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let node = tree.getNodeByPath(nodePath);
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
|
||||||
|
this.exitViewFileState(tree, node);
|
||||||
|
|
||||||
|
// update location url
|
||||||
|
let fileUrl = serviceUrl + '/wikis/' + slug + node.path;
|
||||||
|
window.history.pushState({urlPath: fileUrl, filePath: node.path},node.path, fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMainNodeClick = (node) => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
if (node.isMarkdown()) {
|
||||||
|
this.initMainPanelData(node.path);
|
||||||
|
this.enterViewFileState(tree, node, node.path);
|
||||||
|
} else if (node.isDir()){
|
||||||
|
this.exitViewFileState(tree, node);
|
||||||
|
} else {
|
||||||
|
const w=window.open('about:blank');
|
||||||
|
const url = serviceUrl + '/lib/' + repoID + '/file' + node.path;
|
||||||
|
w.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeClick = (e, node) => {
|
||||||
|
if (node instanceof Node && node.isMarkdown()){
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
this.initMainPanelData(node.path);
|
||||||
|
this.enterViewFileState(tree, node, node.path);
|
||||||
|
} else if(node instanceof Node && node.isDir()){
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
this.exitViewFileState(tree, node);
|
||||||
|
} else {
|
||||||
|
const w=window.open('about:blank');
|
||||||
|
const url = serviceUrl + '/lib/' + repoID + '/file' + node.path;
|
||||||
|
w.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDirCollapse = (e, node) => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let findNode = tree.getNodeByPath(node.path);
|
||||||
|
findNode.isExpanded = !findNode.isExpanded;
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuClick = () => {
|
||||||
|
this.setState({
|
||||||
|
closeSideBar: !this.state.closeSideBar,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseSide = () => {
|
||||||
|
this.setState({
|
||||||
|
closeSideBar: !this.state.closeSideBar,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddFolderNode = (dirPath) => {
|
||||||
|
editorUtilities.createDir(dirPath).then(res => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let name = this.getFileNameByPath(dirPath);
|
||||||
|
let index = dirPath.lastIndexOf("/");
|
||||||
|
let parentPath = dirPath.substring(0, index);
|
||||||
|
if (!parentPath) {
|
||||||
|
parentPath = "/";
|
||||||
|
}
|
||||||
|
let node = this.buildNewNode(name, "dir");
|
||||||
|
let parentNode = tree.getNodeByPath(parentPath);
|
||||||
|
tree.addNodeToParent(node, parentNode);
|
||||||
|
if (this.state.isViewFileState) {
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
this.setState({
|
||||||
|
tree_data: tree,
|
||||||
|
changedNode: node
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.exitViewFileState(tree, parentNode);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddFileNode = (filePath) => {
|
||||||
|
editorUtilities.createFile(filePath).then(res => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let name = this.getFileNameByPath(filePath);
|
||||||
|
let index = filePath.lastIndexOf("/");
|
||||||
|
let parentPath = filePath.substring(0, index);
|
||||||
|
if (!parentPath) {
|
||||||
|
parentPath = "/";
|
||||||
|
}
|
||||||
|
let node = this.buildNewNode(name, "file");
|
||||||
|
let parentNode = tree.getNodeByPath(parentPath);
|
||||||
|
tree.addNodeToParent(node, parentNode);
|
||||||
|
if (this.state.isViewFileState) {
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
this.setState({
|
||||||
|
tree_data: tree,
|
||||||
|
changedNode: node
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.exitViewFileState(tree, parentNode);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onRenameNode = (node, newName) => {
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
let filePath = node.path;
|
||||||
|
if (node.isMarkdown()) {
|
||||||
|
editorUtilities.renameFile(filePath, newName).then(res => {
|
||||||
|
let date = new Date().getTime()/1000;
|
||||||
|
tree.updateNodeParam(node, "name", newName);
|
||||||
|
node.name = newName;
|
||||||
|
tree.updateNodeParam(node, "last_update_time", moment.unix(date).fromNow());
|
||||||
|
|
||||||
|
node.last_update_time = moment.unix(date).fromNow();
|
||||||
|
if (this.state.isViewFileState) {
|
||||||
|
if (this.isModifyCurrentFile(node)) {
|
||||||
|
tree.setNodeToActivated(node);
|
||||||
|
this.setState({
|
||||||
|
tree_data: tree,
|
||||||
|
changedNode: node
|
||||||
|
});
|
||||||
|
this.initMainPanelData(node.path);
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (node.isDir()) {
|
||||||
|
editorUtilities.renameDir(filePath, newName).then(res => {
|
||||||
|
let currentFilePath = this.state.filePath;// the sequence is must right
|
||||||
|
let currentFileNode = tree.getNodeByPath(currentFilePath);
|
||||||
|
|
||||||
|
let date = new Date().getTime()/1000;
|
||||||
|
tree.updateNodeParam(node, "name", newName);
|
||||||
|
node.name = newName; // just synchronization node data && tree data;
|
||||||
|
tree.updateNodeParam(node, "last_update_time", moment.unix(date).fromNow());
|
||||||
|
node.last_update_time = moment.unix(date).fromNow();
|
||||||
|
if (this.state.isViewFileState) {
|
||||||
|
if (this.isModifyContainsCurrentFile(node)) {
|
||||||
|
tree.setNodeToActivated(currentFileNode);
|
||||||
|
this.setState({
|
||||||
|
tree_data: tree,
|
||||||
|
changedNode: currentFileNode
|
||||||
|
});
|
||||||
|
this.initMainPanelData(currentFileNode.path);
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (node.path.indexOf(currentFilePath) > -1) {
|
||||||
|
tree.setNodeToActivated(currentFileNode);
|
||||||
|
this.exitViewFileState(tree, currentFileNode);
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteNode = (node) => {
|
||||||
|
let filePath = node.path;
|
||||||
|
if (node.isMarkdown()) {
|
||||||
|
editorUtilities.deleteFile(filePath);
|
||||||
|
} else if (node.isDir()) {
|
||||||
|
editorUtilities.deleteDir(filePath);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isCurrentFile = false;
|
||||||
|
if (node.isDir()) {
|
||||||
|
isCurrentFile = this.isModifyContainsCurrentFile(node);
|
||||||
|
} else {
|
||||||
|
isCurrentFile = this.isModifyCurrentFile(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = this.state.tree_data.clone();
|
||||||
|
|
||||||
|
if (this.state.isViewFileState) {
|
||||||
|
if (isCurrentFile) {
|
||||||
|
let homeNode = this.getHomeNode(tree);
|
||||||
|
tree.setNodeToActivated(homeNode);
|
||||||
|
this.setState({
|
||||||
|
tree_data: tree,
|
||||||
|
changedNode: homeNode
|
||||||
|
})
|
||||||
|
this.initMainPanelData(homeNode.path);
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let parentNode = tree.getNodeByPath(this.state.filePath);
|
||||||
|
let isChild = tree.isNodeChild(parentNode, node);
|
||||||
|
if (isChild) {
|
||||||
|
this.exitViewFileState(tree, parentNode);
|
||||||
|
} else {
|
||||||
|
this.setState({tree_data: tree});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tree.deleteNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enterViewFileState(newTree, newNode, newPath) {
|
||||||
|
this.setState({
|
||||||
|
tree_data: newTree,
|
||||||
|
changedNode: newNode,
|
||||||
|
filePath: newPath,
|
||||||
|
isViewFileState: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exitViewFileState(newTree, newNode) {
|
||||||
|
this.setState({
|
||||||
|
tree_data: newTree,
|
||||||
|
changedNode: newNode,
|
||||||
|
filePath: newNode.path,
|
||||||
|
isViewFileState: false
|
||||||
|
});
|
||||||
|
let fileUrl = serviceUrl + '/wikis/' + slug + newNode.path;
|
||||||
|
window.history.pushState({urlPath: fileUrl, filePath: newNode.path}, newNode.path, fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileNameByPath(path) {
|
||||||
|
let index = path.lastIndexOf("/");
|
||||||
|
if (index === -1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return path.slice(index+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHomeNode(treeData) {
|
||||||
|
return treeData.getNodeByPath("/home.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
buildNewNode(name, type) {
|
||||||
|
let date = new Date().getTime()/1000;
|
||||||
|
let node = new Node({
|
||||||
|
name : name,
|
||||||
|
type: type,
|
||||||
|
size: '0',
|
||||||
|
last_update_time: moment.unix(date).fromNow(),
|
||||||
|
isExpanded: false,
|
||||||
|
children: []
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifyCurrentFile(node) {
|
||||||
|
let nodeName = node.name;
|
||||||
|
let fileName = this.getFileNameByPath(this.state.filePath);
|
||||||
|
return nodeName === fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifyContainsCurrentFile(node) {
|
||||||
|
let filePath = this.state.filePath;
|
||||||
|
let nodePath = node.path;
|
||||||
|
|
||||||
|
if (filePath.indexOf(nodePath) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMarkdownFile(filePath) {
|
||||||
|
let index = filePath.lastIndexOf(".");
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
let type = filePath.substring(index).toLowerCase();
|
||||||
|
if (type === ".md" || type === ".markdown") {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,94 +406,37 @@ class Wiki extends Component {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkClick = (event) => {
|
|
||||||
const url = event.target.href;
|
|
||||||
if (this.isInternalMarkdownLink(url)) {
|
|
||||||
let path = this.getPathFromInternalMarkdownLink(url);
|
|
||||||
this.loadFile(path);
|
|
||||||
} else {
|
|
||||||
window.location.href = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearchedClick = (path) => {
|
|
||||||
if (this.state.currentFilePath !== path) {
|
|
||||||
this.setState({searchedPath : path});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onFileClick = (e, node) => {
|
|
||||||
if (node.isMarkdown()) {
|
|
||||||
this.loadFile(node.path);
|
|
||||||
} else {
|
|
||||||
const w=window.open('about:blank');
|
|
||||||
const url = serviceUrl + '/lib/' + repoID + '/file' + node.path;
|
|
||||||
w.location.href = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFile(filePath) {
|
|
||||||
this.setState({isFileLoading: true});
|
|
||||||
seafileAPI.getWikiFileContent(slug, filePath)
|
|
||||||
.then(res => {
|
|
||||||
this.setState({
|
|
||||||
content: res.data.content,
|
|
||||||
latestContributor: res.data.latest_contributor,
|
|
||||||
lastModified: moment.unix(res.data.last_modified).fromNow(),
|
|
||||||
permission: res.data.permission,
|
|
||||||
fileName: this.fileNameFromPath(filePath),
|
|
||||||
filePath: filePath,
|
|
||||||
isFileLoading: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const hash = window.location.hash;
|
|
||||||
let fileUrl = serviceUrl + '/wikis/' + slug + filePath + hash;
|
|
||||||
window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
onpopstate = (event) => {
|
|
||||||
if (event.state && event.state.filePath) {
|
|
||||||
this.loadFile(event.state.filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMenuClick = () => {
|
|
||||||
this.setState({
|
|
||||||
closeSideBar: !this.state.closeSideBar,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onCloseSide = () => {
|
|
||||||
this.setState({
|
|
||||||
closeSideBar: !this.state.closeSideBar,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id="main" className="wiki-main">
|
<div id="main" className="wiki-main">
|
||||||
<SidePanel
|
<SidePanel
|
||||||
onFileClick={this.onFileClick}
|
onNodeClick={this.onNodeClick}
|
||||||
closeSideBar={this.state.closeSideBar}
|
closeSideBar={this.state.closeSideBar}
|
||||||
onCloseSide ={this.onCloseSide}
|
onCloseSide ={this.onCloseSide}
|
||||||
editorUtilities={editorUtilities}
|
treeData={this.state.tree_data}
|
||||||
permission={this.state.permission}
|
permission={this.state.permission}
|
||||||
currentFilePath={this.state.filePath}
|
currentFilePath={this.state.filePath}
|
||||||
searchedPath={this.state.searchedPath}
|
changedNode={this.state.changedNode}
|
||||||
|
onAddFolderNode={this.onAddFolderNode}
|
||||||
|
onAddFileNode={this.onAddFileNode}
|
||||||
|
onRenameNode={this.onRenameNode}
|
||||||
|
onDeleteNode={this.onDeleteNode}
|
||||||
|
onDirCollapse={this.onDirCollapse}
|
||||||
/>
|
/>
|
||||||
<MainPanel
|
<MainPanel
|
||||||
content={this.state.content}
|
content={this.state.content}
|
||||||
fileName={this.state.fileName}
|
|
||||||
filePath={this.state.filePath}
|
filePath={this.state.filePath}
|
||||||
|
latestContributor={this.state.latestContributor}
|
||||||
|
lastModified={this.state.lastModified}
|
||||||
|
permission={this.state.permission}
|
||||||
|
isViewFileState={this.state.isViewFileState}
|
||||||
|
changedNode={this.state.changedNode}
|
||||||
|
isFileLoading={this.state.isFileLoading}
|
||||||
onLinkClick={this.onLinkClick}
|
onLinkClick={this.onLinkClick}
|
||||||
onMenuClick={this.onMenuClick}
|
onMenuClick={this.onMenuClick}
|
||||||
onSearchedClick={this.onSearchedClick}
|
onSearchedClick={this.onSearchedClick}
|
||||||
latestContributor={this.state.latestContributor}
|
onMainNavBarClick={this.onMainNavBarClick}
|
||||||
lastModified={this.state.lastModified}
|
onMainNodeClick={this.onMainNodeClick}
|
||||||
seafileAPI={seafileAPI}
|
|
||||||
permission={this.state.permission}
|
|
||||||
isFileLoading={this.state.isFileLoading}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@@ -219,6 +219,11 @@ class WikiPagesDirView(APIView):
|
|||||||
error_msg = "Wiki not found."
|
error_msg = "Wiki not found."
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
path = request.GET.get("p", '')
|
||||||
|
if not path:
|
||||||
|
error_msg = "Folder not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
# perm check
|
# perm check
|
||||||
if not wiki.check_access_wiki(request):
|
if not wiki.check_access_wiki(request):
|
||||||
error_msg = "Permission denied"
|
error_msg = "Permission denied"
|
||||||
@@ -233,12 +238,12 @@ class WikiPagesDirView(APIView):
|
|||||||
error_msg = "Internal Server Error"
|
error_msg = "Internal Server Error"
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
dir_id = seafile_api.get_dir_id_by_path(repo.repo_id, '/')
|
dir_id = seafile_api.get_dir_id_by_path(repo.repo_id, path)
|
||||||
if not dir_id:
|
if not dir_id:
|
||||||
error_msg = 'Folder %s not found.' % '/'
|
error_msg = 'Folder %s not found.' % path
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
all_dirs = get_wiki_dirs_by_path(repo.repo_id, '/', [])
|
all_dirs = get_wiki_dirs_by_path(repo.repo_id, path, [])
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
"dir_file_list": all_dirs
|
"dir_file_list": all_dirs
|
||||||
|
@@ -239,6 +239,8 @@ def get_wiki_dirs_by_path(repo_id, path, all_dirs):
|
|||||||
|
|
||||||
entry["parent_dir"] = path
|
entry["parent_dir"] = path
|
||||||
entry["name"] = dirent.obj_name
|
entry["name"] = dirent.obj_name
|
||||||
|
entry["size"] = dirent.size
|
||||||
|
entry["last_update_time"] = dirent.mtime
|
||||||
|
|
||||||
all_dirs.append(entry)
|
all_dirs.append(entry)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user