mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 23:20:51 +00:00
view repo via wiki (#2354)
* view repo via wiki * Modify details * add list/grid mode * [dist]
This commit is contained in:
@@ -196,7 +196,7 @@ class Search extends Component {
|
||||
type="text"
|
||||
className="search-input"
|
||||
name="query"
|
||||
placeholder={gettext("Search files in this wiki")}
|
||||
placeholder={this.props.placeholder}
|
||||
style={style}
|
||||
value={this.state.value}
|
||||
onFocus={this.onFocusHandler}
|
||||
|
99
frontend/src/pages/repo-wiki-mode/main-panel.js
Normal file
99
frontend/src/pages/repo-wiki-mode/main-panel.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { Component } from 'react';
|
||||
import { gettext, repoID, serviceUrl, slug, siteRoot } from '../../components/constance';
|
||||
import Search from '../../components/search';
|
||||
import Account from '../../components/account';
|
||||
import MarkdownViewer from '../../components/markdown-viewer';
|
||||
import TreeDirView from '../../components/tree-dir-view/tree-dir-view';
|
||||
|
||||
// const repoName = window.repo.config.repo_name
|
||||
|
||||
class MainPanel extends Component {
|
||||
|
||||
onMenuClick = () => {
|
||||
this.props.onMenuClick();
|
||||
}
|
||||
|
||||
onEditClick = (e) => {
|
||||
// const w=window.open('about:blank')
|
||||
e.preventDefault();
|
||||
window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit';
|
||||
}
|
||||
|
||||
onMainNavBarClick = (e) => {
|
||||
this.props.onMainNavBarClick(e.target.dataset.path);
|
||||
}
|
||||
|
||||
switchViewMode = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.switchViewMode(e.target.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let filePathList = this.props.filePath.split('/');
|
||||
let nodePath = "";
|
||||
let pathElem = filePathList.map((item, index) => {
|
||||
if (item === "") {
|
||||
return;
|
||||
}
|
||||
if (index === (filePathList.length - 1)) {
|
||||
return (
|
||||
<span key={index}><span className="path-split">/</span>{item}</span>
|
||||
)
|
||||
} else {
|
||||
nodePath += "/" + item;
|
||||
return (
|
||||
<a key={index} className="custom-link" data-path={nodePath} onClick={this.onMainNavBarClick}><span className="path-split">/</span>{item}</a>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="wiki-main-panel o-hidden">
|
||||
<div className="main-panel-top panel-top">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.onMenuClick}></span>
|
||||
<div className="wiki-page-ops">
|
||||
{ this.props.permission === 'rw' &&
|
||||
<a className="btn btn-secondary btn-topbar" title="Edit File" onClick={this.onEditClick}>{gettext("Edit")}</a>
|
||||
}
|
||||
<a className="btn btn-secondary btn-topbar sf2-icon-list-view" id='list' title={gettext("List")} onClick={this.switchViewMode}></a>
|
||||
<a className="btn btn-secondary btn-topbar sf2-icon-grid-view" id='grid' title={gettext("Grid")} onClick={this.switchViewMode}></a>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Search onSearchedClick={this.props.onSearchedClick}
|
||||
placeholder={gettext("Search files in this library")}/>
|
||||
<Account />
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-main">
|
||||
<div className="cur-view-path">
|
||||
<div className="path-containter">
|
||||
<a href={siteRoot + '#my-libs/'} className="normal">{gettext("Libraries")}</a>
|
||||
<span className="path-split">/</span>
|
||||
<a href={siteRoot + 'wiki/lib/' + repoID + '/'} className="normal">{slug}</a>
|
||||
{pathElem}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-container">
|
||||
{ this.props.isViewFileState && <MarkdownViewer
|
||||
markdownContent={this.props.content}
|
||||
latestContributor={this.props.latestContributor}
|
||||
lastModified = {this.props.lastModified}
|
||||
onLinkClick={this.props.onLinkClick}
|
||||
isFileLoading={this.props.isFileLoading}
|
||||
/>}
|
||||
{ !this.props.isViewFileState &&
|
||||
<TreeDirView
|
||||
node={this.props.changedNode}
|
||||
onMainNodeClick={this.props.onMainNodeClick}
|
||||
>
|
||||
</TreeDirView>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MainPanel;
|
164
frontend/src/pages/repo-wiki-mode/side-panel.js
Normal file
164
frontend/src/pages/repo-wiki-mode/side-panel.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { Component } from 'react';
|
||||
import TreeView from '../../components/tree-view/tree-view';
|
||||
import { siteRoot, logoPath, mediaUrl, siteTitle, logoWidth, logoHeight } from '../../components/constance';
|
||||
import NodeMenu from '../../components/menu-component/node-menu';
|
||||
import MenuControl from '../../components/menu-component/node-menu-control';
|
||||
|
||||
const gettext = window.gettext;
|
||||
|
||||
class SidePanel extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentNode: null,
|
||||
isNodeItemFrezee: false,
|
||||
isShowMenu: false,
|
||||
menuPosition: {
|
||||
left: 0,
|
||||
top: 0
|
||||
},
|
||||
isLoadFailed: false,
|
||||
isMenuIconShow: false
|
||||
}
|
||||
this.searchedPath = null;
|
||||
}
|
||||
|
||||
closeSide = () => {
|
||||
this.props.onCloseSide();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.setState({
|
||||
isMenuIconShow: true
|
||||
})
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.setState({
|
||||
isMenuIconShow: false
|
||||
})
|
||||
}
|
||||
|
||||
onNodeClick = (e, node) => {
|
||||
this.setState({currentNode: node})
|
||||
this.props.onNodeClick(e, node)
|
||||
}
|
||||
|
||||
onShowContextMenu = (e, node) => {
|
||||
let left = e.clientX - 8*16;
|
||||
let top = e.clientY + 10;
|
||||
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
||||
this.setState({
|
||||
isShowMenu: !this.state.isShowMenu,
|
||||
currentNode: node,
|
||||
menuPosition: position,
|
||||
isNodeItemFrezee: true
|
||||
})
|
||||
}
|
||||
|
||||
onHeadingMenuClick = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
let node = this.props.treeData.root;
|
||||
let left = e.clientX - 8*16;
|
||||
let top = e.clientY + 10;
|
||||
let position = Object.assign({},this.state.menuPosition, {left: left, top: top});
|
||||
this.setState({
|
||||
isShowMenu: !this.state.isShowMenu,
|
||||
currentNode: node,
|
||||
menuPosition: position
|
||||
})
|
||||
}
|
||||
|
||||
onHideContextMenu = () => {
|
||||
this.setState({
|
||||
isShowMenu: false,
|
||||
isNodeItemFrezee: false
|
||||
})
|
||||
}
|
||||
|
||||
onAddFolderNode = (dirPath) => {
|
||||
this.props.onAddFolderNode(dirPath);
|
||||
}
|
||||
|
||||
onAddFileNode = (filePath) => {
|
||||
this.props.onAddFileNode(filePath);
|
||||
}
|
||||
|
||||
onRenameNode = (newName) => {
|
||||
let node = this.state.currentNode;
|
||||
this.props.onRenameNode(node, newName)
|
||||
}
|
||||
|
||||
onDeleteNode = () => {
|
||||
let node = this.state.currentNode;
|
||||
this.props.onDeleteNode(node);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
currentNode: nextProps.changedNode
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`wiki-side-panel ${this.props.closeSideBar ? "": "left-zero"}`}>
|
||||
<div className="side-panel-top panel-top">
|
||||
<a href={siteRoot} id="logo">
|
||||
<img src={mediaUrl + logoPath} title={siteTitle} alt="logo" width={logoWidth} height={logoHeight} />
|
||||
</a>
|
||||
<a title="Close" aria-label="Close" onClick={this.closeSide} className="sf2-icon-x1 sf-popover-close side-panel-close op-icon d-md-none "></a>
|
||||
</div>
|
||||
<div id="side-nav" className="wiki-side-nav" role="navigation">
|
||||
<h3
|
||||
className="wiki-pages-heading"
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
{gettext("Files")}
|
||||
<div className="heading-icon">
|
||||
<MenuControl
|
||||
isShow={this.state.isMenuIconShow}
|
||||
onClick={this.onHeadingMenuClick}
|
||||
/>
|
||||
</div>
|
||||
</h3>
|
||||
<div className="wiki-pages-container">
|
||||
{this.props.treeData &&
|
||||
<TreeView
|
||||
permission={this.props.permission}
|
||||
currentFilePath={this.props.currentFilePath}
|
||||
treeData={this.props.treeData}
|
||||
currentNode={this.state.currentNode}
|
||||
isNodeItemFrezee={this.state.isNodeItemFrezee}
|
||||
onNodeClick={this.onNodeClick}
|
||||
onShowContextMenu={this.onShowContextMenu}
|
||||
onDirCollapse={this.props.onDirCollapse}
|
||||
/>
|
||||
}
|
||||
<NodeMenu
|
||||
isShowMenu={this.state.isShowMenu}
|
||||
menuPosition={this.state.menuPosition}
|
||||
currentNode={this.state.currentNode}
|
||||
onHideContextMenu={this.onHideContextMenu}
|
||||
onDeleteNode={this.onDeleteNode}
|
||||
onAddFileNode={this.onAddFileNode}
|
||||
onAddFolderNode={this.onAddFolderNode}
|
||||
onRenameNode={this.onRenameNode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default SidePanel;
|
@@ -49,7 +49,9 @@ class MainPanel extends Component {
|
||||
<a className="btn btn-secondary btn-topbar" onClick={this.onEditClick}>{gettext("Edit Page")}</a>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Search onSearchedClick={this.props.onSearchedClick}/>
|
||||
<Search onSearchedClick={this.props.onSearchedClick}
|
||||
placeholder={gettext("Search files in this wiki")}
|
||||
/>
|
||||
<Account />
|
||||
</div>
|
||||
</div>
|
||||
|
502
frontend/src/repo-wiki-mode.js
Normal file
502
frontend/src/repo-wiki-mode.js
Normal file
@@ -0,0 +1,502 @@
|
||||
import React, { Component } from 'react';
|
||||
import cookie from 'react-cookies';
|
||||
import ReactDOM from 'react-dom';
|
||||
import SidePanel from './pages/repo-wiki-mode/side-panel';
|
||||
import MainPanel from './pages/repo-wiki-mode/main-panel';
|
||||
import moment from 'moment';
|
||||
import { slug, repoID, serviceUrl, initialFilePath } from './components/constance';
|
||||
import editorUtilities from './utils/editor-utilties';
|
||||
import { seafileAPI } from './utils/editor-utilties';
|
||||
import Node from './components/tree-view/node'
|
||||
import Tree from './components/tree-view/tree'
|
||||
import 'seafile-ui';
|
||||
import './assets/css/fa-solid.css';
|
||||
import './assets/css/fa-regular.css';
|
||||
import './assets/css/fontawesome.css';
|
||||
import './css/side-panel.css';
|
||||
import './css/wiki.css';
|
||||
import './css/search.css';
|
||||
|
||||
class Wiki extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
content: '',
|
||||
tree_data: new Tree(),
|
||||
closeSideBar: false,
|
||||
filePath: '',
|
||||
latestContributor: '',
|
||||
lastModified: '',
|
||||
permission: '',
|
||||
isFileLoading: false,
|
||||
changedNode: null,
|
||||
isViewFileState: true
|
||||
};
|
||||
window.onpopstate = this.onpopstate;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initWikiData(initialFilePath);
|
||||
}
|
||||
|
||||
initWikiData(filePath){
|
||||
this.setState({isFileLoading: true});
|
||||
editorUtilities.listRepoDir().then((files) => {
|
||||
// construct the tree object
|
||||
var treeData = new Tree();
|
||||
treeData.parseListToTree(files);
|
||||
|
||||
let node = treeData.getNodeByPath(filePath);
|
||||
if (node.isDir()) {
|
||||
this.exitViewFileState(treeData, node);
|
||||
this.setState({isFileLoading: false});
|
||||
} else {
|
||||
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
|
||||
let { mtime, size, starred, permission, last_modifier_name } = res.data;
|
||||
|
||||
this.setState({
|
||||
tree_data: treeData,
|
||||
latestContributor: last_modifier_name,
|
||||
lastModified: moment.unix(mtime).fromNow(),
|
||||
permission: permission,
|
||||
filePath: filePath,
|
||||
})
|
||||
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => {
|
||||
const downLoadUrl = res.data;
|
||||
seafileAPI.getFileContent(downLoadUrl).then((res) => {
|
||||
this.setState({
|
||||
content: res.data,
|
||||
isFileLoading: false
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
let fileUrl = serviceUrl + '/wiki/lib/' + repoID + filePath;
|
||||
window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl);
|
||||
}
|
||||
}, () => {
|
||||
console.log("failed to load files");
|
||||
this.setState({
|
||||
isLoadFailed: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
initMainPanelData(filePath) {
|
||||
this.setState({isFileLoading: true});
|
||||
|
||||
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
|
||||
let { mtime, size, starred, permission, last_modifier_name } = res.data;
|
||||
|
||||
this.setState({
|
||||
latestContributor: last_modifier_name,
|
||||
lastModified: moment.unix(mtime).fromNow(),
|
||||
permission: permission,
|
||||
filePath: filePath,
|
||||
})
|
||||
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => {
|
||||
const downLoadUrl = res.data;
|
||||
seafileAPI.getFileContent(downLoadUrl).then((res) => {
|
||||
this.setState({
|
||||
content: res.data,
|
||||
isFileLoading: false
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
let fileUrl = serviceUrl + '/wiki/lib/' + repoID + filePath;
|
||||
window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl);
|
||||
}
|
||||
|
||||
switchViewMode = (mode) => {
|
||||
let dirPath;
|
||||
let tree = this.state.tree_data
|
||||
let node = tree.getNodeByPath(this.state.filePath)
|
||||
if (node.isDir()) {
|
||||
dirPath = this.state.filePath
|
||||
} else {
|
||||
const index = this.state.filePath.lastIndexOf('/');
|
||||
dirPath = this.state.filePath.substring(0, index);
|
||||
}
|
||||
|
||||
cookie.save("view_mode", mode, { path: '/' })
|
||||
|
||||
window.location.href = serviceUrl + "/#common/lib/" + repoID + dirPath;
|
||||
}
|
||||
|
||||
onLinkClick = (event) => {
|
||||
const url = event.target.href;
|
||||
if (this.isInternalMarkdownLink(url)) {
|
||||
let path = this.getPathFromInternalMarkdownLink(url);
|
||||
this.initMainPanelData(path);
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
onpopstate = (event) => {
|
||||
if (event.state && event.state.filePath) {
|
||||
this.initMainPanelData(event.state.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onSearchedClick = (path) => {
|
||||
if (this.state.currentFilePath !== path) {
|
||||
this.initMainPanelData(path);
|
||||
|
||||
let tree = this.state.tree_data.clone();
|
||||
let node = tree.getNodeByPath(path);
|
||||
tree.setNodeToActivated(node);
|
||||
|
||||
let path = node.path; //node is file
|
||||
this.enterViewFileState(tree, node, path);
|
||||
}
|
||||
}
|
||||
|
||||
onMainNavBarClick = (nodePath) => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
let node = tree.getNodeByPath(nodePath);
|
||||
tree.setNodeToActivated(node);
|
||||
|
||||
this.exitViewFileState(tree, node);
|
||||
|
||||
// update location url
|
||||
let fileUrl = serviceUrl + '/wiki/lib/' + repoID + node.path;
|
||||
window.history.pushState({urlPath: fileUrl, filePath: node.path},node.path, fileUrl);
|
||||
}
|
||||
|
||||
onMainNodeClick = (node) => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
tree.setNodeToActivated(node);
|
||||
if (node.isMarkdown()) {
|
||||
this.initMainPanelData(node.path);
|
||||
this.enterViewFileState(tree, node, node.path);
|
||||
} else if (node.isDir()){
|
||||
this.exitViewFileState(tree, node);
|
||||
} else {
|
||||
const w=window.open('about:blank');
|
||||
const url = serviceUrl + '/lib/' + repoID + '/file' + node.path;
|
||||
w.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
onNodeClick = (e, node) => {
|
||||
if (node instanceof Node && node.isMarkdown()){
|
||||
let tree = this.state.tree_data.clone();
|
||||
this.initMainPanelData(node.path);
|
||||
this.enterViewFileState(tree, node, node.path);
|
||||
} else if(node instanceof Node && node.isDir()){
|
||||
let tree = this.state.tree_data.clone();
|
||||
this.exitViewFileState(tree, node);
|
||||
} else {
|
||||
const w=window.open('about:blank');
|
||||
const url = serviceUrl + '/lib/' + repoID + '/file' + node.path;
|
||||
w.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
onDirCollapse = (e, node) => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
let findNode = tree.getNodeByPath(node.path);
|
||||
findNode.isExpanded = !findNode.isExpanded;
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
|
||||
onMenuClick = () => {
|
||||
this.setState({
|
||||
closeSideBar: !this.state.closeSideBar,
|
||||
})
|
||||
}
|
||||
|
||||
onCloseSide = () => {
|
||||
this.setState({
|
||||
closeSideBar: !this.state.closeSideBar,
|
||||
})
|
||||
}
|
||||
|
||||
onAddFolderNode = (dirPath) => {
|
||||
editorUtilities.createDir(dirPath).then(res => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
let name = this.getFileNameByPath(dirPath);
|
||||
let index = dirPath.lastIndexOf("/");
|
||||
let parentPath = dirPath.substring(0, index);
|
||||
if (!parentPath) {
|
||||
parentPath = "/";
|
||||
}
|
||||
let node = this.buildNewNode(name, "dir");
|
||||
let parentNode = tree.getNodeByPath(parentPath);
|
||||
tree.addNodeToParent(node, parentNode);
|
||||
if (this.state.isViewFileState) {
|
||||
tree.setNodeToActivated(node);
|
||||
this.setState({
|
||||
tree_data: tree,
|
||||
changedNode: node
|
||||
})
|
||||
} else {
|
||||
this.exitViewFileState(tree, parentNode);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onAddFileNode = (filePath) => {
|
||||
editorUtilities.createFile(filePath).then(res => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
let name = this.getFileNameByPath(filePath);
|
||||
let index = filePath.lastIndexOf("/");
|
||||
let parentPath = filePath.substring(0, index);
|
||||
if (!parentPath) {
|
||||
parentPath = "/";
|
||||
}
|
||||
let node = this.buildNewNode(name, "file");
|
||||
let parentNode = tree.getNodeByPath(parentPath);
|
||||
tree.addNodeToParent(node, parentNode);
|
||||
if (this.state.isViewFileState) {
|
||||
tree.setNodeToActivated(node);
|
||||
this.setState({
|
||||
tree_data: tree,
|
||||
changedNode: node
|
||||
})
|
||||
} else {
|
||||
this.exitViewFileState(tree, parentNode);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onRenameNode = (node, newName) => {
|
||||
let tree = this.state.tree_data.clone();
|
||||
let filePath = node.path;
|
||||
if (node.isMarkdown()) {
|
||||
editorUtilities.renameFile(filePath, newName).then(res => {
|
||||
let date = new Date().getTime()/1000;
|
||||
tree.updateNodeParam(node, "name", newName);
|
||||
node.name = newName;
|
||||
tree.updateNodeParam(node, "last_update_time", moment.unix(date).fromNow());
|
||||
|
||||
node.last_update_time = moment.unix(date).fromNow();
|
||||
if (this.state.isViewFileState) {
|
||||
if (this.isModifyCurrentFile(node)) {
|
||||
tree.setNodeToActivated(node);
|
||||
this.setState({
|
||||
tree_data: tree,
|
||||
changedNode: node
|
||||
});
|
||||
this.initMainPanelData(node.path);
|
||||
} else {
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
} else {
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
})
|
||||
} else if (node.isDir()) {
|
||||
editorUtilities.renameDir(filePath, newName).then(res => {
|
||||
let currentFilePath = this.state.filePath;// the sequence is must right
|
||||
let currentFileNode = tree.getNodeByPath(currentFilePath);
|
||||
|
||||
let isPathEqual = (node.path === currentFilePath);
|
||||
let date = new Date().getTime()/1000;
|
||||
tree.updateNodeParam(node, "name", newName);
|
||||
node.name = newName; // just synchronization node data && tree data;
|
||||
tree.updateNodeParam(node, "last_update_time", moment.unix(date).fromNow());
|
||||
node.last_update_time = moment.unix(date).fromNow();
|
||||
if (this.state.isViewFileState) {
|
||||
if (this.isModifyContainsCurrentFile(node)) {
|
||||
tree.setNodeToActivated(currentFileNode);
|
||||
this.setState({
|
||||
tree_data: tree,
|
||||
changedNode: currentFileNode
|
||||
});
|
||||
this.initMainPanelData(currentFileNode.path);
|
||||
} else {
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
} else {
|
||||
if (isPathEqual || node.path.indexOf(currentFilePath) > -1) {
|
||||
tree.setNodeToActivated(currentFileNode);
|
||||
this.exitViewFileState(tree, currentFileNode);
|
||||
} else {
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onDeleteNode = (node) => {
|
||||
let filePath = node.path;
|
||||
if (node.isMarkdown()) {
|
||||
editorUtilities.deleteFile(filePath);
|
||||
} else if (node.isDir()) {
|
||||
editorUtilities.deleteDir(filePath);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
let isCurrentFile = false;
|
||||
if (node.isDir()) {
|
||||
isCurrentFile = this.isModifyContainsCurrentFile(node);
|
||||
} else {
|
||||
isCurrentFile = this.isModifyCurrentFile(node);
|
||||
}
|
||||
|
||||
let tree = this.state.tree_data.clone();
|
||||
|
||||
if (this.state.isViewFileState) {
|
||||
if (isCurrentFile) {
|
||||
let homeNode = this.getHomeNode(tree);
|
||||
tree.setNodeToActivated(homeNode);
|
||||
this.setState({
|
||||
tree_data: tree,
|
||||
changedNode: homeNode
|
||||
})
|
||||
this.initMainPanelData(homeNode.path);
|
||||
} else {
|
||||
this.setState({tree_data: tree})
|
||||
}
|
||||
} else {
|
||||
let parentNode = tree.getNodeByPath(this.state.filePath);
|
||||
let isChild = tree.isNodeChild(parentNode, node);
|
||||
if (isChild) {
|
||||
this.exitViewFileState(tree, parentNode);
|
||||
} else {
|
||||
this.setState({tree_data: tree});
|
||||
}
|
||||
}
|
||||
tree.deleteNode(node);
|
||||
}
|
||||
|
||||
|
||||
enterViewFileState(newTree, newNode, newPath) {
|
||||
this.setState({
|
||||
tree_data: newTree,
|
||||
changedNode: newNode,
|
||||
filePath: newPath,
|
||||
isViewFileState: true
|
||||
});
|
||||
}
|
||||
|
||||
exitViewFileState(newTree, newNode) {
|
||||
this.setState({
|
||||
tree_data: newTree,
|
||||
changedNode: newNode,
|
||||
filePath: newNode.path,
|
||||
isViewFileState: false
|
||||
});
|
||||
let fileUrl = serviceUrl + '/wiki/lib/' + repoID + newNode.path;
|
||||
window.history.pushState({urlPath: fileUrl, filePath: newNode.path}, newNode.path, fileUrl);
|
||||
}
|
||||
|
||||
getFileNameByPath(path) {
|
||||
let index = path.lastIndexOf("/");
|
||||
if (index === -1) {
|
||||
return "";
|
||||
}
|
||||
return path.slice(index+1);
|
||||
}
|
||||
|
||||
getHomeNode(treeData) {
|
||||
return treeData.getNodeByPath("/home.md");
|
||||
}
|
||||
|
||||
buildNewNode(name, type) {
|
||||
let date = new Date().getTime()/1000;
|
||||
let node = new Node({
|
||||
name : name,
|
||||
type: type,
|
||||
size: '0',
|
||||
last_update_time: moment.unix(date).fromNow(),
|
||||
isExpanded: false,
|
||||
children: []
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
isModifyCurrentFile(node) {
|
||||
let nodeName = node.name;
|
||||
let fileName = this.getFileNameByPath(this.state.filePath);
|
||||
return nodeName === fileName;
|
||||
}
|
||||
|
||||
isModifyContainsCurrentFile(node) {
|
||||
let filePath = this.state.filePath;
|
||||
let nodePath = node.path;
|
||||
|
||||
if (filePath.indexOf(nodePath) > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isMarkdownFile(filePath) {
|
||||
let index = filePath.lastIndexOf(".");
|
||||
if (index === -1) {
|
||||
return false;
|
||||
} else {
|
||||
let type = filePath.substring(index).toLowerCase();
|
||||
if (type === ".md" || type === ".markdown") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isInternalMarkdownLink(url) {
|
||||
var re = new RegExp(serviceUrl + '/lib/' + repoID + '/file' + '.*\.md$');
|
||||
return re.test(url);
|
||||
}
|
||||
|
||||
getPathFromInternalMarkdownLink(url) {
|
||||
var re = new RegExp(serviceUrl + '/lib/' + repoID + '/file' + "(.*\.md)");
|
||||
var array = re.exec(url);
|
||||
var path = decodeURIComponent(array[1]);
|
||||
return path;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="main" className="wiki-main">
|
||||
<SidePanel
|
||||
onNodeClick={this.onNodeClick}
|
||||
closeSideBar={this.state.closeSideBar}
|
||||
onCloseSide ={this.onCloseSide}
|
||||
treeData={this.state.tree_data}
|
||||
permission={this.state.permission}
|
||||
currentFilePath={this.state.filePath}
|
||||
changedNode={this.state.changedNode}
|
||||
onAddFolderNode={this.onAddFolderNode}
|
||||
onAddFileNode={this.onAddFileNode}
|
||||
onRenameNode={this.onRenameNode}
|
||||
onDeleteNode={this.onDeleteNode}
|
||||
onDirCollapse={this.onDirCollapse}
|
||||
/>
|
||||
<MainPanel
|
||||
content={this.state.content}
|
||||
filePath={this.state.filePath}
|
||||
latestContributor={this.state.latestContributor}
|
||||
lastModified={this.state.lastModified}
|
||||
permission={this.state.permission}
|
||||
isViewFileState={this.state.isViewFileState}
|
||||
changedNode={this.state.changedNode}
|
||||
isFileLoading={this.state.isFileLoading}
|
||||
onLinkClick={this.onLinkClick}
|
||||
onMenuClick={this.onMenuClick}
|
||||
onSearchedClick={this.onSearchedClick}
|
||||
onMainNavBarClick={this.onMainNavBarClick}
|
||||
onMainNodeClick={this.onMainNodeClick}
|
||||
switchViewMode={this.switchViewMode}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render (
|
||||
<Wiki />,
|
||||
document.getElementById('wrapper')
|
||||
)
|
@@ -1,6 +1,7 @@
|
||||
import { slug, repoID, siteRoot } from '../components/constance';
|
||||
import { SeafileAPI } from 'seafile-js';
|
||||
import cookie from 'react-cookies';
|
||||
import { bytesToSize } from '../components/utils'
|
||||
|
||||
let seafileAPI = new SeafileAPI();
|
||||
let xcsrfHeaders = cookie.load('sfcsrftoken');
|
||||
@@ -17,13 +18,31 @@ class EditorUtilities {
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.last_update_time,
|
||||
size: item.size
|
||||
size: bytesToSize(item.size)
|
||||
}
|
||||
})
|
||||
return files;
|
||||
})
|
||||
}
|
||||
|
||||
listRepoDir() {
|
||||
return seafileAPI.listDir(repoID, "/",{recursive: true}).then(items => {
|
||||
const files = items.data.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: item.type === 'dir' ? 'dir' : 'file',
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.mtime,
|
||||
size: item.size ? bytesToSize(item.size) : '0 bytes'
|
||||
}
|
||||
})
|
||||
|
||||
return files;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
createFile(filePath) {
|
||||
return seafileAPI.createFile(repoID, filePath)
|
||||
}
|
||||
@@ -69,3 +88,4 @@ class EditorUtilities {
|
||||
const editorUtilities = new EditorUtilities();
|
||||
|
||||
export default editorUtilities;
|
||||
export { seafileAPI };
|
||||
|
Reference in New Issue
Block a user