From c53a1f0b3b412d8eafbfbda04ccf5dc1a8821a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Mon, 28 Jan 2019 16:48:03 +0800 Subject: [PATCH] Wiki improve (#2894) --- frontend/src/components/index-viewer.js | 9 +- .../components/tree-view-2/tree-node-view.js | 167 ----- .../src/components/tree-view-2/tree-view.js | 60 -- frontend/src/components/tree-view-2/tree.js | 139 ---- .../src/components/tree-view/node-menu.js | 79 -- frontend/src/components/tree-view/node.js | 128 ---- .../{tree-view-2 => tree-view}/tree-helper.js | 0 .../tree-node-menu.js | 0 .../components/tree-view/tree-node-view.js | 257 +++---- .../{tree-view-2 => tree-view}/tree-node.js | 0 .../src/components/tree-view/tree-view.js | 62 +- frontend/src/components/tree-view/tree.js | 284 ++----- .../wiki-dir-list-item.js} | 32 +- .../wiki-dir-list-view.js} | 20 +- .../src/pages/repo-wiki-mode/side-panel.js | 9 +- frontend/src/pages/wiki/main-panel.js | 73 +- frontend/src/pages/wiki/side-panel.js | 248 +----- frontend/src/repo-wiki-mode.js | 6 +- frontend/src/wiki.js | 709 ++++++++---------- seahub/templates/wiki/wiki.html | 2 + seahub/wiki/views.py | 31 +- 21 files changed, 646 insertions(+), 1669 deletions(-) delete mode 100644 frontend/src/components/tree-view-2/tree-node-view.js delete mode 100644 frontend/src/components/tree-view-2/tree-view.js delete mode 100644 frontend/src/components/tree-view-2/tree.js delete mode 100644 frontend/src/components/tree-view/node-menu.js delete mode 100644 frontend/src/components/tree-view/node.js rename frontend/src/components/{tree-view-2 => tree-view}/tree-helper.js (100%) rename frontend/src/components/{tree-view-2 => tree-view}/tree-node-menu.js (100%) rename frontend/src/components/{tree-view-2 => tree-view}/tree-node.js (100%) rename frontend/src/components/{tree-dir-view/tree-dir-list.js => wiki-dir-list-view/wiki-dir-list-item.js} (56%) rename frontend/src/components/{tree-dir-view/tree-dir-view.js => wiki-dir-list-view/wiki-dir-list-view.js} (53%) diff --git a/frontend/src/components/index-viewer.js b/frontend/src/components/index-viewer.js index 98f87407d5..73e9ce801e 100644 --- a/frontend/src/components/index-viewer.js +++ b/frontend/src/components/index-viewer.js @@ -3,9 +3,8 @@ import PropTypes from 'prop-types'; import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer'; const viewerPropTypes = { - onLinkClick: PropTypes.func, - onContentRendered: PropTypes.func.isRequired, - indexContent: PropTypes.string, + indexContent: PropTypes.string.isRequired, + onLinkClick: PropTypes.func.isRequired, }; class IndexContentViewer extends React.Component { @@ -15,6 +14,10 @@ class IndexContentViewer extends React.Component { this.props.onLinkClick(event); } + onContentRendered = () => { + // todo + } + render() { return (
diff --git a/frontend/src/components/tree-view-2/tree-node-view.js b/frontend/src/components/tree-view-2/tree-node-view.js deleted file mode 100644 index 469bb2dfb0..0000000000 --- a/frontend/src/components/tree-view-2/tree-node-view.js +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import TreeNodeMenu from './tree-node-menu'; -import { permission } from '../../utils/constants'; - -const propTypes = { - node: PropTypes.object.isRequired, - currentPath: PropTypes.string.isRequired, - paddingLeft: PropTypes.number.isRequired, - isItemFreezed: PropTypes.bool.isRequired, - onNodeClick: PropTypes.func.isRequired, - onNodeExpanded: PropTypes.func.isRequired, - onNodeCollapse: PropTypes.func.isRequired, - onNodeDragStart: PropTypes.func.isRequired, - onFreezedItem: PropTypes.func.isRequired, - onUnFreezedItem: PropTypes.func.isRequired, -}; - -class TreeNodeView extends React.Component { - - constructor(props) { - super(props); - this.state = { - isHighlight: false, - isShowOperationMenu: false - }; - } - - onMouseEnter = () => { - if (!this.props.isItemFreezed) { - this.setState({ - isShowOperationMenu: true, - isHighlight: true, - }); - } - } - - onMouseLeave = () => { - if (!this.props.isItemFreezed) { - this.setState({ - isShowOperationMenu: false, - isHighlight: false, - }); - } - } - - onNodeClick = () => { - this.props.onNodeClick(this.props.node); - } - - onLoadToggle = () => { - let { node } = this.props; - if (node.isExpanded) { - this.props.onNodeCollapse(node); - } else { - this.props.onNodeExpanded(node); - } - } - - onNodeDragStart = (e) => { - this.props.onNodeDragStart(e, this.props.node); - } - - onUnFreezedItem = () => { - this.setState({isShowOperationMenu: false}); - this.props.onUnFreezedItem(); - } - - onMenuItemClick = (operation, node) => { - this.props.onMenuItemClick(operation, node); - } - - getNodeTypeAndIcon = () => { - let { node } = this.props; - let icon = ''; - let type = ''; - if (node.object.type === 'dir') { - icon = - type = 'dir'; - } else { - let index = node.object.name.lastIndexOf('.'); - if (index === -1) { - icon = - type = 'file'; - } else { - let suffix = node.object.name.slice(index).toLowerCase(); - if (suffix === '.png' || suffix === '.jpg') { - icon = - type = 'image'; - } else { - icon = - type = 'file'; - } - } - } - return {icon, type}; - } - - renderChildren = () => { - let { node, paddingLeft } = this.props; - if (!node.hasChildren()) { - return ''; - } - return ( -
- {node.children.map(item => { - return ( - - ); - })} -
- ); - } - - render() { - let { currentPath, node } = this.props; - let { type, icon } = this.getNodeTypeAndIcon(); - let hlClass = this.state.isHighlight ? 'tree-node-inner-hover ' : ''; - if (node.path === currentPath) { - hlClass = 'tree-node-hight-light'; - } - return ( -
-
-
{node.object.name}
-
- {type === 'dir' && (!node.isLoaded || (node.isLoaded && node.hasChildren())) && ( - e.stopPropagation()} - onClick={this.onLoadToggle} - > - )} - {icon} -
-
- {(permission && this.state.isShowOperationMenu) && ( - - )} -
-
- {node.isExpanded && this.renderChildren()} -
- ); - } -} - -TreeNodeView.propTypes = propTypes; - -export default TreeNodeView; diff --git a/frontend/src/components/tree-view-2/tree-view.js b/frontend/src/components/tree-view-2/tree-view.js deleted file mode 100644 index 6b85ec3ddc..0000000000 --- a/frontend/src/components/tree-view-2/tree-view.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import TreeNodeView from './tree-node-view'; - -const propTypes = { - treeData: PropTypes.object.isRequired, - currentPath: PropTypes.string.isRequired, - onMenuItemClick: PropTypes.func.isRequired, - onNodeClick: PropTypes.func.isRequired, - onNodeExpanded: PropTypes.func.isRequired, - onNodeCollapse: PropTypes.func.isRequired, -}; - -const PADDING_LEFT = 12; - -class TreeView extends React.Component { - - constructor(props) { - super(props); - this.state = { - isItemFreezed: false, - }; - } - - onNodeDragStart = (e, node) => { - // todo - } - - onFreezedItem = () => { - this.setState({isItemFreezed: true}); - } - - onUnFreezedItem = () => { - this.setState({isItemFreezed: false}); - } - - render() { - return ( -
- -
- ); - } -} - -TreeView.propTypes = propTypes; - -export default TreeView; diff --git a/frontend/src/components/tree-view-2/tree.js b/frontend/src/components/tree-view-2/tree.js deleted file mode 100644 index dc1a148448..0000000000 --- a/frontend/src/components/tree-view-2/tree.js +++ /dev/null @@ -1,139 +0,0 @@ -import TreeNode from './tree-node'; - -class Tree { - - constructor() { - this.root = null; - } - - clone() { - let tree = new Tree(); - if (this.root) { - tree.root = this.root.clone(); - } - return tree; - } - - setRoot(node) { - this.root = node; - } - - getNodeByPath(path) { - let findNode = null; - function callback(currentNode) { - if (currentNode.path === path) { - findNode = currentNode; - return true; - } - return false; - } - this.traverseDF(callback); - return findNode; - } - - getNodeChildrenObject(node) { - let objects = node.children.map(item => { - let object = item.object; - return object; - }); - return objects; - } - - addNodeToParent(node, parentNode) { - parentNode.addChild(node); - } - - addNodeListToParent(nodeList, parentNode) { - nodeList.forEach(node => { - parentNode.addChild(node); - }); - } - - deleteNode(node) { - let parentNode = this.getNodeByPath(node.parentNode.path); - parentNode.deleteChild(node); - } - - deleteNodeList(nodeList) { - nodeList.forEach(node => { - this.deleteNode(node); - }); - } - - renameNode(node, newName) { - node.rename(newName); - } - - updateNode(node, keys, newValues) { - node.updateObjectParam(keys, newValues); - } - - moveNode(node, destNode) { - this.deleteNode(node); - destNode.addChild(node); - } - - copyNode(node, destNode) { - destNode.addChild(node); - } - - traverseDF(callback) { - let stack = []; - let found = false; - stack.unshift(this.root); - let currentNode = stack.shift(); - while (!found && currentNode) { - found = callback(currentNode) == true ? true : false; - if (!found) { - stack.unshift(...currentNode.children); - currentNode = stack.shift(); - } - } - } - - traverseBF(callback) { - let queue = []; - let found = false; - queue.push(this.root); - let currentNode = queue.shift(); - while (!found && currentNode) { - found = callback(currentNode) === true ? true : false; - if (!found) { - queue.push(...currentNode.children); - currentNode = queue.shift(); - } - } - } - - expandNode(node) { - node.isExpanded = true; - while (node.parentNode) { - node.parentNode.isExpanded = true; - node = node.parentNode; - } - } - - collapseNode(node) { - node.isExpanded = false; - } - - isNodeChild(node, parentNode) { - return parentNode.children.some(item => { - return item.path === node.path; - }); - } - - serializeToJson() { - return this.root.serializeToJson(); - } - - deserializefromJson(json) { - let root = TreeNode.deserializefromJson(json); - let tree = new Tree(); - tree.setRoot(root); - return tree; - } - -} - -export default Tree; diff --git a/frontend/src/components/tree-view/node-menu.js b/frontend/src/components/tree-view/node-menu.js deleted file mode 100644 index 1488c63d07..0000000000 --- a/frontend/src/components/tree-view/node-menu.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; - -const propTypes = { - menuPosition: PropTypes.object.isRequired, - currentNode: PropTypes.object.isRequired, - toggleRename: PropTypes.func.isRequired, - toggleDelete: PropTypes.func.isRequired, - toggleAddFile: PropTypes.func.isRequired, - toggleAddFolder: PropTypes.func.isRequired, -}; - -class NodeMenu extends React.Component { - - toggleAddFile = () => { - this.props.toggleAddFile(); - } - - toggleAddFolder = () => { - this.props.toggleAddFolder(); - } - - toggleRename = () => { - this.props.toggleRename(); - } - - toggleDelete = () => { - this.props.toggleDelete(); - } - - renderNodeMenu() { - let position = this.props.menuPosition; - let style = {position: 'fixed',left: position.left, top: position.top, display: 'block'}; - - if (this.props.currentNode.type === 'dir') { - if (this.props.currentNode.name === '/') { - return ( - - ); - } - - return ( - - ); - } - - return ( - - ); - - } - - render() { - if (!this.props.currentNode) { - return (
); - } - return ( -
- {this.renderNodeMenu()} -
- ); - } -} - -NodeMenu.propTypes = propTypes; - -export default NodeMenu; diff --git a/frontend/src/components/tree-view/node.js b/frontend/src/components/tree-view/node.js deleted file mode 100644 index 42bbb8f9e9..0000000000 --- a/frontend/src/components/tree-view/node.js +++ /dev/null @@ -1,128 +0,0 @@ -class Node { - - static deserializefromJson(object) { - const {name, type, size, last_update_time, permission, parent_path, isExpanded = true, children = []} = object; - - const node = new Node({ - name, - type, - size, - last_update_time, - permission, - parent_path, - isExpanded, - children: children.map(item => Node.deserializefromJson(item)), - }); - - return node; - } - - constructor({name, type, size, last_update_time, permission, parent_path, isExpanded, children}) { - this.name = name; - this.type = type; - this.size = size; - this.last_update_time = last_update_time; - this.permission = permission; - this.parent_path = parent_path; - 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, - permission: this.permission, - parent_path: this.parent_path, - 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 { - let p = this.parent.path; - return p === '/' ? (p + this.name) : (p + '/' + this.name); - } - } - - hasChildren() { - return this.children.length > 0; - } - - isRoot() { - return this.parent === undefined; - } - - isMarkdown() { - if (this.isDir()) { - return false; - } - let index = this.name.lastIndexOf('.'); - if (index == -1) { - return false; - } else { - let type = this.name.substring(index).toLowerCase(); - if (type == '.md' || type == '.markdown') { - return true; - } else { - return false; - } - } - } - - isFile() { - return this.type === 'file'; - } - - isDir() { - return this.type == 'dir'; - } - - 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; - } - } - } - - serializeToJson() { - var children = []; - if (this.hasChildren()) { - children = this.children.map(m => m.toJSON()); - } - - const object = { - name: this.name, - type: this.type, - size: this.size, - last_update_time: this.last_update_time, - permission: this.permission, - parent_path: this.parent_path, - isExpanded: this.isExpanded, - children: children - }; - - return object; - } - -} - -export default Node; diff --git a/frontend/src/components/tree-view-2/tree-helper.js b/frontend/src/components/tree-view/tree-helper.js similarity index 100% rename from frontend/src/components/tree-view-2/tree-helper.js rename to frontend/src/components/tree-view/tree-helper.js diff --git a/frontend/src/components/tree-view-2/tree-node-menu.js b/frontend/src/components/tree-view/tree-node-menu.js similarity index 100% rename from frontend/src/components/tree-view-2/tree-node-menu.js rename to frontend/src/components/tree-view/tree-node-menu.js diff --git a/frontend/src/components/tree-view/tree-node-view.js b/frontend/src/components/tree-view/tree-node-view.js index 2af8ec3dc3..51a3fd66a6 100644 --- a/frontend/src/components/tree-view/tree-node-view.js +++ b/frontend/src/components/tree-view/tree-node-view.js @@ -1,210 +1,169 @@ import React from 'react'; import PropTypes from 'prop-types'; -import MenuControl from '../menu-control'; +import TreeNodeMenu from './tree-node-menu'; import { permission } from '../../utils/constants'; const propTypes = { - isNodeItemFrezee: PropTypes.bool.isRequired, + node: PropTypes.object.isRequired, currentPath: PropTypes.string.isRequired, paddingLeft: PropTypes.number.isRequired, - node: PropTypes.object.isRequired, - treeView: PropTypes.object.isRequired, - onDirCollapse: PropTypes.func.isRequired, + isNodeMenuShow: PropTypes.bool.isRequired, + isItemFreezed: PropTypes.bool.isRequired, + onNodeClick: PropTypes.func.isRequired, + onNodeExpanded: PropTypes.func.isRequired, + onNodeCollapse: PropTypes.func.isRequired, + onNodeDragStart: PropTypes.func.isRequired, + onFreezedItem: PropTypes.func.isRequired, + onUnFreezedItem: PropTypes.func.isRequired, }; -function sortByType(a, b) { - if (a.type == 'dir' && b.type != 'dir') { - return -1; - } else if (a.type != 'dir' && b.type == 'dir') { - return 1; - } else { - return a.name.localeCompare(b.name); - } -} - class TreeNodeView extends React.Component { constructor(props) { super(props); this.state = { - isMenuIconShow: false + isHighlight: false, + isShowOperationMenu: false }; } - onClick = () => { - // e.nativeEvent.stopImmediatePropagation(); - let { node } = this.props; - this.props.treeView.onNodeClick(node); - } - onMouseEnter = () => { - if (!this.props.isNodeItemFrezee) { + if (!this.props.isItemFreezed) { this.setState({ - isMenuIconShow: true + isShowOperationMenu: true, + isHighlight: true, }); } } onMouseLeave = () => { - if (!this.props.isNodeItemFrezee) { + if (!this.props.isItemFreezed) { this.setState({ - isMenuIconShow: false + isShowOperationMenu: false, + isHighlight: false, }); } } - handleCollapse = (e) => { - e.stopPropagation(); - this.props.onDirCollapse(this.props.node); + onNodeClick = () => { + this.props.onNodeClick(this.props.node); } - onDragStart = (e) => { - const { node } = this.props; - this.props.treeView.onDragStart(e, node); - } - - onMenuControlClick = (e) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - const { node } = this.props; - this.props.treeView.onShowContextMenu(e, node); - } - - hideMenuIcon = () => { - this.setState({ - isMenuIconShow: false - }); - } - - componentDidMount() { - document.addEventListener('click', this.hideMenuIcon); - } - - componentWillUnmount() { - document.removeEventListener('click', this.hideMenuIcon); - } - - renderCollapse = () => { - const { node } = this.props; - - if (node.hasChildren()) { - const { isExpanded } = node; - return ( - e.stopPropagation()} - onClick={this.handleCollapse} - /> - ); + onLoadToggle = () => { + let { node } = this.props; + if (node.isExpanded) { + this.props.onNodeCollapse(node); + } else { + this.props.onNodeExpanded(node); } - - return null; } - renderChildren = () => { - const { node } = this.props; - if (node.children && node.children.length) { - const childrenStyles = { - paddingLeft: this.props.paddingLeft - }; - var l = node.children.sort(sortByType); - /* - the `key` property is needed. Otherwise there is a warning in the console - */ - return ( -
- {l.map(child => { - return ( - - ); - })} -
- ); - } - - return null; + onNodeDragStart = (e) => { + this.props.onNodeDragStart(e, this.props.node); } - renderMenuController() { - if (permission) { - let isShow = (this.props.node.path === this.props.currentPath); - return ( -
- -
- ); - } - return; + onUnFreezedItem = () => { + this.setState({isShowOperationMenu: false}); + this.props.onUnFreezedItem(); } - getNodeTypeAndIcon() { - const node = this.props.node; + onMenuItemClick = (operation, node) => { + this.props.onMenuItemClick(operation, node); + } + + getNodeTypeAndIcon = () => { + let { node } = this.props; let icon = ''; let type = ''; - if (node.type === 'dir') { - icon = ; + if (node.object.type === 'dir') { + icon = type = 'dir'; } else { - let index = node.name.lastIndexOf('.'); - if (index === -1) { - icon = ; + let index = node.object.name.lastIndexOf('.'); + if (index === -1) { + icon = type = 'file'; } else { - type = node.name.substring(index).toLowerCase(); - if (type === '.png' || type === '.jpg') { - icon = ; + let suffix = node.object.name.slice(index).toLowerCase(); + if (suffix === '.png' || suffix === '.jpg') { + icon = type = 'image'; } else { - icon = ; + icon = type = 'file'; } } } - - return { type, icon }; + return {icon, type}; } - render() { - const styles = {}; - let node = this.props.node; - let { type, icon } = this.getNodeTypeAndIcon(); - let hlClass = ''; - if (node.path === this.props.currentPath) { - hlClass = 'tree-node-hight-light'; + renderChildren = () => { + let { node, paddingLeft } = this.props; + if (!node.hasChildren()) { + return ''; } - return ( -
-
-
{node.name}
-
- {this.renderCollapse()} - {icon} -
- {this.renderMenuController()} -
- {node.isExpanded ? this.renderChildren() : null} +
+ {node.children.map(item => { + return ( + + ); + })}
); } + render() { + let { currentPath, node, isNodeMenuShow } = this.props; + let { type, icon } = this.getNodeTypeAndIcon(); + let hlClass = this.state.isHighlight ? 'tree-node-inner-hover ' : ''; + if (node.path === currentPath) { + hlClass = 'tree-node-hight-light'; + } + return ( +
+
+
{node.object.name}
+
+ {type === 'dir' && (!node.isLoaded || (node.isLoaded && node.hasChildren())) && ( + e.stopPropagation()} + onClick={this.onLoadToggle} + > + )} + {icon} +
+ {isNodeMenuShow && ( +
+ {(permission && this.state.isShowOperationMenu) && ( + + )} +
+ )} +
+ {node.isExpanded && this.renderChildren()} +
+ ); + } } TreeNodeView.propTypes = propTypes; diff --git a/frontend/src/components/tree-view-2/tree-node.js b/frontend/src/components/tree-view/tree-node.js similarity index 100% rename from frontend/src/components/tree-view-2/tree-node.js rename to frontend/src/components/tree-view/tree-node.js diff --git a/frontend/src/components/tree-view/tree-view.js b/frontend/src/components/tree-view/tree-view.js index 1871211adb..fd986c098a 100644 --- a/frontend/src/components/tree-view/tree-view.js +++ b/frontend/src/components/tree-view/tree-view.js @@ -1,62 +1,60 @@ import React from 'react'; import PropTypes from 'prop-types'; import TreeNodeView from './tree-node-view'; -import editorUtilities from '../../utils/editor-utilties'; const propTypes = { - permission: PropTypes.string, - isNodeItemFrezee: PropTypes.bool.isRequired, - currentPath: PropTypes.string.isRequired, + isNodeMenuShow: PropTypes.bool.isRequired, treeData: PropTypes.object.isRequired, - onShowContextMenu: PropTypes.func.isRequired, + currentPath: PropTypes.string.isRequired, + onMenuItemClick: PropTypes.func, onNodeClick: PropTypes.func.isRequired, - onDirCollapse: PropTypes.func.isRequired, + onNodeExpanded: PropTypes.func.isRequired, + onNodeCollapse: PropTypes.func.isRequired, }; -class TreeView extends React.PureComponent { +const PADDING_LEFT = 12; - change = (tree) => { - /* - this._updated = true; - if (this.props.onChange) this.props.onChange(tree.obj); - */ +class TreeView extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemFreezed: false, + }; } - onDragStart = (e, node) => { - const url = editorUtilities.getFileURL(node); - e.dataTransfer.setData('text/uri-list', url); - e.dataTransfer.setData('text/plain', url); + onNodeDragStart = (e, node) => { + // todo } - onNodeClick = (node) => { - this.props.onNodeClick(node); + onFreezedItem = () => { + this.setState({isItemFreezed: true}); } - onShowContextMenu = (e, node) => { - this.props.onShowContextMenu(e, node); + onUnFreezedItem = () => { + this.setState({isItemFreezed: false}); } render() { - if (!this.props.treeData.root) { - return
Loading...
; - } - return (
-
); } - } TreeView.propTypes = propTypes; diff --git a/frontend/src/components/tree-view/tree.js b/frontend/src/components/tree-view/tree.js index b094a5a725..dc1a148448 100644 --- a/frontend/src/components/tree-view/tree.js +++ b/frontend/src/components/tree-view/tree.js @@ -1,181 +1,82 @@ -import Node from './node'; -import moment from 'moment'; -import { Utils } from '../../utils/utils'; - -const lang = window.app.config.lang; -moment.locale(lang); +import TreeNode from './tree-node'; class Tree { - + constructor() { this.root = null; } clone() { - var t = new Tree(); - if (this.root) - t.root = this.root.clone(); - return t; + let tree = new Tree(); + if (this.root) { + tree.root = this.root.clone(); + } + return tree; } setRoot(node) { this.root = node; } - addNodeToParent(node, parentNode) { - node.parent = parentNode; - parentNode.children.push(node); - return node; - } - - removeNodeFromParent(node, parentNode) { - let children = parentNode.children; - let removeNode = null; - let index = null; - for (let i = 0; i < children.length; i++) { - if (node.path === children[i].path) { - removeNode = children[i]; - index = i; - break; + getNodeByPath(path) { + let findNode = null; + function callback(currentNode) { + if (currentNode.path === path) { + findNode = currentNode; + return true; } + return false; } - node.parent = null; - parentNode.children.splice(index, 1); - return removeNode ? removeNode : null; + this.traverseDF(callback); + return findNode; } - addNode(node) { - let treeNodeParent = this.findNodeParentFromTree(node); - if (treeNodeParent) { - this.addNodeToParent(node, treeNodeParent); - return true; - } - return false; + getNodeChildrenObject(node) { + let objects = node.children.map(item => { + let object = item.object; + return object; + }); + return objects; + } + + addNodeToParent(node, parentNode) { + parentNode.addChild(node); + } + + addNodeListToParent(nodeList, parentNode) { + nodeList.forEach(node => { + parentNode.addChild(node); + }); } deleteNode(node) { - let treeNodeParent = this.findNodeParentFromTree(node); - if (treeNodeParent) { - this.removeNodeFromParent(node, treeNodeParent); - return true; - } - return false; + let parentNode = this.getNodeByPath(node.parentNode.path); + parentNode.deleteChild(node); } - deleteNodeByPath(path) { - let node = this.getNodeByPath(path); - this.deleteNode(node); - } - - moveNode(node, moveToNode, isDestroy) { - let moveNode = node.clone(); - this.addNodeToParent(moveNode, moveToNode); - if (isDestroy) { + deleteNodeList(nodeList) { + nodeList.forEach(node => { this.deleteNode(node); - } + }); } - moveNodeByPath(path, moveToPath, isDestroy) { - let node = this.getNodeByPath(path); - let moveToNode = this.getNodeByPath(moveToPath); - this.moveNode(node, moveToNode, isDestroy); + renameNode(node, newName) { + node.rename(newName); } - updateNodeParam(node, param, newValue) { - let treeNode = this.findNodeFromTree(node); - if (treeNode && treeNode[param]) { - treeNode[param] = newValue; - return true; - } - return false; + updateNode(node, keys, newValues) { + node.updateObjectParam(keys, newValues); } - updateNodeParamByPath(path, param, newValue) { - let node = this.getNodeByPath(path); - this.updateNodeParam(node, param, newValue); + moveNode(node, destNode) { + this.deleteNode(node); + destNode.addChild(node); } - findNode(node) { - return this.findNodeFromTree(node); + copyNode(node, destNode) { + destNode.addChild(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; - } - - expandNode(node) { - let treeNode = this.findNodeFromTree(node); - if (treeNode) { - treeNode.isExpanded = true; - while (treeNode.parent) { - treeNode.parent.isExpanded = true; - treeNode = treeNode.parent; - } - return true; - } - return false; - } - - collapseNode(node) { - let treeNode = this.findNodeFromTree(node); - if (treeNode) { - treeNode.isExpanded = false; - return true; - } - return false; - } - - resetTreeState() { - 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) { let stack = []; let found = false; @@ -204,82 +105,33 @@ class Tree { } } - parseModelToTree(model) { - var node = new Node({ - name: model.name, - type: model.type, - size: Utils.bytesToSize(model.size), - last_update_time: moment.unix(model.last_update_time).fromNow(), - permission: model.permission, - parent_path: model.parent_path, - isExpanded: false - }); - if (model.children instanceof Array) { - for (let child of model.children) { - this.addNodeToParent(this.parseNodeToTree(child), node); - } + expandNode(node) { + node.isExpanded = true; + while (node.parentNode) { + node.parentNode.isExpanded = true; + node = node.parentNode; } - return node; + } + + collapseNode(node) { + node.isExpanded = false; } - parseListToTree(nodeList) { - - function getNodePath(parentPath, nodeName) { - return parentPath === '/' ? (parentPath + nodeName) : (parentPath + '/' + nodeName); - } - - 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) { - let node = new Node({ - name: nodeObj.name, - type: nodeObj.type, - size: Utils.bytesToSize(nodeObj.size), - last_update_time: moment.unix(nodeObj.last_update_time).fromNow(), - permission: nodeObj.permission, - parent_path: nodeObj.parent_path, - isExpanded: false - }); - node.parent_path = nodeObj.parent_path; - treeNodeList.push(node); - if (node.isDir()) { - map.set(getNodePath(node.parent_path, node.name), node); - } - } - - for (let node of treeNodeList) { - let p = map.get(node.parent_path); - if (p === undefined) { - /* eslint-disable */ - console.log('warning: node ' + node.parent_path + ' not exist'); - /* eslint-enable */ - } else { - this.addNodeToParent(node, p); - } - } - + isNodeChild(node, parentNode) { + return parentNode.children.some(item => { + return item.path === node.path; + }); } - parseNodeToTree(node) { - var newNode = new Node({ - name: node.name, - type: node.type, - size: Utils.bytesToSize(node.size), - last_update_time: moment.unix(node.last_update_time).fromNow(), - permission: node.permission, - parent_path: node.parent_path, - isExpanded: false - }); - if (node.children instanceof Array) { - for (let child of node.children) { - this.addNodeToParent(this.parseNodeToTree(child), newNode); - } - } - return newNode; + serializeToJson() { + return this.root.serializeToJson(); + } + + deserializefromJson(json) { + let root = TreeNode.deserializefromJson(json); + let tree = new Tree(); + tree.setRoot(root); + return tree; } } diff --git a/frontend/src/components/tree-dir-view/tree-dir-list.js b/frontend/src/components/wiki-dir-list-view/wiki-dir-list-item.js similarity index 56% rename from frontend/src/components/tree-dir-view/tree-dir-list.js rename to frontend/src/components/wiki-dir-list-view/wiki-dir-list-item.js index 8333fd2519..324159a53f 100644 --- a/frontend/src/components/tree-dir-view/tree-dir-list.js +++ b/frontend/src/components/wiki-dir-list-view/wiki-dir-list-item.js @@ -4,17 +4,17 @@ import { siteRoot } from '../../utils/constants'; import { Utils } from '../../utils/utils'; const propTypes = { - node: PropTypes.object.isRequired, - onMainNodeClick: PropTypes.func.isRequired, + path: PropTypes.string.isRequired, + dirent: PropTypes.object.isRequired, + onDirentClick: PropTypes.func.isRequired, }; -class TreeDirList extends React.Component { +class WikiDirListItem extends React.Component { constructor(props) { super(props); this.state = { highlight: false, - isOperationShow: false, }; } @@ -26,22 +26,22 @@ class TreeDirList extends React.Component { this.setState({highlight: false}); } - onMainNodeClick = (e) => { + onDirentClick = (e) => { e.preventDefault(); - this.props.onMainNodeClick(this.props.node); + this.props.onDirentClick(this.props.dirent); } render() { - let node = this.props.node; - let href = siteRoot + 'wikis' + node.path; + let { path, dirent } = this.props; + let href = siteRoot + 'wikis' + Utils.joinPath(path, dirent.name); let size = Utils.isHiDPI() ? 48 : 24; let iconUrl = ''; - if (node.type === 'file') { - iconUrl = Utils.getFileIconUrl(node.name, size); + if (dirent.type === 'file') { + iconUrl = Utils.getFileIconUrl(dirent.name, size); } else { let isReadOnly = false; - if (node.permission === 'r' || node.permission === 'preview') { + if (dirent.permission === 'r' || dirent.permission === 'preview') { isReadOnly = true; } iconUrl = Utils.getFolderIconUrl({isReadOnly, size}); @@ -53,15 +53,15 @@ class TreeDirList extends React.Component { - {node.name} + {dirent.name} - {node.size} - {node.last_update_time} + {dirent.size} + {dirent.last_update_time} ); } } -TreeDirList.propTypes = propTypes; +WikiDirListItem.propTypes = propTypes; -export default TreeDirList; +export default WikiDirListItem; diff --git a/frontend/src/components/tree-dir-view/tree-dir-view.js b/frontend/src/components/wiki-dir-list-view/wiki-dir-list-view.js similarity index 53% rename from frontend/src/components/tree-dir-view/tree-dir-view.js rename to frontend/src/components/wiki-dir-list-view/wiki-dir-list-view.js index b349b985a1..f1069e2719 100644 --- a/frontend/src/components/tree-dir-view/tree-dir-view.js +++ b/frontend/src/components/wiki-dir-list-view/wiki-dir-list-view.js @@ -1,19 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; -import TreeDirList from './tree-dir-list'; +import WikiDirListItem from './wiki-dir-list-item'; const propTypes = { - node: PropTypes.object.isRequired, - onMainNodeClick: PropTypes.func.isRequired, + path: PropTypes.string.isRequired, + direntList: PropTypes.array.isRequired, + onDirentClick: PropTypes.func.isRequired, }; -class TreeDirView extends React.Component { +class WikiDirListView extends React.Component { render() { - let node = this.props.node; - let children = node.hasChildren() ? node.children : null; - return ( @@ -25,9 +23,9 @@ class TreeDirView extends React.Component { - {children && children.map((node, index) => { + {this.props.direntList.length !== 0 && this.props.direntList.map((dirent, index) => { return ( - + ); })} @@ -36,6 +34,6 @@ class TreeDirView extends React.Component { } } -TreeDirView.propTypes = propTypes; +WikiDirListView.propTypes = propTypes; -export default TreeDirView; \ No newline at end of file +export default WikiDirListView; diff --git a/frontend/src/pages/repo-wiki-mode/side-panel.js b/frontend/src/pages/repo-wiki-mode/side-panel.js index 22e1508791..2f4a7dcd7c 100644 --- a/frontend/src/pages/repo-wiki-mode/side-panel.js +++ b/frontend/src/pages/repo-wiki-mode/side-panel.js @@ -1,9 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { gettext, permission } from '../../utils/constants'; import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; -import { gettext } from '../../utils/constants'; -import { Utils } from '../../utils/utils'; -import TreeView from '../../components/tree-view-2/tree-view'; +import TreeView from '../../components/tree-view/tree-view'; import Logo from '../../components/logo'; import Loading from '../../components/loading'; import toaster from '../../components/toast'; @@ -43,6 +42,7 @@ class SidePanel extends Component { isAddFolderDialogShow: false, isRenameDialogShow: false, }; + this.isNodeMenuShow = true; } componentWillReceiveProps(nextProps) { @@ -166,7 +166,7 @@ class SidePanel extends Component {

{gettext('Files')}
- {this.state.isMenuIconShow && ( + {(permission && this.state.isMenuIconShow) && ( ) : ( { this.props.onMenuClick(); } onEditClick = (e) => { - // const w=window.open('about:blank') e.preventDefault(); window.location.href= siteRoot + 'lib/' + repoID + '/file' + this.props.filePath + '?mode=edit'; } @@ -47,16 +38,14 @@ class MainPanel extends Component { this.props.onMainNavBarClick(e.target.dataset.path); } - - render() { - - let filePathList = this.props.filePath.split('/'); + renderNavPath = () => { + let paths = this.props.path.split('/'); let nodePath = ''; - let pathElem = filePathList.map((item, index) => { + let pathElem = paths.map((item, index) => { if (item === '') { return; } - if (index === (filePathList.length - 1)) { + if (index === (paths.length - 1)) { return ( /{item} ); @@ -75,18 +64,21 @@ class MainPanel extends Component { ); } }); + return pathElem; + } + + render() { return (
- {loginUser && + {username && (
- { - this.props.permission === 'rw' && + {this.props.permission === 'rw' && ( - } + )}
- } + )}
- {loginUser && + {username && {gettext('Wikis')} / } {slug} - {pathElem} + {this.renderNavPath()}
- {this.props.isViewFileState && + {this.props.isDataLoading && } + {(!this.props.isDataLoading && this.props.isViewFile) && ( - } - {!this.props.isViewFileState && - - } + )} + {(!this.props.isDataLoading && !this.props.isViewFile) && ( + + )}
diff --git a/frontend/src/pages/wiki/side-panel.js b/frontend/src/pages/wiki/side-panel.js index 5e80fa3061..059777e7a1 100644 --- a/frontend/src/pages/wiki/side-panel.js +++ b/frontend/src/pages/wiki/side-panel.js @@ -1,194 +1,52 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; import { gettext, siteRoot, repoID } from '../../utils/constants'; import Logo from '../../components/logo'; +import Loading from '../../components/loading'; import TreeView from '../../components/tree-view/tree-view'; -import NodeMenu from '../../components/tree-view/node-menu'; -import Delete from '../../components/dialog/delete-dialog'; -import Rename from '../../components/dialog/rename-dialog'; -import CreateFolder from '../../components/dialog/create-folder-dialog'; -import CreateFile from '../../components/dialog/create-file-dialog'; import IndexContentViewer from '../../components/index-viewer'; const propTypes = { - changedNode: PropTypes.object, - treeData: PropTypes.object.isRequired, - currentPath: PropTypes.string.isRequired, closeSideBar: PropTypes.bool.isRequired, + isTreeDataLoading: PropTypes.bool.isRequired, + treeData: PropTypes.object.isRequired, + indexNode: PropTypes.object, + indexContent: PropTypes.string.isRequired, + currentPath: PropTypes.string.isRequired, onCloseSide: PropTypes.func.isRequired, - onDirCollapse: PropTypes.func.isRequired, onNodeClick: PropTypes.func.isRequired, - onRenameNode: PropTypes.func.isRequired, - onDeleteNode: PropTypes.func.isRequired, - onAddFileNode: PropTypes.func.isRequired, - onAddFolderNode: PropTypes.func.isRequired, - onLinkClick: PropTypes.func, - hasIndex: PropTypes.bool.isRequired, - indexContent: PropTypes.string, - indexPath: PropTypes.string, - indexPermission: PropTypes.string, + onNodeCollapse: PropTypes.func.isRequired, + onNodeExpanded: PropTypes.func.isRequired, + onLinkClick: PropTypes.func.isRequired, }; 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, - isHeaderMenuShow: false, - isDeleteDialogShow: false, - isAddFileDialogShow: false, - isAddFolderDialogShow: false, - isRenameDialogShow: false, - }; - } - - componentDidMount() { - document.addEventListener('click', this.onHideContextMenu); - } - - componentWillReceiveProps(nextProps) { - this.setState({currentNode: nextProps.changedNode}); - } - - componentWillUnmount() { - document.removeEventListener('click', this.onHideContextMenu); - } - - onMouseEnter = () => { - this.setState({isMenuIconShow: true}); - } - - onMouseLeave = () => { - this.setState({isMenuIconShow: false}); - } - - onDropdownToggleClick = (e) => { - e.preventDefault(); - this.toggleOperationMenu(); - } - - toggleOperationMenu = () => { - this.setState({isHeaderMenuShow: !this.state.isHeaderMenuShow}); - } - - 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 - }); - } - - onHideContextMenu = () => { - if (!this.state.isShowMenu) { - return; - } - this.setState({ - isShowMenu: false, - isNodeItemFrezee: false - }); - } - - onAddFolderToggle = () => { - this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow}); - this.onHideContextMenu(); - } - - onAddFileToggle = () => { - this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow}); - this.onHideContextMenu(); - } - - onRenameToggle = () => { - this.setState({isRenameDialogShow: !this.state.isRenameDialogShow}); - this.onHideContextMenu(); - } - - onDeleteToggle = () => { - this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); - this.onHideContextMenu(); - } - - onAddFolderNode = (dirPath) => { - this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow}); - this.props.onAddFolderNode(dirPath); - } - - onAddFileNode = (filePath) => { - this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow}); - this.props.onAddFileNode(filePath); - } - - onRenameNode = (newName) => { - this.setState({isRenameDialogShow: !this.state.isRenameDialogShow}); - let node = this.state.currentNode; - this.props.onRenameNode(node, newName); - } - - onDeleteNode = () => { - this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); - let node = this.state.currentNode; - this.props.onDeleteNode(node); - } - - addFolderCancel = () => { - this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow}); - } - - addFileCancel = () => { - this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow}); - } - - deleteCancel = () => { - this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); - } - - renameCancel = () => { - this.setState({isRenameDialogShow: !this.state.isRenameDialogShow}); + this.isNodeMenuShow = false; } onEditClick = (e) => { e.preventDefault(); - window.location.href= siteRoot + 'lib/' + repoID + '/file' + this.props.indexPath + '?mode=edit'; - } - - onContentRendered = () => { - // todo + let indexNode = this.props.indexNode + window.location.href= siteRoot + 'lib/' + repoID + '/file' + indexNode.path + '?mode=edit'; } renderIndexView = () => { + let indexNode = this.props.indexNode; return (

{gettext('Contents')} - {this.props.indexPermission === 'rw' && + {indexNode.object.permission === 'rw' && }

@@ -198,76 +56,16 @@ class SidePanel extends Component { renderTreeView = () => { return ( -

- {gettext('Pages')} -
- {this.state.isMenuIconShow && ( - - - - {gettext('New Folder')} - {gettext('New File')} - - - )} -
-

+

{gettext('Pages')}

{this.props.treeData && ( - )} - {this.state.isShowMenu && ( - - )} - {this.state.isDeleteDialogShow && ( - - )} - {this.state.isAddFileDialogShow && ( - - )} - {this.state.isAddFolderDialogShow && ( - - )} - {this.state.isRenameDialogShow && ( - )}
@@ -282,7 +80,9 @@ class SidePanel extends Component {
); diff --git a/frontend/src/repo-wiki-mode.js b/frontend/src/repo-wiki-mode.js index 590e2540cc..d483bea0a0 100644 --- a/frontend/src/repo-wiki-mode.js +++ b/frontend/src/repo-wiki-mode.js @@ -7,8 +7,8 @@ import { Utils } from './utils/utils'; import collabServer from './utils/collab-server'; import SidePanel from './pages/repo-wiki-mode/side-panel'; import MainPanel from './pages/repo-wiki-mode/main-panel'; -import TreeNode from './components/tree-view-2/tree-node'; -import treeHelper from './components/tree-view-2/tree-helper'; +import TreeNode from './components/tree-view/tree-node'; +import treeHelper from './components/tree-view/tree-helper'; import toaster from './components/toast'; import LibDecryptDialog from './components/dialog/lib-decrypt-dialog'; import ModalPortal from './components/modal-portal'; @@ -332,7 +332,7 @@ class Wiki extends Component { onSearchedClick = (item) => { let path = item.is_dir ? item.path.slice(0, item.path.length - 1) : item.path; - if (this.state.currentFilePath === path) { + if (this.state.currentPath === path) { return; } diff --git a/frontend/src/wiki.js b/frontend/src/wiki.js index 4413fbaf17..67c15e4b66 100644 --- a/frontend/src/wiki.js +++ b/frontend/src/wiki.js @@ -1,13 +1,15 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; +import moment from 'moment'; +import { slug, repoID, siteRoot, initialPath, isDir } from './utils/constants'; +import { Utils } from './utils/utils'; +import { seafileAPI } from './utils/seafile-api'; +import Dirent from './models/dirent'; +import TreeNode from './components/tree-view/tree-node'; +import treeHelper from './components/tree-view/tree-helper'; import SidePanel from './pages/wiki/side-panel'; import MainPanel from './pages/wiki/main-panel'; -import moment from 'moment'; -import { slug, repoID, siteRoot, initialPath } from './utils/constants'; -import editorUtilities from './utils/editor-utilties'; -import { Utils } from './utils/utils'; -import Node from './components/tree-view/node'; -import Tree from './components/tree-view/tree'; + import './assets/css/fa-solid.css'; import './assets/css/fa-regular.css'; import './assets/css/fontawesome.css'; @@ -17,104 +19,183 @@ import './css/wiki.css'; import './css/toolbar.css'; import './css/search.css'; + class Wiki extends Component { constructor(props) { super(props); this.state = { - content: '', - tree_data: new Tree(), + path: '', + pathExist: true, closeSideBar: false, - filePath: '', - latestContributor: '', - lastModified: '', + isViewFile: true, + isDataLoading: true, + direntList: [], + content: '', permission: '', - isFileLoading: false, - changedNode: null, - isViewFileState: true, - hasIndex: false, + lastModified: '', + latestContributor: '', + isTreeDataLoading: true, + treeData: treeHelper.buildTree(), + currentNode: null, + indexNode: null, indexContent: '', - indexPath: '', - indexPermission: '', }; + window.onpopstate = this.onpopstate; + this.indexPath = '/index.md'; + this.homePath = '/home.md'; } componentDidMount() { - this.initWikiData(initialPath); + this.loadWikiData(initialPath); } - getIndexContent = (files) => { - files.some(file => { - if (file.parent_path === '/' && file.type === 'file' && file.name === 'index.md') { - let filePath = Utils.joinPath(file.parent_path, file.name); - editorUtilities.getWikiFileContent(slug, filePath).then((res) => { - this.setState({ - hasIndex: true, - indexContent: res.data.content, - indexPath: filePath, - indexPermission: res.data.permission, + loadWikiData = () => { + this.loadSidePanel(initialPath); + + if (isDir === 'None') { + this.setState({pathExist: false}); + } else if (isDir === 'True') { + this.showDir(initialPath); + } else if (isDir === 'False') { + this.showFile(initialPath); + } + } + + loadSidePanel = (initialPath) => { + if (initialPath === this.homePath || isDir === 'None') { + seafileAPI.listDir(repoID, '/').then(res => { + let tree = this.state.treeData; + this.addResponseListToNode(res.data.dirent_list, tree.root); + let indexNode = tree.getNodeByPath(this.indexPath); + if (indexNode) { + seafileAPI.getFileDownloadLink(repoID, indexNode.path).then(res => { + seafileAPI.getFileContent(res.data).then(res => { + this.setState({ + treeData: tree, + indexNode: indexNode, + indexContent: res.data, + isTreeDataLoading: false, + }); + }); }); - return; - }); - } - }); + } else { + this.setState({ + treeData: tree, + isTreeDataLoading: false, + }); + } + }).catch(() => { + this.setState({isLoadFailed: true}); + }); + } else { + this.loadNodeAndParentsByPath(initialPath); + } } - initWikiData(filePath){ - this.setState({isFileLoading: true}); - editorUtilities.getFiles().then((files) => { - // construct the tree object - var treeData = new Tree(); - treeData.parseListToTree(files); + showDir = (dirPath) => { + this.loadDirentList(dirPath); - let node = treeData.getNodeByPath(filePath); - treeData.expandNode(node); - if (node.isDir()) { - this.exitViewFileState(treeData, node); - this.setState({isFileLoading: false}); - } else { - treeData.expandNode(node); - editorUtilities.getWikiFileContent(slug, filePath).then(res => { + // update location url + let fileUrl = siteRoot + 'wikis/' + slug + dirPath; + window.history.pushState({url: fileUrl, path: dirPath}, dirPath, fileUrl); + } + + showFile = (filePath) => { + this.setState({ + isDataLoading: true, + isViewFile: true, + path: filePath, + }); + + seafileAPI.getFileInfo(repoID, filePath).then(res => { + let { mtime, permission, last_modifier_name } = res.data; + seafileAPI.getFileDownloadLink(repoID, filePath).then(res => { + seafileAPI.getFileContent(res.data).then(res => { this.setState({ - tree_data: treeData, - 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 + isDataLoading: false, + content: res.data, + permission: permission, + lastModified: moment.unix(mtime).fromNow(), + latestContributor: last_modifier_name, }); }); - const hash = window.location.hash; - let fileUrl = siteRoot + 'wikis/' + slug + filePath + hash; - window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl); - } - this.getIndexContent(files); - }, () => { - this.setState({ - isLoadFailed: true }); }); - } - - initMainPanelData(filePath){ - this.setState({isFileLoading: true}); - editorUtilities.getWikiFileContent(slug, filePath) - .then(res => { - this.setState({ - content: res.data.content, - isViewFileState: true, - 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 = siteRoot + 'wikis/' + slug + filePath + hash; - window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl); + window.history.pushState({url: fileUrl, path: filePath}, filePath, fileUrl); + } + + loadDirentList = (dirPath) => { + this.setState({isDataLoading: true}); + seafileAPI.listDir(repoID, dirPath).then(res => { + let direntList = res.data.dirent_list.map(item => { + let dirent = new Dirent(item); + return dirent; + }); + direntList = Utils.sortDirents(direntList, 'name', 'asc'); + this.setState({ + path: dirPath, + isViewFile: false, + direntList: direntList, + isDataLoading: false, + }); + }).catch(() => { + this.setState({isLoadFailed: true}) + }); + } + + loadTreeNodeByPath = (path) => { + let tree = this.state.treeData.clone(); + let node = tree.getNodeByPath(path); + if (!node.isLoaded) { + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + let parentNode = tree.getNodeByPath(node.parentNode.path); + parentNode.isExpanded = true; + this.setState({ + treeData: tree, + currentNode: node + }); + }) + } else { + let parentNode = tree.getNodeByPath(node.parentNode.path); + parentNode.isExpanded = true; + this.setState({treeData: tree, currentNode: node}); //tree + } + } + + loadNodeAndParentsByPath = (path) => { + let tree = this.state.treeData.clone(); + if (Utils.isMarkdownFile(path)) { + path = Utils.getDirName(path); + } + seafileAPI.listDir(repoID, path, {with_parents: true}).then(res => { + let direntList = res.data.dirent_list; + let results = {}; + for (let i = 0; i < direntList.length; i++) { + let object = direntList[i]; + let key = object.parent_dir; + if (!results[key]) { + results[key] = []; + } + results[key].push(object); + } + for (let key in results) { + let node = tree.getNodeByPath(key); + if (!node.isLoaded) { + this.addResponseListToNode(results[key], node); + } + } + this.setState({ + isTreeDataLoading: false, + treeData: tree + }); + }).catch(() => { + this.setState({isLoadFailed: true}); + }); } onLinkClick = (link) => { @@ -131,365 +212,203 @@ class Wiki extends Component { } onpopstate = (event) => { - if (event.state && event.state.filePath) { - this.initMainPanelData(event.state.filePath); + if (event.state && event.state.path) { + let path = event.state.path; + if (Utils.isMarkdownFile(path)) { + this.showFile(path); + } else { + this.loadDirentList(path); + this.setState({ + path: path, + isViewFile: false + }); + } } } onSearchedClick = (item) => { + let path = item.is_dir ? item.path.slice(0, item.path.length - 1) : item.path; + if (this.state.currentPath === path) { + return; + } + + // load sidePanel + let index = -1; + let paths = Utils.getPaths(path); + for (let i = 0; i < paths.length; i++) { + let node = this.state.treeData.getNodeByPath(node); + if (!node) { + index = i; + break; + } + } + if (index === -1) { // all the data has been loaded already. + let tree = this.state.treeData.clone(); + let node = tree.getNodeByPath(item.path); + treeHelper.expandNode(node); + this.setState({treeData: tree}); + } else { + this.loadNodeAndParentsByPath(path); + } + + // load mainPanel if (item.is_dir) { - let path = item.path.slice(0, item.path.length - 1); - if (this.state.filePath !== path) { - let tree = this.state.tree_data.clone(); - let node = tree.getNodeByPath(path); - tree.expandNode(node); - this.exitViewFileState(tree, node); - } - } else if (Utils.isMarkdownFile(item.path)) { - let path = item.path; - if (this.state.filePath !== path) { - this.initMainPanelData(path); - - let tree = this.state.tree_data.clone(); - let node = tree.getNodeByPath(path); - tree.expandNode(node); - this.enterViewFileState(tree, node, node.path); - } + this.showDir(path); } else { - let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(item.path); - let newWindow = window.open('about:blank'); - newWindow.location.href = url; - } - } - - onMainNavBarClick = (nodePath) => { - let tree = this.state.tree_data.clone(); - let node = tree.getNodeByPath(nodePath); - tree.expandNode(node); - - this.exitViewFileState(tree, node); - - // update location url - let fileUrl = siteRoot + '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.expandNode(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 = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); - w.location.href = url; - } - } - - onNodeClick = (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(); - if (this.state.filePath === node.path) { - if (node.isExpanded) { - tree.collapseNode(node); - } else { - tree.expandNode(node); - } + if (Utils.isMarkdownFile(path)) { + this.showFile(path); + } else { + let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(path); + let newWindow = window.open('about:blank'); + newWindow.location.href = url; } - this.exitViewFileState(tree, node); - } else { - const w=window.open('about:blank'); - const url = siteRoot + 'lib/' + repoID + '/file' + node.path; - w.location.href = url; } } - onDirCollapse = (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, - }); + this.setState({closeSideBar: !this.state.closeSideBar,}); + } + + onMainNavBarClick = (nodePath) => { + let tree = this.state.treeData.clone(); + let node = tree.getNodeByPath(nodePath); + tree.expandNode(node); + + this.setState({treeData: tree, currentNode: node}); + this.showDir(node.path); + } + + onDirentClick = (dirent) => { + let direntPath = Utils.joinPath(this.state.path, dirent.name); + if (dirent.isDir()) { // is dir + this.loadTreeNodeByPath(direntPath); + this.showDir(direntPath); + } else { // is file + if (Utils.isMarkdownFile(direntPath)) { + this.showFile(direntPath); + } else { + const w=window.open('about:blank'); + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + w.location.href = url; + } + } } onCloseSide = () => { - this.setState({ - closeSideBar: !this.state.closeSideBar, - }); + 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.expandNode(node); - this.setState({ - tree_data: tree, - changedNode: node + onNodeClick = (node) => { + if (!this.state.pathExist) { + this.setState({pathExist: true}); + } + + if (node.object.isDir()) { + if (!node.isLoaded) { + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + tree.collapseNode(node); + this.setState({treeData: tree}); }); - } 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.expandNode(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 cloneNode = node.clone(); - - tree.updateNodeParam(node, 'name', newName); - node.name = newName; - let date = new Date().getTime()/1000; - 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(cloneNode)) { - tree.expandNode(node); - this.setState({ - tree_data: tree, - changedNode: node - }); - this.initMainPanelData(node.path); - } else { - this.setState({tree_data: tree}); - } + if (node.path === this.state.path) { + if (node.isExpanded) { + let tree = treeHelper.collapseNode(this.state.treeData, node); + this.setState({treeData: tree}); } else { - let parentNode = tree.findNodeParentFromTree(node); - this.setState({ - tree_data: tree, - changedNode: parentNode - }); + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + tree.expandNode(node); + this.setState({treeData: tree}); } - }); - } else if (node.isDir()) { - editorUtilities.renameDir(filePath, newName).then(res => { - - let currentFilePath = this.state.filePath; - let currentFileNode = tree.getNodeByPath(currentFilePath); - let nodePath = node.path; - - tree.updateNodeParam(node, 'name', newName); - node.name = newName; - let date = new Date().getTime()/1000; - tree.updateNodeParam(node, 'last_update_time', moment.unix(date).fromNow()); - node.last_update_time = moment.unix(date).fromNow(); - - if (this.state.isViewFileState) { - if (currentFilePath.indexOf(nodePath) > -1) { - tree.expandNode(currentFileNode); - this.setState({ - tree_data: tree, - changedNode: currentFileNode - }); - this.initMainPanelData(currentFileNode.path); - } else { - this.setState({tree_data: tree}); - } - } else { - if (nodePath === currentFilePath) { // old node - tree.expandNode(node); - this.exitViewFileState(tree, node); - } else if (node.path.indexOf(currentFilePath) > -1) { // new node - tree.expandNode(currentFileNode); - this.exitViewFileState(tree, currentFileNode); - } else { - this.setState({tree_data: tree}); - } - } - }); - } - } - - onDeleteNode = (node) => { - let filePath = node.path; - if (node.isDir()) { - editorUtilities.deleteDir(filePath); - } else { - editorUtilities.deleteFile(filePath); - } - - - 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.expandNode(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 = siteRoot + '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 ''; + if (node.path === this.state.path ) { + 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; + if (node.object.isDir()) { // isDir + this.showDir(node.path); + } else { + if (Utils.isMarkdownFile(node.path)) { + if (node.path !== this.state.path) { + this.showFile(node.path); + } + } else { + const w = window.open('about:blank'); + const url = siteRoot + 'lib/' + repoID + '/file' + node.path; + w.location.href = url; + } } - return false; + } + + onNodeCollapse = (node) => { + let tree = treeHelper.collapseNode(this.state.treeData, node); + this.setState({treeData: tree}); + } + + onNodeExpanded = (node) => { + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + if (!node.isLoaded) { + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + this.setState({treeData: tree}); + }); + } else { + tree.expandNode(node); + this.setState({treeData: tree}); + } + } + + addResponseListToNode = (list, node) => { + node.isLoaded = true; + node.isExpanded = true; + let direntList = list.map(item => { + return new Dirent(item); + }); + direntList = Utils.sortDirents(direntList, 'name', 'asc'); + + let nodeList = direntList.map(object => { + return new TreeNode({object}); + }); + node.addChildren(nodeList); } render() { return (
); diff --git a/seahub/templates/wiki/wiki.html b/seahub/templates/wiki/wiki.html index 43fb099ba4..011b35e301 100644 --- a/seahub/templates/wiki/wiki.html +++ b/seahub/templates/wiki/wiki.html @@ -11,7 +11,9 @@ slug: "{{ wiki.slug }}", repoId: "{{ wiki.repo_id }}", initial_path: "{{ file_path }}", + permission: "{{ user_can_write }}", isPublicWiki: "{{ is_public_wiki }}", + isDir: "{{ is_dir }}", } }; diff --git a/seahub/wiki/views.py b/seahub/wiki/views.py index 1031087d8e..4914a951c6 100644 --- a/seahub/wiki/views.py +++ b/seahub/wiki/views.py @@ -4,6 +4,7 @@ import urllib2 import posixpath import seaserv +from seaserv import seafile_api from django.core.urlresolvers import reverse from django.http import Http404, HttpResponseRedirect from django.shortcuts import render, get_object_or_404, redirect @@ -42,15 +43,25 @@ def wiki_list(request): def slug(request, slug, file_path="home.md"): """Show wiki page. """ - # compatible with old wiki url - if len(file_path.split('.')) == 1: - new_path = file_path + '.md' - return HttpResponseRedirect(reverse('wiki:slug', args=[slug, new_path])) - # get wiki object or 404 wiki = get_object_or_404(Wiki, slug=slug) file_path = "/" + file_path + is_dir = None + file_id = seafile_api.get_file_id_by_path(wiki.repo_id, file_path) + if file_id: + is_dir = False + + dir_id = seafile_api.get_dir_id_by_path(wiki.repo_id, file_path) + if dir_id: + is_dir = True + + # compatible with old wiki url + if is_dir is None: + if len(file_path.split('.')) == 1: + new_path = file_path[1:] + '.md' + return HttpResponseRedirect(reverse('wiki:slug', args=[slug, new_path])) + # perm check req_user = request.user.username @@ -65,6 +76,14 @@ def slug(request, slug, file_path="home.md"): file_url = reverse('view_lib_file', args=[wiki.repo_id, file_path]) return HttpResponseRedirect(file_url + "?raw=1") + if not req_user: + user_can_write = False + elif req_user == wiki.username or check_folder_permission( + request, wiki.repo_id, '/') == 'rw': + user_can_write = True + else: + user_can_write = False + is_public_wiki = False if wiki.permission == 'public': is_public_wiki = True @@ -72,11 +91,13 @@ def slug(request, slug, file_path="home.md"): return render(request, "wiki/wiki.html", { "wiki": wiki, "page_name": file_path, + "user_can_write": user_can_write, "file_path": file_path, "repo_id": wiki.repo_id, "search_repo_id": wiki.repo_id, "search_wiki": True, "is_public_wiki": is_public_wiki, + "is_dir": is_dir, })