1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-01 15:09:14 +00:00

Rewrite wiki page with react (#2258)

This commit is contained in:
Daniel Pan
2018-08-06 18:29:12 +08:00
committed by GitHub
parent ee8e4135ea
commit fa38dd9151
36 changed files with 2810 additions and 408 deletions

View File

@@ -0,0 +1,127 @@
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;
const node = new Node({
name,
type,
isExpanded,
children: children.map(Node.fromJSON),
});
return node;
}
constructor({ name, type, isExpanded, children }) {
this.name = name;
this.type = type;
this.children = children ? children : [];
this.isExpanded = isExpanded !== undefined ? isExpanded : true;
}
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;
}
}
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;
}
}
}
isMarkdown() {
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;
}
}
}
isDir() {
return this.type == "dir";
}
/**
* Return a JSON representation of the node.
*
* @return {Object}
*/
toJSON() {
var children = []
if (this.hasChildren()) {
children = this.children.map(m => m.toJSON());
}
const object = {
name: this.name,
type: this.type,
isExpanded: this.isExpanded,
children: children
}
return object
}
}
export { Node }

View File

@@ -0,0 +1,135 @@
import React from 'react';
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 {
renderCollapse = () => {
const { node } = this.props;
if (node.hasChildren()) {
const { isExpanded } = node;
return (
<i
className={isExpanded ? 'folder-toggle-icon fa fa-caret-down' : 'folder-toggle-icon fa fa-caret-right'}
onMouseDown={e => e.stopPropagation()}
onClick={this.handleCollapse}
/>
);
}
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);
l = l.filter((node) => { return node.type == "dir" || node.isMarkdown(); })
/*
the `key` property is needed. Otherwise there is a warning in the console
*/
return (
<div className="children" style={childrenStyles}>
{l.map(child => {
return (
<TreeNodeView
node={child}
key={child.path}
paddingLeft={this.props.paddingLeft}
treeView={this.props.treeView}
/>
);
})}
</div>
);
}
return null;
}
render() {
const { node } = this.props;
const styles = {};
var icon, type;
if (node.type === "dir") {
icon = <i className="far fa-folder"/>;
type = 'dir';
} else {
let index = node.name.lastIndexOf(".");
if (index === -1) {
icon = <i className="far fa-file"/>;
type = 'file';
} else {
type = node.name.substring(index).toLowerCase();
if (type === ".png" || type === ".jpg") {
icon = <i className="far fa-image"/>;
type = 'image';
} else {
icon = <i className="far fa-file"/>;
type = 'file';
}
}
}
return (
<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': ''}`}>
{this.renderCollapse()}
<span type={type} className="tree-node-icon">
{icon}
</span>
<span type={type} draggable="true" onDragStart={this.onDragStart}>{node.name}</span>
</div>
{node.isExpanded ? this.renderChildren() : null}
</div>
);
}
onClick = e => {
let { node } = this.props;
this.props.treeView.onClick(e, node);
}
onMouseEnter = e => {
let { node } = this.props;
this.props.treeView.showImagePreview(e, node);
}
onMouseLeave = e => {
this.props.treeView.hideImagePreview(e);
}
handleCollapse = e => {
e.stopPropagation();
const { node } = this.props;
if (this.props.treeView.toggleCollapse) {
this.props.treeView.toggleCollapse(node);
}
}
onDragStart = e => {
const { node } = this.props;
this.props.treeView.onDragStart(e, node);
}
}
export default TreeNodeView;

View File

