1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-04 16:31:13 +00:00

Combine view mode (#2959)

This commit is contained in:
杨顺强
2019-02-20 11:54:25 +08:00
committed by Daniel Pan
parent c73bf667ed
commit a7b0fb17f4
30 changed files with 2178 additions and 1124 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -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>
)}
{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>
{this.props.showShareBtn &&
<button className="btn btn-secondary operation-item" title={gettext('Share')} onClick={this.onShareClick}>{gettext('Share')}</button>
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) => {

View File

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