mirror of
https://github.com/haiwen/seahub.git
synced 2025-07-15 07:52:14 +00:00
tree view
This commit is contained in:
parent
fba6a7f9bf
commit
33b0e6a5c8
@ -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 (
|
||||
<div className={contentClass}>
|
||||
<MarkdownViewer
|
||||
markdownContent={this.props.indexContent}
|
||||
onContentRendered={this.onContentRendered}
|
||||
modifyValueBeforeRender={this.modifyValueBeforeRender}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <FolderItem rootNode={this.rootNode} bindClickEvent={this.bindClickEvent} hasChild={false}/>;
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="folder-item px-2">
|
||||
{
|
||||
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 (
|
||||
<div key={node.key} className="px-4 wiki-nav-content">
|
||||
<a href={href}>{node.text}</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const hasChild = (rootNode.type === 'unordered_list' && rootNode.nodes.size > 1);
|
||||
return (
|
||||
<React.Fragment key={node.key}>
|
||||
{this.props.hasChild &&
|
||||
<span className="switch-btn" onClick={this.toggleShowChild}>
|
||||
{this.state.showChild ? <i className="fa fa-caret-down"></i> : <i className="fa fa-caret-right"></i>}
|
||||
</span>
|
||||
}
|
||||
{this.state.showChild &&
|
||||
<FolderItem
|
||||
rootNode={node}
|
||||
key={node.key}
|
||||
hasChild={hasChild}
|
||||
bindClickEvent={this.props.bindClickEvent}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FolderItem.propTypes = FolderItemPropTypes;
|
||||
|
||||
export default IndexContentViewer;
|
||||
|
16
frontend/src/css/index-viewer.css
Normal file
16
frontend/src/css/index-viewer.css
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user