1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-11 01:17:02 +00:00

view repo via wiki ()

* view repo via wiki

* Modify details

* add list/grid mode

* [dist]
This commit is contained in:
C_Q 2018-09-12 10:32:31 +08:00 committed by Daniel Pan
parent dcf35018c1
commit 7bd50ad009
28 changed files with 877 additions and 18 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -60,6 +60,7 @@ module.exports = {
entry: {
main: [require.resolve('./polyfills'), paths.appIndexJs],
wiki: [require.resolve('./polyfills'), paths.appSrc + "/wiki.js"],
repoview: [require.resolve('./polyfills'), paths.appSrc + "/repo-wiki-mode.js"],
},
output: {

View File

@ -48,7 +48,7 @@
"remark-parse": "5.0.0",
"remark-rehype": "3.0.0",
"remark-slug": "5.0.0",
"seafile-js": "0.2.12",
"seafile-js": "0.2.13",
"seafile-ui": "0.1.10",
"slate": "0.34.2",
"slate-drop-or-paste-images": "0.8.3",
@ -10338,9 +10338,9 @@
}
},
"seafile-js": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.12.tgz",
"integrity": "sha512-sXt6L0S2ejPFemQ54jknYG6RQCzDrwArVCy59azD1d+5OChQFcaBQNGpGU6Yr3M0MONtaAYZJxvXXy8a7+/Qyg==",
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.13.tgz",
"integrity": "sha512-NANdpSi75zaGANvrgCcdiamoFKzkyUyTmwhM/sp/oQt0+1zr+p57/oOduCRvL1+MvKQC0nK0Qf/Z/WdQqri/mA==",
"requires": {
"axios": "0.18.0",
"form-data": "2.3.2"

View File

@ -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}

View 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;

View 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;

View File

@ -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>

View 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')
)

View File

@ -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 };

View File

@ -1 +1 @@
{"status":"done","chunks":{"main":[{"name":"js/main.js","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/main.js"},{"name":"css/main.css","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/main.css"},{"name":"js/main.js.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/main.js.map"},{"name":"css/main.css.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/main.css.map"}],"wiki":[{"name":"js/wiki.js","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/wiki.js"},{"name":"css/wiki.css","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/wiki.css"},{"name":"js/wiki.js.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/wiki.js.map"},{"name":"css/wiki.css.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/wiki.css.map"}]}}
{"status":"done","chunks":{"main":[{"name":"js/main.js","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/main.js"},{"name":"css/main.css","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/main.css"},{"name":"js/main.js.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/main.js.map"},{"name":"css/main.css.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/main.css.map"}],"wiki":[{"name":"js/wiki.js","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/wiki.js"},{"name":"css/wiki.css","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/wiki.css"},{"name":"js/wiki.js.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/wiki.js.map"},{"name":"css/wiki.css.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/wiki.css.map"}],"repoview":[{"name":"js/repoview.js","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/repoview.js"},{"name":"css/repoview.css","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/repoview.css"},{"name":"js/repoview.js.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/js/repoview.js.map"},{"name":"css/repoview.css.map","path":"/home/yxzhang/dev/seahub/frontend/build/frontend/css/repoview.css.map"}]}}

View File