@@ -0,0 +1,143 @@
import React from 'react';
import TreeNodeView from './tree-node-view';
import Tree from './tree';
class TreeView extends React.PureComponent {
static defaultProps = {
paddingLeft: 20
};
imagePreviewTimeout = null
state = {
tree: new Tree(),
loadingFailed: false,
imagePreviewPosition: {
left: 10+'px',
top: 10+'px'
},
isShowImagePreview: false,
imagePreviewLoading: false,
imageSrc: '',
}
showImagePreview = (e, node) => {
e.persist();
let type = e.target.getAttribute('type');
if (type === 'image') {
this.imagePreviewTimeout = setTimeout(() => {
let X = e.clientX + 20;
let Y = e.clientY - 55;
if (e.view.innerHeight < e.clientY + 150) {
Y = e.clientY - 219;
}
this.setState({
isShowImagePreview: true,
imagePreviewLoading: true,
imageSrc: this.props.editorUtilities.getFileURL(node),
imagePreviewPosition: {
left: X + 'px',
top: Y + 'px'
}
});
}, 1000)
}
}
hideImagePreview = (e) => {
clearTimeout(this.imagePreviewTimeout);
this.setState({
isShowImagePreview: false,
imagePreviewLoading: false,
});
}
imageLoaded = () => {
this.setState({
imagePreviewLoading: false,
});
}
componentDidMount() {
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);
this.setState({
tree: treeData
})
}, () => {
console.log("failed to load files");
this.setState({
loadingFailed: true
})
})
}
render() {
const tree = this.state.tree;
if (!tree.root) {
return <div>Loading...</div>
}
return (
<div className="tree-view tree">
<TreeNodeView
node={tree.root}
paddingLeft={20}
treeView={this}
/>
{ this.state.isShowImagePreview &&
<div style={this.state.imagePreviewPosition} className={'image-view'}>
{ this.state.imagePreviewLoading && <i className={'rotate fa fa-spinner'}/> }
<img src={this.state.imageSrc} onLoad={this.imageLoaded} alt=""/>
</div>
}
</div>
);
}
change = (tree) => {
/*
this._updated = true;
if (this.props.onChange) this.props.onChange(tree.obj);
*/
}
toggleCollapse = (node) => {
const tree = this.state.tree;
node.isExpanded = !node.isExpanded;
// copy the tree to make PureComponent work
this.setState({
tree: tree.copy()
});
this.change(tree);
}
onDragStart = (e, node) => {
const url = this.props.editorUtilities.getFileURL(node);
e.dataTransfer.setData("text/uri-list", url);
e.dataTransfer.setData("text/plain", url);
}
onClick = (e, node) => {
if (node.isDir()) {
this.toggleCollapse(node);
return;
}
this.props.onClick(e, node);
}
}
export default TreeView;

View File

@@ -0,0 +1,113 @@
import { Node } from './node'
class Tree {
constructor() {
this.root = null;
}
copy() {
var t = new Tree();
if (this.root)
t.root = this.root.copy();
return t;
}
setRoot(dir) {
this.root = dir;
}
addChildToNode(node, child) {
child.parent = node;
node.children.push(child);
return child;
}
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);
}
/*
* parse tree from javascript object
*/
parse(model) {
var node = new Node({
name: model.name,
type: model.type,
isExpanded: model.isExpanded
});
this.root = node;
for (let child of model.children) {
this.addChildToNode(node, this.parseNode(child));
}
}
parseFromList(rootObj, nodeList) {
var root = new Node({
name: rootObj.name,
type: rootObj.type,
isExpanded: rootObj.isExpanded
});
this.root = root;
var map = new Map();
map.set(root.name, root);
function joinPath(parent_path, name) {
if (parent_path === "/")
return parent_path + name;
else
return parent_path + "/" + name;
}
var treeNodeList = []
for (let nodeObj of nodeList) {
var node = new Node({
name: nodeObj.name,
type: nodeObj.type,
isExpanded: false
});
node.parent_path = nodeObj.parent_path;
treeNodeList.push(node);
if (nodeObj.type === "dir") {
map.set(joinPath(nodeObj.parent_path, nodeObj.name), node);
}
}
for (let node of treeNodeList) {
let p = map.get(node.parent_path);
if (p === undefined) {
console.log("warning: node " + node.parent_path + " not exist");
} else {
this.addChildToNode(p, node);
}
}
}
parseNode(model) {
var node = new Node({
name: model.name,
type: model.type,
isExpanded: model.isExpanded
});
if (model.children instanceof Array) {
for (let child of model.children) {
this.addChildToNode(node, this.parseNode(child));
}
}
return node;
}
}
export default Tree;