mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 15:53:28 +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": {
|
"axios": {
|
||||||
"version": "0.18.0",
|
"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=",
|
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.3.0",
|
"follow-redirects": "^1.3.0",
|
||||||
@@ -5282,7 +5282,7 @@
|
|||||||
},
|
},
|
||||||
"git-up": {
|
"git-up": {
|
||||||
"version": "1.2.1",
|
"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=",
|
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-ssh": "^1.0.0",
|
"is-ssh": "^1.0.0",
|
||||||
@@ -5291,7 +5291,7 @@
|
|||||||
},
|
},
|
||||||
"git-url-parse": {
|
"git-url-parse": {
|
||||||
"version": "5.0.1",
|
"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=",
|
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"git-up": "^1.0.0"
|
"git-up": "^1.0.0"
|
||||||
@@ -7844,7 +7844,7 @@
|
|||||||
},
|
},
|
||||||
"node-status-codes": {
|
"node-status-codes": {
|
||||||
"version": "1.0.0",
|
"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="
|
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
|
||||||
},
|
},
|
||||||
"noop6": {
|
"noop6": {
|
||||||
@@ -8151,7 +8151,7 @@
|
|||||||
},
|
},
|
||||||
"package.json": {
|
"package.json": {
|
||||||
"version": "2.0.1",
|
"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=",
|
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"git-package-json": "^1.4.0",
|
"git-package-json": "^1.4.0",
|
||||||
@@ -8161,7 +8161,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"got": {
|
"got": {
|
||||||
"version": "5.7.1",
|
"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=",
|
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-error-class": "^3.0.1",
|
"create-error-class": "^3.0.1",
|
||||||
@@ -8183,7 +8183,7 @@
|
|||||||
},
|
},
|
||||||
"package-json": {
|
"package-json": {
|
||||||
"version": "2.4.0",
|
"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=",
|
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"got": "^5.0.0",
|
"got": "^5.0.0",
|
||||||
|
@@ -1,17 +1,114 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer';
|
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 = {
|
const viewerPropTypes = {
|
||||||
indexContent: PropTypes.string.isRequired,
|
indexContent: PropTypes.string.isRequired,
|
||||||
onLinkClick: PropTypes.func.isRequired,
|
onLinkClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentClass = 'wiki-page-content';
|
||||||
|
|
||||||
class IndexContentViewer extends React.Component {
|
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) => {
|
onLinkClick = (event) => {
|
||||||
event.preventDefault();
|
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 = () => {
|
onContentRendered = () => {
|
||||||
@@ -20,10 +117,11 @@ class IndexContentViewer extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="markdown-content">
|
<div className={contentClass}>
|
||||||
<MarkdownViewer
|
<MarkdownViewer
|
||||||
markdownContent={this.props.indexContent}
|
markdownContent={this.props.indexContent}
|
||||||
onContentRendered={this.props.onContentRendered}
|
onContentRendered={this.props.onContentRendered}
|
||||||
|
modifyValueBeforeRender={this.modifyValueBeforeRender}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -56,7 +56,7 @@ class WikiDirListItem extends React.Component {
|
|||||||
<a href={href} onClick={this.onDirentClick}>{dirent.name}</a>
|
<a href={href} onClick={this.onDirentClick}>{dirent.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{dirent.size}</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>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,8 @@ class MainPanel extends Component {
|
|||||||
|
|
||||||
onEditClick = (e) => {
|
onEditClick = (e) => {
|
||||||
e.preventDefault();
|
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) => {
|
onMainNavBarClick = (e) => {
|
||||||
@@ -69,6 +70,7 @@ class MainPanel extends Component {
|
|||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const errMessage = (<div className="message empty-tip err-message"><h2>{gettext('Folder does not exist.')}</h2></div>);
|
||||||
return (
|
return (
|
||||||
<div className="main-panel wiki-main-panel o-hidden">
|
<div className="main-panel wiki-main-panel o-hidden">
|
||||||
<div className="main-panel-top panel-top">
|
<div className="main-panel-top panel-top">
|
||||||
@@ -102,8 +104,9 @@ class MainPanel extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
{this.props.isDataLoading && <Loading />}
|
{!this.props.pathExist && errMessage}
|
||||||
{(!this.props.isDataLoading && this.props.isViewFile) && (
|
{this.props.pathExist && this.props.isDataLoading && <Loading />}
|
||||||
|
{(this.props.pathExist && !this.props.isDataLoading && this.props.isViewFile) && (
|
||||||
<WikiMarkdownViewer
|
<WikiMarkdownViewer
|
||||||
markdownContent={this.props.content}
|
markdownContent={this.props.content}
|
||||||
isFileLoading={this.props.isDataLoading}
|
isFileLoading={this.props.isDataLoading}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 Logo from '../../components/logo';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import TreeView from '../../components/tree-view/tree-view';
|
import TreeView from '../../components/tree-view/tree-view';
|
||||||
@@ -27,21 +27,11 @@ class SidePanel extends Component {
|
|||||||
this.isNodeMenuShow = false;
|
this.isNodeMenuShow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditClick = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
let indexNode = this.props.indexNode
|
|
||||||
window.location.href= siteRoot + 'lib/' + repoID + '/file' + indexNode.path + '?mode=edit';
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIndexView = () => {
|
renderIndexView = () => {
|
||||||
let indexNode = this.props.indexNode;
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h3 className="wiki-pages-heading">
|
<h3 className="wiki-pages-heading">
|
||||||
{gettext('Contents')}
|
{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>
|
</h3>
|
||||||
<div className="wiki-pages-container">
|
<div className="wiki-pages-container">
|
||||||
<IndexContentViewer
|
<IndexContentViewer
|
||||||
@@ -83,6 +73,7 @@ class SidePanel extends Component {
|
|||||||
{this.props.isTreeDataLoading && <Loading /> }
|
{this.props.isTreeDataLoading && <Loading /> }
|
||||||
{!this.props.isTreeDataLoading && this.props.indexNode && this.renderIndexView() }
|
{!this.props.isTreeDataLoading && this.props.indexNode && this.renderIndexView() }
|
||||||
{!this.props.isTreeDataLoading && !this.props.indexNode && this.renderTreeView() }
|
{!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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -50,11 +50,17 @@ class Wiki extends Component {
|
|||||||
this.loadWikiData(initialPath);
|
this.loadWikiData(initialPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWikiData = () => {
|
loadWikiData = (initialPath) => {
|
||||||
this.loadSidePanel(initialPath);
|
this.loadSidePanel(initialPath);
|
||||||
|
|
||||||
if (isDir === 'None') {
|
if (isDir === 'None') {
|
||||||
|
if (initialPath === '/home.md') {
|
||||||
|
this.showDir('/');
|
||||||
|
} else {
|
||||||
this.setState({pathExist: false});
|
this.setState({pathExist: false});
|
||||||
|
let fileUrl = siteRoot + 'wikis/' + slug + initialPath;
|
||||||
|
window.history.pushState({url: fileUrl, path: initialPath}, initialPath, fileUrl);
|
||||||
|
}
|
||||||
} else if (isDir === 'True') {
|
} else if (isDir === 'True') {
|
||||||
this.showDir(initialPath);
|
this.showDir(initialPath);
|
||||||
} else if (isDir === 'False') {
|
} else if (isDir === 'False') {
|
||||||
@@ -66,9 +72,10 @@ class Wiki extends Component {
|
|||||||
if (initialPath === this.homePath || isDir === 'None') {
|
if (initialPath === this.homePath || isDir === 'None') {
|
||||||
seafileAPI.listDir(repoID, '/').then(res => {
|
seafileAPI.listDir(repoID, '/').then(res => {
|
||||||
let tree = this.state.treeData;
|
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);
|
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.getFileDownloadLink(repoID, indexNode.path).then(res => {
|
||||||
seafileAPI.getFileContent(res.data).then(res => {
|
seafileAPI.getFileContent(res.data).then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -202,10 +209,10 @@ class Wiki extends Component {
|
|||||||
const url = link;
|
const url = link;
|
||||||
if (Utils.isWikiInternalMarkdownLink(url, slug)) {
|
if (Utils.isWikiInternalMarkdownLink(url, slug)) {
|
||||||
let path = Utils.getPathFromWikiInternalMarkdownLink(url, slug);
|
let path = Utils.getPathFromWikiInternalMarkdownLink(url, slug);
|
||||||
this.initMainPanelData(path);
|
this.showFile(path);
|
||||||
} else if (Utils.isWikiInternalDirLink(url, slug)) {
|
} else if (Utils.isWikiInternalDirLink(url, slug)) {
|
||||||
let path = Utils.getPathFromWikiInternalDirLink(url, slug);
|
let path = Utils.getPathFromWikiInternalDirLink(url, slug);
|
||||||
this.initWikiData(path);
|
this.showDir(path);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = url;
|
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) => {
|
addResponseListToNode = (list, node) => {
|
||||||
node.isLoaded = true;
|
node.isLoaded = true;
|
||||||
node.isExpanded = true;
|
node.isExpanded = true;
|
||||||
|
@@ -149,6 +149,10 @@ ul,ol,li {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-decoration-underline {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
.sf-dropdown-toggle {
|
.sf-dropdown-toggle {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
Reference in New Issue
Block a user