mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-16 07:08:55 +00:00
Wiki module improve (#2901)
This commit is contained in:
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@@ -641,7 +641,7 @@
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.3.0",
|
||||
@@ -5282,7 +5282,7 @@
|
||||
},
|
||||
"git-up": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
||||
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
|
||||
"requires": {
|
||||
"is-ssh": "^1.0.0",
|
||||
@@ -5291,7 +5291,7 @@
|
||||
},
|
||||
"git-url-parse": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
||||
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
|
||||
"requires": {
|
||||
"git-up": "^1.0.0"
|
||||
@@ -7844,7 +7844,7 @@
|
||||
},
|
||||
"node-status-codes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
||||
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
|
||||
},
|
||||
"noop6": {
|
||||
@@ -8151,7 +8151,7 @@
|
||||
},
|
||||
"package.json": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
||||
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
|
||||
"requires": {
|
||||
"git-package-json": "^1.4.0",
|
||||
@@ -8161,7 +8161,7 @@
|
||||
"dependencies": {
|
||||
"got": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
||||
"requires": {
|
||||
"create-error-class": "^3.0.1",
|
||||
@@ -8183,7 +8183,7 @@
|
||||
},
|
||||
"package-json": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
||||
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
|
||||
"requires": {
|
||||
"got": "^5.0.0",
|
||||
|
@@ -1,17 +1,114 @@
|
||||
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';
|
||||
|
||||
const viewerPropTypes = {
|
||||
indexContent: PropTypes.string.isRequired,
|
||||
onLinkClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const contentClass = 'wiki-page-content';
|
||||
|
||||
class IndexContentViewer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.links = [];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Bind event when first loaded
|
||||
this.links = document.querySelectorAll(`.${contentClass} a`);
|
||||
this.links.forEach(link => {
|
||||
link.addEventListener('click', this.onLinkClick);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
// Unbound event when updating
|
||||
this.links.forEach(link => {
|
||||
link.removeEventListener('click', this.onLinkClick);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Update completed, rebind event
|
||||
this.links = document.querySelectorAll(`.${contentClass} a`);
|
||||
this.links.forEach(link => {
|
||||
link.addEventListener('click', this.onLinkClick);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Rebinding events when the component is destroyed
|
||||
this.links.forEach(link => {
|
||||
link.removeEventListener('click', this.onLinkClick);
|
||||
});
|
||||
}
|
||||
|
||||
onLinkClick = (event) => {
|
||||
event.preventDefault();
|
||||
this.props.onLinkClick(event);
|
||||
event.stopPropagation();
|
||||
let link = '';
|
||||
if (event.target.tagName !== 'A') {
|
||||
let target = event.target.parentNode;
|
||||
while (target.tagName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
link = target.href;
|
||||
} else {
|
||||
link = event.target.href;
|
||||
}
|
||||
this.props.onLinkClick(link);
|
||||
}
|
||||
|
||||
changeInlineNode = (item) => {
|
||||
if (item.object == 'inline') {
|
||||
let url;
|
||||
|
||||
// change image url
|
||||
if (item.type == 'image' && isPublicWiki) {
|
||||
url = item.data.src;
|
||||
const re = new RegExp(serviceURL + '/lib/' + repoID +'/file.*raw=1');
|
||||
// different repo
|
||||
if (!re.test(url)) {
|
||||
return;
|
||||
}
|
||||
// get image path
|
||||
let index = url.indexOf('/file');
|
||||
let index2 = url.indexOf('?');
|
||||
const imagePath = url.substring(index + 5, index2);
|
||||
// replace url
|
||||
item.data.src = serviceURL + '/view-image-via-public-wiki/?slug=' + slug + '&path=' + imagePath;
|
||||
}
|
||||
|
||||
else if (item.type == 'link') {
|
||||
url = item.data.href;
|
||||
// change file url
|
||||
if (Utils.isInternalMarkdownLink(url, repoID)) {
|
||||
let path = Utils.getPathFromInternalMarkdownLink(url, repoID);
|
||||
// replace url
|
||||
item.data.href = serviceURL + '/wikis/' + slug + path;
|
||||
}
|
||||
// change dir url
|
||||
else if (Utils.isInternalDirLink(url, repoID)) {
|
||||
let path = Utils.getPathFromInternalDirLink(url, repoID, slug);
|
||||
// replace url
|
||||
item.data.href = serviceURL + '/wikis/' + slug + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
modifyValueBeforeRender = (value) => {
|
||||
let nodes = value.document.nodes;
|
||||
let newNodes = Utils.changeMarkdownNodes(nodes, this.changeInlineNode);
|
||||
value.document.nodes = newNodes;
|
||||
return value;
|
||||
}
|
||||
|
||||
onContentRendered = () => {
|
||||
@@ -20,10 +117,11 @@ class IndexContentViewer extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="markdown-content">
|
||||
<div className={contentClass}>
|
||||
<MarkdownViewer
|
||||
markdownContent={this.props.indexContent}
|
||||
onContentRendered={this.props.onContentRendered}
|
||||
modifyValueBeforeRender={this.modifyValueBeforeRender}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@@ -56,7 +56,7 @@ class WikiDirListItem extends React.Component {
|
||||
<a href={href} onClick={this.onDirentClick}>{dirent.name}</a>
|
||||
</td>
|
||||
<td>{dirent.size}</td>
|
||||
<td title={dirent.last_update_time}>{dirent.last_update_time}</td>
|
||||
<td title={dirent.mtime_relative}>{dirent.mtime_relative}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
@@ -31,7 +31,8 @@ class MainPanel extends Component {
|
||||
|
||||
onEditClick = (e) => {
|
||||
e.preventDefault();
|
||||
window.location.href= siteRoot + 'lib/' + repoID + '/file' + this.props.filePath + '?mode=edit';
|
||||
let url = siteRoot + 'lib/' + repoID + '/file' + this.props.path + '?mode=edit';
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onMainNavBarClick = (e) => {
|
||||
@@ -69,6 +70,7 @@ class MainPanel extends Component {
|
||||
|
||||
|
||||
render() {
|
||||
const errMessage = (<div className="message empty-tip err-message"><h2>{gettext('Folder does not exist.')}</h2></div>);
|
||||
return (
|
||||
<div className="main-panel wiki-main-panel o-hidden">
|
||||
<div className="main-panel-top panel-top">
|
||||
@@ -102,8 +104,9 @@ class MainPanel extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.props.isDataLoading && <Loading />}
|
||||
{(!this.props.isDataLoading && this.props.isViewFile) && (
|
||||
{!this.props.pathExist && errMessage}
|
||||
{this.props.pathExist && this.props.isDataLoading && <Loading />}
|
||||
{(this.props.pathExist && !this.props.isDataLoading && this.props.isViewFile) && (
|
||||
<WikiMarkdownViewer
|
||||
markdownContent={this.props.content}
|
||||
isFileLoading={this.props.isDataLoading}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, siteRoot, repoID } from '../../utils/constants';
|
||||
import { gettext, siteRoot, repoID, slug } from '../../utils/constants';
|
||||
import Logo from '../../components/logo';
|
||||
import Loading from '../../components/loading';
|
||||
import TreeView from '../../components/tree-view/tree-view';
|
||||
@@ -27,21 +27,11 @@ class SidePanel extends Component {
|
||||
this.isNodeMenuShow = false;
|
||||
}
|
||||
|
||||
onEditClick = (e) => {
|
||||
e.preventDefault();
|
||||
let indexNode = this.props.indexNode
|
||||
window.location.href= siteRoot + 'lib/' + repoID + '/file' + indexNode.path + '?mode=edit';
|
||||
}
|
||||
|
||||
renderIndexView = () => {
|
||||
let indexNode = this.props.indexNode;
|
||||
return (
|
||||
<Fragment>
|
||||
<h3 className="wiki-pages-heading">
|
||||
{gettext('Contents')}
|
||||
{indexNode.object.permission === 'rw' &&
|
||||
<button className="btn btn-secondary operation-item index-edit" title="Edit Index" onClick={this.onEditClick}>{gettext('Edit')}</button>
|
||||
}
|
||||
</h3>
|
||||
<div className="wiki-pages-container">
|
||||
<IndexContentViewer
|
||||
@@ -83,6 +73,7 @@ class SidePanel extends Component {
|
||||
{this.props.isTreeDataLoading && <Loading /> }
|
||||
{!this.props.isTreeDataLoading && this.props.indexNode && this.renderIndexView() }
|
||||
{!this.props.isTreeDataLoading && !this.props.indexNode && this.renderTreeView() }
|
||||
<div className="text-left p-2"><a href={siteRoot + 'library/' + repoID + "/" + slug} className="text-dark text-decoration-underline">{gettext('Go to Library')}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -50,11 +50,17 @@ class Wiki extends Component {
|
||||
this.loadWikiData(initialPath);
|
||||
}
|
||||
|
||||
loadWikiData = () => {
|
||||
loadWikiData = (initialPath) => {
|
||||
this.loadSidePanel(initialPath);
|
||||
|
||||
if (isDir === 'None') {
|
||||
if (initialPath === '/home.md') {
|
||||
this.showDir('/');
|
||||
} else {
|
||||
this.setState({pathExist: false});
|
||||
let fileUrl = siteRoot + 'wikis/' + slug + initialPath;
|
||||
window.history.pushState({url: fileUrl, path: initialPath}, initialPath, fileUrl);
|
||||
}
|
||||
} else if (isDir === 'True') {
|
||||
this.showDir(initialPath);
|
||||
} else if (isDir === 'False') {
|
||||
@@ -66,9 +72,10 @@ class Wiki extends Component {
|
||||
if (initialPath === this.homePath || isDir === 'None') {
|
||||
seafileAPI.listDir(repoID, '/').then(res => {
|
||||
let tree = this.state.treeData;
|
||||
this.addResponseListToNode(res.data.dirent_list, tree.root);
|
||||
this.addFirstResponseListToNode(res.data.dirent_list, tree.root);
|
||||
let indexNode = tree.getNodeByPath(this.indexPath);
|
||||
if (indexNode) {
|
||||
let homeNode = tree.getNodeByPath(this.homePath);
|
||||
if (homeNode && indexNode) {
|
||||
seafileAPI.getFileDownloadLink(repoID, indexNode.path).then(res => {
|
||||
seafileAPI.getFileContent(res.data).then(res => {
|
||||
this.setState({
|
||||
@@ -202,10 +209,10 @@ class Wiki extends Component {
|
||||
const url = link;
|
||||
if (Utils.isWikiInternalMarkdownLink(url, slug)) {
|
||||
let path = Utils.getPathFromWikiInternalMarkdownLink(url, slug);
|
||||
this.initMainPanelData(path);
|
||||
this.showFile(path);
|
||||
} else if (Utils.isWikiInternalDirLink(url, slug)) {
|
||||
let path = Utils.getPathFromWikiInternalDirLink(url, slug);
|
||||
this.initWikiData(path);
|
||||
this.showDir(path);
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
@@ -364,6 +371,27 @@ class Wiki extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
addFirstResponseListToNode = (list, node) => {
|
||||
node.isLoaded = true;
|
||||
node.isExpanded = true;
|
||||
let direntList = list.map(item => {
|
||||
return new Dirent(item);
|
||||
});
|
||||
direntList = direntList.filter(item => {
|
||||
if (item.type === 'dir') {
|
||||
let name = item.name.toLowerCase();
|
||||
return name !== 'drafts' && name !== 'images' && name !== 'downloads';
|
||||
}
|
||||
return true;
|
||||
});
|
||||
direntList = Utils.sortDirents(direntList, 'name', 'asc');
|
||||
|
||||
let nodeList = direntList.map(object => {
|
||||
return new TreeNode({object});
|
||||
});
|
||||
node.addChildren(nodeList);
|
||||
}
|
||||
|
||||
addResponseListToNode = (list, node) => {
|
||||
node.isLoaded = true;
|
||||
node.isExpanded = true;
|
||||
|
@@ -149,6 +149,10 @@ ul,ol,li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-decoration-underline {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
.sf-dropdown-toggle {
|
||||
margin-left: 0.5rem;
|
||||
vertical-align: middle;
|
||||
|
Reference in New Issue
Block a user