mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 15:38:15 +00:00
Combine view mode (#2959)
This commit is contained in:
@@ -20,7 +20,7 @@ import SharedLibraries from './pages/shared-libs/shared-libs';
|
||||
import MyLibraries from './pages/my-libs/my-libs';
|
||||
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
|
||||
import PublicSharedView from './pages/shared-with-all/public-shared-view';
|
||||
import DirView from './components/dir-view/dir-view';
|
||||
import LibContentView from './pages/lib-content-view/lib-content-view';
|
||||
import Group from './pages/groups/group-view';
|
||||
import Groups from './pages/groups/groups-view';
|
||||
import Wikis from './pages/wikis/wikis';
|
||||
@@ -212,7 +212,7 @@ class App extends Component {
|
||||
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibraries path={siteRoot + 'my-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibDeleted path={siteRoot + 'my-libs/deleted/'} onSearchedClick={this.onSearchedClick} />
|
||||
<DirView path={siteRoot + 'library/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<LibContentView path={siteRoot + 'library/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<Groups path={siteRoot + 'groups'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
|
||||
<Group
|
||||
path={siteRoot + 'group/:groupID'}
|
||||
|
@@ -11,7 +11,7 @@ const propTypes = {
|
||||
onTabNavClick: PropTypes.func,
|
||||
pathPrefix: PropTypes.array,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
isViewFile: PropTypes.bool.isRequired,
|
||||
isViewFile: PropTypes.bool,
|
||||
};
|
||||
|
||||
class DirPath extends React.Component {
|
||||
|
@@ -11,7 +11,7 @@ const propTypes = {
|
||||
onPathClick: PropTypes.func.isRequired,
|
||||
onTabNavClick: PropTypes.func,
|
||||
pathPrefix: PropTypes.array,
|
||||
isViewFile: PropTypes.bool.isRequired,
|
||||
isViewFile: PropTypes.bool,
|
||||
};
|
||||
|
||||
class CurDirPath extends React.Component {
|
||||
|
@@ -1,287 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cookie from 'react-cookies';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import CommonToolbar from '../toolbar/common-toolbar';
|
||||
import ViewModeToolbar from '../toolbar/view-mode-toolbar';
|
||||
import DirOperationToolBar from '../toolbar/dir-operation-toolbar';
|
||||
import MutipleDirOperationToolbar from '../toolbar/mutilple-dir-operation-toolbar';
|
||||
import CurDirPath from '../cur-dir-path';
|
||||
import DirentListView from '../dirent-list-view/dirent-list-view';
|
||||
import DirentDetail from '../dirent-detail/dirent-details';
|
||||
import FileUploader from '../file-uploader/file-uploader';
|
||||
import ModalPortal from '../modal-portal';
|
||||
import LibDecryptDialog from '../dialog/lib-decrypt-dialog';
|
||||
import RepoInfoBar from '../repo-info-bar';
|
||||
|
||||
const propTypes = {
|
||||
currentRepoInfo: PropTypes.object,
|
||||
pathPrefix: PropTypes.array.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
repoEncrypted: PropTypes.bool.isRequired,
|
||||
showShareBtn: PropTypes.bool.isRequired,
|
||||
enableDirPrivateShare: PropTypes.bool.isRequired,
|
||||
userPerm: PropTypes.string.isRequired,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
isGroupOwnedRepo: PropTypes.bool.isRequired,
|
||||
pathExist: PropTypes.bool.isRequired,
|
||||
permission: PropTypes.bool.isRequired,
|
||||
isDirentListLoading: PropTypes.bool.isRequired,
|
||||
isDirentSelected: PropTypes.bool.isRequired,
|
||||
isAllDirentSelected: PropTypes.bool.isRequired,
|
||||
direntList: PropTypes.array.isRequired,
|
||||
sortBy: PropTypes.string.isRequired,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
sortItems: PropTypes.func.isRequired,
|
||||
selectedDirentList: PropTypes.array.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
onAddFile: PropTypes.func.isRequired,
|
||||
onAddFolder: PropTypes.func.isRequired,
|
||||
onItemMove: PropTypes.func.isRequired,
|
||||
onItemCopy: PropTypes.func.isRequired,
|
||||
onItemRename: PropTypes.func.isRequired,
|
||||
onItemDelete: PropTypes.func.isRequired,
|
||||
onItemSelected: PropTypes.func.isRequired,
|
||||
onItemsCopy: PropTypes.func.isRequired,
|
||||
onItemsMove: PropTypes.func.isRequired,
|
||||
onItemsDelete: PropTypes.func.isRequired,
|
||||
onAllItemSelected: PropTypes.func.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired,
|
||||
onMenuClick: PropTypes.func.isRequired,
|
||||
onPathClick: PropTypes.func.isRequired,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
updateDirent: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
onFileUploadSuccess: PropTypes.func.isRequired,
|
||||
usedRepoTags: PropTypes.array.isRequired,
|
||||
readmeMarkdown: PropTypes.object,
|
||||
draftCounts: PropTypes.number,
|
||||
reviewCounts: PropTypes.number,
|
||||
updateUsedRepoTags: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class DirPanel extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentDirent: null,
|
||||
currentMode: 'list',
|
||||
isDirentDetailShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.setState({isDirentDetailShow: false});
|
||||
this.props.onPathClick(path);
|
||||
}
|
||||
|
||||
onItemClick = (dirent) => {
|
||||
this.setState({isDirentDetailShow: false});
|
||||
this.props.onItemClick(dirent);
|
||||
}
|
||||
|
||||
// on '<tr>'
|
||||
onDirentClick = (dirent) => {
|
||||
if (this.state.isDirentDetailShow) {
|
||||
this.onItemDetails(dirent);
|
||||
}
|
||||
}
|
||||
|
||||
onItemDetails = (dirent) => {
|
||||
this.setState({
|
||||
currentDirent: dirent,
|
||||
isDirentDetailShow: true,
|
||||
});
|
||||
}
|
||||
|
||||
onItemDetailsClose = () => {
|
||||
// todo there is bug when change item
|
||||
this.setState({isDirentDetailShow: false});
|
||||
}
|
||||
|
||||
onUploadFile = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
this.uploader.onFileUpload();
|
||||
}
|
||||
|
||||
onUploadFolder = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
this.uploader.onFolderUpload();
|
||||
}
|
||||
|
||||
switchViewMode = (mode) => {
|
||||
let { path, repoID } = this.props;
|
||||
if (mode === this.state.currentMode) {
|
||||
return;
|
||||
}
|
||||
if (mode === 'wiki') {
|
||||
var url = siteRoot + 'wiki/lib/' + repoID + path;
|
||||
window.location = url;
|
||||
}
|
||||
cookie.save('view_mode', mode, { path: '/' });
|
||||
|
||||
this.setState({currentMode: mode});
|
||||
}
|
||||
|
||||
render() {
|
||||
const errMessage = (<div className="message err-tip">{gettext('Folder does not exist.')}</div>);
|
||||
const showRepoInfoBar = this.props.path === '/' && (
|
||||
this.props.usedRepoTags.length != 0 || this.props.readmeMarkdown != null ||
|
||||
this.props.draftCounts != 0 || this.props.reviewCounts != 0);
|
||||
return (
|
||||
<div className="main-panel wiki-main-panel o-hidden">
|
||||
<div className="main-panel-north">
|
||||
{!this.props.libNeedDecrypt &&
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.props.onMenuClick}></span>
|
||||
<div className="dir-operation">
|
||||
{this.props.isDirentSelected ?
|
||||
<MutipleDirOperationToolbar
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
selectedDirentList={this.props.selectedDirentList}
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onItemsCopy={this.props.onItemsCopy}
|
||||
onItemsDelete={this.props.onItemsDelete}
|
||||
/> :
|
||||
<DirOperationToolBar
|
||||
isViewFile={false}
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
repoName={this.props.repoName}
|
||||
repoEncrypted={this.props.repoEncrypted}
|
||||
showShareBtn={this.props.showShareBtn}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
userPerm={this.props.userPerm}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
direntList={this.props.direntList}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
onUploadFile={this.onUploadFile}
|
||||
onUploadFolder={this.onUploadFolder}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<ViewModeToolbar
|
||||
currentMode={this.state.currentMode}
|
||||
switchViewMode={this.switchViewMode}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<CommonToolbar
|
||||
repoID={this.props.repoID}
|
||||
onSearchedClick={this.props.onSearchedClick}
|
||||
searchPlaceholder={gettext('Search files in this library')}
|
||||
/>
|
||||
</div>
|
||||
<div className="main-panel-center flex-row">
|
||||
{!this.props.libNeedDecrypt &&
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<CurDirPath
|
||||
isViewFile={false}
|
||||
repoID={this.props.repoID}
|
||||
repoName={this.props.repoName}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentPath={this.props.path}
|
||||
permission={this.props.permission}
|
||||
onPathClick={this.onPathClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.props.errorMsg ?
|
||||
<p className="error text-center">{this.props.errorMsg}</p> :
|
||||
<Fragment>
|
||||
{!this.props.pathExist ?
|
||||
errMessage :
|
||||
<Fragment>
|
||||
{showRepoInfoBar && (
|
||||
<RepoInfoBar
|
||||
repoID={this.props.repoID}
|
||||
currentPath={this.props.path}
|
||||
usedRepoTags={this.props.usedRepoTags}
|
||||
readmeMarkdown={this.props.readmeMarkdown}
|
||||
draftCounts={this.props.draftCounts}
|
||||
reviewCounts={this.props.reviewCounts}
|
||||
updateUsedRepoTags={this.props.updateUsedRepoTags}
|
||||
/>
|
||||
)}
|
||||
<DirentListView
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
repoEncrypted={this.props.repoEncrypted}
|
||||
isRepoOwner={this.props.isRepoOwner}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
direntList={this.props.direntList}
|
||||
sortBy={this.props.sortBy}
|
||||
sortOrder={this.props.sortOrder}
|
||||
sortItems={this.props.sortItems}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
isDirentListLoading={this.props.isDirentListLoading}
|
||||
isAllItemSelected={this.props.isAllDirentSelected}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onDirentClick={this.onDirentClick}
|
||||
onItemDetails={this.onItemDetails}
|
||||
onItemMove={this.props.onItemMove}
|
||||
onItemCopy={this.props.onItemCopy}
|
||||
onItemClick={this.onItemClick}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemRename={this.props.onItemRename}
|
||||
onItemSelected={this.props.onItemSelected}
|
||||
onAllItemSelected={this.props.onAllItemSelected}
|
||||
updateDirent={this.props.updateDirent}
|
||||
/>
|
||||
<FileUploader
|
||||
dragAndDrop={true}
|
||||
ref={uploader => this.uploader = uploader}
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
direntList={this.props.direntList}
|
||||
onFileUploadSuccess={this.props.onFileUploadSuccess}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{this.state.isDirentDetailShow && (
|
||||
<div className="cur-view-detail">
|
||||
<DirentDetail
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
dirent={this.state.currentDirent}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onItemDetailsClose={this.onItemDetailsClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{this.props.libNeedDecrypt && (
|
||||
<ModalPortal>
|
||||
<LibDecryptDialog
|
||||
repoID={this.props.repoID}
|
||||
onLibDecryptDialog={this.props.onLibDecryptDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirPanel.propTypes = propTypes;
|
||||
|
||||
export default DirPanel;
|
@@ -1,756 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { isPro, siteRoot, canGenerateShareLink, canGenerateUploadLink, username } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import collabServer from '../../utils/collab-server';
|
||||
import toaster from '../toast';
|
||||
import DirPanel from './dir-panel';
|
||||
import Dirent from '../../models/dirent';
|
||||
import FileTag from '../../models/file-tag';
|
||||
import RepoTag from '../../models/repo-tag';
|
||||
import RepoInfo from '../../models/repo-info';
|
||||
|
||||
const propTypes = {
|
||||
pathPrefix: PropTypes.array.isRequired,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
onMenuClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class DirView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
path: '/',
|
||||
pathExist: true,
|
||||
|
||||
repoName: '',
|
||||
repoID: '',
|
||||
repoEncrypted: false,
|
||||
isGroupOwnedRepo: false,
|
||||
isAdmin: false,
|
||||
ownerEmail: '',
|
||||
userPerm: '',
|
||||
|
||||
permission: true,
|
||||
libNeedDecrypt: false,
|
||||
isDirentSelected: false,
|
||||
isAllDirentSelected: false,
|
||||
isDirentListLoading: true,
|
||||
currentRepoInfo: null,
|
||||
sortBy: 'name', // 'name' or 'time'
|
||||
sortOrder: 'asc', // 'asc' or 'desc'
|
||||
direntList: [],
|
||||
selectedDirentList: [],
|
||||
dirID: '',
|
||||
errorMsg: '',
|
||||
usedRepoTags: [],
|
||||
readmeMarkdown: null,
|
||||
draftCounts: 0,
|
||||
reviewCounts: 0,
|
||||
};
|
||||
window.onpopstate = this.onpopstate;
|
||||
this.lastModifyTime = new Date();
|
||||
}
|
||||
|
||||
onpopstate = (event) => {
|
||||
if (event.state && event.state.path) {
|
||||
this.updateDirentList(event.state.path);
|
||||
this.setState({path: event.state.path});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// eg: http://127.0.0.1:8000/library/repo_id/repo_name/**/**/\
|
||||
let location = decodeURIComponent(window.location.href);
|
||||
let repoID = this.props.repoID;
|
||||
collabServer.watchRepo(repoID, this.onRepoUpdateEvent);
|
||||
seafileAPI.getRepoDraftReviewCounts(repoID).then(res => {
|
||||
this.setState({
|
||||
draftCounts: res.data.draft_counts,
|
||||
reviewCounts: res.data.review_counts,
|
||||
});
|
||||
});
|
||||
this.updateUsedRepoTags();
|
||||
seafileAPI.getRepoInfo(repoID).then(res => {
|
||||
let repoInfo = new RepoInfo(res.data);
|
||||
this.setState({
|
||||
currentRepoInfo: repoInfo,
|
||||
repoID: repoInfo.repo_id,
|
||||
repoName: repoInfo.repo_name,
|
||||
repoEncrypted: repoInfo.encrypted,
|
||||
isVirtual: repoInfo.is_virtual,
|
||||
isAdmin: repoInfo.is_admin,
|
||||
ownerEmail: repoInfo.owner_email,
|
||||
permission: repoInfo.permission === 'rw',
|
||||
libNeedDecrypt: res.data.lib_need_decrypt,
|
||||
});
|
||||
|
||||
const ownerEmail = repoInfo.owner_email;
|
||||
if (repoInfo.owner_email.indexOf('@seafile_group') != -1) {
|
||||
const groupID = ownerEmail.substring(0, ownerEmail.indexOf('@'));
|
||||
this.getGroupInfo(groupID);
|
||||
this.setState({
|
||||
isGroupOwnedRepo: true
|
||||
});
|
||||
}
|
||||
|
||||
let repoID = repoInfo.repo_id;
|
||||
let path = location.slice(location.indexOf(repoID) + repoID.length + 1); // get the string after repoID
|
||||
path = path.slice(path.indexOf('/')); // get current path
|
||||
this.setState({path: path});
|
||||
if (!res.data.lib_need_decrypt) {
|
||||
this.updateDirentList(path);
|
||||
|
||||
let fileUrl = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path);
|
||||
window.history.pushState({url: fileUrl, path: path}, path, fileUrl);
|
||||
}
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
isDirentListLoading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
isDirentListLoading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
isDirentListLoading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.lastModifyTime = new Date();
|
||||
}
|
||||
|
||||
getGroupInfo = (groupID) => {
|
||||
seafileAPI.getGroup(groupID).then(res => {
|
||||
if (res.data.admins.indexOf(username) != -1) {
|
||||
this.setState({
|
||||
isDepartmentAdmin: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onRepoUpdateEvent = () => {
|
||||
let currentTime = new Date();
|
||||
if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) {
|
||||
return;
|
||||
}
|
||||
let repoID = this.props.repoID;
|
||||
let { path, dirID } = this.state;
|
||||
seafileAPI.dirMetaData(repoID, path).then((res) => {
|
||||
if (res.data.id !== dirID) {
|
||||
toaster.notify(
|
||||
<span>
|
||||
{gettext('This folder has been updated. ')}
|
||||
<a href='' >{gettext('Refresh')}</a>
|
||||
</span>,
|
||||
{id: 'repo_updated', duration: 3600}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateUsedRepoTags = () => {
|
||||
seafileAPI.listRepoTags(this.props.repoID).then(res => {
|
||||
let usedRepoTags = [];
|
||||
res.data.repo_tags.forEach(item => {
|
||||
let usedRepoTag = new RepoTag(item);
|
||||
if (usedRepoTag.fileCount > 0) {
|
||||
usedRepoTags.push(usedRepoTag);
|
||||
}
|
||||
});
|
||||
this.setState({usedRepoTags: usedRepoTags});
|
||||
});
|
||||
}
|
||||
|
||||
updateReadmeMarkdown = (direntList) => {
|
||||
this.setState({readmeMarkdown: null});
|
||||
direntList.some(item => {
|
||||
let fileName = item.name.toLowerCase();
|
||||
if (fileName === 'readme.md' || fileName === 'readme.markdown') {
|
||||
this.setState({readmeMarkdown: item});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateDirentList = (path) => {
|
||||
let repoID = this.state.repoID;
|
||||
this.setState({isDirentListLoading: true});
|
||||
seafileAPI.listDir(repoID, path, {'with_thumbnail': true}).then(res => {
|
||||
let direntList = res.data.dirent_list.map(item => {
|
||||
let fileName = item.name.toLowerCase();
|
||||
if (fileName === 'readme.md' || fileName === 'readme.markdown') {
|
||||
this.setState({readmeMarkdown: item});
|
||||
}
|
||||
return new Dirent(item);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isDirentListLoading: false,
|
||||
pathExist: true,
|
||||
userPerm: res.data.user_perm,
|
||||
direntList: Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder),
|
||||
dirID: res.headers.oid,
|
||||
});
|
||||
|
||||
if (!this.state.repoEncrypted && direntList.length) {
|
||||
this.getThumbnails(repoID, path, this.state.direntList);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
isDirentListLoading: false,
|
||||
pathExist: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getThumbnails = (repoID, path, direntList) => {
|
||||
let items = direntList.filter((item) => {
|
||||
return Utils.imageCheck(item.name) && !item.encoded_thumbnail_src;
|
||||
});
|
||||
if (items.length == 0) {
|
||||
return ;
|
||||
}
|
||||
|
||||
const _this = this;
|
||||
const len = items.length;
|
||||
const thumbnailSize = 48;
|
||||
let getThumbnail = (i) => {
|
||||
const curItem = items[i];
|
||||
const curItemPath = [path, curItem.name].join('/');
|
||||
seafileAPI.createThumbnail(repoID, curItemPath, thumbnailSize).then((res) => {
|
||||
curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src;
|
||||
}).catch((error) => {
|
||||
// do nothing
|
||||
}).then(() => {
|
||||
if (i < len - 1) {
|
||||
getThumbnail(++i);
|
||||
} else {
|
||||
_this.setState({
|
||||
direntList: direntList
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
getThumbnail(0);
|
||||
}
|
||||
|
||||
onItemClick = (dirent) => {
|
||||
this.resetSelected();
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (dirent.isDir()) {
|
||||
this.updateDirentList(direntPath);
|
||||
this.setState({path: direntPath});
|
||||
|
||||
let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(direntPath);
|
||||
window.history.pushState({url: fileUrl, path: direntPath}, direntPath, fileUrl);
|
||||
} else {
|
||||
const w=window.open('about:blank');
|
||||
const url = siteRoot + 'lib/' + this.state.repoID + '/file' + Utils.encodePath(direntPath);
|
||||
w.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
onAddFolder = (dirPath) => {
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.createDir(repoID, dirPath).then(() => {
|
||||
let name = Utils.getFileName(dirPath);
|
||||
let dirent = this.createDirent(name, 'dir');
|
||||
let direntList = this.addItem(dirent, 'dir');
|
||||
this.setState({direntList: direntList});
|
||||
});
|
||||
}
|
||||
|
||||
onAddFile = (filePath, isDraft) => {
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.createFile(repoID, filePath, isDraft).then(res => {
|
||||
let name = Utils.getFileName(filePath);
|
||||
let dirent = this.createDirent(name, 'file', res.data);
|
||||
let direntList = this.addItem(dirent, 'file');
|
||||
this.setState({direntList: direntList});
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
});
|
||||
}
|
||||
|
||||
onItemDelete = (dirent) => {
|
||||
let repoID = this.state.repoID;
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (dirent.isDir()) {
|
||||
seafileAPI.deleteDir(repoID, direntPath).then(() => {
|
||||
let direntList = this.deleteItem(dirent);
|
||||
this.setState({direntList: direntList});
|
||||
}).catch(() => {
|
||||
// todo
|
||||
});
|
||||
} else {
|
||||
seafileAPI.deleteFile(repoID, direntPath).then(() => {
|
||||
let direntList = this.deleteItem(dirent);
|
||||
this.setState({direntList: direntList});
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
}).catch(() => {
|
||||
// todo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onItemRename = (dirent, newName) => {
|
||||
let repoID = this.state.repoID;
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (dirent.isDir()) {
|
||||
seafileAPI.renameDir(repoID, direntPath, newName).then(() => {
|
||||
let direntList = this.renameItem(dirent, newName);
|
||||
this.setState({direntList: direntList});
|
||||
}).catch(() => {
|
||||
//todo
|
||||
});
|
||||
} else {
|
||||
seafileAPI.renameFile(repoID, direntPath, newName).then(() => {
|
||||
let direntList = this.renameItem(dirent, newName);
|
||||
this.setState({direntList: direntList});
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
}).catch(() => {
|
||||
//todo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onItemMove = (destRepo, dirent, moveToDirentPath) => {
|
||||
let dirName = dirent.name;
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, this.state.path, dirName).then(() => {
|
||||
|
||||
let direntList = this.deleteItem(dirent);
|
||||
this.setState({direntList: direntList});
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
|
||||
let message = gettext('Successfully moved %(name)s.');
|
||||
message = message.replace('%(name)s', dirName);
|
||||
toaster.success(message);
|
||||
}).catch(() => {
|
||||
let message = gettext('Failed to move %(name)s');
|
||||
message = message.replace('%(name)s', dirName);
|
||||
toaster.danger(message);
|
||||
});
|
||||
}
|
||||
|
||||
onItemCopy = (destRepo, dirent, copyToDirentPath) => {
|
||||
let dirName = dirent.name;
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, this.state.path, dirName).then(() => {
|
||||
let message = gettext('Successfully copied %(name)s.');
|
||||
message = message.replace('%(name)s', dirName);
|
||||
toaster.success(message);
|
||||
}).catch(() => {
|
||||
let message = gettext('Failed to copy %(name)s');
|
||||
message = message.replace('%(name)s', dirName);
|
||||
toaster.danger(message);
|
||||
});
|
||||
}
|
||||
|
||||
onItemSelected = (dirent) => {
|
||||
let direntList = this.state.direntList.map(item => {
|
||||
if (item.name === dirent.name) {
|
||||
item.isSelected = !item.isSelected;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
let selectedDirentList = direntList.filter(item => {
|
||||
return item.isSelected;
|
||||
});
|
||||
|
||||
if (selectedDirentList.length) {
|
||||
this.setState({isDirentSelected: true});
|
||||
if (selectedDirentList.length === direntList.length) {
|
||||
this.setState({
|
||||
isAllDirentSelected: true,
|
||||
direntList: direntList,
|
||||
selectedDirentList: selectedDirentList,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
isAllDirentSelected: false,
|
||||
direntList: direntList,
|
||||
selectedDirentList: selectedDirentList
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
isDirentSelected: false,
|
||||
isAllDirentSelected: false,
|
||||
direntList: direntList,
|
||||
selectedDirentList: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onItemsMove = (destRepo, destDirentPath) => {
|
||||
let dirNames = this.getSelectedDirentNames();
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => {
|
||||
let direntList = this.deleteItems(dirNames);
|
||||
this.setState({
|
||||
direntList: direntList,
|
||||
isDirentSelected: false,
|
||||
selectedDirentList: [],
|
||||
});
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
let message = gettext('Successfully moved %(name)s.');
|
||||
message = message.replace('%(name)s', dirNames);
|
||||
toaster.success(message);
|
||||
}).catch(() => {
|
||||
let message = gettext('Failed to move %(name)s');
|
||||
message = message.replace('%(name)s', dirNames);
|
||||
toaster.danger(message);
|
||||
});
|
||||
}
|
||||
|
||||
onItemsCopy = (destRepo, destDirentPath) => {
|
||||
let dirNames = this.getSelectedDirentNames();
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => {
|
||||
let message = gettext('Successfully copied %(name)s.');
|
||||
message = message.replace('%(name)s', dirNames);
|
||||
toaster.success(message);
|
||||
}).catch(() => {
|
||||
let message = gettext('Failed to copy %(name)s');
|
||||
message = message.replace('%(name)s', dirNames);
|
||||
toaster.danger(message);
|
||||
});
|
||||
}
|
||||
|
||||
onItemsDelete = () => {
|
||||
let dirNames = this.getSelectedDirentNames();
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => {
|
||||
let direntList = this.deleteItems(dirNames);
|
||||
this.updateReadmeMarkdown(direntList);
|
||||
this.setState({
|
||||
direntList: direntList,
|
||||
isDirentSelected: false,
|
||||
selectedDirentList: [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onAllItemSelected = () => {
|
||||
if (this.state.isAllDirentSelected) {
|
||||
let direntList = this.state.direntList.map(item => {
|
||||
item.isSelected = false;
|
||||
return item;
|
||||
});
|
||||
this.setState({
|
||||
isDirentSelected: false,
|
||||
isAllDirentSelected: false,
|
||||
direntList: direntList,
|
||||
selectedDirentList: [],
|
||||
});
|
||||
} else {
|
||||
let direntList = this.state.direntList.map(item => {
|
||||
item.isSelected = true;
|
||||
return item;
|
||||
});
|
||||
this.setState({
|
||||
isDirentSelected: true,
|
||||
isAllDirentSelected: true,
|
||||
direntList: direntList,
|
||||
selectedDirentList: direntList,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFileTagChanged = (dirent, direntPath) => {
|
||||
let repoID = this.state.repoID;
|
||||
seafileAPI.listFileTags(repoID, direntPath).then(res => {
|
||||
let fileTags = res.data.file_tags.map(item => {
|
||||
return new FileTag(item);
|
||||
});
|
||||
this.updateDirent(dirent, 'file_tags', fileTags);
|
||||
});
|
||||
|
||||
this.updateUsedRepoTags();
|
||||
}
|
||||
|
||||
onMenuClick = () => {
|
||||
this.props.onMenuClick();
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.updateDirentList(path);
|
||||
this.setState({path: path});
|
||||
|
||||
let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(path);
|
||||
window.history.pushState({url: fileUrl, path: path}, path, fileUrl);
|
||||
}
|
||||
|
||||
updateDirent = (dirent, paramKey, paramValue) => {
|
||||
let newDirentList = this.state.direntList.map(item => {
|
||||
if (item.name === dirent.name) {
|
||||
item[paramKey] = paramValue;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({direntList: newDirentList});
|
||||
}
|
||||
|
||||
onFileUploadSuccess = (direntObject) => {
|
||||
let isExist = this.state.direntList.some(item => {
|
||||
return item.name === direntObject.name && item.type === direntObject.type;
|
||||
});
|
||||
if (isExist) {
|
||||
let direntList = this.state.direntList;
|
||||
for (let i = 0; i < direntList.length; i++) {
|
||||
let dirent = direntList[i];
|
||||
if (dirent.name === direntObject.name && dirent.type === direntObject.type) {
|
||||
let mtime = moment.unix(direntObject.mtime).fromNow();
|
||||
this.updateDirent(dirent, 'mtime', mtime); // todo file size is need update too, api is not return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
direntObject.permission = 'rw';
|
||||
let dirent = new Dirent(direntObject);
|
||||
if (direntObject.type === 'dir') {
|
||||
this.setState({direntList: [dirent, ...this.state.direntList]});
|
||||
} else {
|
||||
this.setState({direntList: [...this.state.direntList, dirent]});
|
||||
this.updateReadmeMarkdown(this.state.direntList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSearchedClick = (selectedItem) => {
|
||||
if (selectedItem.is_dir === true) {
|
||||
this.updateDirentList(selectedItem.path);
|
||||
this.setState({path: selectedItem.path});
|
||||
} else {
|
||||
let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + selectedItem.path;
|
||||
let newWindow = window.open('about:blank');
|
||||
newWindow.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
resetSelected = () => {
|
||||
this.setState({isDirentSelected: false, isAllDirentSelected: false});
|
||||
}
|
||||
|
||||
addItem = (dirent, type) => {
|
||||
let direntList = this.state.direntList.map(item => {return item;}); //clone
|
||||
if (type === 'dir') {
|
||||
direntList.unshift(dirent);
|
||||
return direntList;
|
||||
} else { // type === 'file'
|
||||
// there will be there conditions;
|
||||
// first: direntList.length === 0;
|
||||
// second: all the direntList's items are dir;
|
||||
// third: direntList has dir and file;
|
||||
let length = direntList.length;
|
||||
if (length === 0 || direntList[length - 1].type === 'dir') {
|
||||
direntList.push(dirent);
|
||||
} else {
|
||||
let index = 0;
|
||||
for (let i = 0; i <= length; i++) {
|
||||
if (direntList[i].type === 'file') {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
direntList.splice(index, 0, dirent);
|
||||
}
|
||||
return direntList;
|
||||
}
|
||||
}
|
||||
|
||||
deleteItem = (dirent) => {
|
||||
return this.state.direntList.filter(item => {
|
||||
return item.name !== dirent.name;
|
||||
});
|
||||
}
|
||||
|
||||
renameItem = (dirent, newName) => {
|
||||
return this.state.direntList.map(item => {
|
||||
if (item.name === dirent.name) {
|
||||
item.name = newName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
deleteItems = (dirNames) => {
|
||||
let direntList = this.state.direntList.map(item => {return item;}); //clone
|
||||
while (dirNames.length) {
|
||||
for (let i = 0; i < direntList.length; i++) {
|
||||
if (direntList[i].name === dirNames[0]) {
|
||||
direntList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
dirNames.shift();
|
||||
}
|
||||
return direntList;
|
||||
}
|
||||
|
||||
createDirent(name, type, direntInfo) {
|
||||
let data = new Date().getTime()/1000;
|
||||
let dirent = null;
|
||||
if (type === 'dir') {
|
||||
dirent = new Dirent({
|
||||
id: '000000000000000000',
|
||||
name: name,
|
||||
type: type,
|
||||
mtime: data,
|
||||
permission: 'rw',
|
||||
});
|
||||
} else {
|
||||
dirent = new Dirent({
|
||||
id: '000000000000000000',
|
||||
name: name,
|
||||
type: type,
|
||||
mtime: data,
|
||||
permission: 'rw',
|
||||
size: direntInfo.size,
|
||||
starred: false,
|
||||
is_locked: false,
|
||||
lock_time: '',
|
||||
lock_owner: null,
|
||||
locked_by_me: false,
|
||||
modifier_name: '',
|
||||
modifier_email: '',
|
||||
modifier_contact_email: '',
|
||||
file_tags: []
|
||||
});
|
||||
}
|
||||
return dirent;
|
||||
}
|
||||
|
||||
getSelectedDirentNames = () => {
|
||||
let names = [];
|
||||
this.state.selectedDirentList.forEach(selectedDirent => {
|
||||
names.push(selectedDirent.name);
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLibDecryptDialog = () => {
|
||||
this.setState({
|
||||
libNeedDecrypt: !this.state.libNeedDecrypt
|
||||
});
|
||||
this.updateDirentList(this.state.path);
|
||||
}
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
items: Utils.sortDirents(this.state.direntList, sortBy, sortOrder)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let showShareBtn = false,
|
||||
enableDirPrivateShare = false;
|
||||
const { repoEncrypted, isAdmin, ownerEmail, userPerm, isVirtual, isDepartmentAdmin } = this.state;
|
||||
const isRepoOwner = ownerEmail == username;
|
||||
if (!repoEncrypted && (
|
||||
canGenerateShareLink || canGenerateUploadLink ||
|
||||
isRepoOwner || isAdmin) && (
|
||||
userPerm == 'rw' || userPerm == 'r')) {
|
||||
showShareBtn = true;
|
||||
if (!isVirtual && (isRepoOwner || isAdmin || isDepartmentAdmin)) {
|
||||
enableDirPrivateShare = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DirPanel
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
path={this.state.path}
|
||||
pathExist={this.state.pathExist}
|
||||
errorMsg={this.state.errorMsg}
|
||||
repoID={this.state.repoID}
|
||||
repoName={this.state.repoName}
|
||||
repoEncrypted={this.state.repoEncrypted}
|
||||
permission={this.state.permission}
|
||||
isDirentListLoading={this.state.isDirentListLoading}
|
||||
isDirentSelected={this.state.isDirentSelected}
|
||||
isAllDirentSelected={this.state.isAllDirentSelected}
|
||||
showShareBtn={showShareBtn}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
userPerm={userPerm}
|
||||
isRepoOwner={isRepoOwner}
|
||||
isAdmin={isAdmin}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
direntList={this.state.direntList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
selectedDirentList={this.state.selectedDirentList}
|
||||
onItemClick={this.onItemClick}
|
||||
onAddFile={this.onAddFile}
|
||||
onAddFolder={this.onAddFolder}
|
||||
onItemMove={this.onItemMove}
|
||||
onItemCopy={this.onItemCopy}
|
||||
onItemRename={this.onItemRename}
|
||||
onItemDelete={this.onItemDelete}
|
||||
onItemSelected={this.onItemSelected}
|
||||
onItemsMove={this.onItemsMove}
|
||||
onItemsCopy={this.onItemsCopy}
|
||||
onItemsDelete={this.onItemsDelete}
|
||||
onAllItemSelected={this.onAllItemSelected}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
onMenuClick={this.onMenuClick}
|
||||
onPathClick={this.onPathClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
updateDirent={this.updateDirent}
|
||||
switchViewMode={this.switchViewMode}
|
||||
onSearchedClick={this.onSearchedClick}
|
||||
onFileUploadSuccess={this.onFileUploadSuccess}
|
||||
libNeedDecrypt={this.state.libNeedDecrypt}
|
||||
onLibDecryptDialog={this.onLibDecryptDialog}
|
||||
usedRepoTags={this.state.usedRepoTags}
|
||||
readmeMarkdown={this.state.readmeMarkdown}
|
||||
draftCounts={this.state.draftCounts}
|
||||
reviewCounts={this.state.reviewCounts}
|
||||
updateUsedRepoTags={this.updateUsedRepoTags}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirView.propTypes = propTypes;
|
||||
|
||||
export default DirView;
|
145
frontend/src/components/file-content-view.js
Normal file
145
frontend/src/components/file-content-view.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Utils } from '../utils/utils';
|
||||
import { gettext, siteRoot } from '../utils/constants';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import CommonToolbar from './toolbar/common-toolbar';
|
||||
import CurDirPath from './cur-dir-path';
|
||||
import WikiMarkdownViewer from './wiki-markdown-viewer';
|
||||
|
||||
const propTypes = {
|
||||
pathPrefix: PropTypes.array.isRequired,
|
||||
currentMode: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hash: PropTypes.string,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
onSideNavMenuClick: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
onMainNavBarClick: PropTypes.func.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
currentRepoInfo: PropTypes.object,
|
||||
repoPermission: PropTypes.bool,
|
||||
isDraft: PropTypes.bool,
|
||||
hasDraft: PropTypes.bool,
|
||||
goDraftPage: PropTypes.func.isRequired,
|
||||
reviewStatus: PropTypes.any,
|
||||
goReviewPage: PropTypes.func.isRequired,
|
||||
isFileLoading: PropTypes.bool.isRequired,
|
||||
filePermission: PropTypes.bool,
|
||||
content: PropTypes.string,
|
||||
lastModified: PropTypes.string,
|
||||
latestContributor: PropTypes.string,
|
||||
onLinkClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class FileContentView extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.hash) {
|
||||
let hash = this.props.hash;
|
||||
setTimeout(function() {
|
||||
window.location.hash = hash;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
onEditClick = (e) => {
|
||||
e.preventDefault();
|
||||
let { path, repoID } = this.props;
|
||||
let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(path) + '?mode=edit';
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onNewDraft = (e) => {
|
||||
e.preventDefault();
|
||||
let { path, repoID } = this.props;
|
||||
seafileAPI.createDraft(repoID, path).then(res => {
|
||||
window.location.href = siteRoot + 'lib/' + res.data.origin_repo_id + '/file' + res.data.draft_file_path + '?mode=edit';
|
||||
});
|
||||
}
|
||||
|
||||
goDraftPage = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.goDraftPage();
|
||||
}
|
||||
|
||||
goReviewPage = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.goReviewPage();
|
||||
}
|
||||
|
||||
render() {
|
||||
let repoID = this.props.repoID;
|
||||
return (
|
||||
<div className={`main-panel o-hidden ${this.props.currentMode === 'column' ? 'dir-main-content' : ''}`}>
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.props.onSideNavMenuClick}></span>
|
||||
<div className="dir-operation">
|
||||
{(this.props.filePermission && !this.props.hasDraft ) && (
|
||||
<Fragment>
|
||||
<button className="btn btn-secondary operation-item" title={gettext('Edit File')} onClick={this.onEditClick}>{gettext('Edit')}</button>
|
||||
</Fragment>
|
||||
)}
|
||||
{/* default have read priv */}
|
||||
{(!this.props.isDraft && !this.props.hasDraft) && (
|
||||
<button className="btn btn-secondary operation-item" title={gettext('New Draft')} onClick={this.onNewDraft}>{gettext('New Draft')}</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CommonToolbar repoID={repoID} onSearchedClick={this.props.onSearchedClick} searchPlaceholder={gettext('Search files in this library')}/>
|
||||
</div>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
{this.props.currentRepoInfo && (
|
||||
<CurDirPath
|
||||
repoID={repoID}
|
||||
repoName={this.props.currentRepoInfo.repo_name}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentPath={this.props.path}
|
||||
permission={this.props.repoPermission}
|
||||
onPathClick={this.props.onMainNavBarClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
isViewFile={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<WikiMarkdownViewer
|
||||
isFileLoading={this.props.isFileLoading}
|
||||
markdownContent={this.props.content}
|
||||
lastModified = {this.props.lastModified}
|
||||
latestContributor={this.props.latestContributor}
|
||||
onLinkClick={this.props.onLinkClick}
|
||||
>
|
||||
<Fragment>
|
||||
{this.props.reviewStatus === 'open' &&
|
||||
<div className='seafile-btn-view-review text-center'>
|
||||
<div className='tag tag-green'>
|
||||
{gettext('This file is in review stage')}
|
||||
<span className="ml-2" onClick={this.goReviewPage}>{gettext('View Review')}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{(this.props.reviewStatus !== 'open' && !this.props.isDraft && this.props.hasDraft) &&
|
||||
<div className='seafile-btn-view-review text-center'>
|
||||
<div className='tag tag-green'>
|
||||
{gettext('This file is in draft stage.')}
|
||||
<span className="ml-2" onClick={this.goDraftPage}>{gettext('Edit Draft')}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
</WikiMarkdownViewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FileContentView.propTypes = propTypes;
|
||||
|
||||
export default FileContentView;
|
@@ -1,16 +1,13 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import ModalPortal from '../modal-portal';
|
||||
import CreateFolder from '../../components/dialog/create-folder-dialog';
|
||||
import CreateFile from '../../components/dialog/create-file-dialog';
|
||||
import ShareDialog from '../../components/dialog/share-dialog';
|
||||
|
||||
const propTypes = {
|
||||
isViewFile: PropTypes.bool, // just for view file,
|
||||
permission: PropTypes.string, //just for view file, and premission is file permission
|
||||
path: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
showShareBtn: PropTypes.bool.isRequired,
|
||||
@@ -18,8 +15,6 @@ const propTypes = {
|
||||
onAddFolder: PropTypes.func.isRequired,
|
||||
onUploadFile: PropTypes.func.isRequired,
|
||||
onUploadFolder: PropTypes.func.isRequired,
|
||||
isDraft: PropTypes.bool,
|
||||
hasDraft: PropTypes.bool,
|
||||
direntList: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
@@ -62,21 +57,6 @@ class DirOperationToolbar extends React.Component {
|
||||
this.setState({operationMenuStyle: style});
|
||||
}
|
||||
|
||||
onEditClick = (e) => {
|
||||
e.preventDefault();
|
||||
let { path, repoID } = this.props;
|
||||
let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(path) + '?mode=edit';
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onNewDraft = (e) => {
|
||||
e.preventDefault();
|
||||
let { path, repoID } = this.props;
|
||||
seafileAPI.createDraft(repoID, path).then(res => {
|
||||
window.location.href = siteRoot + 'lib/' + res.data.origin_repo_id + '/file' + res.data.draft_file_path + '?mode=edit';
|
||||
});
|
||||
}
|
||||
|
||||
onUploadClick = (e) => {
|
||||
this.toggleOperationMenu(e);
|
||||
this.setState({
|
||||
@@ -108,7 +88,6 @@ class DirOperationToolbar extends React.Component {
|
||||
this.setState({
|
||||
isShareDialogShow: !this.state.isShareDialogShow
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onCreateFolderToggle = () => {
|
||||
@@ -177,31 +156,17 @@ class DirOperationToolbar extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { path, isViewFile, repoName } = this.props;
|
||||
let itemType = isViewFile ? 'file' : 'dir';
|
||||
let itemName = isViewFile ? Utils.getFileName(path) : (path == '/' ? repoName : Utils.getFolderName(path));
|
||||
let { path, repoName } = this.props;
|
||||
let itemType = 'dir';
|
||||
let itemName = path == '/' ? repoName : Utils.getFolderName(path);
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="operation">
|
||||
{(this.props.isViewFile && this.props.permission === 'rw' && !this.props.hasDraft ) && (
|
||||
<Fragment>
|
||||
<button className="btn btn-secondary operation-item" title={gettext('Edit File')} onClick={this.onEditClick}>{gettext('Edit')}</button>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{(this.props.isViewFile && !this.props.isDraft && !this.props.hasDraft) && (
|
||||
<button className="btn btn-secondary operation-item" title={gettext('New Draft')} onClick={this.onNewDraft}>{gettext('New Draft')}</button>
|
||||
)}
|
||||
|
||||
{!this.props.isViewFile && (
|
||||
<Fragment>
|
||||
{Utils.isSupportUploadFolder() ?
|
||||
<button className="btn btn-secondary operation-item" title={gettext('Upload')} onClick={this.onUploadClick}>{gettext('Upload')}</button> :
|
||||
<button className="btn btn-secondary operation-item" title={gettext('Upload')} onClick={this.uploadFile}>{gettext('Upload')}</button>
|
||||
}
|
||||
<button className="btn btn-secondary operation-item" title={gettext('New')} onClick={this.onCreateClick}>{gettext('New')}</button>
|
||||
</Fragment>
|
||||
)}
|
||||
{this.props.showShareBtn &&
|
||||
<button className="btn btn-secondary operation-item" title={gettext('Share')} onClick={this.onShareClick}>{gettext('Share')}</button>
|
||||
}
|
||||
|
@@ -20,8 +20,8 @@ class GroupsToolbar extends React.Component {
|
||||
render() {
|
||||
let { onShowSidePanel, onSearchedClick } = this.props;
|
||||
return (
|
||||
<div className="main-panel-north">
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<div className="operation">
|
||||
<Button color="btn btn-secondary operation-item" onClick={this.props.toggleAddGroupModal}>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
|
||||
|
@@ -40,7 +40,7 @@ class RepoViewToolbar extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<div className="operation">
|
||||
|
@@ -24,7 +24,7 @@ class ViewModeToolbar extends React.Component {
|
||||
<div className="view-mode btn-group">
|
||||
<button className={`${baseClass} sf2-icon-list-view ${this.props.currentMode === 'list' ? 'current-mode' : ''}`} id='list' title={gettext('List')} onClick={this.switchViewMode}></button>
|
||||
{/* <button className={`${baseClass} sf2-icon-grid-view ${this.props.currentMode === 'grid' ? 'current-mode' : ''}`} id='grid' title={gettext('Grid')} onClick={this.switchViewMode}></button> */}
|
||||
<button className={`${baseClass} sf2-icon-two-columns ${this.props.currentMode === 'wiki' ? 'current-mode' : ''}`} id='wiki' title={gettext('Wiki')} onClick={this.switchViewMode}></button>
|
||||
<button className={`${baseClass} sf2-icon-two-columns ${this.props.currentMode === 'column' ? 'current-mode' : ''}`} id='column' title={gettext('column')} onClick={this.switchViewMode}></button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import TreeNodeMenu from './tree-node-menu';
|
||||
import { permission } from '../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
repoPermission: PropTypes.bool,
|
||||
node: PropTypes.object.isRequired,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
paddingLeft: PropTypes.number.isRequired,
|
||||
@@ -111,6 +112,7 @@ class TreeNodeView extends React.Component {
|
||||
key={item.path}
|
||||
node={item}
|
||||
paddingLeft={paddingLeft}
|
||||
repoPermission={this.props.repoPermission}
|
||||
currentPath={this.props.currentPath}
|
||||
isNodeMenuShow={this.props.isNodeMenuShow}
|
||||
isItemFreezed={this.props.isItemFreezed}
|
||||
@@ -150,7 +152,7 @@ class TreeNodeView extends React.Component {
|
||||
</div>
|
||||
{isNodeMenuShow && (
|
||||
<div className="right-icon">
|
||||
{(permission && this.state.isShowOperationMenu) && (
|
||||
{((this.props.repoPermission || permission) && this.state.isShowOperationMenu) && (
|
||||
<TreeNodeMenu
|
||||
node={this.props.node}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
|
@@ -10,17 +10,17 @@ class TreeNode {
|
||||
this.parentNode = parentNode || null;
|
||||
}
|
||||
|
||||
clone() {
|
||||
clone(parentNode) {
|
||||
let treeNode = new TreeNode({
|
||||
path: this.path,
|
||||
object: this.object.clone(),
|
||||
isLoaded: this.isLoaded,
|
||||
isPreload: this.isPreload,
|
||||
isExpanded: this.isExpanded,
|
||||
parentNode: this.parentNode,
|
||||
parentNode: parentNode || null,
|
||||
});
|
||||
treeNode.children = this.children.map(child => {
|
||||
let newChild = child.clone();
|
||||
let newChild = child.clone(treeNode);
|
||||
return newChild;
|
||||
});
|
||||
|
||||
@@ -77,9 +77,24 @@ class TreeNode {
|
||||
rename(newName) {
|
||||
this.object.name = newName;
|
||||
this.path = this.generatePath(this.parentNode);
|
||||
if (this.isExpanded) {
|
||||
this.updateChildrenPath(this);
|
||||
}
|
||||
// this.isLoaded = false;
|
||||
}
|
||||
|
||||
updateChildrenPath(node) {
|
||||
let children = node.children;
|
||||
children.forEach(child => {
|
||||
child.path = child.generatePath(child.parentNode);
|
||||
if (child.isExpanded) {
|
||||
child.updateChildrenPath(child);
|
||||
} else {
|
||||
child.isLoaded = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateObjectProperties(keys, newValues) {
|
||||
if (Array.isArray(keys) && Array.isArray(newValues)) {
|
||||
keys.forEach((key, index) => {
|
||||
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import TreeNodeView from './tree-node-view';
|
||||
|
||||
const propTypes = {
|
||||
repoPermission: PropTypes.bool,
|
||||
isNodeMenuShow: PropTypes.bool.isRequired,
|
||||
treeData: PropTypes.object.isRequired,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
@@ -39,6 +40,7 @@ class TreeView extends React.Component {
|
||||
return (
|
||||
<div className="tree-view tree">
|
||||
<TreeNodeView
|
||||
repoPermission={this.props.repoPermission}
|
||||
node={this.props.treeData.root}
|
||||
currentPath={this.props.currentPath}
|
||||
paddingLeft={PADDING_LEFT}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
.file-uploader-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
/* flex: 1; */
|
||||
}
|
||||
|
||||
.file-uploader {
|
||||
|
@@ -18,14 +18,14 @@
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
flex: 0 0 25%;
|
||||
flex: 0 0 22%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-panel {
|
||||
flex: 1 0 75%;
|
||||
flex: 1 0 78%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
.side-panel-north,
|
||||
.main-panel-north {
|
||||
position: relative;
|
||||
padding: .5rem 1rem;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
168
frontend/src/css/lib-content-view.css
Normal file
168
frontend/src/css/lib-content-view.css
Normal file
@@ -0,0 +1,168 @@
|
||||
|
||||
.view-mode-container {
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
.dir-side-nav {
|
||||
flex: 0 0 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dir-main-content {
|
||||
flex: 1 0 75% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dir-nav-heading {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f4f4f7;
|
||||
border-bottom: solid 1px #e8e8e8;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
height: 49px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dir-nav-container {
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
padding: 12px 0;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dir-nav-container:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.heading-icon {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 30%;
|
||||
color: #888;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
/*tree view */
|
||||
.tree-view {
|
||||
padding: 0 12px 0 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tree-node-inner {
|
||||
position: relative;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
.tree-node-inner-hover {
|
||||
background-color: rgb(255,239,178);
|
||||
}
|
||||
|
||||
.tree-node-inner .tree-node-text {
|
||||
padding-left: 2.8rem;
|
||||
width: calc(100% - 2.8rem);
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tree-node-inner .left-icon {
|
||||
position: absolute;
|
||||
display:flex;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.folder-toggle-icon {
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
color: #c0c0c0;
|
||||
line-height: 1.625;
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tree-node-icon {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
text-align: center;
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.tree-node-inner .right-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1rem;
|
||||
bottom: 0;
|
||||
width: 1.5rem;
|
||||
color: #888;
|
||||
z-index: 2;
|
||||
font-size: 0.8125rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tree-node-inner .right-icon i {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.625rem !important;
|
||||
}
|
||||
|
||||
.tree-node-hight-light {
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
background-color: #feac74 !important;
|
||||
}
|
||||
|
||||
.tree-node-hight-light i {
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 8rem;
|
||||
}
|
||||
|
||||
.parent-path {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.parent-path .form-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-top: 0;
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.dir-main-content .wiki-page-content .ml-2 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dir-main-content .wiki-page-content .ml-2:hover {
|
||||
text-decoration: underline;
|
||||
color:#eb8205;
|
||||
}
|
||||
|
||||
/* todo optimized */
|
||||
#wiki-page-last-modified {
|
||||
padding: 40px 10px;
|
||||
font-size:12px;
|
||||
color: #666;
|
||||
}
|
@@ -92,7 +92,7 @@
|
||||
|
||||
/*tree view */
|
||||
.tree-view {
|
||||
padding-left: 10px;
|
||||
padding: 0 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
|
||||
.tree-node-hight-light {
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
background-color: #feac74 !important;
|
||||
}
|
||||
|
||||
|
@@ -8,11 +8,12 @@
|
||||
|
||||
.border-left-show:before {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
bottom: 3px;
|
||||
left: -16px;
|
||||
border-left: 1px solid #ddd;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
content: '';
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* end toolbar-container */
|
||||
|
@@ -342,8 +342,8 @@ class GroupView extends React.Component {
|
||||
let isShowSettingIcon = !(currentGroup && currentGroup.parent_group_id !== 0 && currentGroup.admins.indexOf(username) === -1);
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north">
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<div className="operation">
|
||||
{canAddRepo && (
|
||||
|
285
frontend/src/pages/lib-content-view/lib-content-main.js
Normal file
285
frontend/src/pages/lib-content-view/lib-content-main.js
Normal file
@@ -0,0 +1,285 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import ViewModeToolbar from '../../components/toolbar/view-mode-toolbar';
|
||||
import DirOperationToolBar from '../../components/toolbar/dir-operation-toolbar';
|
||||
import MutipleDirOperationToolbar from '../../components/toolbar/mutilple-dir-operation-toolbar';
|
||||
import CurDirPath from '../../components/cur-dir-path';
|
||||
import DirentListView from '../../components/dirent-list-view/dirent-list-view';
|
||||
import DirentDetail from '../../components/dirent-detail/dirent-details';
|
||||
import FileUploader from '../../components/file-uploader/file-uploader';
|
||||
import RepoInfoBar from '../../components/repo-info-bar';
|
||||
|
||||
const propTypes = {
|
||||
pathPrefix: PropTypes.array.isRequired,
|
||||
currentMode: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
pathExist: PropTypes.bool.isRequired,
|
||||
// repo
|
||||
currentRepoInfo: PropTypes.object, // Initially not loaded
|
||||
repoID: PropTypes.string.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
repoPermission: PropTypes.bool.isRequired,
|
||||
repoEncrypted: PropTypes.bool.isRequired,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
userPerm: PropTypes.string.isRequired,
|
||||
showShareBtn: PropTypes.bool.isRequired,
|
||||
enableDirPrivateShare: PropTypes.bool.isRequired,
|
||||
isRepoOwner: PropTypes.bool.isRequired,
|
||||
isGroupOwnedRepo: PropTypes.bool.isRequired,
|
||||
// toolbar
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
onSideNavMenuClick: PropTypes.func.isRequired,
|
||||
selectedDirentList: PropTypes.array.isRequired,
|
||||
onItemsMove: PropTypes.func.isRequired,
|
||||
onItemsCopy: PropTypes.func.isRequired,
|
||||
onItemsDelete: PropTypes.func.isRequired,
|
||||
switchViewMode: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
onMainNavBarClick: PropTypes.func.isRequired,
|
||||
// repo content
|
||||
draftCounts: PropTypes.number,
|
||||
reviewCounts: PropTypes.number,
|
||||
usedRepoTags: PropTypes.array.isRequired,
|
||||
readmeMarkdown: PropTypes.object,
|
||||
updateUsedRepoTags: PropTypes.func.isRequired,
|
||||
// dirent list
|
||||
isDirentListLoading: PropTypes.bool.isRequired,
|
||||
direntList: PropTypes.array.isRequired,
|
||||
sortBy: PropTypes.string.isRequired,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
sortItems: PropTypes.func.isRequired,
|
||||
updateDirent: PropTypes.func.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
onItemSelected: PropTypes.func.isRequired,
|
||||
onItemDelete: PropTypes.func.isRequired,
|
||||
onItemRename: PropTypes.func.isRequired,
|
||||
onItemMove: PropTypes.func.isRequired,
|
||||
onItemCopy: PropTypes.func.isRequired,
|
||||
onAddFolder: PropTypes.func.isRequired,
|
||||
onAddFile: PropTypes.func.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired,
|
||||
isDirentSelected: PropTypes.bool.isRequired,
|
||||
isAllDirentSelected: PropTypes.bool.isRequired,
|
||||
onAllDirentSelected: PropTypes.func.isRequired,
|
||||
onFileUploadSuccess: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class LibContentMain extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentDirent: null,
|
||||
isDirentDetailShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
onSideNavMenuClick = () => {
|
||||
this.props.onSideNavMenuClick();
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.setState({isDirentDetailShow: false});
|
||||
this.props.onPathClick(path);
|
||||
}
|
||||
|
||||
onItemClick = (dirent) => {
|
||||
this.setState({isDirentDetailShow: false});
|
||||
this.props.onItemClick(dirent);
|
||||
}
|
||||
|
||||
// on '<tr>'
|
||||
onDirentClick = (dirent) => {
|
||||
if (this.state.isDirentDetailShow) {
|
||||
this.onItemDetails(dirent);
|
||||
}
|
||||
}
|
||||
|
||||
onItemDetails = (dirent) => {
|
||||
this.setState({
|
||||
currentDirent: dirent,
|
||||
isDirentDetailShow: true,
|
||||
});
|
||||
}
|
||||
|
||||
onItemDetailsClose = () => {
|
||||
this.setState({isDirentDetailShow: false});
|
||||
}
|
||||
|
||||
onUploadFile = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
this.uploader.onFileUpload();
|
||||
}
|
||||
|
||||
onUploadFolder = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
this.uploader.onFolderUpload();
|
||||
}
|
||||
|
||||
switchViewMode = (mode) => {
|
||||
this.props.switchViewMode(mode);
|
||||
}
|
||||
|
||||
onFileUploadSuccess = (direntObject) => {
|
||||
this.props.onFileUploadSuccess(direntObject);
|
||||
}
|
||||
|
||||
renderListView = () => {
|
||||
let repoID = this.props.repoID;
|
||||
const showRepoInfoBar = this.props.path === '/' && (
|
||||
this.props.usedRepoTags.length != 0 || this.props.readmeMarkdown != null ||
|
||||
this.props.draftCounts != 0 || this.props.reviewCounts != 0);
|
||||
return (
|
||||
<Fragment>
|
||||
{showRepoInfoBar && (
|
||||
<RepoInfoBar
|
||||
repoID={repoID}
|
||||
currentPath={this.props.path}
|
||||
usedRepoTags={this.props.usedRepoTags}
|
||||
readmeMarkdown={this.props.readmeMarkdown}
|
||||
draftCounts={this.props.draftCounts}
|
||||
reviewCounts={this.props.reviewCounts}
|
||||
updateUsedRepoTags={this.props.updateUsedRepoTags}
|
||||
/>
|
||||
)}
|
||||
<DirentListView
|
||||
path={this.props.path}
|
||||
repoID={repoID}
|
||||
repoEncrypted={this.props.repoEncrypted}
|
||||
isRepoOwner={this.props.isRepoOwner}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
direntList={this.props.direntList}
|
||||
sortBy={this.props.sortBy}
|
||||
sortOrder={this.props.sortOrder}
|
||||
sortItems={this.props.sortItems}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onItemClick={this.onItemClick}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemRename={this.props.onItemRename}
|
||||
onItemMove={this.props.onItemMove}
|
||||
onItemCopy={this.props.onItemCopy}
|
||||
onDirentClick={this.onDirentClick}
|
||||
onItemDetails={this.onItemDetails}
|
||||
isDirentListLoading={this.props.isDirentListLoading}
|
||||
updateDirent={this.props.updateDirent}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
isAllItemSelected={this.props.isAllDirentSelected}
|
||||
onAllItemSelected={this.props.onAllDirentSelected}
|
||||
onItemSelected={this.props.onItemSelected}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderGridView = () => {
|
||||
// todo
|
||||
return (
|
||||
<div>grid-mode</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderColumnView = () => {
|
||||
return (
|
||||
this.renderListView()
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
let repoID = this.props.repoID;
|
||||
const errMessage = (<div className="message err-tip">{gettext('Folder does not exist.')}</div>);
|
||||
return (
|
||||
<div className={`main-panel o-hidden ${this.props.currentMode === 'column' ? 'dir-main-content' : ''}`}>
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.onSideNavMenuClick}></span>
|
||||
<div className="dir-operation">
|
||||
{this.props.isDirentSelected ?
|
||||
<MutipleDirOperationToolbar
|
||||
repoID={repoID}
|
||||
path={this.props.path}
|
||||
selectedDirentList={this.props.selectedDirentList}
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onItemsCopy={this.props.onItemsCopy}
|
||||
onItemsDelete={this.props.onItemsDelete}
|
||||
/> :
|
||||
<DirOperationToolBar
|
||||
path={this.props.path}
|
||||
repoID={repoID}
|
||||
repoName={this.props.repoName}
|
||||
repoEncrypted={this.props.repoEncrypted}
|
||||
direntList={this.props.direntList}
|
||||
showShareBtn={this.props.showShareBtn}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
userPerm={this.props.userPerm}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
onUploadFile={this.onUploadFile}
|
||||
onUploadFolder={this.onUploadFolder}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<ViewModeToolbar currentMode={this.props.currentMode} switchViewMode={this.switchViewMode}/>
|
||||
</div>
|
||||
<CommonToolbar repoID={repoID} onSearchedClick={this.props.onSearchedClick} searchPlaceholder={gettext('Search files in this library')}/>
|
||||
</div>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
{this.props.currentRepoInfo && (
|
||||
<CurDirPath
|
||||
repoID={repoID}
|
||||
repoName={this.props.currentRepoInfo.repo_name}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentPath={this.props.path}
|
||||
permission={this.props.repoPermission}
|
||||
onPathClick={this.props.onMainNavBarClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{!this.props.pathExist && errMessage }
|
||||
{(this.props.pathExist && (
|
||||
<Fragment>
|
||||
{this.props.currentMode === 'list' && this.renderListView()}
|
||||
{this.props.currentMode === 'grid' && this.renderGridView()}
|
||||
{this.props.currentMode === 'column' && this.renderColumnView()}
|
||||
<FileUploader
|
||||
ref={uploader => this.uploader = uploader}
|
||||
dragAndDrop={true}
|
||||
path={this.props.path}
|
||||
repoID={repoID}
|
||||
direntList={this.props.direntList}
|
||||
onFileUploadSuccess={this.onFileUploadSuccess}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isDirentDetailShow && (
|
||||
<div className="cur-view-detail">
|
||||
<DirentDetail
|
||||
repoID={repoID}
|
||||
path={this.props.path}
|
||||
dirent={this.state.currentDirent}
|
||||
direntPath={this.state.direntPath}
|
||||
onItemDetailsClose={this.onItemDetailsClose}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LibContentMain.propTypes = propTypes;
|
||||
|
||||
export default LibContentMain;
|
235
frontend/src/pages/lib-content-view/lib-content-nav.js
Normal file
235
frontend/src/pages/lib-content-view/lib-content-nav.js
Normal file
@@ -0,0 +1,235 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import TreeView from '../../components/tree-view/tree-view';
|
||||
import Loading from '../../components/loading';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import Delete from '../../components/dialog/delete-dialog';
|
||||
import Rename from '../../components/dialog/rename-dialog';
|
||||
import CreateFolder from '../../components/dialog/create-folder-dialog';
|
||||
import CreateFile from '../../components/dialog/create-file-dialog';
|
||||
|
||||
const propTypes = {
|
||||
repoPermission: PropTypes.bool.isRequired,
|
||||
currentPath: PropTypes.string,
|
||||
currentRepoInfo: PropTypes.object, // Initially not loaded
|
||||
isTreeDataLoading: PropTypes.bool.isRequired,
|
||||
treeData: PropTypes.object.isRequired,
|
||||
onNodeClick: PropTypes.func.isRequired,
|
||||
onNodeCollapse: PropTypes.func.isRequired,
|
||||
onNodeExpanded: PropTypes.func.isRequired,
|
||||
onRenameNode: PropTypes.func.isRequired,
|
||||
onDeleteNode: PropTypes.func.isRequired,
|
||||
onAddFileNode: PropTypes.func.isRequired,
|
||||
onAddFolderNode: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class LibContentNav extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
opNode: null,
|
||||
isLoadFailed: false,
|
||||
isMenuIconShow: false,
|
||||
isHeaderMenuShow: false,
|
||||
isHeaderMenuFreezed: false,
|
||||
isDeleteDialogShow: false,
|
||||
isAddFileDialogShow: false,
|
||||
isAddFolderDialogShow: false,
|
||||
isRenameDialogShow: false,
|
||||
};
|
||||
this.isNodeMenuShow = true;
|
||||
}
|
||||
|
||||
onDropdownToggleClick = (e) => {
|
||||
e.preventDefault();
|
||||
this.toggleOperationMenu();
|
||||
}
|
||||
|
||||
toggleOperationMenu = () => {
|
||||
this.setState({isHeaderMenuShow: !this.state.isHeaderMenuShow});
|
||||
}
|
||||
|
||||
onNodeClick = (node) => {
|
||||
this.setState({opNode: node});
|
||||
this.props.onNodeClick(node);
|
||||
}
|
||||
|
||||
onMenuItemClick = (operation, node) => {
|
||||
this.setState({opNode: node});
|
||||
switch (operation) {
|
||||
case 'New Folder':
|
||||
this.onAddFolderToggle();
|
||||
break;
|
||||
case 'New File':
|
||||
this.onAddFileToggle();
|
||||
break;
|
||||
case 'Rename':
|
||||
this.onRenameToggle();
|
||||
break;
|
||||
case 'Delete':
|
||||
this.onDeleteToggle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onAddFileToggle = (type) => {
|
||||
if (type === 'root') {
|
||||
let root = this.props.treeData.root;
|
||||
this.setState({
|
||||
isAddFileDialogShow: !this.state.isAddFileDialogShow,
|
||||
opNode: root,
|
||||
});
|
||||
} else {
|
||||
this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow});
|
||||
}
|
||||
}
|
||||
|
||||
onAddFolderToggle = (type) => {
|
||||
if (type === 'root') {
|
||||
let root = this.props.treeData.root;
|
||||
this.setState({
|
||||
isAddFolderDialogShow: !this.state.isAddFolderDialogShow,
|
||||
opNode: root,
|
||||
});
|
||||
} else {
|
||||
this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow});
|
||||
}
|
||||
}
|
||||
|
||||
onRenameToggle = () => {
|
||||
this.setState({isRenameDialogShow: !this.state.isRenameDialogShow});
|
||||
}
|
||||
|
||||
onDeleteToggle = () => {
|
||||
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
|
||||
}
|
||||
|
||||
onAddFolderNode = (dirPath) => {
|
||||
this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow});
|
||||
this.props.onAddFolderNode(dirPath);
|
||||
}
|
||||
|
||||
onAddFileNode = (filePath, isDraft) => {
|
||||
this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow});
|
||||
this.props.onAddFileNode(filePath, isDraft);
|
||||
}
|
||||
|
||||
onRenameNode = (newName) => {
|
||||
this.setState({isRenameDialogShow: !this.state.isRenameDialogShow});
|
||||
let node = this.state.opNode;
|
||||
this.props.onRenameNode(node, newName);
|
||||
}
|
||||
|
||||
onDeleteNode = () => {
|
||||
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
|
||||
let node = this.state.opNode;
|
||||
this.props.onDeleteNode(node);
|
||||
}
|
||||
|
||||
checkDuplicatedName = (newName) => {
|
||||
let node = this.state.opNode;
|
||||
// root node to new node conditions: parentNode is null,
|
||||
let parentNode = node.parentNode ? node.parentNode : node;
|
||||
let childrenObject = parentNode.children.map(item => {
|
||||
return item.object;
|
||||
});
|
||||
let isDuplicated = childrenObject.some(object => {
|
||||
return object.name === newName;
|
||||
});
|
||||
return isDuplicated;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div id="side-nav" className="dir-side-nav" role="navigation">
|
||||
<h3 className="dir-nav-heading border-left-show" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className="heading-title">{gettext('Files')}</div>
|
||||
<div className="heading-icon">
|
||||
{(this.props.repoPermission) && (
|
||||
<Dropdown isOpen={this.state.isHeaderMenuShow} toggle={this.toggleOperationMenu}>
|
||||
<DropdownToggle
|
||||
tag="span"
|
||||
className="action-icon sf2-icon-plus"
|
||||
title={gettext('More Operations')}
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={this.state.isHeaderMenuShow}
|
||||
onClick={this.onDropdownToggleClick}
|
||||
/>
|
||||
<DropdownMenu right>
|
||||
<DropdownItem onClick={this.onAddFolderToggle.bind(this, 'root')}>{gettext('New Folder')}</DropdownItem>
|
||||
<DropdownItem onClick={this.onAddFileToggle.bind(this, 'root')}>{gettext('New File')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
</h3>
|
||||
<div className="dir-nav-container">
|
||||
{this.props.isTreeDataLoading ?
|
||||
(<Loading/>) :
|
||||
(<TreeView
|
||||
repoPermission={this.props.repoPermission}
|
||||
isNodeMenuShow={this.isNodeMenuShow}
|
||||
treeData={this.props.treeData}
|
||||
currentPath={this.props.currentPath}
|
||||
onNodeClick={this.onNodeClick}
|
||||
onNodeExpanded={this.props.onNodeExpanded}
|
||||
onNodeCollapse={this.props.onNodeCollapse}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnFreezedItem={this.onUnFreezedItem}
|
||||
/>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isAddFolderDialogShow && (
|
||||
<ModalPortal>
|
||||
<CreateFolder
|
||||
parentPath={this.state.opNode.path}
|
||||
onAddFolder={this.onAddFolderNode}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
addFolderCancel={this.onAddFolderToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isAddFileDialogShow && (
|
||||
<ModalPortal>
|
||||
<CreateFile
|
||||
fileType={'.md'}
|
||||
parentPath={this.state.opNode.path}
|
||||
onAddFile={this.onAddFileNode}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
addFileCancel={this.onAddFileToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isRenameDialogShow && (
|
||||
<ModalPortal>
|
||||
<Rename
|
||||
currentNode={this.state.opNode}
|
||||
onRename={this.onRenameNode}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
toggleCancel={this.onRenameToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isDeleteDialogShow && (
|
||||
<ModalPortal>
|
||||
<Delete
|
||||
currentNode={this.state.opNode}
|
||||
handleSubmit={this.onDeleteNode}
|
||||
toggleCancel={this.onDeleteToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LibContentNav.propTypes = propTypes;
|
||||
|
||||
export default LibContentNav;
|
1279
frontend/src/pages/lib-content-view/lib-content-view.js
Normal file
1279
frontend/src/pages/lib-content-view/lib-content-view.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,7 @@ class MyLibraries extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<RepoViewToolbar onShowSidePanel={this.props.onShowSidePanel} onCreateRepo={this.onCreateRepo} libraryType={'mine'}/>
|
||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
|
||||
</div>
|
||||
|
@@ -169,8 +169,8 @@ class MainPanel extends Component {
|
||||
|
||||
return (
|
||||
<div className="main-panel wiki-main-panel o-hidden">
|
||||
<div className="main-panel-north">
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.onSideNavMenuClick}></span>
|
||||
<div className="dir-operation">
|
||||
{this.props.isDirentSelected ?
|
||||
|
@@ -165,8 +165,8 @@ class PublicSharedView extends React.Component {
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north">
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<div className="operation">
|
||||
<Dropdown isOpen={this.state.isCreateMenuShow} toggle={this.onAddRepoToggle}>
|
||||
|
@@ -73,10 +73,10 @@ class MainPanel extends Component {
|
||||
const errMessage = (<div className="message err-tip">{gettext('Folder does not exist.')}</div>);
|
||||
return (
|
||||
<div className="main-panel wiki-main-panel o-hidden">
|
||||
<div className="main-panel-top panel-top">
|
||||
<div className="main-panel-north panel-top border-left-show">
|
||||
{username && (
|
||||
<Fragment>
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title="Side Nav Menu" onClick={this.onMenuClick}></span>
|
||||
{this.props.permission === 'rw' && (
|
||||
<button className="btn btn-secondary operation-item" title="Edit File" onClick={this.onEditClick}>{gettext('Edit Page')}</button>
|
||||
@@ -90,7 +90,7 @@ class MainPanel extends Component {
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div className="cur-view-container">
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-path">
|
||||
<div className="path-containter">
|
||||
{username &&
|
||||
|
@@ -119,8 +119,8 @@ class Wikis extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north">
|
||||
<div className="cur-view-toolbar border-left-show">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<div className="operation">
|
||||
<Dropdown tag="div" isOpen={this.state.isShowAddWikiMenu} toggle={this.onMenuToggle}>
|
||||
|
@@ -118,9 +118,7 @@ class Wiki extends Component {
|
||||
getGroupInfo = (groupID) => {
|
||||
seafileAPI.getGroup(groupID).then(res => {
|
||||
if (res.data.admins.indexOf(username) != -1) {
|
||||
this.setState({
|
||||
isDepartmentAdmin: true
|
||||
});
|
||||
this.setState({isDepartmentAdmin: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -213,7 +213,7 @@ export const Utils = {
|
||||
},
|
||||
|
||||
renameAncestorPath: function(path, ancestor, newAncestor) {
|
||||
return newAncestor + '/' + path.replace(ancestor, '');
|
||||
return path.replace(ancestor, newAncestor);
|
||||
},
|
||||
|
||||
joinPath: function(pathA, pathB) {
|
||||
|
@@ -508,7 +508,7 @@ ul,ol,li {
|
||||
|
||||
.side-nav-con {
|
||||
overflow:hidden;
|
||||
padding:20px;
|
||||
padding:12px;
|
||||
}
|
||||
|
||||
.side-nav-con:hover {
|
||||
|
Reference in New Issue
Block a user