1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-06-26 15:12:32 +00:00
seahub/frontend/src/pages/lib-content-view/lib-content-view.js

2084 lines
66 KiB
JavaScript
Raw Normal View History

import React, { Fragment } from 'react';
2019-02-20 03:54:25 +00:00
import PropTypes from 'prop-types';
import cookie from 'react-cookies';
import moment from 'moment';
import { navigate } from '@gatsbyjs/reach-router';
import { gettext, siteRoot, username, isDocs, enableVideoThumbnail } from '../../utils/constants';
2019-02-20 03:54:25 +00:00
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import collabServer from '../../utils/collab-server';
import Dirent from '../../models/dirent';
import FileTag from '../../models/file-tag';
import RepoTag from '../../models/repo-tag';
import RepoInfo from '../../models/repo-info';
import TreeNode from '../../components/tree-view/tree-node';
import treeHelper from '../../components/tree-view/tree-helper';
import toaster from '../../components/toast';
import ModalPortal from '../../components/modal-portal';
import LibDecryptDialog from '../../components/dialog/lib-decrypt-dialog';
import LibContentToolbar from './lib-content-toolbar';
import LibContentContainer from './lib-content-container';
import FileUploader from '../../components/file-uploader/file-uploader';
import CopyMoveDirentProgressDialog from '../../components/dialog/copy-move-dirent-progress-dialog';
import DeleteFolderDialog from '../../components/dialog/delete-folder-dialog';
2019-02-20 03:54:25 +00:00
const propTypes = {
pathPrefix: PropTypes.array.isRequired,
onTabNavClick: PropTypes.func.isRequired,
onMenuClick: PropTypes.func.isRequired,
repoID: PropTypes.string,
2019-02-20 03:54:25 +00:00
};
class LibContentView extends React.Component {
constructor(props) {
super(props);
this.state = {
2019-04-13 03:56:44 +00:00
currentMode: cookie.load('seafile_view_mode') || 'list',
2019-02-20 03:54:25 +00:00
path: '',
pathExist: true,
isViewFile: false,
hash: '',
currentRepoInfo: null,
repoName: '',
repoEncrypted: false,
libNeedDecrypt: false,
isGroupOwnedRepo: false,
userPerm: '',
selectedDirentList: [],
isDraft: false,
hasDraft: false,
fileTags: [],
2019-03-05 07:37:51 +00:00
draftID: '',
2019-02-20 03:54:25 +00:00
draftCounts: 0,
usedRepoTags: [],
readmeMarkdown: null,
isTreeDataLoading: true,
treeData: treeHelper.buildTree(),
currentNode: null,
isFileLoading: true,
isFileLoadedErr: false,
2019-04-19 07:50:27 +00:00
filePermission: '',
2019-02-20 03:54:25 +00:00
content: '',
lastModified: '',
latestContributor: '',
isDirentListLoading: true,
direntList: [],
isDirentSelected: false,
2019-05-29 05:57:12 +00:00
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
2019-02-20 03:54:25 +00:00
isAllDirentSelected: false,
dirID: '', // for update dir list
2019-02-20 03:54:25 +00:00
errorMsg: '',
2019-03-18 09:32:49 +00:00
isDirentDetailShow: false,
2019-05-29 04:02:07 +00:00
direntDetailPanelTab: '',
2019-04-18 05:39:42 +00:00
updateDetail: false,
2019-04-24 05:37:48 +00:00
itemsShowLength: 100,
2019-05-12 07:05:53 +00:00
isSessionExpired: false,
isCopyMoveProgressDialogShow: false,
isDeleteFolderDialogOpen: false,
asyncCopyMoveTaskId: '',
asyncOperationType: 'move',
asyncOperationProgress: 0,
2020-03-02 08:44:39 +00:00
asyncOperatedFilesLength: 0,
2019-02-20 03:54:25 +00:00
};
2019-04-22 10:05:18 +00:00
this.oldonpopstate = window.onpopstate;
2019-02-20 03:54:25 +00:00
window.onpopstate = this.onpopstate;
this.lastModifyTime = new Date();
this.isNeedUpdateHistoryState = true; // Load, refresh page, switch mode for the first time, no need to set historyState
this.currentMoveItemName = '';
this.currentMoveItemPath = '';
2019-02-20 03:54:25 +00:00
}
2019-05-29 04:02:07 +00:00
showDirentDetail = (direntDetailPanelTab) => {
if (direntDetailPanelTab) {
this.setState({ direntDetailPanelTab: direntDetailPanelTab }, () => {
this.setState({ isDirentDetailShow: true });
});
} else {
this.setState({
direntDetailPanelTab: '',
isDirentDetailShow: true
});
}
2019-03-18 09:32:49 +00:00
}
toggleDirentDetail = () => {
2019-05-29 04:02:07 +00:00
this.setState({
direntDetailPanelTab: '',
isDirentDetailShow: !this.state.isDirentDetailShow
});
}
2019-03-19 10:20:45 +00:00
closeDirentDetail = () => {
2019-05-29 04:02:07 +00:00
this.setState({
isDirentDetailShow: false,
direntDetailPanelTab: '',
});
2019-03-19 10:20:45 +00:00
}
2019-02-20 03:54:25 +00:00
componentWillMount() {
const hash = window.location.hash;
if (hash.slice(0, 1) === '#') {
this.setState({hash: hash});
2019-02-20 03:54:25 +00:00
}
}
async componentDidMount() {
2019-02-20 03:54:25 +00:00
// eg: http://127.0.0.1:8000/library/repo_id/repo_name/**/**/\
let repoID = this.props.repoID;
let location = window.location.href.split('#')[0];
location = decodeURIComponent(location);
let path = location.slice(location.indexOf(repoID) + repoID.length + 1); // get the string after repoID
path = path.slice(path.indexOf('/')); // get current path
try {
const repoRes = await seafileAPI.getRepoInfo(repoID);
const repoInfo = new RepoInfo(repoRes.data);
const isGroupOwnedRepo = repoInfo.owner_email.indexOf('@seafile_group') > -1;
this.setState({
currentRepoInfo: repoInfo,
});
if (repoInfo.permission.startsWith('custom-')) {
const permissionID = repoInfo.permission.split('-')[1];
const permissionRes = await seafileAPI.getCustomPermission(repoID, permissionID);
window.custom_permission = permissionRes.data.permission;
}
this.isNeedUpdateHistoryState = false;
2019-02-20 03:54:25 +00:00
this.setState({
repoName: repoInfo.repo_name,
libNeedDecrypt: repoInfo.lib_need_decrypt,
2019-02-20 03:54:25 +00:00
repoEncrypted: repoInfo.encrypted,
2019-05-17 07:53:51 +00:00
isGroupOwnedRepo: isGroupOwnedRepo,
path: path
2019-02-20 03:54:25 +00:00
});
if (!repoInfo.lib_need_decrypt) {
2019-02-20 03:54:25 +00:00
this.loadDirData(path);
}
} catch (error) {
2019-02-20 03:54:25 +00:00
if (error.response) {
if (error.response.status == 403) {
this.setState({
isDirentListLoading: false,
errorMsg: gettext('Permission denied')
});
let errorMsg = gettext('Permission denied');
toaster.danger(errorMsg);
} else if (error.response.status == 404) {
this.setState({
isDirentListLoading: false,
errorMsg: gettext('Library share permission not found.')
});
2019-02-20 03:54:25 +00:00
} else {
this.setState({
isDirentListLoading: false,
errorMsg: gettext('Error')
});
}
} else {
this.setState({
isDirentListLoading: false,
errorMsg: gettext('Please check the network.')
});
}
}
2019-02-20 03:54:25 +00:00
}
componentWillUnmount() {
2019-04-22 10:05:18 +00:00
window.onpopstate = this.oldonpopstate;
2019-02-20 03:54:25 +00:00
collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent);
}
componentDidUpdate() {
this.lastModifyTime = new Date();
}
onpopstate = (event) => {
2019-03-29 10:27:43 +00:00
if (event.state && event.state.key) { // root path
if (this.state.path === '/') {
return;
} else {
let path = '/';
this.loadDirentList(path);
this.setState({
path: path,
isViewFile: false
});
}
} else if (event.state && event.state.path) { // file path
2019-02-20 03:54:25 +00:00
let path = event.state.path;
if (this.state.currentMode === 'column') {
if (Utils.isMarkdownFile(path)) { // Judging not strict
this.showFile(path);
return;
}
}
this.loadDirentList(path);
this.setState({
path: path,
isViewFile: false
});
}
}
onRepoUpdateEvent = () => {
let currentTime = new Date();
if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) {
return;
}
let repoID = this.props.repoID;
let { path, dirID } = this.state;
2019-05-10 07:40:01 +00:00
if (this.state.currentMode === 'column') {
if (this.state.isViewFile) {
2022-04-11 09:27:02 +00:00
this.updateColumnMarkdownData(path);
2019-05-10 07:40:01 +00:00
} else {
seafileAPI.dirMetaData(repoID, path).then((res) => {
if (res.data.id !== dirID) {
this.loadDirentList(path);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
2019-02-20 03:54:25 +00:00
}
2019-05-10 07:40:01 +00:00
} else {
seafileAPI.dirMetaData(repoID, path).then((res) => {
if (res.data.id !== dirID) {
this.loadDirentList(path);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
2019-05-10 07:40:01 +00:00
}
2019-02-20 03:54:25 +00:00
}
updateUsedRepoTags = () => {
let repoID = this.props.repoID;
seafileAPI.listRepoTags(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});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
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;
}
});
}
2022-04-11 09:27:02 +00:00
updateColumnMarkdownData = (filePath) => {
2019-05-10 07:40:01 +00:00
let repoID = this.props.repoID;
// update state
this.setState({
path: filePath,
isViewFile: true
});
// update data
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
let { mtime, permission, last_modifier_name, is_draft, has_draft, draft_id } = res.data;
seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => {
seafileAPI.getFileContent(res.data).then((res) => {
if (this.state.content !== res.data) {
this.setState({isFileLoading: true});
2019-05-10 07:40:01 +00:00
}
this.setState({
content: res.data,
filePermission: permission,
latestContributor: last_modifier_name,
lastModified: moment.unix(mtime).fromNow(),
isFileLoading: false,
isFileLoadedErr: false,
isDraft: is_draft,
hasDraft: has_draft,
draftID: draft_id
});
});
});
}).catch(() => {
this.setState({
isFileLoading: false,
isFileLoadedErr: true,
});
});
}
2019-02-20 03:54:25 +00:00
// load data
loadDirData = (path) => {
let repoID = this.props.repoID;
// listen current repo
collabServer.watchRepo(repoID, this.onRepoUpdateEvent);
// list used FileTags
this.updateUsedRepoTags();
2022-04-11 09:27:02 +00:00
// list draft counts and review counts
2019-04-17 05:08:47 +00:00
if (isDocs) {
seafileAPI.getRepoDraftCounts(repoID).then(res => {
this.setState({
draftCounts: res.data.draft_counts,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
2019-04-17 05:08:47 +00:00
}
if (Utils.isMarkdownFile(path)) {
seafileAPI.getFileInfo(this.props.repoID, path).then(() => {
if (this.state.currentMode !== 'column') {
2019-04-13 03:56:44 +00:00
cookie.save('seafile_view_mode', 'column');
this.setState({currentMode: 'column'});
}
this.loadSidePanel(path);
this.showFile(path);
}).catch(() => {
if (this.state.currentMode === 'column') { // After an error occurs, follow dir
this.loadSidePanel(path);
this.showDir(path);
} else {
this.showDir(path);
}
});
} else {
if (this.state.currentMode === 'column') {
this.loadSidePanel(path);
this.showDir(path);
2019-02-20 03:54:25 +00:00
} else {
this.showDir(path);
}
}
}
loadSidePanel = (path) => {
let repoID = this.props.repoID;
if (path === '/') {
seafileAPI.listDir(repoID, '/').then(res => {
2022-04-11 09:52:07 +00:00
const { dirent_list, user_perm } = res.data;
2019-02-20 03:54:25 +00:00
let tree = this.state.treeData;
2022-04-11 09:52:07 +00:00
this.addResponseListToNode(dirent_list, tree.root);
2019-02-20 03:54:25 +00:00
this.setState({
isTreeDataLoading: false,
2022-04-11 09:52:07 +00:00
treeData: tree,
userPerm: user_perm,
2019-02-20 03:54:25 +00:00
});
}).catch(() => {
this.setState({isTreeDataLoading: false});
});
} else {
this.loadNodeAndParentsByPath(path);
}
}
showDir = (path) => {
let repoID = this.props.repoID;
2019-05-12 07:05:53 +00:00
if (!this.state.isSessionExpired) {
// update stste
this.setState({
isDirentListLoading: true,
isViewFile: false,
selectedDirentList: [],
});
}
2019-02-20 03:54:25 +00:00
// update data
this.loadDirentList(path);
this.resetShowLength();
2019-02-20 03:54:25 +00:00
if (!this.isNeedUpdateHistoryState) {
this.isNeedUpdateHistoryState = true;
return;
}
2019-02-20 03:54:25 +00:00
// update location
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path);
window.history.pushState({url: url, path: path}, path, url);
}
showFile = (filePath) => {
let repoID = this.props.repoID;
if (this.state.currentMode === 'column') {
seafileAPI.listFileTags(repoID, filePath).then(res => {
let fileTags = res.data.file_tags.map(item => {
return new FileTag(item);
});
this.setState({fileTags: fileTags});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
2019-02-20 03:54:25 +00:00
// update state
this.setState({
isFileLoading: true,
path: filePath,
isViewFile: true
});
// update data
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
2019-03-05 07:37:51 +00:00
let { mtime, permission, last_modifier_name, is_draft, has_draft, draft_id } = res.data;
2019-02-20 03:54:25 +00:00
seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => {
seafileAPI.getFileContent(res.data).then((res) => {
this.setState({
content: res.data,
2019-04-19 07:50:27 +00:00
filePermission: permission,
2019-02-20 03:54:25 +00:00
latestContributor: last_modifier_name,
lastModified: moment.unix(mtime).fromNow(),
isFileLoading: false,
isFileLoadedErr: false,
2019-02-20 03:54:25 +00:00
isDraft: is_draft,
hasDraft: has_draft,
2019-03-05 07:37:51 +00:00
draftID: draft_id
2019-02-20 03:54:25 +00:00
});
});
});
2019-05-12 07:05:53 +00:00
}).catch((err) => {
let errMsg = Utils.getErrorMsg(err, true);
if (!err.response || err.response.status !== 403) {
toaster.danger(errMsg);
2019-05-12 07:05:53 +00:00
}
this.setState({
isFileLoading: false,
isFileLoadedErr: true,
});
2019-02-20 03:54:25 +00:00
});
// update location
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(filePath);
window.history.pushState({url: url, path: filePath}, filePath, url);
}
2019-02-20 03:54:25 +00:00
loadDirentList = (path) => {
2019-03-19 10:20:45 +00:00
let repoID = this.props.repoID;
2019-02-20 03:54:25 +00:00
seafileAPI.listDir(repoID, path, {'with_thumbnail': true}).then(res => {
let direntList = [];
let markdownItem = null;
res.data.dirent_list.forEach(item => {
let fileName = item.name.toLowerCase();
if (fileName === 'readme.md' || fileName === 'readme.markdown') {
markdownItem = item;
}
let dirent = new Dirent(item);
direntList.push(dirent);
});
this.setState({
pathExist: true,
userPerm: res.data.user_perm,
isDirentListLoading: false,
direntList: Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder),
2019-03-06 06:59:24 +00:00
dirID: res.data.dir_id,
2019-02-20 03:54:25 +00:00
readmeMarkdown: markdownItem,
2019-05-12 07:05:53 +00:00
path: path,
isSessionExpired: false,
2019-02-20 03:54:25 +00:00
});
if (!this.state.repoEncrypted && direntList.length) {
this.getThumbnails(repoID, path, this.state.direntList);
}
2019-05-12 07:05:53 +00:00
}).catch((err) => {
Utils.getErrorMsg(err, true);
if (err.response && err.response.status === 403) {
this.setState({isDirentListLoading: false});
2019-05-12 07:05:53 +00:00
return;
}
2019-02-20 03:54:25 +00:00
this.setState({
isDirentListLoading: false,
pathExist: false,
});
});
}
2019-04-24 07:16:26 +00:00
onListContainerScroll = () => {
2019-04-24 07:24:27 +00:00
let itemsShowLength = this.state.itemsShowLength + 100;
2019-04-24 05:37:48 +00:00
this.setState({itemsShowLength: itemsShowLength});
2019-04-23 06:43:47 +00:00
}
2019-04-24 07:16:26 +00:00
resetShowLength = () => {
2019-04-24 05:37:48 +00:00
this.setState({itemsShowLength: 100});
2019-04-23 06:43:47 +00:00
}
2019-02-20 03:54:25 +00:00
getThumbnails = (repoID, path, direntList) => {
let items = direntList.filter((item) => {
return (Utils.imageCheck(item.name) || (enableVideoThumbnail && Utils.videoCheck(item.name))) && !item.encoded_thumbnail_src;
2019-02-20 03:54:25 +00:00
});
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
2019-02-20 03:54:25 +00:00
});
}
});
};
getThumbnail(0);
}
2019-06-12 12:50:12 +00:00
updateMoveCopyTreeNode = (path) => {
2019-06-12 09:50:55 +00:00
let repoID = this.props.repoID;
2019-06-12 12:50:12 +00:00
let tree = this.state.treeData.clone();
let node = tree.getNodeByPath(path);
// for node not loaded, such as a deep folder '/vv/aa'
if (!node) { // node: null
return false;
}
2019-06-12 13:43:33 +00:00
let nodeChildren = node.children.map(item => item.object);
let nodeChildrenNames = nodeChildren.map(item => item.name);
2019-06-12 12:50:12 +00:00
2019-06-12 09:50:55 +00:00
seafileAPI.listDir(repoID, path).then(res => {
2019-06-12 13:43:33 +00:00
let newDirentList = res.data.dirent_list;
let newAddedDirents = newDirentList.filter(item => {
2019-06-21 05:59:17 +00:00
return !nodeChildrenNames.includes(item.name);
});
2019-06-12 13:43:33 +00:00
newAddedDirents.map(item => {
2019-06-12 09:50:55 +00:00
this.addNodeToTree(item.name, path, item.type);
2019-06-21 05:59:17 +00:00
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-06-12 09:50:55 +00:00
});
}
2019-06-12 12:50:12 +00:00
async getAsyncCopyMoveProgress() {
let { asyncOperationType, asyncCopyMoveTaskId } = this.state;
try {
let res = await seafileAPI.queryAsyncOperationProgress(asyncCopyMoveTaskId);
let data = res.data;
if (data.failed) {
2020-03-02 08:44:39 +00:00
let message = gettext('Failed to move files to another library.');
if (asyncOperationType === 'copy') {
2020-03-02 08:44:39 +00:00
message = gettext('Failed to copy files to another library.');
}
toaster.danger(message);
this.setState({
asyncOperationProgress: 0,
isCopyMoveProgressDialogShow: false,
});
return;
}
if (data.successful) {
if (asyncOperationType === 'move') {
if (this.currentMoveItemName && this.currentMoveItemPath) {
if (this.state.currentMode === 'column') {
this.deleteTreeNode(this.currentMoveItemPath);
}
this.moveDirent(this.currentMoveItemName);
2020-04-11 09:54:52 +00:00
this.currentMoveItemName = '';
this.currentMoveItemPath = '';
} else {
if (this.state.currentMode === 'column') {
let direntPaths = this.getSelectedDirentPaths();
this.deleteTreeNodes(direntPaths);
}
let direntNames = this.getSelectedDirentNames();
this.moveDirents(direntNames);
}
}
this.setState({isCopyMoveProgressDialogShow: false});
2020-03-02 08:44:39 +00:00
let message = gettext('Successfully moved files to another library.');
if (asyncOperationType === 'copy') {
2020-03-02 09:47:34 +00:00
message = gettext('Successfully copied files to another library.');
}
toaster.success(message);
return;
}
// init state: total is 0
let asyncOperationProgress = !data.total ? 0 : parseInt((data.done/data.total * 100).toFixed(2));
this.getAsyncCopyMoveProgress();
this.setState({asyncOperationProgress: asyncOperationProgress});
} catch (error) {
this.setState({
asyncOperationProgress: 0,
isCopyMoveProgressDialogShow: false,
});
}
}
cancelCopyMoveDirent = () => {
2020-04-11 10:03:45 +00:00
let taskId = this.state.asyncCopyMoveTaskId;
seafileAPI.cancelCopyMoveOperation(taskId);
2020-04-11 10:09:39 +00:00
this.currentMoveItemName = '';
this.currentMoveItemPath = '';
2020-04-11 10:03:45 +00:00
let direntList = this.state.direntList;
2020-04-11 09:54:52 +00:00
this.setState({direntList: direntList.slice(0)});
}
onMoveProgressDialogToggle = () => {
let { asyncOperationProgress } = this.state;
if (asyncOperationProgress !== 100) {
this.cancelCopyMoveDirent();
}
this.setState({
asyncOperationProgress: 0,
isCopyMoveProgressDialogShow: false,
});
}
2019-02-20 03:54:25 +00:00
// toolbar operations
onMoveItems = (destRepo, destDirentPath) => {
let repoID = this.props.repoID;
let selectedDirentList = this.state.selectedDirentList;
if (repoID !== destRepo.repo_id) {
this.setState(() => ({
asyncOperatedFilesLength: selectedDirentList.length,
asyncOperationProgress: 0,
asyncOperationType: 'move',
isCopyMoveProgressDialogShow: true
}));
}
let dirNames = this.getSelectedDirentNames();
let direntPaths = this.getSelectedDirentPaths();
2019-06-12 06:59:33 +00:00
seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => {
2019-07-23 01:42:09 +00:00
if (repoID !== destRepo.repo_id) {
this.setState({
asyncCopyMoveTaskId: res.data.task_id,
}, () => {
// After moving successfully, delete related files
this.getAsyncCopyMoveProgress();
2019-07-23 01:42:09 +00:00
});
}
if (repoID === destRepo.repo_id) {
2019-06-12 06:59:33 +00:00
if (this.state.currentMode === 'column') {
this.deleteTreeNodes(direntPaths);
2019-06-12 06:59:33 +00:00
}
this.moveDirents(dirNames);
// 2. tow columns mode need update left tree
if (this.state.currentMode === 'column') {
this.updateMoveCopyTreeNode(destDirentPath);
}
// show tip message if move to current repo
2019-07-23 01:42:09 +00:00
let message = Utils.getMoveSuccessMessage(dirNames);
toaster.success(message);
2019-06-12 06:59:33 +00:00
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = Utils.getMoveFailedMessage(dirNames);
}
this.setState({
asyncOperationProgress: 0,
isCopyMoveProgressDialogShow: false,
});
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
onCopyItems = (destRepo, destDirentPath) => {
let repoID = this.props.repoID;
let selectedDirentList = this.state.selectedDirentList;
if (repoID !== destRepo.repo_id) {
this.setState({
asyncOperatedFilesLength: selectedDirentList.length,
asyncOperationProgress: 0,
asyncOperationType: 'copy',
isCopyMoveProgressDialogShow: true
});
}
let dirNames = this.getSelectedDirentNames();
2019-06-12 06:59:33 +00:00
seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => {
2019-07-23 01:42:09 +00:00
if (repoID !== destRepo.repo_id) {
this.setState({
asyncCopyMoveTaskId: res.data.task_id,
}, () => {
this.getAsyncCopyMoveProgress();
2019-07-23 01:42:09 +00:00
});
2019-02-20 03:54:25 +00:00
}
if (repoID === destRepo.repo_id) {
if (this.state.currentMode === 'column') {
this.updateMoveCopyTreeNode(destDirentPath);
}
if (destDirentPath === this.state.path) {
this.loadDirentList(this.state.path);
}
// show tip message if copy to current repo
let message = Utils.getCopySuccessfulMessage(dirNames);
toaster.success(message);
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = Utils.getCopyFailedMessage(dirNames);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
restoreDeletedDirents = (commitID, paths, e) => {
const { repoID } = this.props;
e.preventDefault();
toaster.closeAll();
seafileAPI.restoreDirents(repoID, commitID, paths).then(res => {
const { success, failed } = res.data;
success.forEach(dirent => {
let name = Utils.getFileName(dirent.path);
let parentPath = Utils.getDirName(dirent.path);
if (!dirent.is_dir) {
if (this.state.currentMode === 'column') {
this.addNodeToTree(name, parentPath, 'file');
}
if (parentPath === this.state.path && !this.state.isViewFile) {
this.addDirent(name, 'file');
}
} else {
if (this.state.currentMode === 'column') {
this.addNodeToTree(name, parentPath, 'dir');
}
if (parentPath === this.state.path && !this.state.isViewFile) {
this.addDirent(name, 'dir');
}
}
});
if (success.length) {
let msg = success.length > 1 ? gettext('Restored {name} and {n} other items') :
gettext('Restored {name}');
msg = msg.replace('{name}', success[0].path.split('/').pop())
.replace('{n}', success.length - 1);
toaster.success(msg);
}
if (failed.length) {
let msg = failed.length > 1 ? gettext('Failed to restore {name} and {n} other items') :
gettext('Failed to restore {name}');
msg = msg.replace('{name}', failed[0].path.split('/').pop())
.replace('{n}', failed.length - 1);
toaster.danger(msg);
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
2019-02-20 03:54:25 +00:00
onDeleteItems = () => {
let repoID = this.props.repoID;
let direntPaths = this.getSelectedDirentPaths();
let dirNames = this.getSelectedDirentNames();
2019-04-18 05:39:42 +00:00
this.setState({updateDetail: !this.state.updateDetail});
2019-02-20 03:54:25 +00:00
seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => {
if (this.state.currentMode === 'column') {
this.deleteTreeNodes(direntPaths);
}
this.deleteDirents(dirNames);
2019-06-05 13:10:42 +00:00
let msg = '';
if (direntPaths.length > 1) {
msg = gettext('Successfully deleted {name} and {n} other items.');
2019-06-05 13:10:42 +00:00
msg = msg.replace('{name}', dirNames[0]);
msg = msg.replace('{n}', dirNames.length - 1);
} else {
msg = gettext('Successfully deleted {name}.');
msg = msg.replace('{name}', dirNames[0]);
}
const successTipWithUndo = (
<>
<span>{msg}</span>
<a className="action-link p-0 ml-1" href="#" onClick={this.restoreDeletedDirents.bind(this, res.data.commit_id, direntPaths)}>{gettext('Undo')}</a>
</>
);
toaster.success(successTipWithUndo, {duration: 5});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = gettext('Failed to delete {name} and {n} other items.');
errMessage = errMessage.replace('{name}', dirNames[0]);
errMessage = errMessage.replace('{n}', dirNames.length - 1);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
onAddFolder = (dirPath) => {
let repoID = this.props.repoID;
seafileAPI.createDir(repoID, dirPath).then(() => {
let name = Utils.getFileName(dirPath);
let parentPath = Utils.getDirName(dirPath);
if (this.state.currentMode === 'column') {
this.addNodeToTree(name, parentPath, 'dir');
}
if (parentPath === this.state.path && !this.state.isViewFile) {
this.addDirent(name, 'dir');
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
onAddFile = (filePath, isMarkdownDraft, isSdocDraft) => {
2019-02-20 03:54:25 +00:00
let repoID = this.props.repoID;
seafileAPI.createFile(repoID, filePath, isMarkdownDraft).then(res => {
2019-02-20 03:54:25 +00:00
let name = Utils.getFileName(filePath);
let parentPath = Utils.getDirName(filePath);
if (this.state.currentMode === 'column') {
this.addNodeToTree(name, parentPath, 'file');
}
if (parentPath === this.state.path && !this.state.isViewFile) {
if (isSdocDraft) { // the new file is marked to be draft
seafileAPI.sdocMarkAsDraft(repoID, filePath).then((res) => {
this.addDirent(name, 'file', res.data.size, isSdocDraft);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
this.addDirent(name, 'file', res.data.size);
});
return;
}
2019-02-20 03:54:25 +00:00
this.addDirent(name, 'file', res.data.size);
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
switchViewMode = (mode) => {
if (mode === this.state.currentMode) {
return;
}
if (mode === 'detail') {
this.toggleDirentDetail();
return;
}
2019-04-13 03:56:44 +00:00
cookie.save('seafile_view_mode', mode);
2019-02-20 03:54:25 +00:00
let path = this.state.path;
if (this.state.currentMode === 'column' && this.state.isViewFile) {
path = Utils.getDirName(path);
this.setState({
path: path,
isViewFile: false,
});
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path);
window.history.pushState({url: url, path: path}, path, url);
}
if (mode === 'column') {
this.loadSidePanel(this.state.path);
}
this.isNeedUpdateHistoryState = false;
2019-02-20 03:54:25 +00:00
this.setState({currentMode: mode});
this.showDir(path);
2019-02-20 03:54:25 +00:00
}
onSearchedClick = (item) => {
let path = item.is_dir ? item.path.slice(0, item.path.length - 1) : item.path;
2019-02-20 03:54:25 +00:00
if (this.state.currentPath === path) {
return;
}
if (this.state.currentMode === 'column') {
// load sidePanel
let index = -1;
let paths = Utils.getPaths(path);
for (let i = 0; i < paths.length; i++) {
let node = this.state.treeData.getNodeByPath(node);
if (!node) {
index = i;
break;
}
2019-02-20 03:54:25 +00:00
}
if (index === -1) { // all the data has been loaded already.
let node = this.state.treeData.getNodeByPath(path);
this.setState({currentNode: node});
} else {
this.loadNodeAndParentsByPath(path);
}
2019-04-13 03:56:44 +00:00
// load mainPanel
if (item.is_dir) {
this.showDir(path);
} else {
if (Utils.isMarkdownFile(path)) {
this.showFile(path);
} else {
let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(path);
2019-10-22 04:01:00 +00:00
let isWeChat = Utils.isWeChat();
if (!isWeChat) {
let newWindow = window.open('about:blank');
newWindow.location.href = url;
} else {
location.href = url;
}
2019-04-13 03:56:44 +00:00
}
}
2019-02-20 03:54:25 +00:00
} else {
2019-04-13 03:56:44 +00:00
if (item.is_dir) {
this.showDir(path);
2019-02-20 03:54:25 +00:00
} else {
let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(path);
2019-10-22 04:01:00 +00:00
let isWeChat = Utils.isWeChat();
if (!isWeChat) {
let newWindow = window.open('about:blank');
newWindow.location.href = url;
} else {
location.href = url;
}
2019-02-20 03:54:25 +00:00
}
}
}
onMainNavBarClick = (nodePath) => {
//just for dir
this.resetSelected();
if (this.state.currentMode === 'column') {
let tree = this.state.treeData.clone();
let node = tree.getNodeByPath(nodePath);
tree.expandNode(node);
this.setState({treeData: tree, currentNode: node});
}
this.showDir(nodePath);
}
onLinkClick = (link) => {
const url = link;
let repoID = this.props.repoID;
if (Utils.isInternalMarkdownLink(url, repoID)) {
let path = Utils.getPathFromInternalMarkdownLink(url, repoID);
this.showFile(path);
} else if (Utils.isInternalDirLink(url, repoID)) {
let path = Utils.getPathFromInternalDirLink(url, repoID);
this.showDir(path);
} else {
window.open(url);
}
}
// list&tree operations
onMainPanelItemRename = (dirent, newName) => {
let path = Utils.joinPath(this.state.path, dirent.name);
this.renameItem(path, dirent.isDir(), newName);
}
onMainPanelItemDelete = (dirent) => {
let path = Utils.joinPath(this.state.path, dirent.name);
this.deleteItem(path, dirent.isDir());
}
onRenameTreeNode = (node, newName) => {
this.renameItem(node.path, node.object.isDir(), newName);
}
onDeleteTreeNode = (node) => {
this.deleteItem(node.path, node.object.isDir());
}
renameItem = (path, isDir, newName) => {
let repoID = this.props.repoID;
if (isDir) {
seafileAPI.renameDir(repoID, path, newName).then(() => {
this.renameItemAjaxCallback(path, newName);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
let name = Utils.getFileName(path);
errMessage = gettext('Renaming {name} failed').replace('{name}', name);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
} else {
seafileAPI.renameFile(repoID, path, newName).then(() => {
this.renameItemAjaxCallback(path, newName);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
let name = Utils.getFileName(path);
errMessage = gettext('Renaming {name} failed').replace('{name}', name);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
}
renameItemAjaxCallback(path, newName) {
if (this.state.currentMode === 'column') {
this.renameTreeNode(path, newName);
}
this.renameDirent(path, newName);
}
toggleDeleteFolderDialog = () => {
this.setState({isDeleteFolderDialogOpen: !this.state.isDeleteFolderDialogOpen});
}
deleteFolder = () => {
const { repoID } = this.props;
const { folderToDelete: path } = this.state;
seafileAPI.deleteDir(repoID, path).then((res) => {
this.deleteItemAjaxCallback(path, true);
let name = Utils.getFileName(path);
var msg = gettext('Successfully deleted {name}').replace('{name}', name);
const successTipWithUndo = (
<>
<span>{msg}</span>
<a className="action-link p-0 ml-1" href="#" onClick={this.restoreDeletedDirents.bind(this, res.data.commit_id, [path])}>{gettext('Undo')}</a>
</>
);
toaster.success(successTipWithUndo, {duration: 5});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
let name = Utils.getFileName(path);
errMessage = gettext('Failed to delete {name}').replace('{name}', name);
}
toaster.danger(errMessage);
});
}
2019-02-20 03:54:25 +00:00
deleteItem(path, isDir) {
let repoID = this.props.repoID;
if (isDir) {
this.setState({ folderToDelete: path }, () => {
this.toggleDeleteFolderDialog();
2019-02-20 03:54:25 +00:00
});
} else {
seafileAPI.deleteFile(repoID, path).then((res) => {
2019-02-20 03:54:25 +00:00
this.deleteItemAjaxCallback(path, isDir);
let name = Utils.getFileName(path);
2019-05-29 04:02:07 +00:00
var msg = gettext('Successfully deleted {name}').replace('{name}', name);
const successTipWithUndo = (
<>
<span>{msg}</span>
<a className="action-link p-0 ml-1" href="#" onClick={this.restoreDeletedDirents.bind(this, res.data.commit_id, [path])}>{gettext('Undo')}</a>
</>
);
toaster.success(successTipWithUndo, {duration: 5});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
let name = Utils.getFileName(path);
errMessage = gettext('Failed to delete {name}').replace('{name}', name);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
}
deleteItemAjaxCallback(path) {
if (this.state.currentMode === 'column') {
this.deleteTreeNode(path);
}
this.deleteDirent(path);
}
// list operations
onMoveItem = (destRepo, dirent, moveToDirentPath, nodeParentPath) => {
2019-02-20 03:54:25 +00:00
let repoID = this.props.repoID;
//just for view list state
let dirName = dirent.name;
if (!nodeParentPath) {
nodeParentPath = this.state.path;
}
let direntPath = Utils.joinPath(nodeParentPath, dirName);
if (repoID !== destRepo.repo_id) {
this.setState({
2020-03-02 08:44:39 +00:00
asyncOperatedFilesLength: 1,
asyncOperationProgress: 0,
asyncOperationType: 'move',
isCopyMoveProgressDialogShow: true,
});
}
seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, nodeParentPath, dirName).then(res => {
2019-07-23 01:42:09 +00:00
if (repoID !== destRepo.repo_id) {
this.setState({asyncCopyMoveTaskId: res.data.task_id}, () => {
this.currentMoveItemName = dirName;
this.currentMoveItemPath = direntPath;
this.getAsyncCopyMoveProgress(dirName, direntPath);
2019-07-23 01:42:09 +00:00
});
}
if (this.state.currentMode === 'column') {
this.deleteTreeNode(direntPath);
}
// 1. move to current repo
// 2. tow columns mode need update left tree
if (repoID === destRepo.repo_id && this.state.currentMode === 'column') {
this.updateMoveCopyTreeNode(moveToDirentPath);
}
this.moveDirent(direntPath, moveToDirentPath);
// show tip message if move to current repo
if (repoID === destRepo.repo_id) {
2019-07-24 23:55:52 +00:00
let message = gettext('Successfully moved {name}.');
message = message.replace('{name}', dirName);
2019-07-23 01:42:09 +00:00
toaster.success(message);
2019-02-20 03:54:25 +00:00
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
2019-07-24 23:55:52 +00:00
errMessage = gettext('Failed to move {name}.');
errMessage = errMessage.replace('{name}', dirName);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
onCopyItem = (destRepo, dirent, copyToDirentPath, nodeParentPath) => {
2019-02-20 03:54:25 +00:00
let repoID = this.props.repoID;
//just for view list state
let dirName = dirent.name;
if (!nodeParentPath) {
nodeParentPath = this.state.path;
}
if (repoID !== destRepo.repo_id) {
this.setState({
2020-03-02 08:44:39 +00:00
asyncOperatedFilesLength: 1,
asyncOperationProgress: 0,
asyncOperationType: 'copy',
isCopyMoveProgressDialogShow: true
});
}
2019-06-12 06:59:33 +00:00
seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, nodeParentPath, dirName).then(res => {
2019-07-23 01:42:09 +00:00
if (repoID !== destRepo.repo_id) {
this.setState({
asyncCopyMoveTaskId: res.data.task_id,
}, () => {
this.getAsyncCopyMoveProgress();
2019-07-23 01:42:09 +00:00
});
}
if (repoID === destRepo.repo_id) {
if (this.state.currentMode === 'column') {
this.updateMoveCopyTreeNode(copyToDirentPath);
}
if (copyToDirentPath === nodeParentPath) {
this.loadDirentList(this.state.path);
}
2019-07-23 01:42:09 +00:00
let message = gettext('Successfully copied %(name)s.');
message = message.replace('%(name)s', dirName);
toaster.success(message);
2019-02-20 03:54:25 +00:00
}
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = gettext('Failed to copy %(name)s');
errMessage = errMessage.replace('%(name)s', dirName);
}
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
onDirentClick = (dirent) => {
let direntList = this.state.direntList.map(dirent => {
dirent.isSelected = false;
return dirent;
});
if (dirent) {
// dirent.isSelected = true;
this.setState({
direntList: direntList,
isDirentSelected: true,
selectedDirentList: [dirent],
});
} else {
this.setState({
direntList: direntList,
isDirentSelected: false,
selectedDirentList: [],
});
}
}
onItemClick = (dirent) => {
2019-02-20 03:54:25 +00:00
this.resetSelected();
let repoID = this.props.repoID;
let direntPath = Utils.joinPath(this.state.path, dirent.name);
if (dirent.isDir()) { // is dir
if (this.state.currentMode === 'column') {
this.loadTreeNodeByPath(direntPath);
}
this.showDir(direntPath);
} else { // is file
if (this.state.currentMode === 'column' && Utils.isMarkdownFile(direntPath)) {
this.showColumnMarkdownFile(direntPath);
2019-02-20 03:54:25 +00:00
} else {
let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath);
if (dirent.is_sdoc_revision && dirent.revision_id) {
url = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/';
}
2019-10-15 02:16:48 +00:00
let isWeChat = Utils.isWeChat();
if (!isWeChat) {
window.open(url);
2019-10-15 02:16:48 +00:00
} else {
location.href = url;
}
2019-02-20 03:54:25 +00:00
}
}
}
onDirentSelected = (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: []
});
}
}
onAllDirentSelected = () => {
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.props.repoID;
seafileAPI.listFileTags(repoID, direntPath).then(res => {
let fileTags = res.data.file_tags.map(item => {
return new FileTag(item);
});
if (this.state.isViewFile) {
this.setState({fileTags: fileTags});
} else {
this.updateDirent(dirent, 'file_tags', fileTags);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
this.updateUsedRepoTags();
}
onFileUploadSuccess = (direntObject) => {
let isExist = this.state.direntList.some(item => {
2019-02-20 03:54:25 +00:00
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 {
// use current dirent parent's permission as it's permission
direntObject.permission = this.state.userPerm;
2019-02-20 03:54:25 +00:00
let dirent = new Dirent(direntObject);
if (this.state.currentMode === 'column') {
this.addNodeToTree(dirent.name, this.state.path, dirent.type);
}
if (direntObject.type === 'dir') {
this.setState({direntList: [dirent, ...this.state.direntList]});
} else {
this.setState({direntList: [...this.state.direntList, dirent]});
this.updateReadmeMarkdown(this.state.direntList);
}
}
}
addDirent = (name, type, size, isSdocDraft) => {
let item = this.createDirent(name, type, size, isSdocDraft);
2019-02-20 03:54:25 +00:00
let direntList = this.state.direntList;
if (type === 'dir') {
direntList.unshift(item);
} else {
// 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(item);
} else {
let index = 0;
for (let i = 0; i <= length; i++) {
if (direntList[i].type === 'file') {
index = i;
break;
}
}
direntList.splice(index, 0, item);
}
}
this.setState({direntList: direntList});
this.updateReadmeMarkdown(direntList);
}
renameDirent = (direntPath, newName) => {
let repoID = this.props.repoID;
let parentPath = Utils.getDirName(direntPath);
let newDirentPath = Utils.joinPath(parentPath, newName);
if (direntPath === this.state.path) {
// the renamed item is current viewed item
// example: direntPath = /A/B/C, state.path = /A/B/C
this.setState({ path: newDirentPath });
2019-03-05 05:36:31 +00:00
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + newDirentPath;
2019-02-20 03:54:25 +00:00
window.history.replaceState({ url: url, path: newDirentPath}, newDirentPath, url);
} else if (Utils.isChildPath(direntPath, this.state.path)) {
// example: direntPath = /A/B/C/D, state.path = /A/B/C
let oldName = Utils.getFileName(direntPath);
let direntList = this.state.direntList.map(item => {
if (item.name === oldName) {
item.name = newName;
}
return item;
});
this.setState({ direntList: direntList });
this.updateReadmeMarkdown(direntList);
} else if (Utils.isAncestorPath(direntPath, this.state.path)) {
// example: direntPath = /A/B, state.path = /A/B/C
let newPath = Utils.renameAncestorPath(this.state.path, direntPath, newDirentPath);
this.setState({ path: newPath });
2019-03-05 05:36:31 +00:00
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + newPath;
2019-02-20 03:54:25 +00:00
window.history.replaceState({ url: url, path: newPath}, newPath, url);
}
}
deleteDirent(direntPath) {
if (direntPath === this.state.path) {
// The deleted item is current item
let parentPath = Utils.getDirName(direntPath);
this.showDir(parentPath);
} else if (Utils.isChildPath(direntPath, this.state.path)) {
// The deleted item is inside current path
let name = Utils.getFileName(direntPath);
let direntList = this.state.direntList.filter(item => {
return item.name !== name;
});
// Recalculate the state of the selection
this.recaculateSelectedStateAfterDirentDeleted(name, direntList);
this.setState({direntList: direntList});
2019-02-20 03:54:25 +00:00
this.updateReadmeMarkdown(direntList);
} else if (Utils.isAncestorPath(direntPath, this.state.path)) {
// the deleted item is ancester of the current item
let parentPath = Utils.getDirName(direntPath);
this.showDir(parentPath);
}
// else do nothing
}
// only one scence: The deleted items are inside current path
deleteDirents = (direntNames) => {
let direntList = this.state.direntList.filter(item => {
return direntNames.indexOf(item.name) === -1;
});
// Recalculate the state of the selection
this.recaculateSelectedStateAfterDirentDeleted(name, direntList);
this.setState({direntList: direntList});
this.updateReadmeMarkdown(direntList);
}
2019-05-07 02:06:24 +00:00
moveDirent = (direntPath, moveToDirentPath = null) => {
let name = Utils.getFileName(direntPath);
if (moveToDirentPath === this.state.path) {
2019-05-07 02:14:29 +00:00
this.loadDirentList(this.state.path);
return;
}
2019-02-20 03:54:25 +00:00
let direntList = this.state.direntList.filter(item => {
return item.name !== name;
});
// Recalculate the state of the selection
this.recaculateSelectedStateAfterDirentDeleted(name, direntList);
2019-02-20 03:54:25 +00:00
this.setState({direntList: direntList});
this.updateReadmeMarkdown(direntList);
}
// only one scence: The moved items are inside current path
moveDirents = (direntNames) => {
let direntList = this.state.direntList.filter(item => {
return direntNames.indexOf(item.name) === -1;
});
// Recalculate the state of the selection
this.recaculateSelectedStateAfterDirentDeleted(name, direntList);
this.setState({direntList: direntList});
this.updateReadmeMarkdown(direntList);
}
2019-02-20 03:54:25 +00:00
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});
}
// tree operations
loadTreeNodeByPath = (path) => {
let repoID = this.props.repoID;
let tree = this.state.treeData.clone();
let node = tree.getNodeByPath(path);
if (!node.isLoaded) {
seafileAPI.listDir(repoID, node.path).then(res => {
this.addResponseListToNode(res.data.dirent_list, node);
let parentNode = tree.getNodeByPath(node.parentNode.path);
parentNode.isExpanded = true;
this.setState({
treeData: tree,
currentNode: node
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
} else {
let parentNode = tree.getNodeByPath(node.parentNode.path);
parentNode.isExpanded = true;
this.setState({treeData: tree, currentNode: node}); //tree
}
}
loadNodeAndParentsByPath = (path) => {
let repoID = this.props.repoID;
let tree = this.state.treeData.clone();
if (Utils.isMarkdownFile(path)) {
path = Utils.getDirName(path);
}
seafileAPI.listDir(repoID, path, {with_parents: true}).then(res => {
2022-04-11 09:52:07 +00:00
const { dirent_list: direntList, user_perm } = res.data;
2019-02-20 03:54:25 +00:00
let results = {};
for (let i = 0; i < direntList.length; i++) {
let object = direntList[i];
2019-04-17 02:48:44 +00:00
let parentDir = object.parent_dir;
let key = parentDir === '/' ? '/' : parentDir.slice(0, parentDir.length - 1);
2019-02-20 03:54:25 +00:00
if (!results[key]) {
results[key] = [];
}
results[key].push(object);
}
for (let key in results) {
let node = tree.getNodeByPath(key);
if (!node.isLoaded) {
this.addResponseListToNode(results[key], node);
}
}
this.setState({
isTreeDataLoading: false,
2022-04-11 09:52:07 +00:00
treeData: tree,
userPerm: user_perm,
2019-02-20 03:54:25 +00:00
});
}).catch(() => {
this.setState({isLoadFailed: true});
});
}
onTreeNodeClick = (node) => {
this.resetSelected();
let repoID = this.props.repoID;
if (!this.state.pathExist) {
this.setState({pathExist: true});
}
if (node.object.isDir()) {
let isLoaded = node.isLoaded;
2019-02-20 03:54:25 +00:00
if (!node.isLoaded) {
let tree = this.state.treeData.clone();
node = tree.getNodeByPath(node.path);
seafileAPI.listDir(repoID, node.path).then(res => {
this.addResponseListToNode(res.data.dirent_list, node);
tree.collapseNode(node);
this.setState({treeData: tree});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
}
if (isLoaded && node.path === this.state.path) {
2019-02-20 03:54:25 +00:00
if (node.isExpanded) {
let tree = treeHelper.collapseNode(this.state.treeData, node);
this.setState({treeData: tree});
} else {
let tree = this.state.treeData.clone();
node = tree.getNodeByPath(node.path);
tree.expandNode(node);
this.setState({treeData: tree});
}
}
}
if (node.path === this.state.path ) {
return;
}
if (node.object.isDir()) { // isDir
this.showDir(node.path);
} else {
if (Utils.isMarkdownFile(node.path)) {
if (node.path !== this.state.path) {
this.showColumnMarkdownFile(node.path);
2019-02-20 03:54:25 +00:00
}
} else {
let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path);
let dirent = node.object;
if (dirent.is_sdoc_revision && dirent.revision_id) {
url = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/';
}
window.open(url);
2019-02-20 03:54:25 +00:00
}
}
}
showColumnMarkdownFile = (filePath) => {
let repoID = this.props.repoID;
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
if (res.data.size === 0) {
// loading of asynchronously obtained data may be blocked
const w = window.open('about:blank');
const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(filePath);
w.location.href = url;
} else {
this.showFile(filePath);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
2019-02-20 03:54:25 +00:00
onTreeNodeCollapse = (node) => {
let tree = treeHelper.collapseNode(this.state.treeData, node);
this.setState({treeData: tree});
}
onTreeNodeExpanded = (node) => {
let repoID = this.props.repoID;
let tree = this.state.treeData.clone();
node = tree.getNodeByPath(node.path);
if (!node.isLoaded) {
seafileAPI.listDir(repoID, node.path).then(res => {
this.addResponseListToNode(res.data.dirent_list, node);
this.setState({treeData: tree});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
2019-02-20 03:54:25 +00:00
});
} else {
tree.expandNode(node);
this.setState({treeData: tree});
}
}
addNodeToTree = (name, parentPath, type) => {
let node = this.createTreeNode(name, type);
let tree = treeHelper.addNodeToParentByPath(this.state.treeData, node, parentPath);
this.setState({treeData: tree});
}
renameTreeNode = (path, newName) => {
let tree = treeHelper.renameNodeByPath(this.state.treeData, path, newName);
this.setState({treeData: tree});
}
deleteTreeNode = (path) => {
let tree = treeHelper.deleteNodeByPath(this.state.treeData, path);
this.setState({treeData: tree});
}
deleteTreeNodes = (paths) => {
let tree = treeHelper.deleteNodeListByPaths(this.state.treeData, paths);
this.setState({treeData: tree});
}
2019-02-20 03:54:25 +00:00
moveTreeNode = (nodePath, moveToPath, moveToRepo, nodeName) => {
let repoID = this.props.repoID;
if (repoID !== moveToRepo.repo_id) {
let tree = treeHelper.deleteNodeByPath(this.state.treeData, nodePath);
this.setState({treeData: tree});
return;
}
let tree = treeHelper.moveNodeByPath(this.state.treeData, nodePath, moveToPath, nodeName);
this.setState({treeData: tree});
}
copyTreeNode = (nodePath, copyToPath, destRepo, nodeName) => {
let repoID = this.props.repoID;
if (repoID !== destRepo.repo_id) {
return;
}
let tree = treeHelper.copyNodeByPath(this.state.treeData, nodePath, copyToPath, nodeName);
this.setState({treeData: tree});
}
createTreeNode(name, type) {
let object = this.createDirent(name, type);
return new TreeNode({object});
}
createDirent(name, type, size, isSdocDraft) {
// use current dirent parent's permission as it's permission
const { userPerm: permission } = this.state;
const mtime = new Date().getTime()/1000;
const obj = { name, type, mtime, size, permission };
if (isSdocDraft) {
obj.is_sdoc_draft = isSdocDraft;
}
const dirent = new Dirent(obj);
2019-02-20 03:54:25 +00:00
return dirent;
}
addResponseListToNode = (list, node) => {
node.isLoaded = true;
node.isExpanded = true;
let direntList = list.map(item => {
return new Dirent(item);
});
direntList = Utils.sortDirents(direntList, 'name', 'asc');
let nodeList = direntList.map(object => {
return new TreeNode({object});
});
node.addChildren(nodeList);
}
getSelectedDirentPaths = () => {
let paths = [];
this.state.selectedDirentList.forEach(selectedDirent => {
paths.push(Utils.joinPath(this.state.path, selectedDirent.name));
});
return paths;
}
getSelectedDirentNames = () => {
let names = [];
this.state.selectedDirentList.forEach(selectedDirent => {
names.push(selectedDirent.name);
});
return names;
}
resetSelected = () => {
this.setState({
isDirentSelected: false,
isAllDirentSelected: false,
});
}
recaculateSelectedStateAfterDirentDeleted = (name, newDirentList) => {
let selectedDirentList = this.state.selectedDirentList.slice(0);
if (selectedDirentList.length > 0) {
selectedDirentList = selectedDirentList.filter(item => {
return item.name !== name;
});
}
this.setState({
selectedDirentList: selectedDirentList,
isDirentSelected: selectedDirentList.length > 0,
isAllDirentSelected: selectedDirentList.length === newDirentList.length,
});
}
2019-02-20 03:54:25 +00:00
onLibDecryptDialog = () => {
this.setState({libNeedDecrypt: false});
this.loadDirData(this.state.path);
}
2019-02-20 03:54:25 +00:00
goDraftPage = () => {
2019-04-26 09:08:16 +00:00
window.open(siteRoot + 'drafts/' + this.state.draftID + '/');
2019-02-20 03:54:25 +00:00
}
sortItems = (sortBy, sortOrder) => {
cookie.save('seafile-repo-dir-sort-by', sortBy);
cookie.save('seafile-repo-dir-sort-order', sortOrder);
2019-02-20 03:54:25 +00:00
this.setState({
sortBy: sortBy,
sortOrder: sortOrder,
items: Utils.sortDirents(this.state.direntList, sortBy, sortOrder)
});
}
onUploadFile = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.uploader.onFileUpload();
}
onUploadFolder = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.uploader.onFolderUpload();
}
onToolbarFileTagChanged = () => {
let repoID = this.props.repoID;
let filePath = this.state.path;
seafileAPI.listFileTags(repoID, filePath).then(res => {
let fileTags = res.data.file_tags.map(item => {
return new FileTag(item);
});
this.setState({fileTags: fileTags});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
2019-03-18 09:32:49 +00:00
unSelectDirent = () => {
this.setState({
isDirentSelected: false,
selectedDirentList: []
});
const dirent = {};
this.onDirentSelected(dirent);
}
onDeleteRepoTag = (deletedTagID) => {
let direntList = this.state.direntList.map(dirent => {
if (dirent.file_tags) {
let fileTags = dirent.file_tags.filter(item => {
return item.repo_tag_id !== deletedTagID;
});
dirent.file_tags = fileTags;
}
return dirent;
});
this.setState({direntList: direntList});
this.updateUsedRepoTags();
}
handleSubmit = (e) => {
let options = {
'share_type': 'personal',
'from': this.state.currentRepoInfo.owner_email
};
seafileAPI.leaveShareRepo(this.props.repoID, options).then(res => {
navigate(siteRoot + 'shared-libs/');
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error, true);
toaster.danger(errorMsg);
});
e.preventDefault();
}
2019-02-20 03:54:25 +00:00
render() {
if (this.state.libNeedDecrypt) {
return (
<ModalPortal>
<LibDecryptDialog
2019-02-20 03:54:25 +00:00
repoID={this.props.repoID}
onLibDecryptDialog={this.onLibDecryptDialog}
/>
</ModalPortal>
);
}
if (this.state.errorMsg) {
return (
<Fragment>
<p className="error mt-6 text-center">{this.state.errorMsg}</p>
<button type="submit" className="btn btn-primary submit" onClick={this.handleSubmit}>{gettext('Leave Share')}</button>
</Fragment>
);
}
if (!this.state.currentRepoInfo) {
return '';
}
2019-02-20 03:54:25 +00:00
let enableDirPrivateShare = false;
let { currentRepoInfo, userPerm, isCopyMoveProgressDialogShow, isDeleteFolderDialogOpen } = this.state;
2019-06-21 09:54:06 +00:00
let showShareBtn = Utils.isHasPermissionToShare(currentRepoInfo, userPerm);
let isRepoOwner = currentRepoInfo.owner_email === username;
2019-06-27 03:48:41 +00:00
let isVirtual = currentRepoInfo.is_virtual;
let isAdmin = currentRepoInfo.is_admin;
if (!isVirtual && (isRepoOwner || isAdmin)) {
enableDirPrivateShare = true;
}
2019-04-24 05:52:09 +00:00
let direntItemsList = this.state.direntList.filter((item, index) => {
return index < this.state.itemsShowLength;
});
2019-04-24 05:52:09 +00:00
let canUpload = true;
const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (isCustomPermission) {
const { upload } = customPermission.permission;
canUpload = upload;
}
2019-02-20 03:54:25 +00:00
return (
<Fragment>
<div className="main-panel-north border-left-show">
<LibContentToolbar
isViewFile={this.state.isViewFile}
filePermission={this.state.filePermission}
isDraft={this.state.isDraft}
hasDraft={this.state.hasDraft}
fileTags={this.state.fileTags}
onFileTagChanged={this.onToolbarFileTagChanged}
onSideNavMenuClick={this.props.onMenuClick}
repoID={this.props.repoID}
path={this.state.path}
isDirentSelected={this.state.isDirentSelected}
selectedDirentList={this.state.selectedDirentList}
onItemsMove={this.onMoveItems}
onItemsCopy={this.onCopyItems}
onItemsDelete={this.onDeleteItems}
onItemRename={this.onMainPanelItemRename}
direntList={this.state.direntList}
repoName={this.state.repoName}
repoEncrypted={this.state.repoEncrypted}
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
userPerm={this.state.userPerm}
showShareBtn={showShareBtn}
enableDirPrivateShare={enableDirPrivateShare}
onAddFile={this.onAddFile}
onAddFolder={this.onAddFolder}
onUploadFile={this.onUploadFile}
onUploadFolder={this.onUploadFolder}
currentMode={this.state.currentMode}
switchViewMode={this.switchViewMode}
onSearchedClick={this.onSearchedClick}
isRepoOwner={isRepoOwner}
currentRepoInfo={this.state.currentRepoInfo}
updateDirent={this.updateDirent}
onDirentSelected={this.onDirentSelected}
showDirentDetail={this.showDirentDetail}
unSelectDirent={this.unSelectDirent}
onFilesTagChanged={this.onFileTagChanged}
/>
</div>
<div className="main-panel-center flex-row">
<LibContentContainer
pathPrefix={this.props.pathPrefix}
currentMode={this.state.currentMode}
path={this.state.path}
pathExist={this.state.pathExist}
currentRepoInfo={this.state.currentRepoInfo}
repoID={this.props.repoID}
enableDirPrivateShare={enableDirPrivateShare}
userPerm={userPerm}
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
onTabNavClick={this.props.onTabNavClick}
onMainNavBarClick={this.onMainNavBarClick}
isViewFile={this.state.isViewFile}
hash={this.state.hash}
isDraft={this.state.isDraft}
hasDraft={this.state.hasDraft}
fileTags={this.state.fileTags}
goDraftPage={this.goDraftPage}
isFileLoading={this.state.isFileLoading}
isFileLoadedErr={this.state.isFileLoadedErr}
filePermission={this.state.filePermission}
content={this.state.content}
lastModified={this.state.lastModified}
latestContributor={this.state.latestContributor}
onLinkClick={this.onLinkClick}
isTreeDataLoading={this.state.isTreeDataLoading}
treeData={this.state.treeData}
currentNode={this.state.currentNode}
onNodeClick={this.onTreeNodeClick}
onNodeCollapse={this.onTreeNodeCollapse}
onNodeExpanded={this.onTreeNodeExpanded}
onAddFolderNode={this.onAddFolder}
onAddFileNode={this.onAddFile}
onRenameNode={this.onRenameTreeNode}
onDeleteNode={this.onDeleteTreeNode}
draftCounts={this.state.draftCounts}
usedRepoTags={this.state.usedRepoTags}
readmeMarkdown={this.state.readmeMarkdown}
updateUsedRepoTags={this.updateUsedRepoTags}
isDirentListLoading={this.state.isDirentListLoading}
direntList={direntItemsList}
fullDirentList={this.state.direntList}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
updateDirent={this.updateDirent}
onDirentClick={this.onDirentClick}
onItemClick={this.onItemClick}
onItemSelected={this.onDirentSelected}
onItemDelete={this.onMainPanelItemDelete}
onItemRename={this.onMainPanelItemRename}
onItemMove={this.onMoveItem}
onItemCopy={this.onCopyItem}
onAddFolder={this.onAddFolder}
onAddFile={this.onAddFile}
onFileTagChanged={this.onFileTagChanged}
isDirentSelected={this.state.isDirentSelected}
isAllDirentSelected={this.state.isAllDirentSelected}
onAllDirentSelected={this.onAllDirentSelected}
isDirentDetailShow={this.state.isDirentDetailShow}
selectedDirent={this.state.selectedDirentList && this.state.selectedDirentList[0]}
selectedDirentList={this.state.selectedDirentList}
onItemsMove={this.onMoveItems}
onItemsCopy={this.onCopyItems}
onItemsDelete={this.onDeleteItems}
closeDirentDetail={this.closeDirentDetail}
showDirentDetail={this.showDirentDetail}
direntDetailPanelTab={this.state.direntDetailPanelTab}
onDeleteRepoTag={this.onDeleteRepoTag}
onToolbarFileTagChanged={this.onToolbarFileTagChanged}
updateDetail={this.state.updateDetail}
onListContainerScroll={this.onListContainerScroll}
loadDirentList={this.loadDirentList}
/>
{canUpload && this.state.pathExist && !this.state.isViewFile && (
<FileUploader
ref={uploader => this.uploader = uploader}
dragAndDrop={true}
path={this.state.path}
repoID={this.props.repoID}
direntList={this.state.direntList}
onFileUploadSuccess={this.onFileUploadSuccess}
isCustomPermission={isCustomPermission}
/>
)}
</div>
{isCopyMoveProgressDialogShow && (
<CopyMoveDirentProgressDialog
type={this.state.asyncOperationType}
2020-03-02 08:44:39 +00:00
asyncOperatedFilesLength={this.state.asyncOperatedFilesLength}
asyncOperationProgress={this.state.asyncOperationProgress}
toggleDialog={this.onMoveProgressDialogToggle}
/>
)}
{isDeleteFolderDialogOpen && (
<DeleteFolderDialog
repoID={this.props.repoID}
path={this.state.folderToDelete}
deleteFolder={this.deleteFolder}
toggleDialog={this.toggleDeleteFolderDialog}
/>
)}
</Fragment>
2019-02-20 03:54:25 +00:00
);
}
}
LibContentView.propTypes = propTypes;
export default LibContentView;