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/webpack-stats.dev.json
|
||||
frontend/node_modules
|
||||
frontend/package-lock.json
|
||||
|
||||
/.idea
|
@@ -41,7 +41,7 @@
|
||||
"remark-parse": "^5.0.0",
|
||||
"remark-rehype": "^3.0.0",
|
||||
"remark-slug": "^5.0.0",
|
||||
"seafile-js": "^0.2.12",
|
||||
"seafile-js": "^0.2.13",
|
||||
"seafile-ui": "^0.1.10",
|
||||
"sw-precache-webpack-plugin": "0.11.4",
|
||||
"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 ReactDOM from 'react-dom';
|
||||
import cookie from 'react-cookies';
|
||||
import { keyCodes, bytesToSize } from './utils';
|
||||
import { siteRoot, avatarInfo, gettext } from './constance';
|
||||
import editorUtilities from '../utils/editor-utilties';
|
||||
|
||||
|
||||
class Account extends Component {
|
||||
@@ -72,7 +72,7 @@ class Account extends Component {
|
||||
}
|
||||
|
||||
getAccountInfo = () => {
|
||||
this.props.seafileAPI.getAccountInfo().then(resp => {
|
||||
editorUtilities.getAccountInfo().then(resp => {
|
||||
this.setState({
|
||||
userName: resp.data.name,
|
||||
contactEmail: resp.data.email,
|
||||
|
@@ -1,8 +1,9 @@
|
||||
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 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 {
|
||||
|
||||
@@ -16,15 +17,27 @@ class MainPanel extends Component {
|
||||
window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit';
|
||||
}
|
||||
|
||||
onMainNavBarClick = (e) => {
|
||||
this.props.onMainNavBarClick(e.target.dataset.path);
|
||||
}
|
||||
|
||||
render() {
|
||||
var filePathList = this.props.filePath.split('/');
|
||||
var pathElem = filePathList.map((item, index) => {
|
||||
if (item == "") {
|
||||
|
||||
let filePathList = this.props.filePath.split('/');
|
||||
let nodePath = "";
|
||||
let pathElem = filePathList.map((item, index) => {
|
||||
if (item === "") {
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
if (index === (filePathList.length - 1)) {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Search seafileAPI={this.props.seafileAPI} onSearchedClick={this.props.onSearchedClick}/>
|
||||
<Account seafileAPI={this.props.seafileAPI} />
|
||||
<Search onSearchedClick={this.props.onSearchedClick}/>
|
||||
<Account />
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-main">
|
||||
@@ -50,13 +63,20 @@ class MainPanel extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-container">
|
||||
<MarkdownViewer
|
||||
{ this.props.isViewFileState && <MarkdownViewer
|
||||
markdownContent={this.props.content}
|
||||
latestContributor={this.props.latestContributor}
|
||||
lastModified = {this.props.lastModified}
|
||||
onLinkClick={this.props.onLinkClick}
|
||||
isFileLoading={this.props.isFileLoading}
|
||||
/>
|
||||
/>}
|
||||
{ !this.props.isViewFileState &&
|
||||
<TreeDirView
|
||||
node={this.props.changedNode}
|
||||
onMainNodeClick={this.props.onMainNodeClick}
|
||||
>
|
||||
</TreeDirView>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -55,14 +55,6 @@ class CreateFileForder extends React.Component {
|
||||
this.newInput.setSelectionRange(0,0);
|
||||
}
|
||||
|
||||
changeState(isFile) {
|
||||
if (isFile) {
|
||||
this.setState({childName: '.md'});
|
||||
} else{
|
||||
this.setState({childName: ""});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.changeState(nextProps.isFile);
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
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 {
|
||||
|
||||
@@ -78,13 +79,13 @@ class Search extends Component {
|
||||
isResultGetted: false
|
||||
})
|
||||
|
||||
this.source = this.props.seafileAPI.getSource();
|
||||
this.source = editorUtilities.getSource();
|
||||
this.sendRequest(queryData, this.source.token);
|
||||
}
|
||||
|
||||
sendRequest(queryData, cancelToken) {
|
||||
var _this = this;
|
||||
this.props.seafileAPI.searchFiles(queryData,cancelToken).then(res => {
|
||||
editorUtilities.searchFiles(queryData,cancelToken).then(res => {
|
||||
if (!res.data.total) {
|
||||
_this.setState({
|
||||
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 {
|
||||
|
||||
static create(attrs = {}) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Node` from a JSON `object`.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {Node}
|
||||
*/
|
||||
static fromJSON(object) {
|
||||
const {
|
||||
name,
|
||||
type,
|
||||
isExpanded = true,
|
||||
children = [],
|
||||
} = object;
|
||||
static deserializefromJson(object) {
|
||||
const {name, type, size, last_update_time, isExpanded = true, children = []} = object;
|
||||
|
||||
const node = new Node({
|
||||
name,
|
||||
type,
|
||||
size,
|
||||
last_update_time,
|
||||
isExpanded,
|
||||
children: children.map(Node.fromJSON),
|
||||
children: children.map(item => Node.deserializefromJson(item)),
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
constructor({ name, type, isExpanded, children }) {
|
||||
constructor({name, type, size, last_update_time, isExpanded, children}) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.children = children ? children : [];
|
||||
this.size = size;
|
||||
this.last_update_time = last_update_time;
|
||||
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() {
|
||||
if (!this.parent) {
|
||||
return this.name;
|
||||
} else {
|
||||
var p = this.parent.path;
|
||||
if (p === "/")
|
||||
return p + this.name;
|
||||
else
|
||||
return p + "/" + this.name;
|
||||
let p = this.parent.path;
|
||||
return p === "/" ? (p + this.name) : (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() {
|
||||
return this.children.length > 0;
|
||||
}
|
||||
|
||||
isImage() {
|
||||
let index = this.name.lastIndexOf(".");
|
||||
if (index == -1) {
|
||||
return false;
|
||||
} else {
|
||||
let type = this.name.substring(index).toLowerCase();
|
||||
if (type == ".png" || type == ".jpg") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isRoot() {
|
||||
return this.parent === undefined;
|
||||
}
|
||||
|
||||
isMarkdown() {
|
||||
@@ -100,39 +76,21 @@ class Node {
|
||||
return this.type == "dir";
|
||||
}
|
||||
|
||||
getleafPaths() {
|
||||
let paths = new Map();
|
||||
function getleafPath(node){
|
||||
if (node.hasChildren()) {
|
||||
let children = node.children;
|
||||
children.forEach(child => {
|
||||
if (child.hasChildren()) {
|
||||
getleafPath(child);
|
||||
isImage() {
|
||||
let index = this.name.lastIndexOf(".");
|
||||
if (index == -1) {
|
||||
return false;
|
||||
} else {
|
||||
let path = child.path;
|
||||
paths.set(path,child);
|
||||
}
|
||||
});
|
||||
let type = this.name.substring(index).toLowerCase();
|
||||
if (type == ".png" || type == ".jpg") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
getleafPath(this);
|
||||
return paths;
|
||||
}
|
||||
|
||||
getNodeByPath(path) {
|
||||
let paths = this.getleafPaths();
|
||||
if (paths.has(path)) {
|
||||
return paths.get(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON representation of the node.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
toJSON() {
|
||||
serializeToJson() {
|
||||
var children = []
|
||||
if (this.hasChildren()) {
|
||||
children = this.children.map(m => m.toJSON());
|
||||
@@ -141,6 +99,8 @@ class Node {
|
||||
const object = {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
size: this.size,
|
||||
last_update_time: this.last_update_time,
|
||||
isExpanded: this.isExpanded,
|
||||
children: children
|
||||
}
|
||||
@@ -150,5 +110,4 @@ class Node {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export { Node }
|
||||
export default Node;
|
@@ -44,10 +44,7 @@ class TreeNodeView extends React.Component {
|
||||
|
||||
handleCollapse = (e) => {
|
||||
e.stopPropagation();
|
||||
const { node } = this.props;
|
||||
if (this.props.treeView.toggleCollapse) {
|
||||
this.props.treeView.toggleCollapse(node);
|
||||
}
|
||||
this.props.onDirCollapse(e, this.props.node);
|
||||
}
|
||||
|
||||
onDragStart = (e) => {
|
||||
@@ -115,6 +112,7 @@ class TreeNodeView extends React.Component {
|
||||
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
||||
permission={this.props.permission}
|
||||
currentFilePath={this.props.currentFilePath}
|
||||
onDirCollapse={this.props.onDirCollapse}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -127,10 +125,11 @@ class TreeNodeView extends React.Component {
|
||||
|
||||
renderMenuController() {
|
||||
if (this.props.permission === "rw") {
|
||||
let isShow = (this.props.node.path === this.props.currentFilePath);
|
||||
return (
|
||||
<div className="right-icon">
|
||||
<MenuControl
|
||||
isShow={this.state.isMenuIconShow}
|
||||
isShow={this.state.isMenuIconShow || isShow}
|
||||
onClick={this.onMenuControlClick}
|
||||
/>
|
||||
</div>
|
||||
@@ -176,16 +175,15 @@ class TreeNodeView extends React.Component {
|
||||
if (node.path === this.props.currentFilePath) {
|
||||
hlClass = "tree-node-hight-light";
|
||||
}
|
||||
let customClass = "tree-node " + hlClass;
|
||||
|
||||
return (
|
||||
<div type={type} className={customClass} style={styles}>
|
||||
<div type={type} className="tree-node" style={styles}>
|
||||
<div
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onClick={this.onClick}
|
||||
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="left-icon">
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import TreeNodeView from './tree-node-view';
|
||||
import editorUtilities from '../../utils/editor-utilties'
|
||||
|
||||
class TreeView extends React.PureComponent {
|
||||
|
||||
@@ -10,29 +11,17 @@ class TreeView extends React.PureComponent {
|
||||
*/
|
||||
}
|
||||
|
||||
toggleCollapse = (node) => {
|
||||
const tree = this.props.treeData;
|
||||
node.isExpanded = !node.isExpanded;
|
||||
|
||||
// copy the tree to make PureComponent work
|
||||
this.setState({
|
||||
tree: tree.copy()
|
||||
});
|
||||
|
||||
this.change(tree);
|
||||
toggleCollapse = (e, node) => {
|
||||
this.props.onDirCollapse(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/plain", url);
|
||||
}
|
||||
|
||||
onNodeClick = (e, node) => {
|
||||
if (node.isDir()) {
|
||||
this.toggleCollapse(node);
|
||||
return;
|
||||
}
|
||||
this.props.onNodeClick(e, node);
|
||||
}
|
||||
|
||||
@@ -48,13 +37,14 @@ class TreeView extends React.PureComponent {
|
||||
return (
|
||||
<div className="tree-view tree">
|
||||
<TreeNodeView
|
||||
paddingLeft={20}
|
||||
paddingLeft={12}
|
||||
treeView={this}
|
||||
node={this.props.treeData.root}
|
||||
isNodeItemFrezee={this.props.isNodeItemFrezee}
|
||||
permission={this.props.permission}
|
||||
currentFilePath={this.props.currentFilePath}
|
||||
onShowContextMenu={this.props.onShowContextMenu}
|
||||
onDirCollapse={this.props.onDirCollapse}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Node } from './node'
|
||||
import Node from './node';
|
||||
import moment from 'moment';
|
||||
|
||||
class Tree {
|
||||
|
||||
@@ -6,80 +7,118 @@ class Tree {
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
copy() {
|
||||
clone() {
|
||||
var t = new Tree();
|
||||
if (this.root)
|
||||
t.root = this.root.copy();
|
||||
t.root = this.root.clone();
|
||||
return t;
|
||||
}
|
||||
|
||||
setRoot(dir) {
|
||||
this.root = dir;
|
||||
setRoot(node) {
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
addChildToNode(node, child) {
|
||||
child.parent = node;
|
||||
node.children.push(child);
|
||||
return child;
|
||||
addNodeToParent(node, parentNode) {
|
||||
node.parent = parentNode;
|
||||
parentNode.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addChild(node, child, insertIndex) {
|
||||
if (!(child instanceof Node)) {
|
||||
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;
|
||||
removeNodeFromParent(node, parentNode) {
|
||||
let children = parentNode.children;
|
||||
let removeNode = null;
|
||||
let index = null;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (child.path === children[i].path) {
|
||||
if (node.path === children[i].path) {
|
||||
removeNode = children[i];
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
child.parent = null;
|
||||
node.children.splice(index, 1);
|
||||
node.parent = null;
|
||||
parentNode.children.splice(index, 1);
|
||||
return removeNode ? removeNode : null;
|
||||
}
|
||||
|
||||
addNodeToTree(node) {
|
||||
let parentNode = this.getNodeParentFromTree(node);
|
||||
this.addChildToNode(parentNode, node);
|
||||
}
|
||||
|
||||
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;
|
||||
addNode(node) {
|
||||
let treeNodeParent = this.findNodeParentFromTree(node);
|
||||
if (treeNodeParent) {
|
||||
this.addNodeToParent(node, treeNodeParent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this.traverseBF(cb);
|
||||
|
||||
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(node){
|
||||
if (node.path === path) {
|
||||
findNode = node;
|
||||
function cb(treeNode) {
|
||||
if (treeNode.path === path) {
|
||||
findNode = treeNode;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -88,14 +127,18 @@ class Tree {
|
||||
return findNode;
|
||||
}
|
||||
|
||||
updateNodeParamValue(node, param, value) {
|
||||
let findNode = this.getNodeByPath(node.path);
|
||||
if (findNode[param]) {
|
||||
findNode[param] = value;
|
||||
return true;
|
||||
isNodeChild(parentNode, node) {
|
||||
let isChild = false;
|
||||
while(node.parent){
|
||||
if(node.parent.path === parentNode.path){
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
node = node.parent;
|
||||
}
|
||||
return isChild;
|
||||
}
|
||||
|
||||
|
||||
traverseDF(callback) {
|
||||
let stack = [];
|
||||
@@ -125,105 +168,46 @@ class Tree {
|
||||
}
|
||||
}
|
||||
|
||||
setOneNodeToActived({node}) {
|
||||
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) {
|
||||
parseModelToTree(model) {
|
||||
var node = new Node({
|
||||
name: model.name,
|
||||
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) {
|
||||
this.addChildToNode(node, this.parseNode(child));
|
||||
this.addNodeToParent(this.parseNodeToTree(child), node);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
parseFromList(rootObj, nodeList) {
|
||||
var root = new Node({
|
||||
name: rootObj.name,
|
||||
type: rootObj.type,
|
||||
isExpanded: rootObj.isExpanded
|
||||
});
|
||||
parseListToTree(nodeList) {
|
||||
|
||||
function getNodePath(parentPath, nodeName) {
|
||||
return parentPath === "/" ? (parentPath + nodeName) : (parentPath + "/" + nodeName);
|
||||
}
|
||||
|
||||
let root = new Node({name: '/', type: 'dir', isExpanded: true});
|
||||
this.root = root;
|
||||
|
||||
var map = new Map();
|
||||
let 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 treeNodeList = [];
|
||||
for (let nodeObj of nodeList) {
|
||||
var node = new Node({
|
||||
let node = new Node({
|
||||
name: nodeObj.name,
|
||||
type: nodeObj.type,
|
||||
size: nodeObj.size,
|
||||
last_update_time: moment.unix(nodeObj.last_update_time).fromNow(),
|
||||
isExpanded: false
|
||||
});
|
||||
node.parent_path = nodeObj.parent_path;
|
||||
treeNodeList.push(node);
|
||||
if (nodeObj.type === "dir") {
|
||||
map.set(joinPath(nodeObj.parent_path, nodeObj.name), node);
|
||||
if (node.isDir()) {
|
||||
map.set(getNodePath(node.parent_path, node.name), node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,26 +216,28 @@ class Tree {
|
||||
if (p === undefined) {
|
||||
console.log("warning: node " + node.parent_path + " not exist");
|
||||
} else {
|
||||
this.addChildToNode(p, node);
|
||||
}
|
||||
this.addNodeToParent(node, p);
|
||||
}
|
||||
}
|
||||
|
||||
parseNode(model) {
|
||||
}
|
||||
|
||||
parseNodeToTree(node) {
|
||||
var node = new Node({
|
||||
name: model.name,
|
||||
type: model.type,
|
||||
isExpanded: model.isExpanded
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
size: node.size,
|
||||
last_update_time: moment.unix(node.last_update_time).fromNow(),
|
||||
isExpanded: false
|
||||
});
|
||||
if (model.children instanceof Array) {
|
||||
for (let child of model.children) {
|
||||
this.addChildToNode(node, this.parseNode(child));
|
||||
if (node.children instanceof Array) {
|
||||
for (let child of node.children) {
|
||||
this.addNodeToParent(this.parseNodeToTree(child), node);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default Tree;
|
||||
|
@@ -102,13 +102,14 @@
|
||||
|
||||
.tree-node-inner {
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
height: 24px;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
.tree-node-inner .tree-node-text {
|
||||
padding-left: 1.2rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
padding-left: 2.8rem;
|
||||
width: calc(100% - 2.8rem);
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -122,27 +123,32 @@
|
||||
align-items:center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-left: 0.7rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.folder-toggle-icon {
|
||||
position: absolute;
|
||||
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 {
|
||||
margin-right: 0.4rem;
|
||||
margin-left: 0.1rem;
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
text-align: center;
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.tree-node-inner .right-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 1.5rem;
|
||||
color: #888;
|
||||
z-index: 2;
|
||||
@@ -155,11 +161,16 @@
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.625 !important;
|
||||
line-height: 1.625rem !important;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
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 {
|
||||
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 {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
margin-bottom: 8px;
|
||||
@@ -199,3 +190,12 @@ img[src=""] {
|
||||
.wiki-md-viewer-rendered-content.article h1 {
|
||||
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 ReactDOM from 'react-dom';
|
||||
import SidePanel from './components/SidePanel';
|
||||
import MainPanel from './components/MainPanel';
|
||||
import moment from 'moment';
|
||||
import cookie from 'react-cookies';
|
||||
import { SeafileAPI } from 'seafile-js';
|
||||
import { slug, repoID, serviceUrl, initialFilePath, siteRoot } from './components/constance';
|
||||
import { slug, repoID, serviceUrl, initialFilePath } from './components/constance';
|
||||
import editorUtilities from './utils/editor-utilties';
|
||||
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-regular.css';
|
||||
import './assets/css/fontawesome.css';
|
||||
import 'seafile-ui';
|
||||
import './css/side-panel.css';
|
||||
import './css/wiki.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 {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
content: '',
|
||||
tree_data: new Tree(),
|
||||
closeSideBar: false,
|
||||
fileName: '',
|
||||
filePath: '',
|
||||
latestContributor: '',
|
||||
lastModified: '',
|
||||
permission: '',
|
||||
isFileLoading: false,
|
||||
searchedPath: null,
|
||||
changedNode: null,
|
||||
isViewFileState: true
|
||||
};
|
||||
window.onpopstate = this.onpopstate;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadFile(initialFilePath);
|
||||
this.initSidePanelData();
|
||||
this.initMainPanelData(initialFilePath);
|
||||
}
|
||||
|
||||
fileNameFromPath(filePath) {
|
||||
let index = filePath.lastIndexOf("/");
|
||||
if (index == -1) {
|
||||
return "";
|
||||
initMainPanelData(filePath) {
|
||||
if (!this.isMarkdownFile(filePath)) {
|
||||
filePath = "/home.md";
|
||||
}
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
return (
|
||||
<div id="main" className="wiki-main">
|
||||
<SidePanel
|
||||
onFileClick={this.onFileClick}
|
||||
onNodeClick={this.onNodeClick}
|
||||
closeSideBar={this.state.closeSideBar}
|
||||
onCloseSide ={this.onCloseSide}
|
||||
editorUtilities={editorUtilities}
|
||||
treeData={this.state.tree_data}
|
||||
permission={this.state.permission}
|
||||
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
|
||||
content={this.state.content}
|
||||
fileName={this.state.fileName}
|
||||
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}
|
||||
onMenuClick={this.onMenuClick}
|
||||
onSearchedClick={this.onSearchedClick}
|
||||
latestContributor={this.state.latestContributor}
|
||||
lastModified={this.state.lastModified}
|
||||
seafileAPI={seafileAPI}
|
||||
permission={this.state.permission}
|
||||
isFileLoading={this.state.isFileLoading}
|
||||
onMainNavBarClick={this.onMainNavBarClick}
|
||||
onMainNodeClick={this.onMainNodeClick}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@@ -219,6 +219,11 @@ class WikiPagesDirView(APIView):
|
||||
error_msg = "Wiki not found."
|
||||
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
|
||||
if not wiki.check_access_wiki(request):
|
||||
error_msg = "Permission denied"
|
||||
@@ -233,12 +238,12 @@ class WikiPagesDirView(APIView):
|
||||
error_msg = "Internal Server Error"
|
||||
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:
|
||||
error_msg = 'Folder %s not found.' % '/'
|
||||
error_msg = 'Folder %s not found.' % path
|
||||
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({
|
||||
"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["name"] = dirent.obj_name
|
||||
entry["size"] = dirent.size
|
||||
entry["last_update_time"] = dirent.mtime
|
||||
|
||||
all_dirs.append(entry)
|
||||
|
||||
|
Reference in New Issue
Block a user