From 33b0e6a5c88f2a20f52218965e9f68c2bc12f5eb Mon Sep 17 00:00:00 2001
From: Michael An <1822852997@qq.com>
Date: Sun, 28 Apr 2019 15:38:21 +0800
Subject: [PATCH 1/3] tree view
---
frontend/src/components/index-viewer.js | 156 ++++++++++++++++++------
frontend/src/css/index-viewer.css | 16 +++
2 files changed, 135 insertions(+), 37 deletions(-)
create mode 100644 frontend/src/css/index-viewer.css
diff --git a/frontend/src/components/index-viewer.js b/frontend/src/components/index-viewer.js
index c6f93fdf2d..47a37dd355 100644
--- a/frontend/src/components/index-viewer.js
+++ b/frontend/src/components/index-viewer.js
@@ -1,48 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
-import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer';
import { repoID, slug, serviceURL, isPublicWiki } from '../utils/constants';
import { Utils } from '../utils/utils';
+import { Value } from 'slate';
+import { deserialize } from '@seafile/seafile-editor/dist/utils/slate2markdown';
+import'../css/index-viewer.css';
const viewerPropTypes = {
indexContent: PropTypes.string.isRequired,
onLinkClick: PropTypes.func.isRequired,
};
-const contentClass = 'wiki-nav-content';
-
class IndexContentViewer extends React.Component {
constructor(props) {
super(props);
this.links = [];
+ this.rootNode = {};
+ }
+
+ componentWillMount() {
+ this.getRootNode();
}
componentDidMount() {
- // Bind event when first loaded
- this.links = document.querySelectorAll(`.${contentClass} a`);
- this.links.forEach(link => {
- link.addEventListener('click', this.onLinkClick);
- });
+ this.bindClickEvent();
}
componentWillReceiveProps() {
- // Unbound event when updating
- this.links.forEach(link => {
- link.removeEventListener('click', this.onLinkClick);
- });
+ this.removeClickEvent();
}
componentDidUpdate() {
- // Update completed, rebind event
+ this.bindClickEvent();
+ }
+
+ componentWillUnmount() {
+ this.removeClickEvent();
+ }
+
+ bindClickEvent = () => {
+ const contentClass = 'wiki-nav-content';
this.links = document.querySelectorAll(`.${contentClass} a`);
this.links.forEach(link => {
link.addEventListener('click', this.onLinkClick);
});
}
- componentWillUnmount() {
- // Rebinding events when the component is destroyed
+ removeClickEvent = () => {
this.links.forEach(link => {
link.removeEventListener('click', this.onLinkClick);
});
@@ -51,18 +56,18 @@ class IndexContentViewer extends React.Component {
onLinkClick = (event) => {
event.preventDefault();
event.stopPropagation();
- let link = '';
- if (event.target.tagName !== 'A') {
- let target = event.target.parentNode;
- while (target.tagName !== 'A') {
- target = target.parentNode;
- }
- link = target.href;
+ const link = this.getLink(event.target);
+ if (link) this.props.onLinkClick(link);
+ }
+ getLink = (node) => {
+ const tagName = node.tagName;
+ if (!tagName || tagName === 'HTML') return;
+ if (tagName === 'A') {
+ return node.href;
} else {
- link = event.target.href;
+ return this.getLink(node.parentNode);
}
- this.props.onLinkClick(link);
}
changeInlineNode = (item) => {
@@ -88,17 +93,18 @@ class IndexContentViewer extends React.Component {
else if (item.type == 'link') {
url = item.data.href;
+ /* eslint-disable */
let expression = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
+ /* eslint-enable */
let re = new RegExp(expression);
// Solving relative paths
if (!re.test(url)) {
- item.data.href = serviceURL + "/published/" + slug + '/' + url;
+ item.data.href = serviceURL + '/published/' + slug + '/' + url;
}
// change file url
else if (Utils.isInternalMarkdownLink(url, repoID)) {
let path = Utils.getPathFromInternalMarkdownLink(url, repoID);
- console.log(path);
// replace url
item.data.href = serviceURL + '/published/' + slug + path;
}
@@ -121,23 +127,99 @@ class IndexContentViewer extends React.Component {
return value;
}
- onContentRendered = () => {
- // todo
+ getRootNode = () => {
+ let value = deserialize(this.props.indexContent);
+ value = value.toJSON();
+ value = this.modifyValueBeforeRender(value);
+ value = Value.fromJSON(value);
+ const nodes = value.document.nodes;
+ nodes.map((node) => {
+ if (node.type ==='unordered_list' || node.type === 'ordered_list') {
+ this.rootNode = node;
+ }
+ });
}
render() {
- return (
-
-
-
- );
+ return ;
}
}
IndexContentViewer.propTypes = viewerPropTypes;
+const FolderItemPropTypes = {
+ rootNode: PropTypes.object.isRequired,
+ bindClickEvent: PropTypes.func.isRequired,
+ hasChild: PropTypes.bool,
+};
+
+class FolderItem extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ showChild: true
+ };
+ }
+
+ toggleShowChild = () => {
+ this.setState({showChild: !this.state.showChild}, () => {
+ if (this.state.showChild) {
+ this.props.bindClickEvent();
+ }
+ });
+ }
+
+ componentDidMount() {
+ if (this.props.hasChild) {
+ this.setState({ showChild: false });
+ }
+ }
+
+ render() {
+ const { rootNode } = this.props;
+ return (
+
+ {
+ rootNode.nodes.map((node) => {
+ if (node.type === 'paragraph') {
+ let href = '';
+ node.nodes.map((linkNode) => {
+ // deal with node isn't a link
+ href = linkNode.data ? encodeURI(linkNode.data.get('href')) : 'javascript:void(0);';
+ });
+ return (
+
+ );
+ } else {
+ const hasChild = (rootNode.type === 'unordered_list' && rootNode.nodes.size > 1);
+ return (
+
+ {this.props.hasChild &&
+
+ {this.state.showChild ? : }
+
+ }
+ {this.state.showChild &&
+
+ }
+
+ );
+ }
+ })
+ }
+
+ );
+ }
+}
+
+FolderItem.propTypes = FolderItemPropTypes;
+
export default IndexContentViewer;
diff --git a/frontend/src/css/index-viewer.css b/frontend/src/css/index-viewer.css
new file mode 100644
index 0000000000..a6184f43f0
--- /dev/null
+++ b/frontend/src/css/index-viewer.css
@@ -0,0 +1,16 @@
+.folder-item {
+ position: relative;
+ margin-top: 5px;
+}
+.folder-item .wiki-nav-content a {
+ color: #333;
+}
+.folder-item .wiki-nav-content a:hover {
+ text-decoration: none;
+}
+.switch-btn {
+ position: absolute;
+ left: 0.5rem;
+ top: 0;
+ color: #c0c0c0;
+}
\ No newline at end of file
From 86371553c56104b77a6e2ae03c083b25e16b08b6 Mon Sep 17 00:00:00 2001
From: Michael An <1822852997@qq.com>
Date: Fri, 17 May 2019 15:49:34 +0800
Subject: [PATCH 2/3] update tree class
---
frontend/src/components/index-viewer.js | 144 +++++++++++++-----------
frontend/src/css/index-viewer.css | 18 ++-
2 files changed, 88 insertions(+), 74 deletions(-)
diff --git a/frontend/src/components/index-viewer.js b/frontend/src/components/index-viewer.js
index 47a37dd355..16522b6e91 100644
--- a/frontend/src/components/index-viewer.js
+++ b/frontend/src/components/index-viewer.js
@@ -11,12 +11,33 @@ const viewerPropTypes = {
onLinkClick: PropTypes.func.isRequired,
};
+class TreeNode {
+
+ constructor({ name, href, parentNode }) {
+ this.name = name;
+ this.href = href;
+ this.parentNode = parentNode || null;
+ this.children = [];
+ }
+
+ setParent(parentNode) {
+ this.parentNode = parentNode;
+ }
+
+ addChildren(nodeList) {
+ nodeList.forEach((node) => {
+ node.setParent(this);
+ });
+ this.children = nodeList;
+ }
+}
+
class IndexContentViewer extends React.Component {
constructor(props) {
super(props);
this.links = [];
- this.rootNode = {};
+ this.treeRoot = new TreeNode({ name: '', href: '' });
}
componentWillMount() {
@@ -92,7 +113,6 @@ class IndexContentViewer extends React.Component {
else if (item.type == 'link') {
url = item.data.href;
-
/* eslint-disable */
let expression = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
/* eslint-enable */
@@ -120,37 +140,45 @@ class IndexContentViewer extends React.Component {
return item;
}
- modifyValueBeforeRender = (value) => {
- let nodes = value.document.nodes;
- let newNodes = Utils.changeMarkdownNodes(nodes, this.changeInlineNode);
- value.document.nodes = newNodes;
- return value;
- }
-
getRootNode = () => {
let value = deserialize(this.props.indexContent);
value = value.toJSON();
- value = this.modifyValueBeforeRender(value);
- value = Value.fromJSON(value);
- const nodes = value.document.nodes;
- nodes.map((node) => {
- if (node.type ==='unordered_list' || node.type === 'ordered_list') {
- this.rootNode = node;
+ const newNodes = Utils.changeMarkdownNodes(value.document.nodes, this.changeInlineNode);
+ newNodes.map((node) => {
+ if (node.type === 'unordered_list' || node.type === 'ordered_list') {
+ this.treeRoot = this.transSlateToTree(node.nodes, this.treeRoot);
}
});
}
+ transSlateToTree = (slateNodes, treeRoot) => {
+ let treeNodes = slateNodes.map((slateNode) => {
+ const inline = slateNode.nodes[0].nodes[0];
+ if (slateNode.nodes.length === 2 && slateNode.nodes[1].type === 'unordered_list') {
+ let treeNode = new TreeNode({ name: inline.nodes[0].leaves[0].text, href: inline.data.href });
+ return this.transSlateToTree(slateNode.nodes[1].nodes, treeNode);
+ } else {
+ return new TreeNode({ name: inline.nodes[0].leaves[0].text, href: inline.data.href });
+ }
+ });
+ treeRoot.addChildren(treeNodes);
+ return treeRoot;
+ }
+
render() {
- return ;
+ return (
+
+
+
+ );
}
}
IndexContentViewer.propTypes = viewerPropTypes;
const FolderItemPropTypes = {
- rootNode: PropTypes.object.isRequired,
+ node: PropTypes.object.isRequired,
bindClickEvent: PropTypes.func.isRequired,
- hasChild: PropTypes.bool,
};
class FolderItem extends React.Component {
@@ -158,65 +186,45 @@ class FolderItem extends React.Component {
constructor(props) {
super(props);
this.state = {
- showChild: true
+ expanded: true
};
}
- toggleShowChild = () => {
- this.setState({showChild: !this.state.showChild}, () => {
- if (this.state.showChild) {
- this.props.bindClickEvent();
- }
+ toggleExpanded = () => {
+ this.setState({ expanded: !this.state.expanded }, () => {
+ if (this.state.expanded) this.props.bindClickEvent();
});
}
- componentDidMount() {
- if (this.props.hasChild) {
- this.setState({ showChild: false });
- }
+ renderLink = (node) => {
+ return ;
}
render() {
- const { rootNode } = this.props;
- return (
-
- {
- rootNode.nodes.map((node) => {
- if (node.type === 'paragraph') {
- let href = '';
- node.nodes.map((linkNode) => {
- // deal with node isn't a link
- href = linkNode.data ? encodeURI(linkNode.data.get('href')) : 'javascript:void(0);';
- });
- return (
-
- );
- } else {
- const hasChild = (rootNode.type === 'unordered_list' && rootNode.nodes.size > 1);
- return (
-
- {this.props.hasChild &&
-
- {this.state.showChild ? : }
-
- }
- {this.state.showChild &&
-
- }
-
- );
- }
- })
- }
-
- );
+ const { node } = this.props;
+ if (node.children.length > 0) {
+ return (
+
+ {node.parentNode &&
+
+
+ {this.state.expanded ? : }
+
+ {this.renderLink(node)}
+
+ }
+ {this.state.expanded && node.children.map((child, index) => {
+ return (
+
+
+
+ );
+ })}
+
+ );
+ } else {
+ return this.renderLink(node);
+ }
}
}
diff --git a/frontend/src/css/index-viewer.css b/frontend/src/css/index-viewer.css
index a6184f43f0..fe99a8ce36 100644
--- a/frontend/src/css/index-viewer.css
+++ b/frontend/src/css/index-viewer.css
@@ -1,16 +1,22 @@
-.folder-item {
- position: relative;
- margin-top: 5px;
+.wiki-nav-content {
+ margin-bottom: 5px;
}
-.folder-item .wiki-nav-content a {
+.wiki-nav-content a {
color: #333;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
}
-.folder-item .wiki-nav-content a:hover {
+.wiki-nav-content a:hover {
text-decoration: none;
+ color: #eb8205;
}
.switch-btn {
+ width: 1rem;
position: absolute;
- left: 0.5rem;
+ left: 0;
top: 0;
color: #c0c0c0;
+ cursor: pointer;
}
\ No newline at end of file
From 049c29209b4131d461c28da59c2c7b48211eecfa Mon Sep 17 00:00:00 2001
From: Michael An <1822852997@qq.com>
Date: Sun, 19 May 2019 18:24:06 +0800
Subject: [PATCH 3/3] update
---
frontend/src/components/index-viewer.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frontend/src/components/index-viewer.js b/frontend/src/components/index-viewer.js
index 16522b6e91..efd4583b15 100644
--- a/frontend/src/components/index-viewer.js
+++ b/frontend/src/components/index-viewer.js
@@ -155,9 +155,13 @@ class IndexContentViewer extends React.Component {
let treeNodes = slateNodes.map((slateNode) => {
const inline = slateNode.nodes[0].nodes[0];
if (slateNode.nodes.length === 2 && slateNode.nodes[1].type === 'unordered_list') {
+ // item has children(unordered list)
let treeNode = new TreeNode({ name: inline.nodes[0].leaves[0].text, href: inline.data.href });
+ // nodes[0] is paragraph, create TreeNode, set name and href
+ // nodes[1] is unordered-list, set it as TreeNode's children
return this.transSlateToTree(slateNode.nodes[1].nodes, treeNode);
} else {
+ // item doesn't have children list
return new TreeNode({ name: inline.nodes[0].leaves[0].text, href: inline.data.href });
}
});