@ -4167,6 +4167,7 @@ img.thumbnail {
.switch-mode {
margin-left:15px;
}
.wiki-view-icon-btn,
.grid-view-icon-btn,
.list-view-icon-btn {
display:inline-block;
@ -4179,6 +4180,7 @@ img.thumbnail {
cursor:pointer;
border-radius:0;
}
.grid-view-icon-btn.active,
.list-view-icon-btn.active {
color:#fff;

View File

@ -32,6 +32,8 @@
.sf2-icon-wrench:before { content:"\e001"; }
.sf2-icon-bell:before { content:"\e003"; }
.sf2-icon-x3:before { content:"\e035"; }
.sf2-icon-grid-view:before { content:"\e025"; }
.sf2-icon-list-view:before { content:"\e026"; }
/****** icon-xx ********/

View File

@ -118,6 +118,7 @@ def base(request):
'enable_terms_and_conditions': config.ENABLE_TERMS_AND_CONDITIONS,
'show_logout_icon': SHOW_LOGOUT_ICON,
'is_pro': True if is_pro_version() else False,
'enable_repo_wiki_mode': dj_settings.ENABLE_REPO_WIKI_MODE,
}
if request.user.is_staff:

View File

@ -707,6 +707,9 @@ ENABLE_WIKI = False
# Enable 'repo snapshot label' feature
ENABLE_REPO_SNAPSHOT_LABEL = False
# Repo wiki mode
ENABLE_REPO_WIKI_MODE = True
#####################
# External settings #
#####################

View File

@ -493,6 +493,9 @@
<div class="switch-mode hidden-sm-down">
<button class="list-view-icon-btn sf2-icon-list-view <% if (mode == 'list') { %>active<% } %>" title="{% trans "List" %}" id="js-switch-list-view" aria-label="{% trans "list view" %}"></button><button class="grid-view-icon-btn sf2-icon-grid-view <% if (mode == 'grid') { %>active<% } %>" title="{% trans "Grid" %}" id="js-switch-grid-view" aria-label="{% trans "grid view" %}"></button>
<% if (app.pageOptions.enable_repo_wiki_mode) { %>
<a class="wiki-view-icon-btn sf2-icon-wiki" href="<%= site_root %>wiki/lib/<%- repo_id %><%- path %>" title="{% trans "Wiki" %}" aria-label="{% trans "Wiki view" %}"></a>
<% } %>
</div>
</script>
<script type="text/template" id="dir-view-toolbar2-tmpl">

View File

@ -303,8 +303,8 @@ app["pageOptions"] = {
enable_group_discussion: {% if enable_group_discussion %} true {% else %} false {% endif %},
enable_file_comment: {% if enable_file_comment %} true {% else %} false {% endif %},
enable_office_web_app: {% if enable_office_web_app %} true {% else %} false {% endif %},
enable_onlyoffice: {% if enable_onlyoffice %} true {% else %} false {% endif %}
enable_onlyoffice: {% if enable_onlyoffice %} true {% else %} false {% endif %},
enable_repo_wiki_mode: {% if enable_repo_wiki_mode %} true {% else %} false {% endif %}
};
app.ui = {
currentDropdown: null,

View File

@ -0,0 +1,20 @@
{% extends "base_for_react.html" %}
{% load render_bundle from webpack_loader %}
{% block extra_style %}
{% render_bundle 'repoview' 'css' %}
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
window.wiki = {
config: {
repoId: "{{ repo_id }}",
serviceUrl: "{{ service_url}}",
initial_file_path: "{{ file_path }}",
slug: "{{ repo_name }}"
}
};
</script>
{% render_bundle 'repoview' 'js' %}
{% endblock %}

View File

@ -14,7 +14,7 @@ from seahub.views.file import view_history_file, view_trash_file,\
text_diff, view_raw_file, view_raw_shared_file, \
download_file, view_lib_file, file_access, view_lib_file_via_smart_link
from seahub.views.repo import repo_history_view, view_shared_dir, \
view_shared_upload_link
view_shared_upload_link, view_lib_as_wiki
from notifications.views import notification_list
from seahub.views.wiki import personal_wiki, personal_wiki_pages, \
personal_wiki_create, personal_wiki_page_new, personal_wiki_page_edit, \
@ -155,6 +155,7 @@ urlpatterns = [
### lib (replace the old `repo` urls) ###
# url(r'^lib/(?P<repo_id>[-0-9a-f]{36})/dir/(?P<path>.*)$', view_lib_dir, name='view_lib_dir'),
url(r'^lib/(?P<repo_id>[-0-9a-f]{36})/file(?P<path>.*)$', view_lib_file, name='view_lib_file'),
url(r'^wiki/lib/(?P<repo_id>[-0-9a-f]{36})/(?P<path>.*)$', view_lib_as_wiki, name='view_lib_as_wiki'),
url(r'^smart-link/(?P<dirent_uuid>[-0-9a-f]{36})/(?P<dirent_name>.*)$', view_lib_file_via_smart_link, name="view_lib_file_via_smart_link"),
url(r'^#common/lib/(?P<repo_id>[-0-9a-f]{36})/(?P<path>.*)$', fake_view, name='view_common_lib_dir'),
url(r'^#group/(?P<group_id>\d+)/$', fake_view, name='group_info'),

View File

@ -24,7 +24,7 @@ from seahub.views import gen_path_link, get_repo_dirents, \
from seahub.utils import gen_dir_share_link, \
gen_shared_upload_link, user_traffic_over_limit, render_error, \
get_file_type_and_ext
get_file_type_and_ext, get_service_url
from seahub.settings import ENABLE_UPLOAD_FOLDER, \
ENABLE_RESUMABLE_FILEUPLOAD, ENABLE_THUMBNAIL, \
THUMBNAIL_ROOT, THUMBNAIL_DEFAULT_SIZE, THUMBNAIL_SIZE_FOR_GRID, \
@ -152,6 +152,25 @@ def repo_history_view(request, repo_id):
'referer': referer,
})
@login_required
def view_lib_as_wiki(request, repo_id, path):
if not path.startswith('/'):
path = '/' + path
repo = seafile_api.get_repo(repo_id)
user_perm = check_folder_permission(request, repo.id, '/')
if user_perm is None:
return render_error(request, _(u'Permission denied'))
return render(request, 'view_lib_as_wiki.html', {
'repo_id': repo_id,
'service_url': get_service_url().rstrip('/'),
'file_path': path,
'repo_name': repo.name
})
########## shared dir/uploadlink
@share_link_audit
def view_shared_dir(request, fileshare):