Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>pull/37646/head
parent
c3b6b20862
commit
4f24f68334
@ -1,152 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2014 |
||||
* |
||||
* @author Abijeet <abijeetpatro@gmail.com> |
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* @author Daniel Calviño Sánchez <danxuliu@gmail.com> |
||||
* @author Joas Schilling <coding@schilljs.com> |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* @author Robin Appelman <robin@icewind.nl> |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* @author Vincent Petry <vincent@nextcloud.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
OCA.Trashbin = {} |
||||
/** |
||||
* @namespace OCA.Trashbin.App |
||||
*/ |
||||
OCA.Trashbin.App = { |
||||
_initialized: false, |
||||
/** @type {OC.Files.Client} */ |
||||
client: null, |
||||
|
||||
initialize($el) { |
||||
if (this._initialized) { |
||||
return |
||||
} |
||||
this._initialized = true |
||||
|
||||
this.client = new OC.Files.Client({ |
||||
host: OC.getHost(), |
||||
port: OC.getPort(), |
||||
root: OC.linkToRemoteBase('dav') + '/trashbin/' + OC.getCurrentUser().uid, |
||||
useHTTPS: OC.getProtocol() === 'https', |
||||
}) |
||||
const urlParams = OC.Util.History.parseUrlQuery() |
||||
this.fileList = new OCA.Trashbin.FileList( |
||||
$('#app-content-trashbin'), { |
||||
fileActions: this._createFileActions(), |
||||
detailsViewEnabled: false, |
||||
scrollTo: urlParams.scrollto, |
||||
config: OCA.Files.App.getFilesConfig(), |
||||
multiSelectMenu: [ |
||||
{ |
||||
name: 'restore', |
||||
displayName: t('files_trashbin', 'Restore'), |
||||
iconClass: 'icon-history', |
||||
}, |
||||
{ |
||||
name: 'delete', |
||||
displayName: t('files_trashbin', 'Delete permanently'), |
||||
iconClass: 'icon-delete', |
||||
}, |
||||
], |
||||
client: this.client, |
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true, |
||||
} |
||||
) |
||||
}, |
||||
|
||||
_createFileActions() { |
||||
const client = this.client |
||||
const fileActions = new OCA.Files.FileActions() |
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) { |
||||
const dir = context.fileList.getCurrentDirectory() |
||||
context.fileList.changeDirectory(OC.joinPaths(dir, filename)) |
||||
}) |
||||
|
||||
fileActions.setDefault('dir', 'Open') |
||||
|
||||
fileActions.registerAction({ |
||||
name: 'Restore', |
||||
displayName: t('files_trashbin', 'Restore'), |
||||
type: OCA.Files.FileActions.TYPE_INLINE, |
||||
mime: 'all', |
||||
permissions: OC.PERMISSION_READ, |
||||
iconClass: 'icon-history', |
||||
actionHandler(filename, context) { |
||||
const fileList = context.fileList |
||||
const tr = fileList.findFileEl(filename) |
||||
fileList.showFileBusyState(tr, true) |
||||
const dir = context.fileList.getCurrentDirectory() |
||||
client.move(OC.joinPaths('trash', dir, filename), OC.joinPaths('restore', filename), true) |
||||
.then( |
||||
fileList._removeCallback.bind(fileList, [filename]), |
||||
function() { |
||||
fileList.showFileBusyState(tr, false) |
||||
OC.Notification.show(t('files_trashbin', 'Error while restoring file from trash bin')) |
||||
} |
||||
) |
||||
}, |
||||
}) |
||||
|
||||
fileActions.registerAction({ |
||||
name: 'Delete', |
||||
displayName: t('files_trashbin', 'Delete permanently'), |
||||
mime: 'all', |
||||
permissions: OC.PERMISSION_READ, |
||||
iconClass: 'icon-delete', |
||||
render(actionSpec, isDefault, context) { |
||||
const $actionLink = fileActions._makeActionLink(actionSpec, context) |
||||
$actionLink.attr('original-title', t('files_trashbin', 'Delete permanently')) |
||||
$actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently')) |
||||
context.$file.find('td:last').append($actionLink) |
||||
return $actionLink |
||||
}, |
||||
actionHandler(filename, context) { |
||||
const fileList = context.fileList |
||||
$('.tipsy').remove() |
||||
const tr = fileList.findFileEl(filename) |
||||
fileList.showFileBusyState(tr, true) |
||||
const dir = context.fileList.getCurrentDirectory() |
||||
client.remove(OC.joinPaths('trash', dir, filename)) |
||||
.then( |
||||
fileList._removeCallback.bind(fileList, [filename]), |
||||
function() { |
||||
fileList.showFileBusyState(tr, false) |
||||
OC.Notification.show(t('files_trashbin', 'Error while removing file from trash bin')) |
||||
} |
||||
) |
||||
}, |
||||
}) |
||||
return fileActions |
||||
}, |
||||
} |
||||
|
||||
window.addEventListener('DOMContentLoaded', function() { |
||||
$('#app-content-trashbin').one('show', function() { |
||||
const App = OCA.Trashbin.App |
||||
App.initialize($('#app-content-trashbin')) |
||||
// force breadcrumb init
|
||||
// App.fileList.changeDirectory(App.fileList.getCurrentDirectory(), false, true);
|
||||
}) |
||||
}) |
@ -1,348 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2014 |
||||
* |
||||
* @author Azul <azul@riseup.net> |
||||
* @author Gary Kim <gary@garykim.dev> |
||||
* @author Jan C. Borchardt <hey@jancborchardt.net> |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* @author Robin Appelman <robin@icewind.nl> |
||||
* @author Vincent Petry <vincent@nextcloud.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
// eslint-disable-next-line import/no-unresolved, n/no-missing-import
|
||||
import PQueue from 'p-queue' |
||||
|
||||
/* eslint-disable */ |
||||
(function() { |
||||
var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/) |
||||
var FILENAME_PROP = '{http://nextcloud.org/ns}trashbin-filename' |
||||
var DELETION_TIME_PROP = '{http://nextcloud.org/ns}trashbin-deletion-time' |
||||
var TRASHBIN_ORIGINAL_LOCATION = '{http://nextcloud.org/ns}trashbin-original-location' |
||||
var TRASHBIN_TITLE = '{http://nextcloud.org/ns}trashbin-title' |
||||
|
||||
/** |
||||
* Convert a file name in the format filename.d12345 to the real file name. |
||||
* This will use basename. |
||||
* The name will not be changed if it has no ".d12345" suffix. |
||||
* @param {String} name file name |
||||
* @returns {String} converted file name |
||||
*/ |
||||
function getDeletedFileName(name) { |
||||
name = OC.basename(name) |
||||
var match = DELETED_REGEXP.exec(name) |
||||
if (match && match.length > 1) { |
||||
name = match[1] |
||||
} |
||||
return name |
||||
} |
||||
|
||||
/** |
||||
* @class OCA.Trashbin.FileList |
||||
* @augments OCA.Files.FileList |
||||
* @classdesc List of deleted files |
||||
* |
||||
* @param $el container element with existing markup for the .files-controls |
||||
* and a table |
||||
* @param [options] map of options |
||||
*/ |
||||
var FileList = function($el, options) { |
||||
this.client = options.client |
||||
this.initialize($el, options) |
||||
this.deleteOperationQueue = new PQueue({ concurrency: 4 }) |
||||
} |
||||
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, |
||||
/** @lends OCA.Trashbin.FileList.prototype */ { |
||||
id: 'trashbin', |
||||
appName: t('files_trashbin', 'Deleted files'), |
||||
/** @type {OC.Files.Client} */ |
||||
client: null, |
||||
|
||||
/** |
||||
* @private |
||||
*/ |
||||
initialize: function() { |
||||
this.client.addFileInfoParser(function(response, data) { |
||||
var props = response.propStat[0].properties |
||||
var path = props[TRASHBIN_ORIGINAL_LOCATION] |
||||
var title = props[TRASHBIN_TITLE] |
||||
return { |
||||
displayName: props[FILENAME_PROP], |
||||
mtime: parseInt(props[DELETION_TIME_PROP], 10) * 1000, |
||||
hasPreview: true, |
||||
path: path, |
||||
extraData: title |
||||
} |
||||
}) |
||||
|
||||
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments) |
||||
this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)) |
||||
|
||||
// Sort by most recently deleted first
|
||||
this.setSort('mtime', 'desc') |
||||
|
||||
/** |
||||
* Override crumb making to add "Deleted Files" entry |
||||
* and convert files with ".d" extensions to a more |
||||
* user friendly name. |
||||
*/ |
||||
this.breadcrumb._makeCrumbs = function() { |
||||
var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, [...arguments, 'icon-delete no-hover']) |
||||
for (var i = 1; i < parts.length; i++) { |
||||
parts[i].name = getDeletedFileName(parts[i].name) |
||||
} |
||||
return parts |
||||
} |
||||
|
||||
OC.Plugins.attach('OCA.Trashbin.FileList', this) |
||||
return result |
||||
}, |
||||
|
||||
/** |
||||
* Override to only return read permissions |
||||
*/ |
||||
getDirectoryPermissions: function() { |
||||
return OC.PERMISSION_READ | OC.PERMISSION_DELETE |
||||
}, |
||||
|
||||
_setCurrentDir: function(targetDir) { |
||||
OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments) |
||||
|
||||
var baseDir = OC.basename(targetDir) |
||||
if (baseDir !== '') { |
||||
this.setPageTitle(getDeletedFileName(baseDir)) |
||||
} |
||||
}, |
||||
|
||||
_createRow: function() { |
||||
// FIXME: MEGAHACK until we find a better solution
|
||||
var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments) |
||||
tr.find('td.filesize').remove() |
||||
return tr |
||||
}, |
||||
|
||||
getAjaxUrl: function(action, params) { |
||||
var q = '' |
||||
if (params) { |
||||
q = '?' + OC.buildQueryString(params) |
||||
} |
||||
return OC.filePath('files_trashbin', 'ajax', action + '.php') + q |
||||
}, |
||||
|
||||
setupUploadEvents: function() { |
||||
// override and do nothing
|
||||
}, |
||||
|
||||
linkTo: function(dir) { |
||||
return OC.linkTo('files', 'index.php') + '?view=trashbin&dir=' + encodeURIComponent(dir).replace(/%2F/g, '/') |
||||
}, |
||||
|
||||
elementToFile: function($el) { |
||||
var fileInfo = OCA.Files.FileList.prototype.elementToFile($el) |
||||
if (this.getCurrentDirectory() === '/') { |
||||
fileInfo.displayName = getDeletedFileName(fileInfo.name) |
||||
} |
||||
// no size available
|
||||
delete fileInfo.size |
||||
return fileInfo |
||||
}, |
||||
|
||||
updateEmptyContent: function() { |
||||
var exists = this.$fileList.find('tr:first').exists() |
||||
this.$el.find('.emptyfilelist.emptycontent').toggleClass('hidden', exists) |
||||
this.$el.find('.files-filestable th').toggleClass('hidden', !exists) |
||||
}, |
||||
|
||||
_removeCallback: function(files) { |
||||
var $el |
||||
for (var i = 0; i < files.length; i++) { |
||||
$el = this.remove(OC.basename(files[i]), { updateSummary: false }) |
||||
this.fileSummary.remove({ type: $el.attr('data-type'), size: $el.attr('data-size') }) |
||||
} |
||||
this.fileSummary.update() |
||||
this.updateEmptyContent() |
||||
}, |
||||
|
||||
_onClickRestoreSelected: function(event) { |
||||
event.preventDefault() |
||||
var self = this |
||||
var files = _.pluck(this.getSelectedFiles(), 'name') |
||||
for (var i = 0; i < files.length; i++) { |
||||
var tr = this.findFileEl(files[i]) |
||||
this.showFileBusyState(tr, true) |
||||
} |
||||
|
||||
this.fileMultiSelectMenu.toggleLoading('restore', true) |
||||
var restorePromises = files.map(function(file) { |
||||
return self.deleteOperationQueue.add(async () => { |
||||
self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true) |
||||
self._removeCallback([file]) |
||||
}) |
||||
}) |
||||
return Promise.all(restorePromises).then( |
||||
function() { |
||||
self.fileMultiSelectMenu.toggleLoading('restore', false) |
||||
}, |
||||
function() { |
||||
OC.Notification.show(t('files_trashbin', 'Error while restoring files from trash bin')) |
||||
} |
||||
) |
||||
}, |
||||
|
||||
_onClickDeleteSelected: function(event) { |
||||
event.preventDefault() |
||||
var self = this |
||||
var allFiles = this.$el.find('.select-all').is(':checked') |
||||
var files = _.pluck(this.getSelectedFiles(), 'name') |
||||
for (var i = 0; i < files.length; i++) { |
||||
var tr = this.findFileEl(files[i]) |
||||
this.showFileBusyState(tr, true) |
||||
} |
||||
|
||||
if (allFiles) { |
||||
return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory())) |
||||
.then( |
||||
function() { |
||||
self.hideMask() |
||||
self.setFiles([]) |
||||
}, |
||||
function() { |
||||
OC.Notification.show(t('files_trashbin', 'Error while emptying trash bin')) |
||||
} |
||||
) |
||||
} else { |
||||
this.fileMultiSelectMenu.toggleLoading('delete', true) |
||||
var deletePromises = files.map(function(file) { |
||||
return self.deleteOperationQueue.add(async () => { |
||||
await self.client.remove(OC.joinPaths('trash', self.getCurrentDirectory(), file)) |
||||
self._removeCallback([file]) |
||||
}) |
||||
}) |
||||
return Promise.all(deletePromises).then( |
||||
function() { |
||||
self.fileMultiSelectMenu.toggleLoading('delete', false) |
||||
}, |
||||
function() { |
||||
OC.Notification.show(t('files_trashbin', 'Error while removing files from trash bin')) |
||||
} |
||||
) |
||||
} |
||||
}, |
||||
|
||||
_onClickFile: function(event) { |
||||
var mime = $(this).parent().parent().data('mime') |
||||
if (mime !== 'httpd/unix-directory') { |
||||
event.preventDefault() |
||||
} |
||||
return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments) |
||||
}, |
||||
|
||||
generatePreviewUrl: function(urlSpec) { |
||||
return OC.generateUrl('/apps/files_trashbin/preview?') + $.param(urlSpec) |
||||
}, |
||||
|
||||
getDownloadUrl: function() { |
||||
// no downloads
|
||||
return '#' |
||||
}, |
||||
|
||||
getDefaultActionUrl: function() { |
||||
// no default action
|
||||
return '#' |
||||
}, |
||||
|
||||
updateStorageStatistics: function() { |
||||
// no op because the trashbin doesn't have
|
||||
// storage info like free space / used space
|
||||
}, |
||||
|
||||
isSelectedDeletable: function() { |
||||
return true |
||||
}, |
||||
|
||||
/** |
||||
* Returns list of webdav properties to request |
||||
*/ |
||||
_getWebdavProperties: function() { |
||||
return [FILENAME_PROP, DELETION_TIME_PROP, TRASHBIN_ORIGINAL_LOCATION, TRASHBIN_TITLE].concat(this.filesClient.getPropfindProperties()) |
||||
}, |
||||
|
||||
/** |
||||
* Reloads the file list using ajax call |
||||
* |
||||
* @returns ajax call object |
||||
*/ |
||||
reload: function() { |
||||
this._selectedFiles = {} |
||||
this._selectionSummary.clear() |
||||
this.$el.find('.select-all').prop('checked', false) |
||||
this.showMask() |
||||
if (this._reloadCall?.abort) { |
||||
this._reloadCall.abort() |
||||
} |
||||
this._reloadCall = this.client.getFolderContents( |
||||
'trash/' + this.getCurrentDirectory(), { |
||||
includeParent: false, |
||||
properties: this._getWebdavProperties() |
||||
} |
||||
) |
||||
var callBack = this.reloadCallback.bind(this) |
||||
return this._reloadCall.then(callBack, callBack) |
||||
}, |
||||
reloadCallback: function(status, result) { |
||||
delete this._reloadCall |
||||
this.hideMask() |
||||
|
||||
if (status === 401) { |
||||
return false |
||||
} |
||||
|
||||
// Firewall Blocked request?
|
||||
if (status === 403) { |
||||
// Go home
|
||||
this.changeDirectory('/') |
||||
OC.Notification.show(t('files', 'This operation is forbidden')) |
||||
return false |
||||
} |
||||
|
||||
// Did share service die or something else fail?
|
||||
if (status === 500) { |
||||
// Go home
|
||||
this.changeDirectory('/') |
||||
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator')) |
||||
return false |
||||
} |
||||
|
||||
if (status === 404) { |
||||
// go back home
|
||||
this.changeDirectory('/') |
||||
return false |
||||
} |
||||
// aborted ?
|
||||
if (status === 0) { |
||||
return true |
||||
} |
||||
|
||||
this.setFiles(result) |
||||
return true |
||||
} |
||||
|
||||
}) |
||||
|
||||
OCA.Trashbin.FileList = FileList |
||||
})() |
Loading…
Reference in new issue