2014-01-30 13:41:04 +04:00
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2014
|
|
|
|
|
*
|
|
|
|
|
* This file is licensed under the Affero General Public License version 3
|
|
|
|
|
* or later.
|
|
|
|
|
*
|
|
|
|
|
* See the COPYING-README file.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
(function() {
|
2015-08-27 14:22:58 +03:00
|
|
|
|
|
2016-02-17 13:04:29 +03:00
|
|
|
|
var TEMPLATE_ADDBUTTON = '<a href="#" class="button new">' +
|
|
|
|
|
'<span class="icon {{iconClass}}"></span>' +
|
|
|
|
|
'<span class="hidden-visually">{{addText}}</span>' +
|
|
|
|
|
'</a>';
|
2015-08-27 14:22:58 +03:00
|
|
|
|
|
2014-04-04 20:46:08 +04:00
|
|
|
|
/**
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @class OCA.Files.FileList
|
|
|
|
|
* @classdesc
|
|
|
|
|
*
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* The FileList class manages a file list view.
|
|
|
|
|
* A file list view consists of a controls bar and
|
|
|
|
|
* a file list table.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
*
|
|
|
|
|
* @param $el container element with existing markup for the #controls
|
|
|
|
|
* and a table
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {Object} [options] map of options, see other parameters
|
|
|
|
|
* @param {Object} [options.scrollContainer] scrollable container, defaults to $(window)
|
|
|
|
|
* @param {Object} [options.dragOptions] drag options, disabled by default
|
|
|
|
|
* @param {Object} [options.folderDropOptions] folder drop options, disabled by default
|
|
|
|
|
* @param {boolean} [options.detailsViewEnabled=true] whether to enable details view
|
2015-12-16 19:35:53 +03:00
|
|
|
|
* @param {boolean} [options.enableUpload=false] whether to enable uploader
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.Client} [options.filesClient] files client to use
|
2014-04-04 20:46:08 +04:00
|
|
|
|
*/
|
2014-05-12 21:54:20 +04:00
|
|
|
|
var FileList = function($el, options) {
|
|
|
|
|
this.initialize($el, options);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
};
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* @memberof OCA.Files
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
FileList.prototype = {
|
2014-07-22 01:07:52 +04:00
|
|
|
|
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-n',
|
|
|
|
|
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-s',
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
id: 'files',
|
2014-05-09 00:06:30 +04:00
|
|
|
|
appName: t('files', 'Files'),
|
|
|
|
|
isEmpty: true,
|
|
|
|
|
useUndo:true,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Top-level container with controls and file list
|
|
|
|
|
*/
|
|
|
|
|
$el: null,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Files table
|
|
|
|
|
*/
|
|
|
|
|
$table: null,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List of rows (table tbody)
|
|
|
|
|
*/
|
|
|
|
|
$fileList: null,
|
|
|
|
|
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* @type OCA.Files.BreadCrumb
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
breadcrumb: null,
|
|
|
|
|
|
|
|
|
|
/**
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type OCA.Files.FileSummary
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
fileSummary: null,
|
2014-06-24 01:56:10 +04:00
|
|
|
|
|
2015-07-15 13:06:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* @type OCA.Files.DetailsView
|
|
|
|
|
*/
|
|
|
|
|
_detailsView: null,
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Files client instance
|
|
|
|
|
*
|
|
|
|
|
* @type OC.Files.Client
|
|
|
|
|
*/
|
|
|
|
|
filesClient: null,
|
|
|
|
|
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* Whether the file list was initialized already.
|
|
|
|
|
* @type boolean
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
initialized: false,
|
|
|
|
|
|
2017-05-04 14:23:04 +03:00
|
|
|
|
/**
|
|
|
|
|
* Wheater the file list was already shown once
|
|
|
|
|
* @type boolean
|
|
|
|
|
*/
|
|
|
|
|
shown: false,
|
|
|
|
|
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* Number of files per page
|
|
|
|
|
*
|
|
|
|
|
* @return {int} page size
|
|
|
|
|
*/
|
2014-10-11 17:10:54 +04:00
|
|
|
|
pageSize: function() {
|
2014-10-15 17:24:03 +04:00
|
|
|
|
return Math.ceil(this.$container.height() / 50);
|
2014-10-11 17:10:54 +04:00
|
|
|
|
},
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Array of files in the current folder.
|
|
|
|
|
* The entries are of file data.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @type Array.<OC.Files.FileInfo>
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
files: [],
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Current directory entry
|
|
|
|
|
*
|
|
|
|
|
* @type OC.Files.FileInfo
|
|
|
|
|
*/
|
|
|
|
|
dirInfo: null,
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* File actions handler, defaults to OCA.Files.FileActions
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type OCA.Files.FileActions
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
fileActions: null,
|
|
|
|
|
|
2014-12-11 19:36:14 +03:00
|
|
|
|
/**
|
|
|
|
|
* Whether selection is allowed, checkboxes and selection overlay will
|
|
|
|
|
* be rendered
|
|
|
|
|
*/
|
|
|
|
|
_allowSelection: true,
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Map of file id to file data
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type Object.<int, Object>
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_selectedFiles: {},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Summary of selected files.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type OCA.Files.FileSummary
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_selectionSummary: null,
|
|
|
|
|
|
2014-12-18 12:26:41 +03:00
|
|
|
|
/**
|
|
|
|
|
* If not empty, only files containing this string will be shown
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
_filter: '',
|
|
|
|
|
|
2016-04-12 18:10:09 +03:00
|
|
|
|
/**
|
|
|
|
|
* @type Backbone.Model
|
|
|
|
|
*/
|
2016-04-21 15:23:18 +03:00
|
|
|
|
_filesConfig: undefined,
|
2016-04-12 18:10:09 +03:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Sort attribute
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type String
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_sort: 'name',
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sort direction: 'asc' or 'desc'
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type String
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_sortDirection: 'asc',
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sort comparator function for the current sort
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type Function
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_sortComparator: null,
|
|
|
|
|
|
2014-08-15 18:52:41 +04:00
|
|
|
|
/**
|
|
|
|
|
* Whether to do a client side sort.
|
|
|
|
|
* When false, clicking on a table header will call reload().
|
|
|
|
|
* When true, clicking on a table header will simply resort the list.
|
|
|
|
|
*/
|
2015-07-13 18:38:13 +03:00
|
|
|
|
_clientSideSort: true,
|
2014-08-15 18:52:41 +04:00
|
|
|
|
|
2016-07-22 15:19:04 +03:00
|
|
|
|
/**
|
|
|
|
|
* Whether or not users can change the sort attribute or direction
|
|
|
|
|
*/
|
|
|
|
|
_allowSorting: true,
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Current directory
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @type String
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_currentDirectory: null,
|
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
_dragOptions: null,
|
|
|
|
|
_folderDropOptions: null,
|
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
/**
|
|
|
|
|
* @type OC.Uploader
|
|
|
|
|
*/
|
|
|
|
|
_uploader: null,
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Initialize the file list and its components
|
2014-05-12 21:54:20 +04:00
|
|
|
|
*
|
|
|
|
|
* @param $el container element with existing markup for the #controls
|
|
|
|
|
* and a table
|
|
|
|
|
* @param options map of options, see other parameters
|
2014-09-04 14:20:11 +04:00
|
|
|
|
* @param options.scrollContainer scrollable container, defaults to $(window)
|
|
|
|
|
* @param options.dragOptions drag options, disabled by default
|
|
|
|
|
* @param options.folderDropOptions folder drop options, disabled by default
|
|
|
|
|
* @param options.scrollTo name of file to scroll to after the first load
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.Client} [options.filesClient] files API client
|
2016-08-19 17:44:58 +03:00
|
|
|
|
* @param {OC.Backbone.Model} [options.filesConfig] files app configuration
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @private
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2014-05-12 21:54:20 +04:00
|
|
|
|
initialize: function($el, options) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var self = this;
|
2014-05-12 21:54:20 +04:00
|
|
|
|
options = options || {};
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (this.initialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-04-04 20:46:08 +04:00
|
|
|
|
|
2016-04-12 18:10:09 +03:00
|
|
|
|
if (options.config) {
|
|
|
|
|
this._filesConfig = options.config;
|
2016-04-21 15:23:18 +03:00
|
|
|
|
} else if (!_.isUndefined(OCA.Files) && !_.isUndefined(OCA.Files.App)) {
|
2016-04-12 18:10:09 +03:00
|
|
|
|
this._filesConfig = OCA.Files.App.getFilesConfig();
|
2016-08-22 10:45:21 +03:00
|
|
|
|
} else {
|
|
|
|
|
this._filesConfig = new OC.Backbone.Model({
|
|
|
|
|
'showhidden': false
|
|
|
|
|
});
|
2016-04-12 18:10:09 +03:00
|
|
|
|
}
|
2016-04-21 15:23:18 +03:00
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
if (options.dragOptions) {
|
|
|
|
|
this._dragOptions = options.dragOptions;
|
|
|
|
|
}
|
|
|
|
|
if (options.folderDropOptions) {
|
|
|
|
|
this._folderDropOptions = options.folderDropOptions;
|
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (options.filesClient) {
|
|
|
|
|
this.filesClient = options.filesClient;
|
|
|
|
|
} else {
|
|
|
|
|
// default client if not specified
|
|
|
|
|
this.filesClient = OC.Files.getClient();
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el = $el;
|
2014-12-01 18:17:28 +03:00
|
|
|
|
if (options.id) {
|
|
|
|
|
this.id = options.id;
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$container = options.scrollContainer || $(window);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$table = $el.find('table:first');
|
|
|
|
|
this.$fileList = $el.find('#fileList');
|
2015-09-02 11:41:08 +03:00
|
|
|
|
|
2016-08-17 18:34:15 +03:00
|
|
|
|
if (!_.isUndefined(this._filesConfig)) {
|
|
|
|
|
this._filesConfig.on('change:showhidden', function() {
|
|
|
|
|
var showHidden = this.get('showhidden');
|
|
|
|
|
self.$el.toggleClass('hide-hidden-files', !showHidden);
|
2016-08-19 17:44:58 +03:00
|
|
|
|
self.updateSelectionSummary();
|
2016-08-17 18:34:15 +03:00
|
|
|
|
|
|
|
|
|
if (!showHidden) {
|
|
|
|
|
// hiding files could make the page too small, need to try rendering next page
|
|
|
|
|
self._onScroll();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.$el.toggleClass('hide-hidden-files', !this._filesConfig.get('showhidden'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-09-02 11:41:08 +03:00
|
|
|
|
if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) {
|
|
|
|
|
this._detailsView = new OCA.Files.DetailsView();
|
|
|
|
|
this._detailsView.$el.insertBefore(this.$el);
|
|
|
|
|
this._detailsView.$el.addClass('disappear');
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-20 18:01:34 +04:00
|
|
|
|
this._initFileActions(options.fileActions);
|
2015-09-02 11:41:08 +03:00
|
|
|
|
|
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView({fileList: this, fileActions: this.fileActions}));
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.files = [];
|
|
|
|
|
this._selectedFiles = {};
|
2016-08-19 17:44:58 +03:00
|
|
|
|
this._selectionSummary = new OCA.Files.FileSummary(undefined, {config: this._filesConfig});
|
2015-07-13 18:38:13 +03:00
|
|
|
|
// dummy root dir info
|
|
|
|
|
this.dirInfo = new OC.Files.FileInfo({});
|
2014-04-03 22:57:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.fileSummary = this._createSummary();
|
2014-04-03 22:57:06 +04:00
|
|
|
|
|
2016-04-12 11:19:52 +03:00
|
|
|
|
if (options.sorting) {
|
2016-04-12 12:51:50 +03:00
|
|
|
|
this.setSort(options.sorting.mode, options.sorting.direction, false, false);
|
2016-04-12 11:19:52 +03:00
|
|
|
|
} else {
|
2016-04-12 12:51:50 +03:00
|
|
|
|
this.setSort('name', 'asc', false, false);
|
2016-04-12 11:19:52 +03:00
|
|
|
|
}
|
2014-04-04 16:34:07 +04:00
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
var breadcrumbOptions = {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
onClick: _.bind(this._onClickBreadCrumb, this),
|
2014-05-12 21:54:20 +04:00
|
|
|
|
getCrumbUrl: function(part) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return self.linkTo(part.dir);
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
};
|
|
|
|
|
// if dropping on folders is allowed, then also allow on breadcrumbs
|
|
|
|
|
if (this._folderDropOptions) {
|
|
|
|
|
breadcrumbOptions.onDrop = _.bind(this._onDropOnBreadCrumb, this);
|
2015-11-04 13:44:47 +03:00
|
|
|
|
breadcrumbOptions.onOver = function() {
|
|
|
|
|
self.$el.find('td.filename.ui-droppable').droppable('disable');
|
|
|
|
|
}
|
|
|
|
|
breadcrumbOptions.onOut = function() {
|
|
|
|
|
self.$el.find('td.filename.ui-droppable').droppable('enable');
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
}
|
|
|
|
|
this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions);
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2015-10-13 12:36:24 +03:00
|
|
|
|
var $controls = this.$el.find('#controls');
|
|
|
|
|
if ($controls.length > 0) {
|
|
|
|
|
$controls.prepend(this.breadcrumb.$el);
|
|
|
|
|
this.$table.addClass('has-controls');
|
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2015-08-27 14:22:58 +03:00
|
|
|
|
this._renderNewButton();
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
|
2014-02-11 19:52:56 +04:00
|
|
|
|
|
2017-11-04 11:45:29 +03:00
|
|
|
|
this._onResize = _.debounce(_.bind(this._onResize, this), 250);
|
2015-10-02 19:02:55 +03:00
|
|
|
|
$('#app-content').on('appresized', this._onResize);
|
2014-05-23 21:02:50 +04:00
|
|
|
|
$(window).resize(this._onResize);
|
|
|
|
|
|
|
|
|
|
this.$el.on('show', this._onResize);
|
2014-04-03 22:57:06 +04:00
|
|
|
|
|
2015-01-05 15:11:50 +03:00
|
|
|
|
this.updateSearch();
|
|
|
|
|
|
2016-03-23 14:31:58 +03:00
|
|
|
|
this.$fileList.on('click','td.filename>a.name, td.filesize, td.date', _.bind(this._onClickFile, this));
|
|
|
|
|
|
2017-09-29 02:36:10 +03:00
|
|
|
|
this.$fileList.on('change', 'td.selection>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
|
2017-04-20 15:59:08 +03:00
|
|
|
|
this.$el.on('show', _.bind(this._onShow, this));
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this));
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
|
2017-08-27 18:39:22 +03:00
|
|
|
|
this.$el.find('.copy-move').click(_.bind(this._onClickCopyMoveSelected, this));
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this));
|
|
|
|
|
|
2015-08-07 15:00:44 +03:00
|
|
|
|
this.$el.find('.selectedActions a').tooltip({placement:'top'});
|
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$container.on('scroll', _.bind(this._onScroll, this));
|
2014-09-04 14:20:11 +04:00
|
|
|
|
|
|
|
|
|
if (options.scrollTo) {
|
|
|
|
|
this.$fileList.one('updated', function() {
|
|
|
|
|
self.scrollTo(options.scrollTo);
|
|
|
|
|
});
|
|
|
|
|
}
|
2014-12-01 18:17:28 +03:00
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
if (options.enableUpload) {
|
|
|
|
|
// TODO: auto-create this element
|
|
|
|
|
var $uploadEl = this.$el.find('#file_upload_start');
|
|
|
|
|
if ($uploadEl.exists()) {
|
|
|
|
|
this._uploader = new OC.Uploader($uploadEl, {
|
|
|
|
|
fileList: this,
|
|
|
|
|
filesClient: this.filesClient,
|
2016-10-07 17:27:54 +03:00
|
|
|
|
dropZone: $('#content'),
|
|
|
|
|
maxChunkSize: options.maxChunkSize
|
2015-12-16 19:35:53 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.setupUploadEvents(this._uploader);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-01 18:17:28 +03:00
|
|
|
|
OC.Plugins.attach('OCA.Files.FileList', this);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2014-06-27 15:36:18 +04:00
|
|
|
|
/**
|
|
|
|
|
* Destroy / uninitialize this instance.
|
|
|
|
|
*/
|
|
|
|
|
destroy: function() {
|
2015-08-27 14:22:58 +03:00
|
|
|
|
if (this._newFileMenu) {
|
|
|
|
|
this._newFileMenu.remove();
|
|
|
|
|
}
|
2015-09-03 13:17:35 +03:00
|
|
|
|
if (this._newButton) {
|
|
|
|
|
this._newButton.remove();
|
|
|
|
|
}
|
2015-09-25 13:23:28 +03:00
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
this._detailsView.remove();
|
|
|
|
|
}
|
2014-06-27 15:36:18 +04:00
|
|
|
|
// TODO: also unregister other event handlers
|
2014-07-09 14:26:33 +04:00
|
|
|
|
this.fileActions.off('registerAction', this._onFileActionsUpdated);
|
|
|
|
|
this.fileActions.off('setDefault', this._onFileActionsUpdated);
|
2014-12-01 18:17:28 +03:00
|
|
|
|
OC.Plugins.detach('OCA.Files.FileList', this);
|
2015-10-02 19:02:55 +03:00
|
|
|
|
$('#app-content').off('appresized', this._onResize);
|
2014-06-27 15:36:18 +04:00
|
|
|
|
},
|
|
|
|
|
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* Initializes the file actions, set up listeners.
|
|
|
|
|
*
|
|
|
|
|
* @param {OCA.Files.FileActions} fileActions file actions
|
|
|
|
|
*/
|
2014-05-20 18:01:34 +04:00
|
|
|
|
_initFileActions: function(fileActions) {
|
2015-09-02 11:41:08 +03:00
|
|
|
|
var self = this;
|
2014-05-20 18:01:34 +04:00
|
|
|
|
this.fileActions = fileActions;
|
|
|
|
|
if (!this.fileActions) {
|
|
|
|
|
this.fileActions = new OCA.Files.FileActions();
|
|
|
|
|
this.fileActions.registerDefaultActions();
|
|
|
|
|
}
|
2015-09-02 11:41:08 +03:00
|
|
|
|
|
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
this.fileActions.registerAction({
|
|
|
|
|
name: 'Details',
|
2015-09-22 15:52:52 +03:00
|
|
|
|
displayName: t('files', 'Details'),
|
2015-09-02 11:41:08 +03:00
|
|
|
|
mime: 'all',
|
2015-09-28 12:19:49 +03:00
|
|
|
|
order: -50,
|
2016-02-17 13:04:29 +03:00
|
|
|
|
iconClass: 'icon-details',
|
2017-11-02 17:01:17 +03:00
|
|
|
|
permissions: OC.PERMISSION_NONE,
|
2015-09-02 11:41:08 +03:00
|
|
|
|
actionHandler: function(fileName, context) {
|
|
|
|
|
self._updateDetailsView(fileName);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-27 15:36:18 +04:00
|
|
|
|
this._onFileActionsUpdated = _.debounce(_.bind(this._onFileActionsUpdated, this), 100);
|
2014-07-09 14:26:33 +04:00
|
|
|
|
this.fileActions.on('registerAction', this._onFileActionsUpdated);
|
|
|
|
|
this.fileActions.on('setDefault', this._onFileActionsUpdated);
|
2014-05-20 18:01:34 +04:00
|
|
|
|
},
|
|
|
|
|
|
2015-08-12 18:30:20 +03:00
|
|
|
|
/**
|
|
|
|
|
* Returns a unique model for the given file name.
|
|
|
|
|
*
|
|
|
|
|
* @param {string|object} fileName file name or jquery row
|
|
|
|
|
* @return {OCA.Files.FileInfoModel} file info model
|
|
|
|
|
*/
|
|
|
|
|
getModelForFile: function(fileName) {
|
2015-09-01 20:29:55 +03:00
|
|
|
|
var self = this;
|
2015-08-12 18:30:20 +03:00
|
|
|
|
var $tr;
|
|
|
|
|
// jQuery object ?
|
|
|
|
|
if (fileName.is) {
|
|
|
|
|
$tr = fileName;
|
|
|
|
|
fileName = $tr.attr('data-file');
|
|
|
|
|
} else {
|
|
|
|
|
$tr = this.findFileEl(fileName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$tr || !$tr.length) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if requesting the selected model, return it
|
|
|
|
|
if (this._currentFileModel && this._currentFileModel.get('name') === fileName) {
|
|
|
|
|
return this._currentFileModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: note, this is a temporary model required for synchronising
|
|
|
|
|
// state between different views.
|
|
|
|
|
// In the future the FileList should work with Backbone.Collection
|
|
|
|
|
// and contain existing models that can be used.
|
|
|
|
|
// This method would in the future simply retrieve the matching model from the collection.
|
2016-11-21 17:03:45 +03:00
|
|
|
|
var model = new OCA.Files.FileInfoModel(this.elementToFile($tr), {
|
|
|
|
|
filesClient: this.filesClient
|
|
|
|
|
});
|
2015-09-11 16:24:33 +03:00
|
|
|
|
if (!model.get('path')) {
|
2015-08-12 18:30:20 +03:00
|
|
|
|
model.set('path', this.getCurrentDirectory(), {silent: true});
|
|
|
|
|
}
|
2015-09-01 20:29:55 +03:00
|
|
|
|
|
|
|
|
|
model.on('change', function(model) {
|
|
|
|
|
// re-render row
|
|
|
|
|
var highlightState = $tr.hasClass('highlighted');
|
|
|
|
|
$tr = self.updateRow(
|
|
|
|
|
$tr,
|
2015-07-13 18:38:13 +03:00
|
|
|
|
model.toJSON(),
|
2015-09-01 20:29:55 +03:00
|
|
|
|
{updateSummary: true, silent: false, animate: true}
|
|
|
|
|
);
|
2016-04-13 12:20:26 +03:00
|
|
|
|
|
|
|
|
|
// restore selection state
|
|
|
|
|
var selected = !!self._selectedFiles[$tr.data('id')];
|
|
|
|
|
self._selectFileEl($tr, selected);
|
|
|
|
|
|
2015-09-01 20:29:55 +03:00
|
|
|
|
$tr.toggleClass('highlighted', highlightState);
|
|
|
|
|
});
|
|
|
|
|
model.on('busy', function(model, state) {
|
|
|
|
|
self.showFileBusyState($tr, state);
|
|
|
|
|
});
|
|
|
|
|
|
2015-08-12 18:30:20 +03:00
|
|
|
|
return model;
|
|
|
|
|
},
|
|
|
|
|
|
2015-09-14 18:20:51 +03:00
|
|
|
|
/**
|
|
|
|
|
* Displays the details view for the given file and
|
|
|
|
|
* selects the given tab
|
|
|
|
|
*
|
2016-10-10 18:35:11 +03:00
|
|
|
|
* @param {string|OCA.Files.FileInfoModel} fileName file name or FileInfoModel for which to show details
|
2015-09-14 18:20:51 +03:00
|
|
|
|
* @param {string} [tabId] optional tab id to select
|
|
|
|
|
*/
|
|
|
|
|
showDetailsView: function(fileName, tabId) {
|
|
|
|
|
this._updateDetailsView(fileName);
|
|
|
|
|
if (tabId) {
|
|
|
|
|
this._detailsView.selectTab(tabId);
|
|
|
|
|
}
|
2015-09-22 16:28:48 +03:00
|
|
|
|
OC.Apps.showAppSidebar(this._detailsView.$el);
|
2015-09-14 18:20:51 +03:00
|
|
|
|
},
|
|
|
|
|
|
2015-07-15 13:06:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Update the details view to display the given file
|
|
|
|
|
*
|
2016-10-10 18:35:11 +03:00
|
|
|
|
* @param {string|OCA.Files.FileInfoModel} fileName file name from the current list or a FileInfoModel object
|
2015-10-21 14:43:39 +03:00
|
|
|
|
* @param {boolean} [show=true] whether to open the sidebar if it was closed
|
2015-07-15 13:06:13 +03:00
|
|
|
|
*/
|
2015-10-21 14:43:39 +03:00
|
|
|
|
_updateDetailsView: function(fileName, show) {
|
2015-07-16 13:28:19 +03:00
|
|
|
|
if (!this._detailsView) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-21 14:43:39 +03:00
|
|
|
|
// show defaults to true
|
|
|
|
|
show = _.isUndefined(show) || !!show;
|
2015-07-15 18:35:35 +03:00
|
|
|
|
var oldFileInfo = this._detailsView.getFileInfo();
|
|
|
|
|
if (oldFileInfo) {
|
|
|
|
|
// TODO: use more efficient way, maybe track the highlight
|
2015-08-12 18:30:20 +03:00
|
|
|
|
this.$fileList.children().filterAttr('data-id', '' + oldFileInfo.get('id')).removeClass('highlighted');
|
|
|
|
|
oldFileInfo.off('change', this._onSelectedModelChanged, this);
|
2015-07-15 18:35:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-12 18:30:20 +03:00
|
|
|
|
if (!fileName) {
|
2015-07-15 18:05:25 +03:00
|
|
|
|
this._detailsView.setFileInfo(null);
|
2015-09-01 20:29:55 +03:00
|
|
|
|
if (this._currentFileModel) {
|
|
|
|
|
this._currentFileModel.off();
|
|
|
|
|
}
|
2015-08-12 18:30:20 +03:00
|
|
|
|
this._currentFileModel = null;
|
2015-09-25 13:23:28 +03:00
|
|
|
|
OC.Apps.hideAppSidebar(this._detailsView.$el);
|
2015-07-15 17:09:00 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-21 14:43:39 +03:00
|
|
|
|
if (show && this._detailsView.$el.hasClass('disappear')) {
|
2015-09-25 13:23:28 +03:00
|
|
|
|
OC.Apps.showAppSidebar(this._detailsView.$el);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 18:35:11 +03:00
|
|
|
|
if (fileName instanceof OCA.Files.FileInfoModel) {
|
|
|
|
|
var model = fileName;
|
2016-10-04 20:08:08 +03:00
|
|
|
|
} else {
|
|
|
|
|
var $tr = this.findFileEl(fileName);
|
|
|
|
|
var model = this.getModelForFile($tr);
|
|
|
|
|
$tr.addClass('highlighted');
|
|
|
|
|
}
|
2015-08-12 18:30:20 +03:00
|
|
|
|
|
|
|
|
|
this._currentFileModel = model;
|
|
|
|
|
|
|
|
|
|
this._detailsView.setFileInfo(model);
|
2015-07-16 13:21:40 +03:00
|
|
|
|
this._detailsView.$el.scrollTop(0);
|
2015-07-15 13:06:13 +03:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-23 21:02:50 +04:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when the window size changed
|
|
|
|
|
*/
|
|
|
|
|
_onResize: function() {
|
|
|
|
|
var containerWidth = this.$el.width();
|
|
|
|
|
var actionsWidth = 0;
|
|
|
|
|
$.each(this.$el.find('#controls .actions'), function(index, action) {
|
|
|
|
|
actionsWidth += $(action).outerWidth();
|
|
|
|
|
});
|
|
|
|
|
|
2017-11-04 11:45:29 +03:00
|
|
|
|
this.breadcrumb._resize();
|
2015-01-05 15:11:50 +03:00
|
|
|
|
|
2015-10-02 19:02:55 +03:00
|
|
|
|
this.$table.find('>thead').width($('#app-content').width() - OC.Util.getScrollBarWidth());
|
2014-05-23 21:02:50 +04:00
|
|
|
|
},
|
|
|
|
|
|
2017-04-20 15:59:08 +03:00
|
|
|
|
/**
|
|
|
|
|
* Event handler when leaving previously hidden state
|
|
|
|
|
*/
|
|
|
|
|
_onShow: function(e) {
|
2017-05-04 14:23:04 +03:00
|
|
|
|
if (this.shown) {
|
2017-09-28 16:15:49 +03:00
|
|
|
|
this._setCurrentDir('/', false);
|
2017-05-04 14:23:04 +03:00
|
|
|
|
this.reload();
|
|
|
|
|
}
|
|
|
|
|
this.shown = true;
|
2017-04-20 15:59:08 +03:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when the URL changed
|
|
|
|
|
*/
|
|
|
|
|
_onUrlChanged: function(e) {
|
2016-07-01 12:10:37 +03:00
|
|
|
|
if (e && _.isString(e.dir)) {
|
2017-03-01 14:57:48 +03:00
|
|
|
|
var currentDir = this.getCurrentDirectory();
|
|
|
|
|
// this._currentDirectory is NULL when fileList is first initialised
|
|
|
|
|
if( (this._currentDirectory || this.$el.find('#dir').val()) && currentDir === e.dir) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.changeDirectory(e.dir, false, true);
|
2013-10-28 23:22:06 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Selected/deselects the given file element and updated
|
|
|
|
|
* the internal selection cache.
|
|
|
|
|
*
|
2015-09-25 13:23:28 +03:00
|
|
|
|
* @param {Object} $tr single file row element
|
|
|
|
|
* @param {bool} state true to select, false to deselect
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2015-09-25 13:23:28 +03:00
|
|
|
|
_selectFileEl: function($tr, state, showDetailsView) {
|
2017-09-29 02:36:10 +03:00
|
|
|
|
var $checkbox = $tr.find('td.selection>.selectCheckBox');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var oldData = !!this._selectedFiles[$tr.data('id')];
|
|
|
|
|
var data;
|
|
|
|
|
$checkbox.prop('checked', state);
|
|
|
|
|
$tr.toggleClass('selected', state);
|
|
|
|
|
// already selected ?
|
|
|
|
|
if (state === oldData) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
data = this.elementToFile($tr);
|
|
|
|
|
if (state) {
|
|
|
|
|
this._selectedFiles[$tr.data('id')] = data;
|
|
|
|
|
this._selectionSummary.add(data);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
delete this._selectedFiles[$tr.data('id')];
|
|
|
|
|
this._selectionSummary.remove(data);
|
|
|
|
|
}
|
2016-02-15 16:58:44 +03:00
|
|
|
|
if (this._detailsView && !this._detailsView.$el.hasClass('disappear')) {
|
|
|
|
|
// hide sidebar
|
|
|
|
|
this._updateDetailsView(null);
|
2015-07-15 18:05:25 +03:00
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler for when clicking on files to select them
|
|
|
|
|
*/
|
|
|
|
|
_onClickFile: function(event) {
|
|
|
|
|
var $tr = $(event.target).closest('tr');
|
2015-09-25 13:23:28 +03:00
|
|
|
|
if ($tr.hasClass('dragging')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-01-15 20:16:27 +03:00
|
|
|
|
if (this._allowSelection && (event.ctrlKey || event.shiftKey)) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
event.preventDefault();
|
|
|
|
|
if (event.shiftKey) {
|
|
|
|
|
var $lastTr = $(this._lastChecked);
|
|
|
|
|
var lastIndex = $lastTr.index();
|
|
|
|
|
var currentIndex = $tr.index();
|
|
|
|
|
var $rows = this.$fileList.children('tr');
|
|
|
|
|
|
|
|
|
|
// last clicked checkbox below current one ?
|
|
|
|
|
if (lastIndex > currentIndex) {
|
|
|
|
|
var aux = lastIndex;
|
|
|
|
|
lastIndex = currentIndex;
|
|
|
|
|
currentIndex = aux;
|
|
|
|
|
}
|
2014-04-04 20:46:08 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// auto-select everything in-between
|
|
|
|
|
for (var i = lastIndex + 1; i < currentIndex; i++) {
|
|
|
|
|
this._selectFileEl($rows.eq(i), true);
|
|
|
|
|
}
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
else {
|
|
|
|
|
this._lastChecked = $tr;
|
|
|
|
|
}
|
2017-09-29 02:36:10 +03:00
|
|
|
|
var $checkbox = $tr.find('td.selection>.selectCheckBox');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this._selectFileEl($tr, !$checkbox.prop('checked'));
|
|
|
|
|
this.updateSelectionSummary();
|
|
|
|
|
} else {
|
2015-07-15 17:09:00 +03:00
|
|
|
|
// clicked directly on the name
|
2015-07-16 13:28:19 +03:00
|
|
|
|
if (!this._detailsView || $(event.target).is('.nametext') || $(event.target).closest('.nametext').length) {
|
2015-07-15 17:09:00 +03:00
|
|
|
|
var filename = $tr.attr('data-file');
|
|
|
|
|
var renaming = $tr.data('renaming');
|
|
|
|
|
if (!renaming) {
|
|
|
|
|
this.fileActions.currentFile = $tr.find('td');
|
|
|
|
|
var mime = this.fileActions.getCurrentMimeType();
|
|
|
|
|
var type = this.fileActions.getCurrentType();
|
|
|
|
|
var permissions = this.fileActions.getCurrentPermissions();
|
|
|
|
|
var action = this.fileActions.getDefault(mime,type, permissions);
|
|
|
|
|
if (action) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
// also set on global object for legacy apps
|
|
|
|
|
window.FileActions.currentFile = this.fileActions.currentFile;
|
|
|
|
|
action(filename, {
|
|
|
|
|
$file: $tr,
|
|
|
|
|
fileList: this,
|
|
|
|
|
fileActions: this.fileActions,
|
|
|
|
|
dir: $tr.attr('data-path') || this.getCurrentDirectory()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// deselect row
|
|
|
|
|
$(event.target).closest('a').blur();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2015-07-15 17:09:00 +03:00
|
|
|
|
} else {
|
2015-08-12 18:30:20 +03:00
|
|
|
|
this._updateDetailsView($tr.attr('data-file'));
|
2015-07-15 17:09:00 +03:00
|
|
|
|
event.preventDefault();
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler for when clicking on a file's checkbox
|
|
|
|
|
*/
|
|
|
|
|
_onClickFileCheckbox: function(e) {
|
|
|
|
|
var $tr = $(e.target).closest('tr');
|
2015-09-25 13:23:28 +03:00
|
|
|
|
var state = !$tr.hasClass('selected');
|
|
|
|
|
this._selectFileEl($tr, state);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this._lastChecked = $tr;
|
|
|
|
|
this.updateSelectionSummary();
|
2016-02-15 16:58:44 +03:00
|
|
|
|
if (this._detailsView && !this._detailsView.$el.hasClass('disappear')) {
|
|
|
|
|
// hide sidebar
|
|
|
|
|
this._updateDetailsView(null);
|
2015-09-25 13:23:28 +03:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler for when selecting/deselecting all files
|
|
|
|
|
*/
|
|
|
|
|
_onClickSelectAll: function(e) {
|
|
|
|
|
var checked = $(e.target).prop('checked');
|
2017-09-29 02:36:10 +03:00
|
|
|
|
this.$fileList.find('td.selection>.selectCheckBox').prop('checked', checked)
|
2014-05-09 00:06:30 +04:00
|
|
|
|
.closest('tr').toggleClass('selected', checked);
|
|
|
|
|
this._selectedFiles = {};
|
|
|
|
|
this._selectionSummary.clear();
|
|
|
|
|
if (checked) {
|
|
|
|
|
for (var i = 0; i < this.files.length; i++) {
|
|
|
|
|
var fileData = this.files[i];
|
|
|
|
|
this._selectedFiles[fileData.id] = fileData;
|
|
|
|
|
this._selectionSummary.add(fileData);
|
|
|
|
|
}
|
2014-04-04 20:46:08 +04:00
|
|
|
|
}
|
2014-04-08 19:09:57 +04:00
|
|
|
|
this.updateSelectionSummary();
|
2016-02-15 16:58:44 +03:00
|
|
|
|
if (this._detailsView && !this._detailsView.$el.hasClass('disappear')) {
|
|
|
|
|
// hide sidebar
|
|
|
|
|
this._updateDetailsView(null);
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler for when clicking on "Download" for the selected files
|
|
|
|
|
*/
|
|
|
|
|
_onClickDownloadSelected: function(event) {
|
|
|
|
|
var files;
|
|
|
|
|
var dir = this.getCurrentDirectory();
|
2016-05-24 15:09:05 +03:00
|
|
|
|
if (this.isAllSelected() && this.getSelectedFiles().length > 1) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
files = OC.basename(dir);
|
|
|
|
|
dir = OC.dirname(dir) || '/';
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
files = _.pluck(this.getSelectedFiles(), 'name');
|
|
|
|
|
}
|
2015-06-29 09:52:37 +03:00
|
|
|
|
|
|
|
|
|
var downloadFileaction = $('#selectedActionsList').find('.download');
|
|
|
|
|
|
|
|
|
|
// don't allow a second click on the download action
|
|
|
|
|
if(downloadFileaction.hasClass('disabled')) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-14 18:18:31 +03:00
|
|
|
|
var disableLoadingState = function(){
|
2015-07-22 11:35:15 +03:00
|
|
|
|
OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false);
|
2015-07-14 18:18:31 +03:00
|
|
|
|
};
|
2015-06-29 09:52:37 +03:00
|
|
|
|
|
2015-07-22 11:35:15 +03:00
|
|
|
|
OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
|
2016-01-10 19:56:18 +03:00
|
|
|
|
if(this.getSelectedFiles().length > 1) {
|
|
|
|
|
OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir, true), disableLoadingState);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2017-08-27 16:20:18 +03:00
|
|
|
|
var first = this.getSelectedFiles()[0];
|
2016-01-10 19:56:18 +03:00
|
|
|
|
OCA.Files.Files.handleDownload(this.getDownloadUrl(first.name, dir, true), disableLoadingState);
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
2017-08-27 16:20:18 +03:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when clicking on "Move" for the selected files
|
|
|
|
|
*/
|
2017-08-27 18:39:22 +03:00
|
|
|
|
_onClickCopyMoveSelected: function(event) {
|
2017-08-27 16:20:18 +03:00
|
|
|
|
var files;
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
files = _.pluck(this.getSelectedFiles(), 'name');
|
|
|
|
|
|
|
|
|
|
var moveFileAction = $('#selectedActionsList').find('.move');
|
|
|
|
|
|
|
|
|
|
// don't allow a second click on the download action
|
|
|
|
|
if(moveFileAction.hasClass('disabled')) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var disableLoadingState = function(){
|
|
|
|
|
OCA.Files.FileActions.updateFileActionSpinner(moveFileAction, false);
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-27 18:39:22 +03:00
|
|
|
|
OC.dialogs.filepicker(t('files', 'Target folder'), function(targetPath, type) {
|
|
|
|
|
if (type === OC.dialogs.FILEPICKER_TYPE_COPY) {
|
|
|
|
|
self.copy(files, targetPath, disableLoadingState);
|
|
|
|
|
}
|
|
|
|
|
if (type === OC.dialogs.FILEPICKER_TYPE_MOVE) {
|
|
|
|
|
self.move(files, targetPath, disableLoadingState);
|
|
|
|
|
}
|
|
|
|
|
}, false, "httpd/unix-directory", true, OC.dialogs.FILEPICKER_TYPE_COPY_MOVE);
|
2017-08-27 16:20:18 +03:00
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when clicking on "Delete" for the selected files
|
|
|
|
|
*/
|
|
|
|
|
_onClickDeleteSelected: function(event) {
|
|
|
|
|
var files = null;
|
|
|
|
|
if (!this.isAllSelected()) {
|
|
|
|
|
files = _.pluck(this.getSelectedFiles(), 'name');
|
|
|
|
|
}
|
|
|
|
|
this.do_delete(files);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler when clicking on a table header
|
|
|
|
|
*/
|
|
|
|
|
_onClickHeader: function(e) {
|
2015-09-29 14:24:33 +03:00
|
|
|
|
if (this.$table.hasClass('multiselect')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var $target = $(e.target);
|
|
|
|
|
var sort;
|
|
|
|
|
if (!$target.is('a')) {
|
|
|
|
|
$target = $target.closest('a');
|
|
|
|
|
}
|
|
|
|
|
sort = $target.attr('data-sort');
|
2016-07-22 15:19:04 +03:00
|
|
|
|
if (sort && this._allowSorting) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (this._sort === sort) {
|
2016-04-12 12:51:50 +03:00
|
|
|
|
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc', true, true);
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
else {
|
2014-07-17 23:31:54 +04:00
|
|
|
|
if ( sort === 'name' ) { //default sorting of name is opposite to size and mtime
|
2016-04-12 12:51:50 +03:00
|
|
|
|
this.setSort(sort, 'asc', true, true);
|
2014-07-08 21:52:28 +04:00
|
|
|
|
}
|
|
|
|
|
else {
|
2016-04-12 12:51:50 +03:00
|
|
|
|
this.setSort(sort, 'desc', true, true);
|
2014-07-08 21:52:28 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler when clicking on a bread crumb
|
|
|
|
|
*/
|
|
|
|
|
_onClickBreadCrumb: function(e) {
|
2017-11-07 07:53:17 +03:00
|
|
|
|
// Select a crumb or a crumb in the menu
|
|
|
|
|
var $el = $(e.target).closest('.crumb, .crumblist'),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
$targetDir = $el.data('dir');
|
|
|
|
|
|
2015-03-25 00:05:02 +03:00
|
|
|
|
if ($targetDir !== undefined && e.which === 1) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
e.preventDefault();
|
2017-09-17 09:29:22 +03:00
|
|
|
|
this.changeDirectory($targetDir, true, true);
|
2015-03-25 00:05:02 +03:00
|
|
|
|
this.updateSearch();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
},
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when scrolling the list container.
|
|
|
|
|
* This appends/renders the next page of entries when reaching the bottom.
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
_onScroll: function(e) {
|
2014-08-08 02:27:31 +04:00
|
|
|
|
if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 300) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this._nextPage(true);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event handler when dropping on a breadcrumb
|
|
|
|
|
*/
|
|
|
|
|
_onDropOnBreadCrumb: function( event, ui ) {
|
2015-08-17 17:03:05 +03:00
|
|
|
|
var self = this;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var $target = $(event.target);
|
2017-11-08 21:03:32 +03:00
|
|
|
|
if (!$target.is('.crumb, .crumblist')) {
|
|
|
|
|
$target = $target.closest('.crumb, .crumblist');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
var targetPath = $(event.target).data('dir');
|
|
|
|
|
var dir = this.getCurrentDirectory();
|
|
|
|
|
while (dir.substr(0,1) === '/') {//remove extra leading /'s
|
|
|
|
|
dir = dir.substr(1);
|
|
|
|
|
}
|
|
|
|
|
dir = '/' + dir;
|
|
|
|
|
if (dir.substr(-1,1) !== '/') {
|
|
|
|
|
dir = dir + '/';
|
|
|
|
|
}
|
|
|
|
|
// do nothing if dragged on current dir
|
|
|
|
|
if (targetPath === dir || targetPath + '/' === dir) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var files = this.getSelectedFiles();
|
|
|
|
|
if (files.length === 0) {
|
|
|
|
|
// single one selected without checkbox?
|
2015-08-17 17:03:05 +03:00
|
|
|
|
files = _.map(ui.helper.find('tr'), function(el) {
|
|
|
|
|
return self.elementToFile($(el));
|
|
|
|
|
});
|
2014-04-04 20:46:08 +04:00
|
|
|
|
}
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.move(_.pluck(files, 'name'), targetPath);
|
2015-11-04 13:44:47 +03:00
|
|
|
|
|
|
|
|
|
// re-enable td elements to be droppable
|
|
|
|
|
// sometimes the filename drop handler is still called after re-enable,
|
|
|
|
|
// it seems that waiting for a short time before re-enabling solves the problem
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
self.$el.find('td.filename.ui-droppable').droppable('enable');
|
|
|
|
|
}, 10);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Sets a new page title
|
|
|
|
|
*/
|
|
|
|
|
setPageTitle: function(title){
|
|
|
|
|
if (title) {
|
|
|
|
|
title += ' - ';
|
|
|
|
|
} else {
|
|
|
|
|
title = '';
|
|
|
|
|
}
|
|
|
|
|
title += this.appName;
|
2017-04-12 07:16:27 +03:00
|
|
|
|
// Sets the page title with the " - Nextcloud" suffix as in templates
|
2014-05-09 00:06:30 +04:00
|
|
|
|
window.document.title = title + ' - ' + oc_defaults.title;
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
/**
|
2015-09-28 18:50:11 +03:00
|
|
|
|
* Returns the file info for the given file name from the internal collection.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} fileName file name
|
|
|
|
|
* @return {OCA.Files.FileInfo} file info or null if it was not found
|
|
|
|
|
*
|
|
|
|
|
* @since 8.2
|
|
|
|
|
*/
|
|
|
|
|
findFile: function(fileName) {
|
|
|
|
|
return _.find(this.files, function(aFile) {
|
|
|
|
|
return (aFile.name === fileName);
|
|
|
|
|
}) || null;
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Returns the tr element for a given file name, but only if it was already rendered.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} fileName file name
|
|
|
|
|
* @return {Object} jQuery object of the matching row
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
findFileEl: function(fileName){
|
|
|
|
|
// use filterAttr to avoid escaping issues
|
|
|
|
|
return this.$fileList.find('tr').filterAttr('data-file', fileName);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the file data from a given file element.
|
|
|
|
|
* @param $el file tr element
|
|
|
|
|
* @return file data
|
|
|
|
|
*/
|
|
|
|
|
elementToFile: function($el){
|
|
|
|
|
$el = $($el);
|
2015-10-28 19:43:36 +03:00
|
|
|
|
var data = {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
id: parseInt($el.attr('data-id'), 10),
|
|
|
|
|
name: $el.attr('data-file'),
|
|
|
|
|
mimetype: $el.attr('data-mime'),
|
2015-08-24 18:16:20 +03:00
|
|
|
|
mtime: parseInt($el.attr('data-mtime'), 10),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
type: $el.attr('data-type'),
|
2014-11-20 18:53:32 +03:00
|
|
|
|
etag: $el.attr('data-etag'),
|
2016-07-26 14:51:18 +03:00
|
|
|
|
permissions: parseInt($el.attr('data-permissions'), 10),
|
2017-11-20 22:51:32 +03:00
|
|
|
|
hasPreview: $el.attr('data-has-preview') === 'true',
|
|
|
|
|
isEncrypted: $el.attr('data-e2eencrypted') === 'true'
|
2014-05-09 00:06:30 +04:00
|
|
|
|
};
|
2016-11-21 17:03:45 +03:00
|
|
|
|
var size = $el.attr('data-size');
|
|
|
|
|
if (size) {
|
|
|
|
|
data.size = parseInt(size, 10);
|
|
|
|
|
}
|
2015-10-28 19:43:36 +03:00
|
|
|
|
var icon = $el.attr('data-icon');
|
|
|
|
|
if (icon) {
|
|
|
|
|
data.icon = icon;
|
|
|
|
|
}
|
|
|
|
|
var mountType = $el.attr('data-mounttype');
|
|
|
|
|
if (mountType) {
|
|
|
|
|
data.mountType = mountType;
|
|
|
|
|
}
|
2016-02-08 19:02:05 +03:00
|
|
|
|
var path = $el.attr('data-path');
|
|
|
|
|
if (path) {
|
|
|
|
|
data.path = path;
|
|
|
|
|
}
|
2015-10-28 19:43:36 +03:00
|
|
|
|
return data;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Appends the next page of files into the table
|
|
|
|
|
* @param animate true to animate the new elements
|
2014-07-01 23:32:04 +04:00
|
|
|
|
* @return array of DOM elements of the newly added files
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_nextPage: function(animate) {
|
|
|
|
|
var index = this.$fileList.children().length,
|
2014-10-11 17:10:54 +04:00
|
|
|
|
count = this.pageSize(),
|
2014-12-18 12:26:41 +03:00
|
|
|
|
hidden,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
tr,
|
|
|
|
|
fileData,
|
|
|
|
|
newTrs = [],
|
2016-11-07 15:04:39 +03:00
|
|
|
|
isAllSelected = this.isAllSelected(),
|
|
|
|
|
showHidden = this._filesConfig.get('showhidden');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
if (index >= this.files.length) {
|
2014-07-01 23:32:04 +04:00
|
|
|
|
return false;
|
2014-04-03 22:57:06 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
while (count > 0 && index < this.files.length) {
|
|
|
|
|
fileData = this.files[index];
|
2014-12-18 12:26:41 +03:00
|
|
|
|
if (this._filter) {
|
|
|
|
|
hidden = fileData.name.toLowerCase().indexOf(this._filter.toLowerCase()) === -1;
|
|
|
|
|
} else {
|
|
|
|
|
hidden = false;
|
|
|
|
|
}
|
|
|
|
|
tr = this._renderRow(fileData, {updateSummary: false, silent: true, hidden: hidden});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$fileList.append(tr);
|
|
|
|
|
if (isAllSelected || this._selectedFiles[fileData.id]) {
|
|
|
|
|
tr.addClass('selected');
|
2014-12-11 20:20:04 +03:00
|
|
|
|
tr.find('.selectCheckBox').prop('checked', true);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
if (animate) {
|
|
|
|
|
tr.addClass('appear transparent');
|
|
|
|
|
}
|
2014-07-01 23:32:04 +04:00
|
|
|
|
newTrs.push(tr);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
index++;
|
2016-11-07 15:04:39 +03:00
|
|
|
|
// only count visible rows
|
|
|
|
|
if (showHidden || !tr.hasClass('hidden-file')) {
|
|
|
|
|
count--;
|
|
|
|
|
}
|
2014-04-03 22:57:06 +04:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-01 23:32:04 +04:00
|
|
|
|
// trigger event for newly added rows
|
|
|
|
|
if (newTrs.length > 0) {
|
|
|
|
|
this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: newTrs}));
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (animate) {
|
|
|
|
|
// defer, for animation
|
|
|
|
|
window.setTimeout(function() {
|
|
|
|
|
for (var i = 0; i < newTrs.length; i++ ) {
|
|
|
|
|
newTrs[i].removeClass('transparent');
|
|
|
|
|
}
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
2016-04-12 18:10:09 +03:00
|
|
|
|
|
2014-07-01 23:32:04 +04:00
|
|
|
|
return newTrs;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-06-27 15:36:18 +04:00
|
|
|
|
/**
|
|
|
|
|
* Event handler for when file actions were updated.
|
|
|
|
|
* This will refresh the file actions on the list.
|
|
|
|
|
*/
|
|
|
|
|
_onFileActionsUpdated: function() {
|
|
|
|
|
var self = this;
|
2014-07-01 23:32:04 +04:00
|
|
|
|
var $files = this.$fileList.find('tr');
|
|
|
|
|
if (!$files.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$files.each(function() {
|
|
|
|
|
self.fileActions.display($(this).find('td.filename'), false, self);
|
2014-06-27 15:36:18 +04:00
|
|
|
|
});
|
2014-07-01 23:32:04 +04:00
|
|
|
|
this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: $files}));
|
|
|
|
|
|
2014-06-27 15:36:18 +04:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Sets the files to be displayed in the list.
|
|
|
|
|
* This operation will re-render the list and update the summary.
|
|
|
|
|
* @param filesArray array of file data (map)
|
|
|
|
|
*/
|
|
|
|
|
setFiles: function(filesArray) {
|
2015-03-27 03:34:55 +03:00
|
|
|
|
var self = this;
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// detach to make adding multiple rows faster
|
|
|
|
|
this.files = filesArray;
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$fileList.empty();
|
2014-02-11 18:57:45 +04:00
|
|
|
|
|
2017-09-29 02:36:10 +03:00
|
|
|
|
if (this._allowSelection) {
|
|
|
|
|
// The results table, which has no selection column, checks
|
|
|
|
|
// whether the main table has a selection column or not in order
|
|
|
|
|
// to align its contents with those of the main table.
|
|
|
|
|
this.$el.addClass('has-selection');
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// clear "Select all" checkbox
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$el.find('.select-all').prop('checked', false);
|
2014-04-11 14:46:12 +04:00
|
|
|
|
|
2016-04-12 18:10:09 +03:00
|
|
|
|
// Save full files list while rendering
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.isEmpty = this.files.length === 0;
|
|
|
|
|
this._nextPage();
|
2014-04-11 14:46:12 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.updateEmptyContent();
|
2014-02-20 18:36:52 +04:00
|
|
|
|
|
2016-04-12 18:10:09 +03:00
|
|
|
|
this.fileSummary.calculate(this.files);
|
2014-02-11 18:57:45 +04:00
|
|
|
|
|
2014-06-23 14:55:42 +04:00
|
|
|
|
this._selectedFiles = {};
|
|
|
|
|
this._selectionSummary.clear();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.updateSelectionSummary();
|
|
|
|
|
$(window).scrollTop(0);
|
|
|
|
|
|
2015-03-27 03:34:55 +03:00
|
|
|
|
this.$fileList.trigger(jQuery.Event('updated'));
|
|
|
|
|
_.defer(function() {
|
|
|
|
|
self.$el.closest('#app-content').trigger(jQuery.Event('apprendered'));
|
|
|
|
|
});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2015-07-13 18:38:13 +03:00
|
|
|
|
|
2016-04-12 18:10:09 +03:00
|
|
|
|
/**
|
2016-08-17 18:34:15 +03:00
|
|
|
|
* Returns whether the given file info must be hidden
|
2016-04-12 18:10:09 +03:00
|
|
|
|
*
|
2016-08-17 18:34:15 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo file info
|
2016-11-21 17:03:45 +03:00
|
|
|
|
*
|
2016-08-17 18:34:15 +03:00
|
|
|
|
* @return {boolean} true if the file is a hidden file, false otherwise
|
2016-04-12 18:10:09 +03:00
|
|
|
|
*/
|
2016-08-17 18:34:15 +03:00
|
|
|
|
_isHiddenFile: function(file) {
|
|
|
|
|
return file.name && file.name.charAt(0) === '.';
|
2016-04-12 18:10:09 +03:00
|
|
|
|
},
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Returns the icon URL matching the given file info
|
|
|
|
|
*
|
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo file info
|
|
|
|
|
*
|
|
|
|
|
* @return {string} icon URL
|
|
|
|
|
*/
|
|
|
|
|
_getIconUrl: function(fileInfo) {
|
|
|
|
|
var mimeType = fileInfo.mimetype || 'application/octet-stream';
|
|
|
|
|
if (mimeType === 'httpd/unix-directory') {
|
|
|
|
|
// use default folder icon
|
|
|
|
|
if (fileInfo.mountType === 'shared' || fileInfo.mountType === 'shared-root') {
|
|
|
|
|
return OC.MimeType.getIconUrl('dir-shared');
|
|
|
|
|
} else if (fileInfo.mountType === 'external-root') {
|
|
|
|
|
return OC.MimeType.getIconUrl('dir-external');
|
2017-04-28 11:03:31 +03:00
|
|
|
|
} else if (fileInfo.mountType !== undefined && fileInfo.mountType !== '') {
|
2017-04-26 15:53:11 +03:00
|
|
|
|
return OC.MimeType.getIconUrl('dir-' + fileInfo.mountType);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
}
|
|
|
|
|
return OC.MimeType.getIconUrl('dir');
|
|
|
|
|
}
|
|
|
|
|
return OC.MimeType.getIconUrl(mimeType);
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Creates a new table row element using the given file data.
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileData file info attributes
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @param options map of attributes
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return new tr element (not appended to the table)
|
|
|
|
|
*/
|
|
|
|
|
_createRow: function(fileData, options) {
|
|
|
|
|
var td, simpleSize, basename, extension, sizeColor,
|
2015-07-13 18:38:13 +03:00
|
|
|
|
icon = fileData.icon || this._getIconUrl(fileData),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
name = fileData.name,
|
2015-07-13 18:38:13 +03:00
|
|
|
|
// TODO: get rid of type, only use mime type
|
2014-05-09 00:06:30 +04:00
|
|
|
|
type = fileData.type || 'file',
|
2014-12-05 16:54:43 +03:00
|
|
|
|
mtime = parseInt(fileData.mtime, 10),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
mime = fileData.mimetype,
|
2014-05-21 14:54:34 +04:00
|
|
|
|
path = fileData.path,
|
2015-10-28 19:43:36 +03:00
|
|
|
|
dataIcon = null,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
linkUrl;
|
|
|
|
|
options = options || {};
|
|
|
|
|
|
2014-12-05 16:54:43 +03:00
|
|
|
|
if (isNaN(mtime)) {
|
2015-10-28 19:43:36 +03:00
|
|
|
|
mtime = new Date().getTime();
|
2014-12-05 16:54:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (type === 'dir') {
|
|
|
|
|
mime = mime || 'httpd/unix-directory';
|
2015-07-17 15:30:12 +03:00
|
|
|
|
|
2017-09-19 18:24:53 +03:00
|
|
|
|
if (fileData.isEncrypted) {
|
|
|
|
|
icon = OC.MimeType.getIconUrl('dir-encrypted');
|
|
|
|
|
dataIcon = icon;
|
|
|
|
|
} else if (fileData.mountType && fileData.mountType.indexOf('external') === 0) {
|
2015-07-17 15:30:12 +03:00
|
|
|
|
icon = OC.MimeType.getIconUrl('dir-external');
|
2015-10-28 19:43:36 +03:00
|
|
|
|
dataIcon = icon;
|
2015-07-17 15:30:12 +03:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2017-11-02 16:55:58 +03:00
|
|
|
|
var permissions = fileData.permissions;
|
|
|
|
|
if (permissions === undefined || permissions === null) {
|
|
|
|
|
permissions = this.getDirectoryPermissions();
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
//containing tr
|
|
|
|
|
var tr = $('<tr></tr>').attr({
|
|
|
|
|
"data-id" : fileData.id,
|
|
|
|
|
"data-type": type,
|
|
|
|
|
"data-size": fileData.size,
|
|
|
|
|
"data-file": name,
|
|
|
|
|
"data-mime": mime,
|
|
|
|
|
"data-mtime": mtime,
|
|
|
|
|
"data-etag": fileData.etag,
|
2017-11-02 16:55:58 +03:00
|
|
|
|
"data-permissions": permissions,
|
2017-11-20 22:51:32 +03:00
|
|
|
|
"data-has-preview": fileData.hasPreview !== false,
|
|
|
|
|
"data-e2eencrypted": fileData.isEncrypted === true
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
2014-02-11 18:57:45 +04:00
|
|
|
|
|
2015-10-28 19:43:36 +03:00
|
|
|
|
if (dataIcon) {
|
|
|
|
|
// icon override
|
|
|
|
|
tr.attr('data-icon', dataIcon);
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-10 19:25:46 +04:00
|
|
|
|
if (fileData.mountType) {
|
2015-12-14 12:44:47 +03:00
|
|
|
|
// dirInfo (parent) only exist for the "real" file list
|
|
|
|
|
if (this.dirInfo.id) {
|
|
|
|
|
// FIXME: HACK: detect shared-root
|
|
|
|
|
if (fileData.mountType === 'shared' && this.dirInfo.mountType !== 'shared' && this.dirInfo.mountType !== 'shared-root') {
|
|
|
|
|
// if parent folder isn't share, assume the displayed folder is a share root
|
|
|
|
|
fileData.mountType = 'shared-root';
|
|
|
|
|
} else if (fileData.mountType === 'external' && this.dirInfo.mountType !== 'external' && this.dirInfo.mountType !== 'external-root') {
|
|
|
|
|
// if parent folder isn't external, assume the displayed folder is the external storage root
|
|
|
|
|
fileData.mountType = 'external-root';
|
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
}
|
2014-07-10 19:25:46 +04:00
|
|
|
|
tr.attr('data-mounttype', fileData.mountType);
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-21 14:54:34 +04:00
|
|
|
|
if (!_.isUndefined(path)) {
|
|
|
|
|
tr.attr('data-path', path);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
path = this.getCurrentDirectory();
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-29 02:36:10 +03:00
|
|
|
|
// selection td
|
|
|
|
|
if (this._allowSelection) {
|
|
|
|
|
td = $('<td class="selection"></td>');
|
|
|
|
|
|
|
|
|
|
td.append(
|
|
|
|
|
'<input id="select-' + this.id + '-' + fileData.id +
|
|
|
|
|
'" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
|
|
|
|
|
'<span class="hidden-visually">' + t('files', 'Select') + '</span>' +
|
|
|
|
|
'</label>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
tr.append(td);
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// filename td
|
2014-12-11 19:36:14 +03:00
|
|
|
|
td = $('<td class="filename"></td>');
|
|
|
|
|
|
2014-02-11 18:57:45 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// linkUrl
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (mime === 'httpd/unix-directory') {
|
2014-04-30 19:42:35 +04:00
|
|
|
|
linkUrl = this.linkTo(path + '/' + name);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
else {
|
2015-07-13 18:38:13 +03:00
|
|
|
|
linkUrl = this.getDownloadUrl(name, path, type === 'dir');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
var linkElem = $('<a></a>').attr({
|
|
|
|
|
"class": "name",
|
|
|
|
|
"href": linkUrl
|
|
|
|
|
});
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2017-09-28 15:42:37 +03:00
|
|
|
|
linkElem.append('<div class="thumbnail-wrapper"><div class="thumbnail" style="background-image:url(' + icon + ');"></div></div>');
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// from here work on the display name
|
|
|
|
|
name = fileData.displayName || name;
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2015-03-26 12:29:01 +03:00
|
|
|
|
// show hidden files (starting with a dot) completely in gray
|
|
|
|
|
if(name.indexOf('.') === 0) {
|
|
|
|
|
basename = '';
|
|
|
|
|
extension = name;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// split extension from filename for non dirs
|
2015-07-13 18:38:13 +03:00
|
|
|
|
} else if (mime !== 'httpd/unix-directory' && name.indexOf('.') !== -1) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
basename = name.substr(0, name.lastIndexOf('.'));
|
|
|
|
|
extension = name.substr(name.lastIndexOf('.'));
|
|
|
|
|
} else {
|
|
|
|
|
basename = name;
|
|
|
|
|
extension = false;
|
|
|
|
|
}
|
2014-06-06 16:00:18 +04:00
|
|
|
|
var nameSpan=$('<span></span>').addClass('nametext');
|
2014-06-07 22:24:21 +04:00
|
|
|
|
var innernameSpan = $('<span></span>').addClass('innernametext').text(basename);
|
2016-10-19 12:33:25 +03:00
|
|
|
|
|
|
|
|
|
|
2017-10-16 17:15:15 +03:00
|
|
|
|
var conflictingItems = this.$fileList.find('tr[data-file="' + this._jqSelEscape(name) + '"]');
|
|
|
|
|
if (conflictingItems.length !== 0) {
|
|
|
|
|
if (conflictingItems.length === 1) {
|
|
|
|
|
// Update the path on the first conflicting item
|
|
|
|
|
var $firstConflict = $(conflictingItems[0]),
|
|
|
|
|
firstConflictPath = $firstConflict.attr('data-path') + '/';
|
|
|
|
|
if (firstConflictPath.charAt(0) === '/') {
|
|
|
|
|
firstConflictPath = firstConflictPath.substr(1);
|
2016-10-19 12:33:25 +03:00
|
|
|
|
}
|
2017-10-18 16:16:51 +03:00
|
|
|
|
if (firstConflictPath && firstConflictPath !== '/') {
|
|
|
|
|
$firstConflict.find('td.filename span.innernametext').prepend($('<span></span>').addClass('conflict-path').text(firstConflictPath));
|
|
|
|
|
}
|
2017-10-16 17:15:15 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var conflictPath = path + '/';
|
|
|
|
|
if (conflictPath.charAt(0) === '/') {
|
|
|
|
|
conflictPath = conflictPath.substr(1);
|
|
|
|
|
}
|
|
|
|
|
if (path && path !== '/') {
|
2016-10-19 12:33:25 +03:00
|
|
|
|
nameSpan.append($('<span></span>').addClass('conflict-path').text(conflictPath));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-06 16:00:18 +04:00
|
|
|
|
nameSpan.append(innernameSpan);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
linkElem.append(nameSpan);
|
|
|
|
|
if (extension) {
|
|
|
|
|
nameSpan.append($('<span></span>').addClass('extension').text(extension));
|
|
|
|
|
}
|
2014-08-27 13:28:31 +04:00
|
|
|
|
if (fileData.extraData) {
|
|
|
|
|
if (fileData.extraData.charAt(0) === '/') {
|
|
|
|
|
fileData.extraData = fileData.extraData.substr(1);
|
|
|
|
|
}
|
|
|
|
|
nameSpan.addClass('extra-data').attr('title', fileData.extraData);
|
2017-11-14 13:55:44 +03:00
|
|
|
|
nameSpan.tooltip({placement: 'top'});
|
2014-08-27 13:28:31 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// dirs can show the number of uploaded files
|
2015-12-17 13:50:24 +03:00
|
|
|
|
if (mime === 'httpd/unix-directory') {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
linkElem.append($('<span></span>').attr({
|
|
|
|
|
'class': 'uploadtext',
|
|
|
|
|
'currentUploads': 0
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
td.append(linkElem);
|
|
|
|
|
tr.append(td);
|
2014-02-12 17:50:23 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// size column
|
|
|
|
|
if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) {
|
2014-06-02 12:38:46 +04:00
|
|
|
|
simpleSize = humanFileSize(parseInt(fileData.size, 10), true);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2));
|
|
|
|
|
} else {
|
|
|
|
|
simpleSize = t('files', 'Pending');
|
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
td = $('<td></td>').attr({
|
|
|
|
|
"class": "filesize",
|
|
|
|
|
"style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
|
|
|
|
|
}).text(simpleSize);
|
|
|
|
|
tr.append(td);
|
|
|
|
|
|
2014-07-02 19:20:56 +04:00
|
|
|
|
// date column (1000 milliseconds to seconds, 60 seconds, 60 minutes, 24 hours)
|
|
|
|
|
// difference in days multiplied by 5 - brightest shade for files older than 32 days (160/5)
|
|
|
|
|
var modifiedColor = Math.round(((new Date()).getTime() - mtime )/1000/60/60/24*5 );
|
2014-07-02 18:37:15 +04:00
|
|
|
|
// ensure that the brightest color is still readable
|
|
|
|
|
if (modifiedColor >= '160') {
|
|
|
|
|
modifiedColor = 160;
|
|
|
|
|
}
|
2014-12-05 16:54:43 +03:00
|
|
|
|
var formatted;
|
|
|
|
|
var text;
|
|
|
|
|
if (mtime > 0) {
|
2015-07-15 17:09:00 +03:00
|
|
|
|
formatted = OC.Util.formatDate(mtime);
|
2014-12-05 16:54:43 +03:00
|
|
|
|
text = OC.Util.relativeModifiedDate(mtime);
|
|
|
|
|
} else {
|
|
|
|
|
formatted = t('files', 'Unable to determine date');
|
|
|
|
|
text = '?';
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
td = $('<td></td>').attr({ "class": "date" });
|
|
|
|
|
td.append($('<span></span>').attr({
|
2016-08-31 18:33:00 +03:00
|
|
|
|
"class": "modified live-relative-timestamp",
|
2014-12-05 16:54:43 +03:00
|
|
|
|
"title": formatted,
|
2016-08-31 18:33:00 +03:00
|
|
|
|
"data-timestamp": mtime,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
"style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')'
|
2015-07-27 16:56:03 +03:00
|
|
|
|
}).text(text)
|
|
|
|
|
.tooltip({placement: 'top'})
|
|
|
|
|
);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
tr.find('.filesize').text(simpleSize);
|
|
|
|
|
tr.append(td);
|
|
|
|
|
return tr;
|
|
|
|
|
},
|
|
|
|
|
|
2016-12-20 11:32:47 +03:00
|
|
|
|
/* escape a selector expression for jQuery */
|
|
|
|
|
_jqSelEscape: function (expression) {
|
|
|
|
|
if (expression) {
|
|
|
|
|
return expression.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Adds an entry to the files array and also into the DOM
|
|
|
|
|
* in a sorted manner.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileData map of file attributes
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @param {Object} [options] map of attributes
|
|
|
|
|
* @param {boolean} [options.updateSummary] true to update the summary
|
|
|
|
|
* after adding (default), false otherwise. Defaults to true.
|
|
|
|
|
* @param {boolean} [options.silent] true to prevent firing events like "fileActionsReady",
|
|
|
|
|
* defaults to false.
|
|
|
|
|
* @param {boolean} [options.animate] true to animate the thumbnail image after load
|
|
|
|
|
* defaults to true.
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return new tr element (not appended to the table)
|
|
|
|
|
*/
|
|
|
|
|
add: function(fileData, options) {
|
2017-11-23 19:51:56 +03:00
|
|
|
|
var index;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var $tr;
|
|
|
|
|
var $rows;
|
|
|
|
|
var $insertionPoint;
|
2014-07-17 14:42:09 +04:00
|
|
|
|
options = _.extend({animate: true}, options || {});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
// there are three situations to cover:
|
|
|
|
|
// 1) insertion point is visible on the current page
|
|
|
|
|
// 2) insertion point is on a not visible page (visible after scrolling)
|
|
|
|
|
// 3) insertion point is at the end of the list
|
|
|
|
|
|
|
|
|
|
$rows = this.$fileList.children();
|
|
|
|
|
index = this._findInsertionIndex(fileData);
|
|
|
|
|
if (index > this.files.length) {
|
|
|
|
|
index = this.files.length;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$insertionPoint = $rows.eq(index);
|
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// is the insertion point visible ?
|
|
|
|
|
if ($insertionPoint.length) {
|
|
|
|
|
// only render if it will really be inserted
|
|
|
|
|
$tr = this._renderRow(fileData, options);
|
|
|
|
|
$insertionPoint.before($tr);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// if insertion point is after the last visible
|
|
|
|
|
// entry, append
|
|
|
|
|
if (index === $rows.length) {
|
|
|
|
|
$tr = this._renderRow(fileData, options);
|
|
|
|
|
this.$fileList.append($tr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-11 19:52:56 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.isEmpty = false;
|
|
|
|
|
this.files.splice(index, 0, fileData);
|
2014-02-11 19:52:56 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if ($tr && options.animate) {
|
|
|
|
|
$tr.addClass('appear transparent');
|
|
|
|
|
window.setTimeout(function() {
|
|
|
|
|
$tr.removeClass('transparent');
|
|
|
|
|
});
|
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-09-04 14:33:38 +04:00
|
|
|
|
if (options.scrollTo) {
|
|
|
|
|
this.scrollTo(fileData.name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// defaults to true if not defined
|
|
|
|
|
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
|
|
|
|
|
this.fileSummary.add(fileData, true);
|
|
|
|
|
this.updateEmptyContent();
|
|
|
|
|
}
|
2014-04-17 17:54:45 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return $tr;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new row element based on the given attributes
|
|
|
|
|
* and returns it.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileData map of file attributes
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @param {Object} [options] map of attributes
|
|
|
|
|
* @param {int} [options.index] index at which to insert the element
|
|
|
|
|
* @param {boolean} [options.updateSummary] true to update the summary
|
|
|
|
|
* after adding (default), false otherwise. Defaults to true.
|
|
|
|
|
* @param {boolean} [options.animate] true to animate the thumbnail image after load
|
|
|
|
|
* defaults to true.
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return new tr element (not appended to the table)
|
|
|
|
|
*/
|
|
|
|
|
_renderRow: function(fileData, options) {
|
|
|
|
|
options = options || {};
|
|
|
|
|
var type = fileData.type || 'file',
|
|
|
|
|
mime = fileData.mimetype,
|
2014-04-30 19:42:35 +04:00
|
|
|
|
path = fileData.path || this.getCurrentDirectory(),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
permissions = parseInt(fileData.permissions, 10) || 0;
|
|
|
|
|
|
2017-09-19 14:58:26 +03:00
|
|
|
|
var isEndToEndEncrypted = (type === 'dir' && fileData.isEncrypted);
|
|
|
|
|
|
|
|
|
|
if (!isEndToEndEncrypted && fileData.isShareMountPoint) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
permissions = permissions | OC.PERMISSION_UPDATE;
|
|
|
|
|
}
|
2014-04-17 17:54:45 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (type === 'dir') {
|
|
|
|
|
mime = mime || 'httpd/unix-directory';
|
|
|
|
|
}
|
|
|
|
|
var tr = this._createRow(
|
|
|
|
|
fileData,
|
|
|
|
|
options
|
|
|
|
|
);
|
|
|
|
|
var filenameTd = tr.find('td.filename');
|
|
|
|
|
|
|
|
|
|
// TODO: move dragging to FileActions ?
|
|
|
|
|
// enable drag only for deletable files
|
2014-05-12 21:54:20 +04:00
|
|
|
|
if (this._dragOptions && permissions & OC.PERMISSION_DELETE) {
|
|
|
|
|
filenameTd.draggable(this._dragOptions);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
// allow dropping on folders
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (this._folderDropOptions && mime === 'httpd/unix-directory') {
|
2016-03-12 06:29:59 +03:00
|
|
|
|
tr.droppable(this._folderDropOptions);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (options.hidden) {
|
|
|
|
|
tr.addClass('hidden');
|
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2016-08-17 18:34:15 +03:00
|
|
|
|
if (this._isHiddenFile(fileData)) {
|
|
|
|
|
tr.addClass('hidden-file');
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// display actions
|
2014-05-19 17:20:44 +04:00
|
|
|
|
this.fileActions.display(filenameTd, !options.silent, this);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2016-07-26 14:40:44 +03:00
|
|
|
|
if (mime !== 'httpd/unix-directory' && fileData.hasPreview !== false) {
|
2014-12-11 19:36:14 +03:00
|
|
|
|
var iconDiv = filenameTd.find('.thumbnail');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// lazy load / newly inserted td ?
|
2015-05-06 19:32:52 +03:00
|
|
|
|
// the typeof check ensures that the default value of animate is true
|
|
|
|
|
if (typeof(options.animate) === 'undefined' || !!options.animate) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.lazyLoadPreview({
|
2014-04-30 19:42:35 +04:00
|
|
|
|
path: path + '/' + fileData.name,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
mime: mime,
|
|
|
|
|
etag: fileData.etag,
|
|
|
|
|
callback: function(url) {
|
2015-02-22 18:51:16 +03:00
|
|
|
|
iconDiv.css('background-image', 'url("' + url + '")');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// set the preview URL directly
|
|
|
|
|
var urlSpec = {
|
2014-04-30 19:42:35 +04:00
|
|
|
|
file: path + '/' + fileData.name,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
c: fileData.etag
|
|
|
|
|
};
|
|
|
|
|
var previewUrl = this.generatePreviewUrl(urlSpec);
|
|
|
|
|
previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
|
2015-02-22 18:51:16 +03:00
|
|
|
|
iconDiv.css('background-image', 'url("' + previewUrl + '")');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return tr;
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Returns the current directory
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @method getCurrentDirectory
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return current directory
|
|
|
|
|
*/
|
|
|
|
|
getCurrentDirectory: function(){
|
|
|
|
|
return this._currentDirectory || this.$el.find('#dir').val() || '/';
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Returns the directory permissions
|
|
|
|
|
* @return permission value as integer
|
|
|
|
|
*/
|
|
|
|
|
getDirectoryPermissions: function() {
|
|
|
|
|
return parseInt(this.$el.find('#permissions').val(), 10);
|
|
|
|
|
},
|
|
|
|
|
/**
|
2016-05-04 12:17:53 +03:00
|
|
|
|
* Changes the current directory and reload the file list.
|
|
|
|
|
* @param {string} targetDir target directory (non URL encoded)
|
|
|
|
|
* @param {boolean} [changeUrl=true] if the URL must not be changed (defaults to true)
|
|
|
|
|
* @param {boolean} [force=false] set to true to force changing directory
|
|
|
|
|
* @param {string} [fileId] optional file id, if known, to be appended in the URL
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2016-05-04 12:17:53 +03:00
|
|
|
|
changeDirectory: function(targetDir, changeUrl, force, fileId) {
|
2014-06-30 18:27:31 +04:00
|
|
|
|
var self = this;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var currentDir = this.getCurrentDirectory();
|
|
|
|
|
targetDir = targetDir || '/';
|
|
|
|
|
if (!force && currentDir === targetDir) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-05-04 12:17:53 +03:00
|
|
|
|
this._setCurrentDir(targetDir, changeUrl, fileId);
|
2015-12-16 19:35:53 +03:00
|
|
|
|
|
|
|
|
|
// discard finished uploads list, we'll get it through a regular reload
|
|
|
|
|
this._uploads = {};
|
|
|
|
|
this.reload().then(function(success){
|
2014-06-30 18:27:31 +04:00
|
|
|
|
if (!success) {
|
|
|
|
|
self.changeDirectory(currentDir, true);
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
linkTo: function(dir) {
|
|
|
|
|
return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
|
|
|
|
|
},
|
|
|
|
|
|
2016-09-01 11:06:06 +03:00
|
|
|
|
/**
|
|
|
|
|
* @param {string} path
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2016-07-06 12:55:02 +03:00
|
|
|
|
_isValidPath: function(path) {
|
|
|
|
|
var sections = path.split('/');
|
|
|
|
|
for (var i = 0; i < sections.length; i++) {
|
|
|
|
|
if (sections[i] === '..') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-01 11:06:06 +03:00
|
|
|
|
|
2016-09-01 13:24:14 +03:00
|
|
|
|
return path.toLowerCase().indexOf(decodeURI('%0a')) === -1 &&
|
|
|
|
|
path.toLowerCase().indexOf(decodeURI('%00')) === -1;
|
2016-07-06 12:55:02 +03:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Sets the current directory name and updates the breadcrumb.
|
|
|
|
|
* @param targetDir directory to display
|
|
|
|
|
* @param changeUrl true to also update the URL, false otherwise (default)
|
2016-05-04 12:17:53 +03:00
|
|
|
|
* @param {string} [fileId] file id
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2016-05-04 12:17:53 +03:00
|
|
|
|
_setCurrentDir: function(targetDir, changeUrl, fileId) {
|
2015-02-19 19:12:29 +03:00
|
|
|
|
targetDir = targetDir.replace(/\\/g, '/');
|
2016-07-06 12:55:02 +03:00
|
|
|
|
if (!this._isValidPath(targetDir)) {
|
|
|
|
|
targetDir = '/';
|
|
|
|
|
changeUrl = true;
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
var previousDir = this.getCurrentDirectory(),
|
2014-05-09 00:06:30 +04:00
|
|
|
|
baseDir = OC.basename(targetDir);
|
|
|
|
|
|
|
|
|
|
if (baseDir !== '') {
|
|
|
|
|
this.setPageTitle(baseDir);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.setPageTitle();
|
|
|
|
|
}
|
2014-04-08 01:45:35 +04:00
|
|
|
|
|
2016-07-01 12:10:37 +03:00
|
|
|
|
if (targetDir.length > 0 && targetDir[0] !== '/') {
|
|
|
|
|
targetDir = '/' + targetDir;
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this._currentDirectory = targetDir;
|
2014-04-04 16:34:07 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// legacy stuff
|
|
|
|
|
this.$el.find('#dir').val(targetDir);
|
2014-04-04 18:11:31 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (changeUrl !== false) {
|
2016-05-04 12:17:53 +03:00
|
|
|
|
var params = {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
dir: targetDir,
|
|
|
|
|
previousDir: previousDir
|
2016-05-04 12:17:53 +03:00
|
|
|
|
};
|
|
|
|
|
if (fileId) {
|
|
|
|
|
params.fileId = fileId;
|
|
|
|
|
}
|
|
|
|
|
this.$el.trigger(jQuery.Event('changeDirectory', params));
|
2014-04-04 16:34:07 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.breadcrumb.setDirectory(this.getCurrentDirectory());
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Sets the current sorting and refreshes the list
|
|
|
|
|
*
|
|
|
|
|
* @param sort sort attribute name
|
|
|
|
|
* @param direction sort direction, one of "asc" or "desc"
|
2014-08-15 18:52:41 +04:00
|
|
|
|
* @param update true to update the list, false otherwise (default)
|
2016-04-12 12:51:50 +03:00
|
|
|
|
* @param persist true to save changes in the database (default)
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2016-04-12 12:51:50 +03:00
|
|
|
|
setSort: function(sort, direction, update, persist) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var comparator = FileList.Comparators[sort] || FileList.Comparators.name;
|
|
|
|
|
this._sort = sort;
|
|
|
|
|
this._sortDirection = (direction === 'desc')?'desc':'asc';
|
2016-10-22 23:26:21 +03:00
|
|
|
|
this._sortComparator = function(fileInfo1, fileInfo2) {
|
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 16:01:23 +03:00
|
|
|
|
var isFavorite = function(fileInfo) {
|
|
|
|
|
return fileInfo.tags && fileInfo.tags.indexOf(OC.TAG_FAVORITE) >= 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (isFavorite(fileInfo1) && !isFavorite(fileInfo2)) {
|
2016-10-22 23:26:21 +03:00
|
|
|
|
return -1;
|
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 16:01:23 +03:00
|
|
|
|
} else if (!isFavorite(fileInfo1) && isFavorite(fileInfo2)) {
|
2016-10-22 23:26:21 +03:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 16:01:23 +03:00
|
|
|
|
|
2016-10-22 23:26:21 +03:00
|
|
|
|
return direction === 'asc' ? comparator(fileInfo1, fileInfo2) : -comparator(fileInfo1, fileInfo2);
|
|
|
|
|
};
|
2014-07-17 14:48:50 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('thead th .sort-indicator')
|
2014-07-17 14:48:50 +04:00
|
|
|
|
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
|
|
|
|
|
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
|
|
|
|
|
.toggleClass('hidden', true)
|
|
|
|
|
.addClass(this.SORT_INDICATOR_DESC_CLASS);
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('thead th.column-' + sort + ' .sort-indicator')
|
2014-07-17 14:48:50 +04:00
|
|
|
|
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
|
|
|
|
|
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
|
|
|
|
|
.toggleClass('hidden', false)
|
2014-05-09 00:06:30 +04:00
|
|
|
|
.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
|
2014-08-15 18:52:41 +04:00
|
|
|
|
if (update) {
|
|
|
|
|
if (this._clientSideSort) {
|
|
|
|
|
this.files.sort(this._sortComparator);
|
|
|
|
|
this.setFiles(this.files);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.reload();
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-12 12:08:26 +03:00
|
|
|
|
|
2016-04-12 12:51:50 +03:00
|
|
|
|
if (persist) {
|
|
|
|
|
$.post(OC.generateUrl('/apps/files/api/v1/sorting'), {
|
|
|
|
|
mode: sort,
|
|
|
|
|
direction: direction
|
|
|
|
|
});
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2014-07-17 14:48:50 +04:00
|
|
|
|
|
2015-11-02 16:17:49 +03:00
|
|
|
|
/**
|
|
|
|
|
* Returns list of webdav properties to request
|
|
|
|
|
*/
|
|
|
|
|
_getWebdavProperties: function() {
|
2015-12-22 13:17:24 +03:00
|
|
|
|
return [].concat(this.filesClient.getPropfindProperties());
|
2015-11-02 16:17:49 +03:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
2014-07-04 13:45:36 +04:00
|
|
|
|
* Reloads the file list using ajax call
|
|
|
|
|
*
|
|
|
|
|
* @return ajax call object
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
reload: function() {
|
|
|
|
|
this._selectedFiles = {};
|
|
|
|
|
this._selectionSummary.clear();
|
2015-09-01 20:29:55 +03:00
|
|
|
|
if (this._currentFileModel) {
|
|
|
|
|
this._currentFileModel.off();
|
|
|
|
|
}
|
|
|
|
|
this._currentFileModel = null;
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$el.find('.select-all').prop('checked', false);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.showMask();
|
2015-11-02 16:17:49 +03:00
|
|
|
|
this._reloadCall = this.filesClient.getFolderContents(
|
|
|
|
|
this.getCurrentDirectory(), {
|
|
|
|
|
includeParent: true,
|
|
|
|
|
properties: this._getWebdavProperties()
|
|
|
|
|
}
|
|
|
|
|
);
|
2015-09-25 13:23:28 +03:00
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
// close sidebar
|
|
|
|
|
this._updateDetailsView(null);
|
|
|
|
|
}
|
2017-10-18 20:51:40 +03:00
|
|
|
|
this._setCurrentDir(this.getCurrentDirectory(), false);
|
2014-06-30 18:27:31 +04:00
|
|
|
|
var callBack = this.reloadCallback.bind(this);
|
|
|
|
|
return this._reloadCall.then(callBack, callBack);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2015-07-13 18:38:13 +03:00
|
|
|
|
reloadCallback: function(status, result) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
delete this._reloadCall;
|
|
|
|
|
this.hideMask();
|
|
|
|
|
|
2016-03-02 19:42:51 +03:00
|
|
|
|
if (status === 401) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-19 17:31:22 +03:00
|
|
|
|
// Firewall Blocked request?
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (status === 403) {
|
2015-06-19 17:31:22 +03:00
|
|
|
|
// Go home
|
|
|
|
|
this.changeDirectory('/');
|
2017-02-14 23:26:00 +03:00
|
|
|
|
OC.Notification.show(t('files', 'This operation is forbidden'), {type: 'error'});
|
2015-06-19 17:31:22 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 18:19:59 +03:00
|
|
|
|
// Did share service die or something else fail?
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (status === 500) {
|
2015-06-26 18:19:59 +03:00
|
|
|
|
// Go home
|
|
|
|
|
this.changeDirectory('/');
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'),
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status === 503) {
|
|
|
|
|
// Go home
|
|
|
|
|
if (this.getCurrentDirectory() !== '/') {
|
|
|
|
|
this.changeDirectory('/');
|
|
|
|
|
// TODO: read error message from exception
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Storage is temporarily not available'),
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
}
|
2015-06-26 18:19:59 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-01 21:13:28 +03:00
|
|
|
|
if (status === 400 || status === 404 || status === 405) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// go back home
|
|
|
|
|
this.changeDirectory('/');
|
2014-06-30 18:27:31 +04:00
|
|
|
|
return false;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
// aborted ?
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (status === 0){
|
2014-06-30 18:27:31 +04:00
|
|
|
|
return true;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2013-02-22 20:21:57 +04:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
// TODO: parse remaining quota from PROPFIND response
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.updateStorageStatistics(true);
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
// first entry is the root
|
|
|
|
|
this.dirInfo = result.shift();
|
2016-10-04 13:56:04 +03:00
|
|
|
|
this.breadcrumb.setDirectoryInfo(this.dirInfo);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
|
|
|
|
|
if (this.dirInfo.permissions) {
|
|
|
|
|
this.setDirectoryPermissions(this.dirInfo.permissions);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2013-10-28 23:22:06 +04:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
result.sort(this._sortComparator);
|
|
|
|
|
this.setFiles(result);
|
2016-05-04 12:17:53 +03:00
|
|
|
|
|
|
|
|
|
if (this.dirInfo) {
|
|
|
|
|
var newFileId = this.dirInfo.id;
|
|
|
|
|
// update fileid in URL
|
|
|
|
|
var params = {
|
|
|
|
|
dir: this.getCurrentDirectory()
|
|
|
|
|
};
|
|
|
|
|
if (newFileId) {
|
|
|
|
|
params.fileId = newFileId;
|
|
|
|
|
}
|
|
|
|
|
this.$el.trigger(jQuery.Event('afterChangeDirectory', params));
|
|
|
|
|
}
|
2014-07-04 16:08:48 +04:00
|
|
|
|
return true;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
updateStorageStatistics: function(force) {
|
|
|
|
|
OCA.Files.Files.updateStorageStatistics(this.getCurrentDirectory(), force);
|
|
|
|
|
},
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* @deprecated do not use nor override
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
getAjaxUrl: function(action, params) {
|
2014-05-12 21:54:20 +04:00
|
|
|
|
return OCA.Files.Files.getAjaxUrl(action, params);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
getDownloadUrl: function(files, dir, isDir) {
|
|
|
|
|
return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory(), isDir);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
getUploadUrl: function(fileName, dir) {
|
|
|
|
|
if (_.isUndefined(dir)) {
|
|
|
|
|
dir = this.getCurrentDirectory();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pathSections = dir.split('/');
|
|
|
|
|
if (!_.isUndefined(fileName)) {
|
|
|
|
|
pathSections.push(fileName);
|
|
|
|
|
}
|
|
|
|
|
var encodedPath = '';
|
|
|
|
|
_.each(pathSections, function(section) {
|
|
|
|
|
if (section !== '') {
|
|
|
|
|
encodedPath += '/' + encodeURIComponent(section);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return OC.linkToRemoteBase('webdav') + encodedPath;
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Generates a preview URL based on the URL space.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @param urlSpec attributes for the URL
|
|
|
|
|
* @param {int} urlSpec.x width
|
|
|
|
|
* @param {int} urlSpec.y height
|
|
|
|
|
* @param {String} urlSpec.file path to the file
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return preview URL
|
|
|
|
|
*/
|
|
|
|
|
generatePreviewUrl: function(urlSpec) {
|
|
|
|
|
urlSpec = urlSpec || {};
|
|
|
|
|
if (!urlSpec.x) {
|
2015-09-30 11:49:48 +03:00
|
|
|
|
urlSpec.x = this.$table.data('preview-x') || 32;
|
2013-10-28 23:22:06 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (!urlSpec.y) {
|
2015-09-30 11:49:48 +03:00
|
|
|
|
urlSpec.y = this.$table.data('preview-y') || 32;
|
2013-10-28 23:22:06 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
urlSpec.x *= window.devicePixelRatio;
|
2015-06-11 18:17:40 +03:00
|
|
|
|
urlSpec.y *= window.devicePixelRatio;
|
2015-09-27 13:46:25 +03:00
|
|
|
|
urlSpec.x = Math.ceil(urlSpec.x);
|
|
|
|
|
urlSpec.y = Math.ceil(urlSpec.y);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
urlSpec.forceIcon = 0;
|
|
|
|
|
return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Lazy load a file's preview.
|
2014-05-26 22:32:24 +04:00
|
|
|
|
*
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @param path path of the file
|
|
|
|
|
* @param mime mime type
|
|
|
|
|
* @param callback callback function to call when the image was loaded
|
|
|
|
|
* @param etag file etag (for caching)
|
|
|
|
|
*/
|
|
|
|
|
lazyLoadPreview : function(options) {
|
|
|
|
|
var self = this;
|
|
|
|
|
var path = options.path;
|
|
|
|
|
var mime = options.mime;
|
|
|
|
|
var ready = options.callback;
|
|
|
|
|
var etag = options.etag;
|
|
|
|
|
|
|
|
|
|
// get mime icon url
|
2015-07-08 15:55:11 +03:00
|
|
|
|
var iconURL = OC.MimeType.getIconUrl(mime);
|
|
|
|
|
var previewURL,
|
|
|
|
|
urlSpec = {};
|
|
|
|
|
ready(iconURL); // set mimeicon URL
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2015-07-08 15:55:11 +03:00
|
|
|
|
urlSpec.file = OCA.Files.Files.fixPath(path);
|
2015-07-16 13:49:34 +03:00
|
|
|
|
if (options.x) {
|
|
|
|
|
urlSpec.x = options.x;
|
|
|
|
|
}
|
|
|
|
|
if (options.y) {
|
|
|
|
|
urlSpec.y = options.y;
|
|
|
|
|
}
|
2015-08-28 18:51:26 +03:00
|
|
|
|
if (options.a) {
|
|
|
|
|
urlSpec.a = options.a;
|
|
|
|
|
}
|
|
|
|
|
if (options.mode) {
|
|
|
|
|
urlSpec.mode = options.mode;
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2015-07-08 15:55:11 +03:00
|
|
|
|
if (etag){
|
|
|
|
|
// use etag as cache buster
|
|
|
|
|
urlSpec.c = etag;
|
|
|
|
|
}
|
2014-02-20 18:16:45 +04:00
|
|
|
|
|
2015-07-08 15:55:11 +03:00
|
|
|
|
previewURL = self.generatePreviewUrl(urlSpec);
|
|
|
|
|
previewURL = previewURL.replace('(', '%28');
|
|
|
|
|
previewURL = previewURL.replace(')', '%29');
|
|
|
|
|
|
|
|
|
|
// preload image to prevent delay
|
|
|
|
|
// this will make the browser cache the image
|
|
|
|
|
var img = new Image();
|
|
|
|
|
img.onload = function(){
|
|
|
|
|
// if loading the preview image failed (no preview for the mimetype) then img.width will < 5
|
|
|
|
|
if (img.width > 5) {
|
2015-08-28 18:51:26 +03:00
|
|
|
|
ready(previewURL, img);
|
|
|
|
|
} else if (options.error) {
|
|
|
|
|
options.error();
|
2015-07-08 15:55:11 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
2015-08-28 18:51:26 +03:00
|
|
|
|
if (options.error) {
|
|
|
|
|
img.onerror = options.error;
|
|
|
|
|
}
|
2015-07-08 15:55:11 +03:00
|
|
|
|
img.src = previewURL;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2014-02-20 18:16:45 +04:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* @deprecated
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
setDirectoryPermissions: function(permissions) {
|
|
|
|
|
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
|
|
|
|
|
this.$el.find('#permissions').val(permissions);
|
|
|
|
|
this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
|
|
|
|
|
this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Shows/hides action buttons
|
|
|
|
|
*
|
|
|
|
|
* @param show true for enabling, false for disabling
|
|
|
|
|
*/
|
|
|
|
|
showActions: function(show){
|
|
|
|
|
this.$el.find('.actions,#file_action_panel').toggleClass('hidden', !show);
|
|
|
|
|
if (show){
|
|
|
|
|
// make sure to display according to permissions
|
|
|
|
|
var permissions = this.getDirectoryPermissions();
|
|
|
|
|
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
|
|
|
|
|
this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
|
|
|
|
|
this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
|
|
|
|
|
// remove old style breadcrumbs (some apps might create them)
|
|
|
|
|
this.$el.find('#controls .crumb').remove();
|
|
|
|
|
// refresh breadcrumbs in case it was replaced by an app
|
|
|
|
|
this.breadcrumb.render();
|
2013-08-29 23:56:14 +04:00
|
|
|
|
}
|
|
|
|
|
else{
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('.creatable, .notCreatable').addClass('hidden');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Enables/disables viewer mode.
|
|
|
|
|
* In viewer mode, apps can embed themselves under the controls bar.
|
|
|
|
|
* In viewer mode, the actions of the file list will be hidden.
|
|
|
|
|
* @param show true for enabling, false for disabling
|
|
|
|
|
*/
|
|
|
|
|
setViewerMode: function(show){
|
|
|
|
|
this.showActions(!show);
|
|
|
|
|
this.$el.find('#filestable').toggleClass('hidden', show);
|
2014-05-12 21:54:20 +04:00
|
|
|
|
this.$el.trigger(new $.Event('changeViewerMode', {viewerModeEnabled: show}));
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Removes a file entry from the list
|
|
|
|
|
* @param name name of the file to remove
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @param {Object} [options] map of attributes
|
|
|
|
|
* @param {boolean} [options.updateSummary] true to update the summary
|
|
|
|
|
* after removing, false otherwise. Defaults to true.
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* @return deleted element
|
|
|
|
|
*/
|
|
|
|
|
remove: function(name, options){
|
|
|
|
|
options = options || {};
|
|
|
|
|
var fileEl = this.findFileEl(name);
|
2015-09-25 13:23:28 +03:00
|
|
|
|
var fileId = fileEl.data('id');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var index = fileEl.index();
|
|
|
|
|
if (!fileEl.length) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2015-09-25 13:23:28 +03:00
|
|
|
|
if (this._selectedFiles[fileId]) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// remove from selection first
|
|
|
|
|
this._selectFileEl(fileEl, false);
|
|
|
|
|
this.updateSelectionSummary();
|
|
|
|
|
}
|
2014-05-12 21:54:20 +04:00
|
|
|
|
if (this._dragOptions && (fileEl.data('permissions') & OC.PERMISSION_DELETE)) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// file is only draggable when delete permissions are set
|
|
|
|
|
fileEl.find('td.filename').draggable('destroy');
|
|
|
|
|
}
|
|
|
|
|
this.files.splice(index, 1);
|
2015-09-25 13:23:28 +03:00
|
|
|
|
if (this._currentFileModel && this._currentFileModel.get('id') === fileId) {
|
|
|
|
|
// Note: in the future we should call destroy() directly on the model
|
|
|
|
|
// and the model will take care of the deletion.
|
|
|
|
|
// Here we only trigger the event to notify listeners that
|
|
|
|
|
// the file was removed.
|
|
|
|
|
this._currentFileModel.trigger('destroy');
|
|
|
|
|
this._updateDetailsView(null);
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
fileEl.remove();
|
|
|
|
|
// TODO: improve performance on batch update
|
|
|
|
|
this.isEmpty = !this.files.length;
|
|
|
|
|
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
|
|
|
|
|
this.updateEmptyContent();
|
|
|
|
|
this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
|
2013-08-29 23:56:14 +04:00
|
|
|
|
}
|
2013-08-17 15:07:18 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var lastIndex = this.$fileList.children().length;
|
|
|
|
|
// if there are less elements visible than one page
|
|
|
|
|
// but there are still pending elements in the array,
|
|
|
|
|
// then directly append the next page
|
2014-10-11 17:10:54 +04:00
|
|
|
|
if (lastIndex < this.files.length && lastIndex < this.pageSize()) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this._nextPage(true);
|
|
|
|
|
}
|
2013-08-17 15:07:18 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return fileEl;
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Finds the index of the row before which the given
|
|
|
|
|
* fileData should be inserted, considering the current
|
|
|
|
|
* sorting
|
2014-06-24 01:56:10 +04:00
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileData file info
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
_findInsertionIndex: function(fileData) {
|
|
|
|
|
var index = 0;
|
|
|
|
|
while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) {
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
return index;
|
|
|
|
|
},
|
2017-08-27 16:28:26 +03:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Moves a file to a given target folder.
|
|
|
|
|
*
|
|
|
|
|
* @param fileNames array of file names to move
|
|
|
|
|
* @param targetPath absolute target path
|
2017-08-27 16:20:18 +03:00
|
|
|
|
* @param callback function to call when movement is finished
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2017-08-27 16:20:18 +03:00
|
|
|
|
move: function(fileNames, targetPath, callback) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var self = this;
|
|
|
|
|
var dir = this.getCurrentDirectory();
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (dir.charAt(dir.length - 1) !== '/') {
|
|
|
|
|
dir += '/';
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var target = OC.basename(targetPath);
|
|
|
|
|
if (!_.isArray(fileNames)) {
|
|
|
|
|
fileNames = [fileNames];
|
|
|
|
|
}
|
|
|
|
|
_.each(fileNames, function(fileName) {
|
|
|
|
|
var $tr = self.findFileEl(fileName);
|
2015-07-16 16:28:45 +03:00
|
|
|
|
self.showFileBusyState($tr, true);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (targetPath.charAt(targetPath.length - 1) !== '/') {
|
|
|
|
|
// make sure we move the files into the target dir,
|
|
|
|
|
// not overwrite it
|
|
|
|
|
targetPath = targetPath + '/';
|
|
|
|
|
}
|
|
|
|
|
self.filesClient.move(dir + fileName, targetPath + fileName)
|
|
|
|
|
.done(function() {
|
|
|
|
|
// if still viewing the same directory
|
|
|
|
|
if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
|
|
|
|
|
// recalculate folder size
|
|
|
|
|
var oldFile = self.findFileEl(target);
|
|
|
|
|
var newFile = self.findFileEl(fileName);
|
|
|
|
|
var oldSize = oldFile.data('size');
|
|
|
|
|
var newSize = oldSize + newFile.data('size');
|
|
|
|
|
oldFile.data('size', newSize);
|
|
|
|
|
oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
|
|
|
|
|
|
|
|
|
|
// TODO: also update entry in FileList.files
|
|
|
|
|
self.remove(fileName);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.fail(function(status) {
|
|
|
|
|
if (status === 412) {
|
|
|
|
|
// TODO: some day here we should invoke the conflict dialog
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not move "{file}", target exists',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: fileName}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
} else {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not move "{file}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: fileName}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
})
|
|
|
|
|
.always(function() {
|
2015-07-16 16:28:45 +03:00
|
|
|
|
self.showFileBusyState($tr, false);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
});
|
2017-08-27 16:20:18 +03:00
|
|
|
|
if (callback) {
|
|
|
|
|
callback();
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
2013-11-06 13:55:19 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2017-08-27 16:28:26 +03:00
|
|
|
|
/**
|
|
|
|
|
* Copies a file to a given target folder.
|
|
|
|
|
*
|
|
|
|
|
* @param fileNames array of file names to copy
|
|
|
|
|
* @param targetPath absolute target path
|
|
|
|
|
* @param callback to call when copy is finished with success
|
|
|
|
|
*/
|
|
|
|
|
copy: function(fileNames, targetPath, callback) {
|
|
|
|
|
var self = this;
|
2017-09-15 19:13:45 +03:00
|
|
|
|
var filesToNotify = [];
|
|
|
|
|
var count = 0;
|
|
|
|
|
|
2017-08-27 16:28:26 +03:00
|
|
|
|
var dir = this.getCurrentDirectory();
|
|
|
|
|
if (dir.charAt(dir.length - 1) !== '/') {
|
|
|
|
|
dir += '/';
|
|
|
|
|
}
|
2017-09-15 19:13:45 +03:00
|
|
|
|
var target = OC.basename(targetPath);
|
2017-08-27 16:28:26 +03:00
|
|
|
|
if (!_.isArray(fileNames)) {
|
|
|
|
|
fileNames = [fileNames];
|
|
|
|
|
}
|
|
|
|
|
_.each(fileNames, function(fileName) {
|
|
|
|
|
var $tr = self.findFileEl(fileName);
|
|
|
|
|
self.showFileBusyState($tr, true);
|
|
|
|
|
if (targetPath.charAt(targetPath.length - 1) !== '/') {
|
|
|
|
|
// make sure we move the files into the target dir,
|
|
|
|
|
// not overwrite it
|
|
|
|
|
targetPath = targetPath + '/';
|
|
|
|
|
}
|
|
|
|
|
self.filesClient.copy(dir + fileName, targetPath + fileName)
|
2017-09-15 19:13:45 +03:00
|
|
|
|
.done(function () {
|
|
|
|
|
filesToNotify.push(fileName);
|
|
|
|
|
|
|
|
|
|
// if still viewing the same directory
|
|
|
|
|
if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
|
|
|
|
|
// recalculate folder size
|
|
|
|
|
var oldFile = self.findFileEl(target);
|
|
|
|
|
var newFile = self.findFileEl(fileName);
|
|
|
|
|
var oldSize = oldFile.data('size');
|
|
|
|
|
var newSize = oldSize + newFile.data('size');
|
|
|
|
|
oldFile.data('size', newSize);
|
|
|
|
|
oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
|
|
|
|
|
}
|
|
|
|
|
})
|
2017-08-27 16:28:26 +03:00
|
|
|
|
.fail(function(status) {
|
|
|
|
|
if (status === 412) {
|
|
|
|
|
// TODO: some day here we should invoke the conflict dialog
|
|
|
|
|
OC.Notification.show(t('files', 'Could not copy "{file}", target exists',
|
|
|
|
|
{file: fileName}), {type: 'error'}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
OC.Notification.show(t('files', 'Could not copy "{file}"',
|
|
|
|
|
{file: fileName}), {type: 'error'}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.always(function() {
|
|
|
|
|
self.showFileBusyState($tr, false);
|
2017-09-15 19:13:45 +03:00
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* We only show the notifications once the last file has been copied
|
|
|
|
|
*/
|
|
|
|
|
if (count === fileNames.length) {
|
|
|
|
|
// Remove leading and ending /
|
|
|
|
|
if (targetPath.slice(0, 1) === '/') {
|
|
|
|
|
targetPath = targetPath.slice(1, targetPath.length);
|
|
|
|
|
}
|
|
|
|
|
if (targetPath.slice(-1) === '/') {
|
|
|
|
|
targetPath = targetPath.slice(0, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filesToNotify.length > 0) {
|
|
|
|
|
// Since there's no visual indication that the files were copied, let's send some notifications !
|
|
|
|
|
if (filesToNotify.length === 1) {
|
|
|
|
|
OC.Notification.show(t('files', 'Copied {origin} inside {destination}',
|
|
|
|
|
{
|
|
|
|
|
origin: filesToNotify[0],
|
|
|
|
|
destination: targetPath
|
|
|
|
|
}
|
|
|
|
|
), {timeout: 10});
|
|
|
|
|
} else if (filesToNotify.length > 0 && filesToNotify.length < 3) {
|
|
|
|
|
OC.Notification.show(t('files', 'Copied {origin} inside {destination}',
|
|
|
|
|
{
|
|
|
|
|
origin: filesToNotify.join(', '),
|
|
|
|
|
destination: targetPath
|
|
|
|
|
}
|
|
|
|
|
), {timeout: 10});
|
|
|
|
|
} else {
|
|
|
|
|
OC.Notification.show(t('files', 'Copied {origin} and {nbfiles} other files inside {destination}',
|
|
|
|
|
{
|
|
|
|
|
origin: filesToNotify[0],
|
|
|
|
|
nbfiles: filesToNotify.length - 1,
|
|
|
|
|
destination: targetPath
|
|
|
|
|
}
|
|
|
|
|
), {timeout: 10});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-27 16:28:26 +03:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (callback) {
|
|
|
|
|
callback();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2015-09-01 20:29:55 +03:00
|
|
|
|
/**
|
|
|
|
|
* Updates the given row with the given file info
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} $tr row element
|
|
|
|
|
* @param {OCA.Files.FileInfo} fileInfo file info
|
|
|
|
|
* @param {Object} options options
|
|
|
|
|
*
|
|
|
|
|
* @return {Object} new row element
|
|
|
|
|
*/
|
|
|
|
|
updateRow: function($tr, fileInfo, options) {
|
|
|
|
|
this.files.splice($tr.index(), 1);
|
|
|
|
|
$tr.remove();
|
2016-01-10 00:07:34 +03:00
|
|
|
|
options = _.extend({silent: true}, options);
|
|
|
|
|
options = _.extend(options, {updateSummary: false});
|
|
|
|
|
$tr = this.add(fileInfo, options);
|
2015-09-01 20:29:55 +03:00
|
|
|
|
this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: $tr}));
|
|
|
|
|
return $tr;
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Triggers file rename input field for the given file name.
|
|
|
|
|
* If the user enters a new name, the file will be renamed.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param oldName file name of the file to rename
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
2015-07-13 18:38:13 +03:00
|
|
|
|
rename: function(oldName) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var self = this;
|
|
|
|
|
var tr, td, input, form;
|
2015-07-13 18:38:13 +03:00
|
|
|
|
tr = this.findFileEl(oldName);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var oldFileInfo = this.files[tr.index()];
|
|
|
|
|
tr.data('renaming',true);
|
|
|
|
|
td = tr.children('td.filename');
|
2015-07-13 18:38:13 +03:00
|
|
|
|
input = $('<input type="text" class="filename"/>').val(oldName);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
form = $('<form></form>');
|
|
|
|
|
form.append(input);
|
|
|
|
|
td.children('a.name').hide();
|
|
|
|
|
td.append(form);
|
|
|
|
|
input.focus();
|
|
|
|
|
//preselect input
|
|
|
|
|
var len = input.val().lastIndexOf('.');
|
|
|
|
|
if ( len === -1 ||
|
|
|
|
|
tr.data('type') === 'dir' ) {
|
|
|
|
|
len = input.val().length;
|
|
|
|
|
}
|
|
|
|
|
input.selectRange(0, len);
|
|
|
|
|
var checkInput = function () {
|
|
|
|
|
var filename = input.val();
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (filename !== oldName) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// Files.isFileNameValid(filename) throws an exception itself
|
2014-05-12 21:54:20 +04:00
|
|
|
|
OCA.Files.Files.isFileNameValid(filename);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (self.inList(filename)) {
|
2016-09-30 13:44:49 +03:00
|
|
|
|
throw t('files', '{newName} already exists', {newName: filename}, undefined, {
|
|
|
|
|
escape: false
|
|
|
|
|
});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2013-08-27 13:18:59 +04:00
|
|
|
|
|
2014-06-23 18:35:11 +04:00
|
|
|
|
function restore() {
|
2015-08-07 15:00:44 +03:00
|
|
|
|
input.tooltip('hide');
|
2014-06-23 18:35:11 +04:00
|
|
|
|
tr.data('renaming',false);
|
|
|
|
|
form.remove();
|
|
|
|
|
td.children('a.name').show();
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
function updateInList(fileInfo) {
|
2015-11-19 13:42:17 +03:00
|
|
|
|
self.updateRow(tr, fileInfo);
|
2016-10-10 18:35:11 +03:00
|
|
|
|
self._updateDetailsView(fileInfo, false);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: too many nested blocks, move parts into functions
|
2014-05-09 00:06:30 +04:00
|
|
|
|
form.submit(function(event) {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
event.preventDefault();
|
2014-06-23 18:35:11 +04:00
|
|
|
|
if (input.hasClass('error')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
try {
|
|
|
|
|
var newName = input.val();
|
2015-08-07 15:00:44 +03:00
|
|
|
|
input.tooltip('hide');
|
2014-05-16 14:43:36 +04:00
|
|
|
|
form.remove();
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (newName !== oldName) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
checkInput();
|
2014-05-16 14:43:36 +04:00
|
|
|
|
// mark as loading (temp element)
|
2015-07-16 16:28:45 +03:00
|
|
|
|
self.showFileBusyState(tr, true);
|
2014-05-16 14:43:36 +04:00
|
|
|
|
tr.attr('data-file', newName);
|
|
|
|
|
var basename = newName;
|
|
|
|
|
if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
|
|
|
|
|
basename = newName.substr(0, newName.lastIndexOf('.'));
|
|
|
|
|
}
|
|
|
|
|
td.find('a.name span.nametext').text(basename);
|
|
|
|
|
td.children('a.name').show();
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
var path = tr.attr('data-path') || self.getCurrentDirectory();
|
2015-11-19 13:42:17 +03:00
|
|
|
|
self.filesClient.move(OC.joinPaths(path, oldName), OC.joinPaths(path, newName))
|
2015-07-13 18:38:13 +03:00
|
|
|
|
.done(function() {
|
2015-11-19 13:42:17 +03:00
|
|
|
|
oldFileInfo.name = newName;
|
|
|
|
|
updateInList(oldFileInfo);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
})
|
|
|
|
|
.fail(function(status) {
|
|
|
|
|
// TODO: 409 means current folder does not exist, redirect ?
|
|
|
|
|
if (status === 404) {
|
|
|
|
|
// source not found, so remove it from the list
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not rename "{fileName}", it does not exist any more',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{fileName: oldName}), {timeout: 7, type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
2017-02-14 23:26:00 +03:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
self.remove(newName, {updateSummary: true});
|
|
|
|
|
return;
|
|
|
|
|
} else if (status === 412) {
|
|
|
|
|
// target exists
|
2017-02-14 23:26:00 +03:00
|
|
|
|
OC.Notification.show(
|
|
|
|
|
t('files', 'The name "{targetName}" is already used in the folder "{dir}". Please choose a different name.',
|
|
|
|
|
{
|
|
|
|
|
targetName: newName,
|
|
|
|
|
dir: self.getCurrentDirectory(),
|
|
|
|
|
}),
|
|
|
|
|
{
|
|
|
|
|
type: 'error'
|
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// restore the item to its previous state
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not rename "{fileName}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{fileName: oldName}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
updateInList(oldFileInfo);
|
|
|
|
|
});
|
2014-05-16 14:43:36 +04:00
|
|
|
|
} else {
|
|
|
|
|
// add back the old file info when cancelled
|
|
|
|
|
self.files.splice(tr.index(), 1);
|
|
|
|
|
tr.remove();
|
2014-07-01 23:32:04 +04:00
|
|
|
|
tr = self.add(oldFileInfo, {updateSummary: false, silent: true});
|
|
|
|
|
self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)}));
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
input.attr('title', error);
|
2015-10-05 18:44:25 +03:00
|
|
|
|
input.tooltip({placement: 'right', trigger: 'manual'});
|
2017-05-20 19:29:09 +03:00
|
|
|
|
input.tooltip('fixTitle');
|
2015-08-07 15:00:44 +03:00
|
|
|
|
input.tooltip('show');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
input.addClass('error');
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
input.keyup(function(event) {
|
|
|
|
|
// verify filename on typing
|
|
|
|
|
try {
|
|
|
|
|
checkInput();
|
2015-08-07 15:00:44 +03:00
|
|
|
|
input.tooltip('hide');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
input.removeClass('error');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
input.attr('title', error);
|
2015-10-05 18:44:25 +03:00
|
|
|
|
input.tooltip({placement: 'right', trigger: 'manual'});
|
2017-05-20 19:29:09 +03:00
|
|
|
|
input.tooltip('fixTitle');
|
2015-08-07 15:00:44 +03:00
|
|
|
|
input.tooltip('show');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
input.addClass('error');
|
|
|
|
|
}
|
|
|
|
|
if (event.keyCode === 27) {
|
2014-06-23 18:35:11 +04:00
|
|
|
|
restore();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
input.click(function(event) {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
});
|
|
|
|
|
input.blur(function() {
|
|
|
|
|
form.trigger('submit');
|
|
|
|
|
});
|
|
|
|
|
},
|
2015-08-27 14:22:58 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an empty file inside the current directory.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name name of the file
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} promise that will be resolved after the
|
|
|
|
|
* file was created
|
2015-09-03 13:17:35 +03:00
|
|
|
|
*
|
|
|
|
|
* @since 8.2
|
2015-08-27 14:22:58 +03:00
|
|
|
|
*/
|
|
|
|
|
createFile: function(name) {
|
|
|
|
|
var self = this;
|
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
|
var promise = deferred.promise();
|
|
|
|
|
|
|
|
|
|
OCA.Files.Files.isFileNameValid(name);
|
|
|
|
|
|
|
|
|
|
if (this.lastAction) {
|
|
|
|
|
this.lastAction();
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
name = this.getUniqueName(name);
|
|
|
|
|
var targetPath = this.getCurrentDirectory() + '/' + name;
|
|
|
|
|
|
|
|
|
|
self.filesClient.putFileContents(
|
|
|
|
|
targetPath,
|
2016-11-16 12:47:20 +03:00
|
|
|
|
' ', // dont create empty files which fails on some storage backends
|
2015-07-13 18:38:13 +03:00
|
|
|
|
{
|
|
|
|
|
contentType: 'text/plain',
|
|
|
|
|
overwrite: true
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
.done(function() {
|
|
|
|
|
// TODO: error handling / conflicts
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self.addAndFetchFileInfo(targetPath, '', {scrollTo: true}).then(function(status, data) {
|
|
|
|
|
deferred.resolve(status, data);
|
|
|
|
|
}, function() {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create file "{file}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: name}), {type: 'error'}
|
|
|
|
|
);
|
2015-12-16 19:35:53 +03:00
|
|
|
|
});
|
2015-07-13 18:38:13 +03:00
|
|
|
|
})
|
|
|
|
|
.fail(function(status) {
|
|
|
|
|
if (status === 412) {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create file "{file}" because it already exists',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: name}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
2015-08-27 14:22:58 +03:00
|
|
|
|
} else {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create file "{file}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: name}), {type: 'error'}
|
|
|
|
|
);
|
2015-08-27 14:22:58 +03:00
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
deferred.reject(status);
|
|
|
|
|
});
|
2015-08-27 14:22:58 +03:00
|
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a directory inside the current directory.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name name of the directory
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} promise that will be resolved after the
|
|
|
|
|
* directory was created
|
2015-09-03 13:17:35 +03:00
|
|
|
|
*
|
|
|
|
|
* @since 8.2
|
2015-08-27 14:22:58 +03:00
|
|
|
|
*/
|
|
|
|
|
createDirectory: function(name) {
|
|
|
|
|
var self = this;
|
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
|
var promise = deferred.promise();
|
|
|
|
|
|
|
|
|
|
OCA.Files.Files.isFileNameValid(name);
|
|
|
|
|
|
|
|
|
|
if (this.lastAction) {
|
|
|
|
|
this.lastAction();
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
name = this.getUniqueName(name);
|
|
|
|
|
var targetPath = this.getCurrentDirectory() + '/' + name;
|
|
|
|
|
|
|
|
|
|
this.filesClient.createDirectory(targetPath)
|
2015-12-16 19:35:53 +03:00
|
|
|
|
.done(function() {
|
|
|
|
|
self.addAndFetchFileInfo(targetPath, '', {scrollTo:true}).then(function(status, data) {
|
|
|
|
|
deferred.resolve(status, data);
|
|
|
|
|
}, function() {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create folder "{dir}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{dir: name}), {type: 'error'}
|
|
|
|
|
);
|
2015-12-16 19:35:53 +03:00
|
|
|
|
});
|
2015-07-13 18:38:13 +03:00
|
|
|
|
})
|
|
|
|
|
.fail(function(createStatus) {
|
|
|
|
|
// method not allowed, folder might exist already
|
|
|
|
|
if (createStatus === 405) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
// add it to the list, for completeness
|
|
|
|
|
self.addAndFetchFileInfo(targetPath, '', {scrollTo:true})
|
2015-07-13 18:38:13 +03:00
|
|
|
|
.done(function(status, data) {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create folder "{dir}" because it already exists',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{dir: name}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
// still consider a failure
|
|
|
|
|
deferred.reject(createStatus, data);
|
|
|
|
|
})
|
|
|
|
|
.fail(function() {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create folder "{dir}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{dir: name}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
deferred.reject(status);
|
|
|
|
|
});
|
2015-08-27 14:22:58 +03:00
|
|
|
|
} else {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create folder "{dir}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{dir: name}), {type: 'error'}
|
|
|
|
|
);
|
2015-07-13 18:38:13 +03:00
|
|
|
|
deferred.reject(createStatus);
|
2015-08-27 14:22:58 +03:00
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
});
|
2015-08-27 14:22:58 +03:00
|
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
|
},
|
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
/**
|
|
|
|
|
* Add file into the list by fetching its information from the server first.
|
|
|
|
|
*
|
|
|
|
|
* If the given directory does not match the current directory, nothing will
|
|
|
|
|
* be fetched.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} fileName file name
|
|
|
|
|
* @param {String} [dir] optional directory, defaults to the current one
|
|
|
|
|
* @param {Object} options same options as #add
|
|
|
|
|
* @return {Promise} promise that resolves with the file info, or an
|
|
|
|
|
* already resolved Promise if no info was fetched. The promise rejects
|
|
|
|
|
* if the file was not found or an error occurred.
|
|
|
|
|
*
|
|
|
|
|
* @since 9.0
|
|
|
|
|
*/
|
|
|
|
|
addAndFetchFileInfo: function(fileName, dir, options) {
|
|
|
|
|
var self = this;
|
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
|
if (_.isUndefined(dir)) {
|
|
|
|
|
dir = this.getCurrentDirectory();
|
|
|
|
|
} else {
|
|
|
|
|
dir = dir || '/';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var targetPath = OC.joinPaths(dir, fileName);
|
|
|
|
|
|
|
|
|
|
if ((OC.dirname(targetPath) || '/') !== this.getCurrentDirectory()) {
|
|
|
|
|
// no need to fetch information
|
|
|
|
|
deferred.resolve();
|
|
|
|
|
return deferred.promise();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var addOptions = _.extend({
|
|
|
|
|
animate: true,
|
|
|
|
|
scrollTo: false
|
|
|
|
|
}, options || {});
|
|
|
|
|
|
|
|
|
|
this.filesClient.getFileInfo(targetPath, {
|
|
|
|
|
properties: this._getWebdavProperties()
|
|
|
|
|
})
|
|
|
|
|
.then(function(status, data) {
|
|
|
|
|
// remove first to avoid duplicates
|
|
|
|
|
self.remove(data.name);
|
|
|
|
|
self.add(data, addOptions);
|
|
|
|
|
deferred.resolve(status, data);
|
|
|
|
|
})
|
|
|
|
|
.fail(function(status) {
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Could not create file "{file}"',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{file: name}), {type: 'error'}
|
|
|
|
|
);
|
2015-12-16 19:35:53 +03:00
|
|
|
|
deferred.reject(status);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return deferred.promise();
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-27 14:22:58 +03:00
|
|
|
|
/**
|
|
|
|
|
* Returns whether the given file name exists in the list
|
|
|
|
|
*
|
|
|
|
|
* @param {string} file file name
|
|
|
|
|
*
|
|
|
|
|
* @return {bool} true if the file exists in the list, false otherwise
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
inList:function(file) {
|
2015-09-28 18:50:11 +03:00
|
|
|
|
return this.findFile(file);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2015-07-16 16:28:45 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shows busy state on a given file row or multiple
|
|
|
|
|
*
|
|
|
|
|
* @param {string|Array.<string>} files file name or array of file names
|
|
|
|
|
* @param {bool} [busy=true] busy state, true for busy, false to remove busy state
|
|
|
|
|
*
|
|
|
|
|
* @since 8.2
|
|
|
|
|
*/
|
|
|
|
|
showFileBusyState: function(files, state) {
|
|
|
|
|
var self = this;
|
2015-12-17 14:00:29 +03:00
|
|
|
|
if (!_.isArray(files) && !files.is) {
|
2015-07-16 16:28:45 +03:00
|
|
|
|
files = [files];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_.isUndefined(state)) {
|
|
|
|
|
state = true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-17 14:00:29 +03:00
|
|
|
|
_.each(files, function(fileName) {
|
2015-07-16 16:28:45 +03:00
|
|
|
|
// jquery element already ?
|
2015-12-17 14:00:29 +03:00
|
|
|
|
var $tr;
|
|
|
|
|
if (_.isString(fileName)) {
|
|
|
|
|
$tr = self.findFileEl(fileName);
|
|
|
|
|
} else {
|
|
|
|
|
$tr = $(fileName);
|
2015-07-16 16:28:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var $thumbEl = $tr.find('.thumbnail');
|
|
|
|
|
$tr.toggleClass('busy', state);
|
|
|
|
|
|
|
|
|
|
if (state) {
|
2017-09-21 20:46:29 +03:00
|
|
|
|
$thumbEl.parent().addClass('icon-loading-small');
|
2015-07-16 16:28:45 +03:00
|
|
|
|
} else {
|
2017-09-21 20:46:29 +03:00
|
|
|
|
$thumbEl.parent().removeClass('icon-loading-small');
|
2015-07-16 16:28:45 +03:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Delete the given files from the given dir
|
|
|
|
|
* @param files file names list (without path)
|
|
|
|
|
* @param dir directory in which to delete the files, defaults to the current
|
|
|
|
|
* directory
|
|
|
|
|
*/
|
|
|
|
|
do_delete:function(files, dir) {
|
|
|
|
|
var self = this;
|
|
|
|
|
if (files && files.substr) {
|
|
|
|
|
files=[files];
|
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
if (!files) {
|
|
|
|
|
// delete all files in directory
|
|
|
|
|
files = _.pluck(this.files, 'name');
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (files) {
|
2015-07-16 16:28:45 +03:00
|
|
|
|
this.showFileBusyState(files, true);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
// Finish any existing actions
|
|
|
|
|
if (this.lastAction) {
|
|
|
|
|
this.lastAction();
|
|
|
|
|
}
|
2014-04-04 18:38:27 +04:00
|
|
|
|
|
2015-07-13 18:38:13 +03:00
|
|
|
|
dir = dir || this.getCurrentDirectory();
|
|
|
|
|
|
|
|
|
|
function removeFromList(file) {
|
|
|
|
|
var fileEl = self.remove(file, {updateSummary: false});
|
|
|
|
|
// FIXME: not sure why we need this after the
|
|
|
|
|
// element isn't even in the DOM any more
|
|
|
|
|
fileEl.find('.selectCheckBox').prop('checked', false);
|
|
|
|
|
fileEl.removeClass('selected');
|
|
|
|
|
self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
|
|
|
|
|
// TODO: this info should be returned by the ajax call!
|
|
|
|
|
self.updateEmptyContent();
|
|
|
|
|
self.fileSummary.update();
|
|
|
|
|
self.updateSelectionSummary();
|
|
|
|
|
// FIXME: don't repeat this, do it once all files are done
|
|
|
|
|
self.updateStorageStatistics();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
|
|
|
|
|
_.each(files, function(file) {
|
|
|
|
|
self.filesClient.remove(dir + '/' + file)
|
|
|
|
|
.done(function() {
|
|
|
|
|
removeFromList(file);
|
|
|
|
|
})
|
|
|
|
|
.fail(function(status) {
|
|
|
|
|
if (status === 404) {
|
|
|
|
|
// the file already did not exist, remove it from the list
|
|
|
|
|
removeFromList(file);
|
2014-04-11 14:46:12 +04:00
|
|
|
|
} else {
|
2015-07-13 18:38:13 +03:00
|
|
|
|
// only reset the spinner for that one file
|
2017-11-04 11:45:29 +03:00
|
|
|
|
OC.Notification.show(t('files', 'Error deleting file "{fileName}".',
|
2017-02-14 23:26:00 +03:00
|
|
|
|
{fileName: file}), {type: 'error'}
|
2015-07-13 18:38:13 +03:00
|
|
|
|
);
|
|
|
|
|
var deleteAction = self.findFileEl(file).find('.action.delete');
|
|
|
|
|
deleteAction.removeClass('icon-loading-small').addClass('icon-delete');
|
|
|
|
|
self.showFileBusyState(files, false);
|
2012-07-30 20:21:58 +04:00
|
|
|
|
}
|
|
|
|
|
});
|
2015-07-13 18:38:13 +03:00
|
|
|
|
});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Creates the file summary section
|
|
|
|
|
*/
|
|
|
|
|
_createSummary: function() {
|
|
|
|
|
var $tr = $('<tr class="summary"></tr>');
|
2017-09-29 02:36:10 +03:00
|
|
|
|
|
|
|
|
|
if (this._allowSelection) {
|
|
|
|
|
// Dummy column for selection, as all rows must have the same
|
|
|
|
|
// number of columns.
|
|
|
|
|
$tr.append('<td></td>');
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('tfoot').append($tr);
|
|
|
|
|
|
2016-08-19 17:44:58 +03:00
|
|
|
|
return new OCA.Files.FileSummary($tr, {config: this._filesConfig});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
updateEmptyContent: function() {
|
|
|
|
|
var permissions = this.getDirectoryPermissions();
|
|
|
|
|
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
|
2015-04-21 12:57:29 +03:00
|
|
|
|
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
|
|
|
|
|
this.$el.find('#emptycontent .uploadmessage').toggleClass('hidden', !isCreatable || !this.isEmpty);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Shows the loading mask.
|
|
|
|
|
*
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @see OCA.Files.FileList#hideMask
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
showMask: function() {
|
|
|
|
|
// in case one was shown before
|
|
|
|
|
var $mask = this.$el.find('.mask');
|
|
|
|
|
if ($mask.exists()) {
|
|
|
|
|
return;
|
2014-02-13 23:20:00 +04:00
|
|
|
|
}
|
2012-10-14 23:04:08 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$table.addClass('hidden');
|
2015-08-07 16:57:16 +03:00
|
|
|
|
this.$el.find('#emptycontent').addClass('hidden');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2016-09-27 10:43:21 +03:00
|
|
|
|
$mask = $('<div class="mask transparent icon-loading"></div>');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
this.$el.append($mask);
|
|
|
|
|
|
|
|
|
|
$mask.removeClass('transparent');
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Hide the loading mask.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @see OCA.Files.FileList#showMask
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
hideMask: function() {
|
|
|
|
|
this.$el.find('.mask').remove();
|
|
|
|
|
this.$table.removeClass('hidden');
|
|
|
|
|
},
|
|
|
|
|
scrollTo:function(file) {
|
2014-09-04 14:20:11 +04:00
|
|
|
|
if (!_.isArray(file)) {
|
|
|
|
|
file = [file];
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2017-01-23 20:06:24 +03:00
|
|
|
|
if (file.length === 1) {
|
|
|
|
|
_.defer(function() {
|
|
|
|
|
this.showDetailsView(file[0]);
|
|
|
|
|
}.bind(this));
|
|
|
|
|
}
|
2014-09-04 14:20:11 +04:00
|
|
|
|
this.highlightFiles(file, function($tr) {
|
|
|
|
|
$tr.addClass('searchresult');
|
|
|
|
|
$tr.one('hover', function() {
|
|
|
|
|
$tr.removeClass('searchresult');
|
|
|
|
|
});
|
|
|
|
|
});
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2014-12-18 12:26:41 +03:00
|
|
|
|
/**
|
|
|
|
|
* @deprecated use setFilter(filter)
|
|
|
|
|
*/
|
2014-05-09 00:06:30 +04:00
|
|
|
|
filter:function(query) {
|
2014-12-18 12:26:41 +03:00
|
|
|
|
this.setFilter('');
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* @deprecated use setFilter('')
|
|
|
|
|
*/
|
|
|
|
|
unfilter:function() {
|
|
|
|
|
this.setFilter('');
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* hide files matching the given filter
|
|
|
|
|
* @param filter
|
|
|
|
|
*/
|
|
|
|
|
setFilter:function(filter) {
|
2016-06-23 12:27:02 +03:00
|
|
|
|
var total = 0;
|
2016-06-30 12:10:48 +03:00
|
|
|
|
if (this._filter === filter) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-12-18 12:26:41 +03:00
|
|
|
|
this._filter = filter;
|
2014-12-19 01:11:42 +03:00
|
|
|
|
this.fileSummary.setFilter(filter, this.files);
|
2016-06-23 12:27:02 +03:00
|
|
|
|
total = this.fileSummary.getTotal();
|
2015-01-09 12:49:22 +03:00
|
|
|
|
if (!this.$el.find('.mask').exists()) {
|
|
|
|
|
this.hideIrrelevantUIWhenNoFilesMatch();
|
|
|
|
|
}
|
2016-06-30 12:10:48 +03:00
|
|
|
|
|
2016-06-23 12:27:02 +03:00
|
|
|
|
var visibleCount = 0;
|
2015-10-29 18:30:56 +03:00
|
|
|
|
filter = filter.toLowerCase();
|
2016-06-23 12:27:02 +03:00
|
|
|
|
|
|
|
|
|
function filterRows(tr) {
|
|
|
|
|
var $e = $(tr);
|
2015-10-29 18:30:56 +03:00
|
|
|
|
if ($e.data('file').toString().toLowerCase().indexOf(filter) === -1) {
|
2014-12-18 12:26:41 +03:00
|
|
|
|
$e.addClass('hidden');
|
|
|
|
|
} else {
|
2016-06-23 12:27:02 +03:00
|
|
|
|
visibleCount++;
|
2014-12-18 12:26:41 +03:00
|
|
|
|
$e.removeClass('hidden');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2016-06-23 12:27:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var $trs = this.$fileList.find('tr');
|
|
|
|
|
do {
|
|
|
|
|
_.each($trs, filterRows);
|
|
|
|
|
if (visibleCount < total) {
|
|
|
|
|
$trs = this._nextPage(false);
|
|
|
|
|
}
|
2016-06-30 12:10:48 +03:00
|
|
|
|
} while (visibleCount < total && $trs.length > 0);
|
2016-06-23 12:27:02 +03:00
|
|
|
|
|
|
|
|
|
this.$container.trigger('scroll');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2014-12-19 02:49:05 +03:00
|
|
|
|
hideIrrelevantUIWhenNoFilesMatch:function() {
|
2014-12-19 01:11:42 +03:00
|
|
|
|
if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) {
|
|
|
|
|
this.$el.find('#filestable thead th').addClass('hidden');
|
2014-12-19 02:49:05 +03:00
|
|
|
|
this.$el.find('#emptycontent').addClass('hidden');
|
2015-06-22 16:30:29 +03:00
|
|
|
|
$('#searchresults').addClass('filter-empty');
|
2016-03-31 16:26:37 +03:00
|
|
|
|
$('#searchresults .emptycontent').addClass('emptycontent-search');
|
2015-01-09 12:49:22 +03:00
|
|
|
|
if ( $('#searchresults').length === 0 || $('#searchresults').hasClass('hidden') ) {
|
2017-03-15 14:53:44 +03:00
|
|
|
|
var error = t('files', 'No search results in other folders for {tag}{filter}{endtag}', {filter:this._filter});
|
2015-01-06 16:34:35 +03:00
|
|
|
|
this.$el.find('.nofilterresults').removeClass('hidden').
|
2016-10-11 15:21:37 +03:00
|
|
|
|
find('p').html(error.replace('{tag}', '<strong>').replace('{endtag}', '</strong>'));
|
2015-01-02 14:50:21 +03:00
|
|
|
|
}
|
2014-12-19 01:11:42 +03:00
|
|
|
|
} else {
|
2015-06-22 16:30:29 +03:00
|
|
|
|
$('#searchresults').removeClass('filter-empty');
|
2016-03-31 16:26:37 +03:00
|
|
|
|
$('#searchresults .emptycontent').removeClass('emptycontent-search');
|
2014-12-19 02:49:05 +03:00
|
|
|
|
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
|
2015-01-07 17:59:34 +03:00
|
|
|
|
if (!this.$el.find('.mask').exists()) {
|
|
|
|
|
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
|
|
|
|
|
}
|
2015-01-06 16:34:35 +03:00
|
|
|
|
this.$el.find('.nofilterresults').addClass('hidden');
|
2014-12-19 01:11:42 +03:00
|
|
|
|
}
|
|
|
|
|
},
|
2014-12-18 12:26:41 +03:00
|
|
|
|
/**
|
|
|
|
|
* get the current filter
|
|
|
|
|
* @param filter
|
|
|
|
|
*/
|
|
|
|
|
getFilter:function(filter) {
|
|
|
|
|
return this._filter;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
2015-01-05 15:11:50 +03:00
|
|
|
|
/**
|
|
|
|
|
* update the search object to use this filelist when filtering
|
|
|
|
|
*/
|
|
|
|
|
updateSearch:function() {
|
|
|
|
|
if (OCA.Search.files) {
|
|
|
|
|
OCA.Search.files.setFileList(this);
|
|
|
|
|
}
|
2015-01-05 19:53:14 +03:00
|
|
|
|
if (OC.Search) {
|
|
|
|
|
OC.Search.clear();
|
|
|
|
|
}
|
2015-01-05 15:11:50 +03:00
|
|
|
|
},
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Update UI based on the current selection
|
|
|
|
|
*/
|
|
|
|
|
updateSelectionSummary: function() {
|
|
|
|
|
var summary = this._selectionSummary.summary;
|
2015-08-18 10:31:02 +03:00
|
|
|
|
var selection;
|
|
|
|
|
|
2016-08-19 17:44:58 +03:00
|
|
|
|
var showHidden = !!this._filesConfig.get('showhidden');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (summary.totalFiles === 0 && summary.totalDirs === 0) {
|
|
|
|
|
this.$el.find('#headerName a.name>span:first').text(t('files','Name'));
|
|
|
|
|
this.$el.find('#headerSize a>span:first').text(t('files','Size'));
|
|
|
|
|
this.$el.find('#modified a>span:first').text(t('files','Modified'));
|
|
|
|
|
this.$el.find('table').removeClass('multiselect');
|
|
|
|
|
this.$el.find('.selectedActions').addClass('hidden');
|
2013-08-01 00:24:52 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
else {
|
|
|
|
|
this.$el.find('.selectedActions').removeClass('hidden');
|
|
|
|
|
this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
|
2015-08-14 14:50:25 +03:00
|
|
|
|
|
|
|
|
|
var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
|
|
|
|
|
var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
|
|
|
|
|
|
|
|
|
|
if (summary.totalDirs > 0 && summary.totalFiles > 0) {
|
2015-08-17 15:28:55 +03:00
|
|
|
|
var selectionVars = {
|
|
|
|
|
dirs: directoryInfo,
|
|
|
|
|
files: fileInfo
|
|
|
|
|
};
|
2015-08-18 10:31:02 +03:00
|
|
|
|
selection = t('files', '{dirs} and {files}', selectionVars);
|
2015-08-14 14:50:25 +03:00
|
|
|
|
} else if (summary.totalDirs > 0) {
|
2015-08-18 10:31:02 +03:00
|
|
|
|
selection = directoryInfo;
|
2015-08-14 14:50:25 +03:00
|
|
|
|
} else {
|
2015-08-18 10:31:02 +03:00
|
|
|
|
selection = fileInfo;
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2015-08-14 14:50:25 +03:00
|
|
|
|
|
2016-08-19 17:44:58 +03:00
|
|
|
|
if (!showHidden && summary.totalHidden > 0) {
|
|
|
|
|
var hiddenInfo = n('files', 'including %n hidden', 'including %n hidden', summary.totalHidden);
|
|
|
|
|
selection += ' (' + hiddenInfo + ')';
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
this.$el.find('#headerName a.name>span:first').text(selection);
|
|
|
|
|
this.$el.find('#modified a>span:first').text('');
|
|
|
|
|
this.$el.find('table').addClass('multiselect');
|
2017-11-02 14:47:57 +03:00
|
|
|
|
this.$el.find('.selectedActions .copy-move').toggleClass('hidden', !this.isSelectedCopiableOrMovable());
|
|
|
|
|
this.$el.find('.selectedActions .download').toggleClass('hidden', !this.isSelectedDownloadable());
|
2016-01-15 19:52:51 +03:00
|
|
|
|
this.$el.find('.delete-selected').toggleClass('hidden', !this.isSelectedDeletable());
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
2017-11-02 14:47:57 +03:00
|
|
|
|
/**
|
|
|
|
|
* Check whether all selected files are copiable or movable
|
|
|
|
|
*/
|
|
|
|
|
isSelectedCopiableOrMovable: function() {
|
|
|
|
|
return _.reduce(this.getSelectedFiles(), function(copiableOrMovable, file) {
|
|
|
|
|
return copiableOrMovable && (file.permissions & OC.PERMISSION_UPDATE);
|
|
|
|
|
}, true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether all selected files are downloadable
|
|
|
|
|
*/
|
|
|
|
|
isSelectedDownloadable: function() {
|
|
|
|
|
return _.reduce(this.getSelectedFiles(), function(downloadable, file) {
|
|
|
|
|
return downloadable && (file.permissions & OC.PERMISSION_READ);
|
|
|
|
|
}, true);
|
|
|
|
|
},
|
|
|
|
|
|
2014-11-20 18:53:32 +03:00
|
|
|
|
/**
|
|
|
|
|
* Check whether all selected files are deletable
|
|
|
|
|
*/
|
|
|
|
|
isSelectedDeletable: function() {
|
|
|
|
|
return _.reduce(this.getSelectedFiles(), function(deletable, file) {
|
|
|
|
|
return deletable && (file.permissions & OC.PERMISSION_DELETE);
|
|
|
|
|
}, true);
|
|
|
|
|
},
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Returns whether all files are selected
|
|
|
|
|
* @return true if all files are selected, false otherwise
|
|
|
|
|
*/
|
|
|
|
|
isAllSelected: function() {
|
2014-05-12 21:54:20 +04:00
|
|
|
|
return this.$el.find('.select-all').prop('checked');
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the file info of the selected files
|
|
|
|
|
*
|
|
|
|
|
* @return array of file names
|
|
|
|
|
*/
|
|
|
|
|
getSelectedFiles: function() {
|
|
|
|
|
return _.values(this._selectedFiles);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getUniqueName: function(name) {
|
|
|
|
|
if (this.findFileEl(name).exists()) {
|
|
|
|
|
var numMatch;
|
|
|
|
|
var parts=name.split('.');
|
|
|
|
|
var extension = "";
|
|
|
|
|
if (parts.length > 1) {
|
|
|
|
|
extension=parts.pop();
|
|
|
|
|
}
|
|
|
|
|
var base=parts.join('.');
|
|
|
|
|
numMatch=base.match(/\((\d+)\)/);
|
|
|
|
|
var num=2;
|
|
|
|
|
if (numMatch && numMatch.length>0) {
|
2014-07-04 16:08:48 +04:00
|
|
|
|
num=parseInt(numMatch[numMatch.length-1], 10)+1;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
base=base.split('(');
|
|
|
|
|
base.pop();
|
|
|
|
|
base=$.trim(base.join('('));
|
|
|
|
|
}
|
|
|
|
|
name=base+' ('+num+')';
|
|
|
|
|
if (extension) {
|
|
|
|
|
name = name+'.'+extension;
|
|
|
|
|
}
|
|
|
|
|
// FIXME: ugly recursion
|
|
|
|
|
return this.getUniqueName(name);
|
2014-02-12 17:50:23 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return name;
|
|
|
|
|
},
|
2014-05-12 21:54:20 +04:00
|
|
|
|
|
2014-09-04 21:58:49 +04:00
|
|
|
|
/**
|
|
|
|
|
* Shows a "permission denied" notification
|
|
|
|
|
*/
|
|
|
|
|
_showPermissionDeniedNotification: function() {
|
2017-08-24 15:54:14 +03:00
|
|
|
|
var message = t('files', 'You don’t have permission to upload or create files here');
|
2017-02-14 23:26:00 +03:00
|
|
|
|
OC.Notification.show(message, {type: 'error'});
|
2014-09-04 21:58:49 +04:00
|
|
|
|
},
|
|
|
|
|
|
2014-05-12 21:54:20 +04:00
|
|
|
|
/**
|
|
|
|
|
* Setup file upload events related to the file-upload plugin
|
2016-07-15 17:03:02 +03:00
|
|
|
|
*
|
|
|
|
|
* @param {OC.Uploader} uploader
|
2014-05-12 21:54:20 +04:00
|
|
|
|
*/
|
2016-07-15 17:03:02 +03:00
|
|
|
|
setupUploadEvents: function(uploader) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var self = this;
|
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploads = {};
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2014-05-23 21:02:50 +04:00
|
|
|
|
// detect the progress bar resize
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('resized', this._onResize);
|
2014-05-23 21:02:50 +04:00
|
|
|
|
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('drop', function(e, data) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploader.log('filelist handle fileuploaddrop', e, data);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2015-01-12 14:29:26 +03:00
|
|
|
|
if (self.$el.hasClass('hidden')) {
|
|
|
|
|
// do not upload to invisible lists
|
2017-07-10 15:32:10 +03:00
|
|
|
|
e.preventDefault();
|
2015-01-12 14:29:26 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-14 12:51:03 +03:00
|
|
|
|
var dropTarget = $(e.delegatedEvent.target);
|
|
|
|
|
|
2014-05-22 13:16:42 +04:00
|
|
|
|
// check if dropped inside this container and not another one
|
2015-01-12 14:29:26 +03:00
|
|
|
|
if (dropTarget.length
|
|
|
|
|
&& !self.$el.is(dropTarget) // dropped on list directly
|
|
|
|
|
&& !self.$el.has(dropTarget).length // dropped inside list
|
|
|
|
|
&& !dropTarget.is(self.$container) // dropped on main container
|
|
|
|
|
) {
|
2017-07-10 15:32:10 +03:00
|
|
|
|
e.preventDefault();
|
2014-05-12 21:54:20 +04:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-21 17:55:29 +04:00
|
|
|
|
// find the closest tr or crumb to use as target
|
|
|
|
|
dropTarget = dropTarget.closest('tr, .crumb');
|
|
|
|
|
|
|
|
|
|
// if dropping on tr or crumb, drag&drop upload to folder
|
|
|
|
|
if (dropTarget && (dropTarget.data('type') === 'dir' ||
|
|
|
|
|
dropTarget.hasClass('crumb'))) {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
|
|
|
|
// remember as context
|
|
|
|
|
data.context = dropTarget;
|
|
|
|
|
|
2014-09-04 21:58:49 +04:00
|
|
|
|
// if permissions are specified, only allow if create permission is there
|
|
|
|
|
var permissions = dropTarget.data('permissions');
|
|
|
|
|
if (!_.isUndefined(permissions) && (permissions & OC.PERMISSION_CREATE) === 0) {
|
|
|
|
|
self._showPermissionDeniedNotification();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
var dir = dropTarget.data('file');
|
|
|
|
|
// if from file list, need to prepend parent dir
|
|
|
|
|
if (dir) {
|
|
|
|
|
var parentDir = self.getCurrentDirectory();
|
|
|
|
|
if (parentDir[parentDir.length - 1] !== '/') {
|
|
|
|
|
parentDir += '/';
|
|
|
|
|
}
|
|
|
|
|
dir = parentDir + dir;
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
// read full path from crumb
|
|
|
|
|
dir = dropTarget.data('dir') || '/';
|
|
|
|
|
}
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2014-06-18 22:38:51 +04:00
|
|
|
|
// add target dir
|
|
|
|
|
data.targetDir = dir;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
} else {
|
|
|
|
|
// cancel uploads to current dir if no permission
|
2014-05-12 21:54:20 +04:00
|
|
|
|
var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (!isCreatable) {
|
2014-09-04 21:58:49 +04:00
|
|
|
|
self._showPermissionDeniedNotification();
|
2017-08-24 15:55:17 +03:00
|
|
|
|
e.stopPropagation();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2016-08-31 17:32:14 +03:00
|
|
|
|
// we are dropping somewhere inside the file list, which will
|
|
|
|
|
// upload the file to the current directory
|
|
|
|
|
data.targetDir = self.getCurrentDirectory();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
});
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('add', function(e, data) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploader.log('filelist handle fileuploadadd', e, data);
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
// add ui visualization to existing folder
|
|
|
|
|
if (data.context && data.context.data('type') === 'dir') {
|
|
|
|
|
// add to existing folder
|
|
|
|
|
|
|
|
|
|
// update upload counter ui
|
|
|
|
|
var uploadText = data.context.find('.uploadtext');
|
|
|
|
|
var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
|
|
|
|
|
currentUploads += 1;
|
|
|
|
|
uploadText.attr('currentUploads', currentUploads);
|
|
|
|
|
|
|
|
|
|
var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
|
|
|
|
|
if (currentUploads === 1) {
|
2015-12-17 14:00:29 +03:00
|
|
|
|
self.showFileBusyState(uploadText.closest('tr'), true);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
uploadText.text(translatedText);
|
|
|
|
|
uploadText.show();
|
|
|
|
|
} else {
|
|
|
|
|
uploadText.text(translatedText);
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
if (!data.targetDir) {
|
|
|
|
|
data.targetDir = self.getCurrentDirectory();
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
|
|
|
|
/*
|
|
|
|
|
* when file upload done successfully add row to filelist
|
|
|
|
|
* update counter when uploading to sub folder
|
|
|
|
|
*/
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('done', function(e, upload) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploader.log('filelist handle fileuploaddone', e, data);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
|
2016-07-15 17:03:02 +03:00
|
|
|
|
var data = upload.data;
|
2015-12-16 19:35:53 +03:00
|
|
|
|
var status = data.jqXHR.status;
|
|
|
|
|
if (status < 200 || status >= 300) {
|
|
|
|
|
// error was handled in OC.Uploads already
|
|
|
|
|
return;
|
2013-10-15 18:14:23 +04:00
|
|
|
|
}
|
2014-05-26 22:32:24 +04:00
|
|
|
|
|
2015-12-16 19:35:53 +03:00
|
|
|
|
var fileName = upload.getFileName();
|
|
|
|
|
var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
|
|
|
|
|
if (!self._uploads) {
|
|
|
|
|
self._uploads = {};
|
|
|
|
|
}
|
|
|
|
|
if (OC.isSamePath(OC.dirname(upload.getFullPath() + '/'), self.getCurrentDirectory())) {
|
|
|
|
|
self._uploads[fileName] = fetchInfoPromise;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
2015-12-17 14:00:29 +03:00
|
|
|
|
|
|
|
|
|
var uploadText = self.$fileList.find('tr .uploadtext');
|
|
|
|
|
self.showFileBusyState(uploadText.closest('tr'), false);
|
|
|
|
|
uploadText.fadeOut();
|
|
|
|
|
uploadText.attr('currentUploads', 0);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
2016-09-03 19:06:35 +03:00
|
|
|
|
uploader.on('createdfolder', function(fullPath) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
|
|
|
|
|
});
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('stop', function() {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploader.log('filelist handle fileuploadstop');
|
|
|
|
|
|
|
|
|
|
// prepare list of uploaded file names in the current directory
|
|
|
|
|
// and discard the other ones
|
|
|
|
|
var promises = _.values(self._uploads);
|
|
|
|
|
var fileNames = _.keys(self._uploads);
|
|
|
|
|
self._uploads = [];
|
|
|
|
|
|
|
|
|
|
// as soon as all info is fetched
|
|
|
|
|
$.when.apply($, promises).then(function() {
|
|
|
|
|
// highlight uploaded files
|
|
|
|
|
self.highlightFiles(fileNames);
|
2016-09-21 19:49:15 +03:00
|
|
|
|
self.updateStorageStatistics();
|
2015-12-16 19:35:53 +03:00
|
|
|
|
});
|
2016-09-03 19:06:35 +03:00
|
|
|
|
|
|
|
|
|
var uploadText = self.$fileList.find('tr .uploadtext');
|
|
|
|
|
self.showFileBusyState(uploadText.closest('tr'), false);
|
|
|
|
|
uploadText.fadeOut();
|
|
|
|
|
uploadText.attr('currentUploads', 0);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
2016-07-15 17:03:02 +03:00
|
|
|
|
uploader.on('fail', function(e, data) {
|
2015-12-16 19:35:53 +03:00
|
|
|
|
self._uploader.log('filelist handle fileuploadfail', e, data);
|
|
|
|
|
self._uploads = [];
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
//if user pressed cancel hide upload chrome
|
2016-09-03 19:06:35 +03:00
|
|
|
|
//cleanup uploading to a dir
|
|
|
|
|
var uploadText = self.$fileList.find('tr .uploadtext');
|
|
|
|
|
self.showFileBusyState(uploadText.closest('tr'), false);
|
|
|
|
|
uploadText.fadeOut();
|
|
|
|
|
uploadText.attr('currentUploads', 0);
|
2014-05-12 21:54:20 +04:00
|
|
|
|
self.updateStorageStatistics();
|
2014-05-09 00:06:30 +04:00
|
|
|
|
});
|
2013-03-13 20:26:37 +04:00
|
|
|
|
|
2014-06-19 22:11:57 +04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scroll to the last file of the given list
|
|
|
|
|
* Highlight the list of files
|
2014-09-04 14:20:11 +04:00
|
|
|
|
* @param files array of filenames,
|
|
|
|
|
* @param {Function} [highlightFunction] optional function
|
|
|
|
|
* to be called after the scrolling is finished
|
2014-06-19 22:11:57 +04:00
|
|
|
|
*/
|
2014-09-04 14:20:11 +04:00
|
|
|
|
highlightFiles: function(files, highlightFunction) {
|
2014-06-19 22:11:57 +04:00
|
|
|
|
// Detection of the uploaded element
|
|
|
|
|
var filename = files[files.length - 1];
|
|
|
|
|
var $fileRow = this.findFileEl(filename);
|
|
|
|
|
|
|
|
|
|
while(!$fileRow.exists() && this._nextPage(false) !== false) { // Checking element existence
|
|
|
|
|
$fileRow = this.findFileEl(filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$fileRow.exists()) { // Element not present in the file list
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentOffset = this.$container.scrollTop();
|
|
|
|
|
var additionalOffset = this.$el.find("#controls").height()+this.$el.find("#controls").offset().top;
|
|
|
|
|
|
|
|
|
|
// Animation
|
|
|
|
|
var _this = this;
|
2014-10-15 12:14:20 +04:00
|
|
|
|
var $scrollContainer = this.$container;
|
|
|
|
|
if ($scrollContainer[0] === window) {
|
|
|
|
|
// need to use "body" to animate scrolling
|
|
|
|
|
// when the scroll container is the window
|
|
|
|
|
$scrollContainer = $('body');
|
|
|
|
|
}
|
|
|
|
|
$scrollContainer.animate({
|
2014-06-19 22:11:57 +04:00
|
|
|
|
// Scrolling to the top of the new element
|
|
|
|
|
scrollTop: currentOffset + $fileRow.offset().top - $fileRow.height() * 2 - additionalOffset
|
|
|
|
|
}, {
|
|
|
|
|
duration: 500,
|
|
|
|
|
complete: function() {
|
|
|
|
|
// Highlighting function
|
2014-09-04 14:20:11 +04:00
|
|
|
|
var highlightRow = highlightFunction;
|
|
|
|
|
|
|
|
|
|
if (!highlightRow) {
|
|
|
|
|
highlightRow = function($fileRow) {
|
|
|
|
|
$fileRow.addClass("highlightUploaded");
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
$fileRow.removeClass("highlightUploaded");
|
|
|
|
|
}, 2500);
|
|
|
|
|
};
|
|
|
|
|
}
|
2014-06-19 22:11:57 +04:00
|
|
|
|
|
|
|
|
|
// Loop over uploaded files
|
|
|
|
|
for(var i=0; i<files.length; i++) {
|
|
|
|
|
var $fileRow = _this.findFileEl(files[i]);
|
|
|
|
|
|
|
|
|
|
if($fileRow.length !== 0) { // Checking element existence
|
|
|
|
|
highlightRow($fileRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-07-15 18:05:25 +03:00
|
|
|
|
},
|
|
|
|
|
|
2015-08-27 14:22:58 +03:00
|
|
|
|
_renderNewButton: function() {
|
2015-09-22 17:47:52 +03:00
|
|
|
|
// if an upload button (legacy) already exists or no actions container exist, skip
|
|
|
|
|
var $actionsContainer = this.$el.find('#controls .actions');
|
|
|
|
|
if (!$actionsContainer.length || this.$el.find('.button.upload').length) {
|
2015-08-27 14:22:58 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._addButtonTemplate) {
|
|
|
|
|
this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON);
|
|
|
|
|
}
|
|
|
|
|
var $newButton = $(this._addButtonTemplate({
|
|
|
|
|
addText: t('files', 'New'),
|
2016-02-17 13:04:29 +03:00
|
|
|
|
iconClass: 'icon-add'
|
2015-08-27 14:22:58 +03:00
|
|
|
|
}));
|
|
|
|
|
|
2015-09-22 17:47:52 +03:00
|
|
|
|
$actionsContainer.prepend($newButton);
|
2015-08-27 14:22:58 +03:00
|
|
|
|
$newButton.tooltip({'placement': 'bottom'});
|
|
|
|
|
|
|
|
|
|
$newButton.click(_.bind(this._onClickNewButton, this));
|
|
|
|
|
this._newButton = $newButton;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_onClickNewButton: function(event) {
|
|
|
|
|
var $target = $(event.target);
|
|
|
|
|
if (!$target.hasClass('.button')) {
|
|
|
|
|
$target = $target.closest('.button');
|
|
|
|
|
}
|
|
|
|
|
this._newButton.tooltip('hide');
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
if ($target.hasClass('disabled')) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!this._newFileMenu) {
|
|
|
|
|
this._newFileMenu = new OCA.Files.NewFileMenu({
|
|
|
|
|
fileList: this
|
|
|
|
|
});
|
2017-01-21 22:05:58 +03:00
|
|
|
|
$('.actions').append(this._newFileMenu.$el);
|
2015-08-27 14:22:58 +03:00
|
|
|
|
}
|
|
|
|
|
this._newFileMenu.showAt($target);
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
2015-07-15 18:05:25 +03:00
|
|
|
|
/**
|
|
|
|
|
* Register a tab view to be added to all views
|
|
|
|
|
*/
|
|
|
|
|
registerTabView: function(tabView) {
|
2015-10-15 17:30:50 +03:00
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
this._detailsView.addTabView(tabView);
|
|
|
|
|
}
|
2015-07-15 18:05:25 +03:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a detail view to be added to all views
|
|
|
|
|
*/
|
|
|
|
|
registerDetailView: function(detailView) {
|
2015-10-15 17:30:50 +03:00
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
this._detailsView.addDetailView(detailView);
|
|
|
|
|
}
|
2016-10-04 13:56:04 +03:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a view to be added to the breadcrumb view
|
|
|
|
|
*/
|
|
|
|
|
registerBreadCrumbDetailView: function(detailView) {
|
|
|
|
|
if (this.breadcrumb) {
|
|
|
|
|
this.breadcrumb.addDetailView(detailView);
|
|
|
|
|
}
|
2017-06-09 04:14:23 +03:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the registered detail views.
|
|
|
|
|
*
|
|
|
|
|
* @return null|Array<OCA.Files.DetailFileInfoView> an array with the
|
|
|
|
|
* registered DetailFileInfoViews, or null if the details view
|
|
|
|
|
* is not enabled.
|
|
|
|
|
*/
|
|
|
|
|
getRegisteredDetailViews: function() {
|
|
|
|
|
if (this._detailsView) {
|
|
|
|
|
return this._detailsView.getDetailViews();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
2014-05-09 00:06:30 +04:00
|
|
|
|
}
|
|
|
|
|
};
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
/**
|
|
|
|
|
* Sort comparators.
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @namespace OCA.Files.FileList.Comparators
|
|
|
|
|
* @private
|
2014-05-09 00:06:30 +04:00
|
|
|
|
*/
|
|
|
|
|
FileList.Comparators = {
|
|
|
|
|
/**
|
|
|
|
|
* Compares two file infos by name, making directories appear
|
|
|
|
|
* first.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo1 file info
|
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo2 file info
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @return {int} -1 if the first file must appear before the second one,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* 0 if they are identify, 1 otherwise.
|
|
|
|
|
*/
|
|
|
|
|
name: function(fileInfo1, fileInfo2) {
|
|
|
|
|
if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
|
|
|
|
|
return -1;
|
2013-03-13 20:26:37 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2014-02-18 15:29:05 +04:00
|
|
|
|
return OC.Util.naturalSortCompare(fileInfo1.name, fileInfo2.name);
|
2014-05-09 00:06:30 +04:00
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Compares two file infos by size.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo1 file info
|
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo2 file info
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @return {int} -1 if the first file must appear before the second one,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* 0 if they are identify, 1 otherwise.
|
|
|
|
|
*/
|
|
|
|
|
size: function(fileInfo1, fileInfo2) {
|
|
|
|
|
return fileInfo1.size - fileInfo2.size;
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Compares two file infos by timestamp.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo1 file info
|
|
|
|
|
* @param {OC.Files.FileInfo} fileInfo2 file info
|
2014-06-24 01:56:10 +04:00
|
|
|
|
* @return {int} -1 if the first file must appear before the second one,
|
2014-05-09 00:06:30 +04:00
|
|
|
|
* 0 if they are identify, 1 otherwise.
|
|
|
|
|
*/
|
|
|
|
|
mtime: function(fileInfo1, fileInfo2) {
|
|
|
|
|
return fileInfo1.mtime - fileInfo2.mtime;
|
2013-03-13 20:26:37 +04:00
|
|
|
|
}
|
2014-05-09 00:06:30 +04:00
|
|
|
|
};
|
2013-09-08 19:29:43 +04:00
|
|
|
|
|
2014-06-24 01:56:10 +04:00
|
|
|
|
/**
|
|
|
|
|
* File info attributes.
|
|
|
|
|
*
|
2015-07-13 18:38:13 +03:00
|
|
|
|
* @typedef {Object} OC.Files.FileInfo
|
|
|
|
|
*
|
|
|
|
|
* @lends OC.Files.FileInfo
|
|
|
|
|
*
|
|
|
|
|
* @deprecated use OC.Files.FileInfo instead
|
2014-06-24 01:56:10 +04:00
|
|
|
|
*
|
|
|
|
|
*/
|
2015-07-13 18:38:13 +03:00
|
|
|
|
OCA.Files.FileInfo = OC.Files.FileInfo;
|
2014-06-24 01:56:10 +04:00
|
|
|
|
|
2014-05-09 00:06:30 +04:00
|
|
|
|
OCA.Files.FileList = FileList;
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
$(document).ready(function() {
|
|
|
|
|
// FIXME: unused ?
|
|
|
|
|
OCA.Files.FileList.useUndo = (window.onbeforeunload)?true:false;
|
2013-10-22 20:11:03 +04:00
|
|
|
|
$(window).bind('beforeunload', function () {
|
2014-05-09 00:06:30 +04:00
|
|
|
|
if (OCA.Files.FileList.lastAction) {
|
|
|
|
|
OCA.Files.FileList.lastAction();
|
2012-07-30 20:21:58 +04:00
|
|
|
|
}
|
2011-08-04 02:22:44 +04:00
|
|
|
|
});
|
2016-04-19 13:05:09 +03:00
|
|
|
|
$(window).on('unload', function () {
|
2012-11-05 21:42:44 +04:00
|
|
|
|
$(window).trigger('beforeunload');
|
|
|
|
|
});
|
2013-07-03 21:50:03 +04:00
|
|
|
|
|
2011-08-04 02:22:44 +04:00
|
|
|
|
});
|