diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js
index b865a5209d..02e9f9acda 100644
--- a/frontend/src/pages/lib-content-view/lib-content-view.js
+++ b/frontend/src/pages/lib-content-view/lib-content-view.js
@@ -760,6 +760,53 @@ class LibContentView extends React.Component {
});
}
+ 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);
+ });
+ }
+
onDeleteItems = () => {
let repoID = this.props.repoID;
let direntPaths = this.getSelectedDirentPaths();
@@ -775,18 +822,24 @@ class LibContentView extends React.Component {
let msg = '';
if (direntPaths.length > 1) {
- msg = gettext('Successfully deleted {name} and other {n} items.');
+ msg = gettext('Successfully deleted {name} and {n} other items.');
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]);
}
- toaster.success(msg);
+ const successTipWithUndo = (
+ <>
+ {msg}
+ {gettext('Undo')}
+ >
+ );
+ toaster.success(successTipWithUndo, {duration: 5});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
- errMessage = gettext('Failed to delete {name} and other {n} items.');
+ errMessage = gettext('Failed to delete {name} and {n} other items.');
errMessage = errMessage.replace('{name}', dirNames[0]);
errMessage = errMessage.replace('{n}', dirNames.length - 1);
}
diff --git a/seahub/api2/endpoints/repo_trash.py b/seahub/api2/endpoints/repo_trash.py
index 232a1ca8f3..b65cd98cc8 100644
--- a/seahub/api2/endpoints/repo_trash.py
+++ b/seahub/api2/endpoints/repo_trash.py
@@ -251,3 +251,61 @@ class RepoTrash(APIView):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
+
+
+class RepoTrashRevertDirents(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, )
+ throttle_classes = (UserRateThrottle, )
+
+ def post(self, request, repo_id):
+ """ Revert deleted files/dirs.
+ """
+
+ # argument check
+ path_list = request.data.getlist('path', [])
+ if not path_list:
+ error_msg = 'path invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ commit_id = request.data.get('commit_id', '')
+ if not commit_id:
+ error_msg = 'commit_id invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # permission check
+ if check_folder_permission(request, repo_id, '/') != 'rw':
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ result = {}
+ result['failed'] = []
+ result['success'] = []
+ username = request.user.username
+ for path in path_list:
+ try:
+ if seafile_api.get_dir_id_by_commit_and_path(repo_id, commit_id, path):
+ seafile_api.revert_dir(repo_id, commit_id, path, username)
+ result['success'].append({'path': path, 'is_dir': True})
+ elif seafile_api.get_file_id_by_commit_and_path(repo_id, commit_id, path):
+ seafile_api.revert_file(repo_id, commit_id, path, username)
+ result['success'].append({'path': path, 'is_dir': False})
+ else:
+ result['failed'].append({
+ 'path': path,
+ 'error_msg': f'Dirent {path} not found.'
+ })
+ except Exception as e:
+ result['failed'].append({
+ 'path': path,
+ 'error_msg': str(e)
+ })
+
+ return Response(result)
diff --git a/seahub/api2/endpoints/repos_batch.py b/seahub/api2/endpoints/repos_batch.py
index 989ce68d74..e2e7602756 100644
--- a/seahub/api2/endpoints/repos_batch.py
+++ b/seahub/api2/endpoints/repos_batch.py
@@ -1535,7 +1535,8 @@ class ReposBatchDeleteItemView(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
- if not seafile_api.get_repo(repo_id):
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
@@ -1580,4 +1581,5 @@ class ReposBatchDeleteItemView(APIView):
result = {}
result['success'] = True
+ result['commit_id'] = repo.head_cmmt_id
return Response(result)
diff --git a/seahub/urls.py b/seahub/urls.py
index c5e4bee605..23791a1d7a 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -61,7 +61,7 @@ from seahub.api2.endpoints.file_history import FileHistoryView, NewFileHistoryVi
from seahub.api2.endpoints.dir import DirView, DirDetailView
from seahub.api2.endpoints.file_tag import FileTagView
from seahub.api2.endpoints.file_tag import FileTagsView
-from seahub.api2.endpoints.repo_trash import RepoTrash
+from seahub.api2.endpoints.repo_trash import RepoTrash, RepoTrashRevertDirents
from seahub.api2.endpoints.repo_commit import RepoCommitView
from seahub.api2.endpoints.repo_commit_dir import RepoCommitDirView
from seahub.api2.endpoints.repo_commit_revert import RepoCommitRevertView
@@ -401,6 +401,7 @@ urlpatterns = [
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/commits/(?P[0-9a-f]{40})/revert/$', RepoCommitRevertView.as_view(), name='api-v2.1-repo-commit-revert'),
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/dir/detail/$', DirDetailView.as_view(), name='api-v2.1-dir-detail-view'),
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/$', RepoTrash.as_view(), name='api-v2.1-repo-trash'),
+ url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/revert-dirents/$', RepoTrashRevertDirents.as_view(), name='api-v2.1-repo-trash-revert-dirents'),
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/history/$', RepoHistory.as_view(), name='api-v2.1-repo-history'),
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/set-password/$', RepoSetPassword.as_view(), name="api-v2.1-repo-set-password"),
url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/send-new-password/$', RepoSendNewPassword.as_view(), name="api-v2.1-repo-send-new-password"),