aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2014-04-28 17:39:02 +0200
committerThomas Müller <thomas.mueller@tmit.eu>2014-04-28 17:39:02 +0200
commite055a411ea4b2a32dcf20c910d332867dc91f516 (patch)
treeb2cc655c7f5277e23386d88ae80af6b6c948c84f
parentbe6431bab05265835df79ec1245ccd7df900cca7 (diff)
parentbf61d841a2b3305bc51de6109917725466239061 (diff)
downloadnextcloud-server-e055a411ea4b2a32dcf20c910d332867dc91f516.tar.gz
nextcloud-server-e055a411ea4b2a32dcf20c910d332867dc91f516.zip
Merge pull request #7167 from owncloud/files-ajaxload-infscroll
Infinite scrolling for files app
-rw-r--r--apps/files/css/files.css1
-rw-r--r--apps/files/index.php1
-rw-r--r--apps/files/js/file-upload.js6
-rw-r--r--apps/files/js/filelist.js798
-rw-r--r--apps/files/js/files.js334
-rw-r--r--apps/files/js/filesummary.js195
-rw-r--r--apps/files/templates/index.php2
-rw-r--r--apps/files/tests/js/fileactionsSpec.js1
-rw-r--r--apps/files/tests/js/filelistSpec.js727
-rw-r--r--apps/files/tests/js/filesSpec.js28
-rw-r--r--apps/files/tests/js/filesummarySpec.js87
-rw-r--r--apps/files_sharing/public.php1
-rw-r--r--apps/files_trashbin/index.php1
-rw-r--r--apps/files_trashbin/js/filelist.js132
-rw-r--r--apps/files_trashbin/js/trash.js161
-rw-r--r--apps/files_trashbin/templates/index.php4
-rw-r--r--core/css/apps.css12
-rw-r--r--core/js/js.js5
-rw-r--r--core/js/tests/specs/coreSpec.js17
19 files changed, 1742 insertions, 771 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 474f1af0720..533050691d5 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -310,7 +310,6 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; }
/* Actions for selected files */
.selectedActions {
- display: none;
position: absolute;
top: -1px;
right: 0;
diff --git a/apps/files/index.php b/apps/files/index.php
index b8ff08c1b05..a4e9a938507 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -32,6 +32,7 @@ OCP\Util::addscript('files', 'file-upload');
OCP\Util::addscript('files', 'jquery.iframe-transport');
OCP\Util::addscript('files', 'jquery.fileupload');
OCP\Util::addscript('files', 'jquery-visibility');
+OCP\Util::addscript('files', 'filesummary');
OCP\Util::addscript('files', 'breadcrumb');
OCP\Util::addscript('files', 'filelist');
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 03ebdccb32d..963fc647828 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -606,7 +606,7 @@ OC.Upload = {
{dir:$('#dir').val(), filename:name},
function(result) {
if (result.status === 'success') {
- FileList.add(result.data, {hidden: hidden, insert: true});
+ FileList.add(result.data, {hidden: hidden, animate: true});
} else {
OC.dialogs.alert(result.data.message, t('core', 'Could not create file'));
}
@@ -619,7 +619,7 @@ OC.Upload = {
{dir:$('#dir').val(), foldername:name},
function(result) {
if (result.status === 'success') {
- FileList.add(result.data, {hidden: hidden, insert: true});
+ FileList.add(result.data, {hidden: hidden, animate: true});
} else {
OC.dialogs.alert(result.data.message, t('core', 'Could not create folder'));
}
@@ -657,7 +657,7 @@ OC.Upload = {
var file = data;
$('#uploadprogressbar').fadeOut();
- FileList.add(file, {hidden: hidden, insert: true});
+ FileList.add(file, {hidden: hidden, animate: true});
});
eventSource.listen('error',function(error) {
$('#uploadprogressbar').fadeOut();
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index c33b638b5a6..40ec898635e 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -8,8 +8,8 @@
*
*/
-/* global OC, t, n, FileList, FileActions, Files, BreadCrumb */
-/* global procesSelection, dragOptions, folderDropOptions */
+/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
+/* global dragOptions, folderDropOptions */
window.FileList = {
appName: t('files', 'Files'),
isEmpty: true,
@@ -17,8 +17,47 @@ window.FileList = {
$el: $('#filestable'),
$fileList: $('#fileList'),
breadcrumb: null,
+
+ /**
+ * Instance of FileSummary
+ */
+ fileSummary: null,
initialized: false,
+ // number of files per page
+ pageSize: 20,
+
+ /**
+ * Array of files in the current folder.
+ * The entries are of file data.
+ */
+ files: [],
+
+ /**
+ * Map of file id to file data
+ */
+ _selectedFiles: {},
+
+ /**
+ * Summary of selected files.
+ * Instance of FileSummary.
+ */
+ _selectionSummary: null,
+
+ /**
+ * Compare two file info objects, sorting by
+ * folders first, then by name.
+ */
+ _fileInfoCompare: function(fileInfo1, fileInfo2) {
+ if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
+ return -1;
+ }
+ if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
+ return 1;
+ }
+ return fileInfo1.name.localeCompare(fileInfo2.name);
+ },
+
/**
* Initialize the file list and its components
*/
@@ -31,10 +70,15 @@ window.FileList = {
// TODO: FileList should not know about global elements
this.$el = $('#filestable');
this.$fileList = $('#fileList');
+ this.files = [];
+ this._selectedFiles = {};
+ this._selectionSummary = new FileSummary();
+
+ this.fileSummary = this._createSummary();
this.breadcrumb = new BreadCrumb({
onClick: this._onClickBreadCrumb,
- onDrop: this._onDropOnBreadCrumb,
+ onDrop: _.bind(this._onDropOnBreadCrumb, this),
getCrumbUrl: function(part, index) {
return self.linkTo(part.dir);
}
@@ -47,6 +91,149 @@ window.FileList = {
var width = $(this).width();
FileList.breadcrumb.resize(width, false);
});
+
+ this.$fileList.on('click','td.filename a', _.bind(this._onClickFile, this));
+ this.$fileList.on('change', 'td.filename input:checkbox', _.bind(this._onClickFileCheckbox, this));
+ this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this));
+ this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
+ this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this));
+ },
+
+ /**
+ * Selected/deselects the given file element and updated
+ * the internal selection cache.
+ *
+ * @param $tr single file row element
+ * @param state true to select, false to deselect
+ */
+ _selectFileEl: function($tr, state) {
+ var $checkbox = $tr.find('input:checkbox');
+ 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);
+ }
+ this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
+ },
+
+ /**
+ * Event handler for when clicking on files to select them
+ */
+ _onClickFile: function(event) {
+ var $tr = $(event.target).closest('tr');
+ if (event.ctrlKey || event.shiftKey) {
+ 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;
+ }
+
+ // auto-select everything in-between
+ for (var i = lastIndex + 1; i < currentIndex; i++) {
+ this._selectFileEl($rows.eq(i), true);
+ }
+ }
+ else {
+ this._lastChecked = $tr;
+ }
+ var $checkbox = $tr.find('input:checkbox');
+ this._selectFileEl($tr, !$checkbox.prop('checked'));
+ this.updateSelectionSummary();
+ } else {
+ var filename = $tr.attr('data-file');
+ var renaming = $tr.data('renaming');
+ if (!renaming) {
+ FileActions.currentFile = $tr.find('td');
+ var mime=FileActions.getCurrentMimeType();
+ var type=FileActions.getCurrentType();
+ var permissions = FileActions.getCurrentPermissions();
+ var action=FileActions.getDefault(mime,type, permissions);
+ if (action) {
+ event.preventDefault();
+ action(filename);
+ }
+ }
+ }
+ },
+
+ /**
+ * Event handler for when clicking on a file's checkbox
+ */
+ _onClickFileCheckbox: function(e) {
+ var $tr = $(e.target).closest('tr');
+ this._selectFileEl($tr, !$tr.hasClass('selected'));
+ this._lastChecked = $tr;
+ this.updateSelectionSummary();
+ },
+
+ /**
+ * Event handler for when selecting/deselecting all files
+ */
+ _onClickSelectAll: function(e) {
+ var checked = $(e.target).prop('checked');
+ this.$fileList.find('td.filename input:checkbox').prop('checked', checked)
+ .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);
+ }
+ }
+ this.updateSelectionSummary();
+ },
+
+ /**
+ * Event handler for when clicking on "Download" for the selected files
+ */
+ _onClickDownloadSelected: function(event) {
+ var files;
+ var dir = this.getCurrentDirectory();
+ if (this.isAllSelected()) {
+ files = OC.basename(dir);
+ dir = OC.dirname(dir) || '/';
+ }
+ else {
+ files = _.pluck(this.getSelectedFiles(), 'name');
+ }
+ OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
+ OC.redirect(Files.getDownloadUrl(files, dir));
+ return false;
+ },
+
+ /**
+ * Event handler for when clicking on "Delete" for the selected files
+ */
+ _onClickDeleteSelected: function(event) {
+ var files = null;
+ if (!FileList.isAllSelected()) {
+ files = _.pluck(this.getSelectedFiles(), 'name');
+ }
+ this.do_delete(files);
+ event.preventDefault();
+ return false;
},
/**
@@ -62,48 +249,41 @@ window.FileList = {
}
},
+ _onScroll: function(e) {
+ if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
+ this._nextPage(true);
+ }
+ },
+
/**
* Event handler when dropping on a breadcrumb
*/
_onDropOnBreadCrumb: function( event, ui ) {
- var target=$(this).data('dir');
- var dir = FileList.getCurrentDirectory();
- while(dir.substr(0,1) === '/') {//remove extra leading /'s
- dir=dir.substr(1);
+ var $target = $(event.target);
+ if (!$target.is('.crumb')) {
+ $target = $target.closest('.crumb');
+ }
+ 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 + '/';
}
- if (target === dir || target+'/' === dir) {
+ // do nothing if dragged on current dir
+ if (targetPath === dir || targetPath + '/' === dir) {
return;
}
- var files = ui.helper.find('tr');
- $(files).each(function(i,row) {
- var dir = $(row).data('dir');
- var file = $(row).data('filename');
- //slapdash selector, tracking down our original element that the clone budded off of.
- var origin = $('tr[data-id=' + $(row).data('origin') + ']');
- var td = origin.children('td.filename');
- var oldBackgroundImage = td.css('background-image');
- td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
- $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) {
- if (result) {
- if (result.status === 'success') {
- FileList.remove(file);
- procesSelection();
- $('#notification').hide();
- } else {
- $('#notification').hide();
- $('#notification').text(result.data.message);
- $('#notification').fadeIn();
- }
- } else {
- OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
- }
- td.css('background-image', oldBackgroundImage);
- });
- });
+
+ var files = this.getSelectedFiles();
+ if (files.length === 0) {
+ // single one selected without checkbox?
+ files = _.map(ui.helper.find('tr'), FileList.elementToFile);
+ }
+
+ FileList.move(_.pluck(files, 'name'), targetPath);
},
/**
@@ -129,21 +309,83 @@ window.FileList = {
// 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);
+ return {
+ id: parseInt($el.attr('data-id'), 10),
+ name: $el.attr('data-file'),
+ mimetype: $el.attr('data-mime'),
+ type: $el.attr('data-type'),
+ size: parseInt($el.attr('data-size'), 10),
+ etag: $el.attr('data-etag')
+ };
+ },
+
+ /**
+ * Appends the next page of files into the table
+ * @param animate true to animate the new elements
+ */
+ _nextPage: function(animate) {
+ var index = this.$fileList.children().length,
+ count = this.pageSize,
+ tr,
+ fileData,
+ newTrs = [],
+ isAllSelected = this.isAllSelected();
+
+ if (index >= this.files.length) {
+ return;
+ }
+
+ while (count > 0 && index < this.files.length) {
+ fileData = this.files[index];
+ tr = this._renderRow(fileData, {updateSummary: false});
+ this.$fileList.append(tr);
+ if (isAllSelected || this._selectedFiles[fileData.id]) {
+ tr.addClass('selected');
+ tr.find('input:checkbox').prop('checked', true);
+ }
+ if (animate) {
+ tr.addClass('appear transparent');
+ newTrs.push(tr);
+ }
+ index++;
+ count--;
+ }
+
+ if (animate) {
+ // defer, for animation
+ window.setTimeout(function() {
+ for (var i = 0; i < newTrs.length; i++ ) {
+ newTrs[i].removeClass('transparent');
+ }
+ }, 0);
+ }
+ },
+
/**
* Sets the files to be displayed in the list.
- * This operation will rerender the list and update the summary.
+ * This operation will re-render the list and update the summary.
* @param filesArray array of file data (map)
*/
- setFiles:function(filesArray) {
+ setFiles: function(filesArray) {
// detach to make adding multiple rows faster
- this.$fileList.detach();
+ this.files = filesArray;
+ this.$fileList.detach();
this.$fileList.empty();
- this.isEmpty = filesArray.length === 0;
- for (var i = 0; i < filesArray.length; i++) {
- this.add(filesArray[i], {updateSummary: false});
- }
+ // clear "Select all" checkbox
+ this.$el.find('#select_all').prop('checked', false);
+
+ this.isEmpty = this.files.length === 0;
+ this._nextPage();
this.$el.find('thead').after(this.$fileList);
@@ -153,8 +395,10 @@ window.FileList = {
if (window.Files) {
Files.setupDragAndDrop();
}
- this.updateFileSummary();
- procesSelection();
+
+ this.fileSummary.calculate(filesArray);
+
+ FileList.updateSelectionSummary();
$(window).scrollTop(0);
this.$fileList.trigger(jQuery.Event("updated"));
@@ -276,15 +520,82 @@ window.FileList = {
tr.append(td);
return tr;
},
+
/**
- * Adds an entry to the files table using the data from the given file data
+ * Adds an entry to the files array and also into the DOM
+ * in a sorted manner.
+ *
* @param fileData map of file attributes
* @param options map of attributes:
- * - "insert" true to insert in a sorted manner, false to append (default)
* - "updateSummary" true to update the summary after adding (default), false otherwise
* @return new tr element (not appended to the table)
*/
add: function(fileData, options) {
+ var index = -1;
+ var $tr;
+ var $rows;
+ var $insertionPoint;
+ options = options || {};
+
+ // 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);
+ }
+
+ // 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);
+ }
+ }
+
+ this.isEmpty = false;
+ this.files.splice(index, 0, fileData);
+
+ if ($tr && options.animate) {
+ $tr.addClass('appear transparent');
+ window.setTimeout(function() {
+ $tr.removeClass('transparent');
+ });
+ }
+
+ // defaults to true if not defined
+ if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+ this.fileSummary.add(fileData, true);
+ this.updateEmptyContent();
+ }
+
+ return $tr;
+ },
+
+ /**
+ * Creates a new row element based on the given attributes
+ * and returns it.
+ *
+ * @param fileData map of file attributes
+ * @param options map of attributes:
+ * - "index" optional index at which to insert the element
+ * - "updateSummary" true to update the summary after adding (default), false otherwise
+ * @return new tr element (not appended to the table)
+ */
+ _renderRow: function(fileData, options) {
options = options || {};
var type = fileData.type || 'file',
mime = fileData.mimetype,
@@ -303,16 +614,6 @@ window.FileList = {
);
var filenameTd = tr.find('td.filename');
- // sorted insert is expensive, so needs to be explicitly
- // requested
- if (options.insert) {
- this.insertElement(fileData.name, type, tr);
- }
- else {
- this.$fileList.append(tr);
- }
- FileList.isEmpty = false;
-
// TODO: move dragging to FileActions ?
// enable drag only for deletable files
if (permissions & OC.PERMISSION_DELETE) {
@@ -348,12 +649,6 @@ window.FileList = {
filenameTd.css('background-image', 'url(' + previewUrl + ')');
}
}
-
- // defaults to true if not defined
- if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
- this.updateFileSummary();
- this.updateEmptyContent();
- }
return tr;
},
/**
@@ -378,7 +673,6 @@ window.FileList = {
*/
changeDirectory: function(targetDir, changeUrl, force) {
var $dir = $('#dir'),
- url,
currentDir = $dir.val() || '/';
targetDir = targetDir || '/';
if (!force && currentDir === targetDir) {
@@ -391,7 +685,9 @@ window.FileList = {
previousDir: currentDir
}
));
- FileList.reload();
+ this._selectedFiles = {};
+ this._selectionSummary.clear();
+ this.reload();
},
linkTo: function(dir) {
return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
@@ -519,60 +815,130 @@ window.FileList = {
* @param name name of the file to remove
* @param options optional options as map:
* "updateSummary": true to update the summary (default), false otherwise
+ * @return deleted element
*/
- remove:function(name, options){
+ remove: function(name, options){
options = options || {};
var fileEl = FileList.findFileEl(name);
+ var index = fileEl.index();
+ if (!fileEl.length) {
+ return null;
+ }
+ if (this._selectedFiles[fileEl.data('id')]) {
+ // remove from selection first
+ this._selectFileEl(fileEl, false);
+ this.updateSelectionSummary();
+ }
if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
// file is only draggable when delete permissions are set
fileEl.find('td.filename').draggable('destroy');
}
+ this.files.splice(index, 1);
fileEl.remove();
// TODO: improve performance on batch update
- FileList.isEmpty = !this.$fileList.find('tr:not(.summary)').length;
+ FileList.isEmpty = !this.files.length;
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
FileList.updateEmptyContent();
- FileList.updateFileSummary();
+ this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
+ }
+
+ 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
+ if (lastIndex < this.files.length && lastIndex < this.pageSize) {
+ this._nextPage(true);
}
+
return fileEl;
},
- insertElement:function(name, type, element) {
- // find the correct spot to insert the file or folder
- var pos,
- fileElements = this.$fileList.find('tr[data-file][data-type="'+type+'"]:not(.hidden)');
- if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) {
- pos = -1;
- } else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) {
- pos = fileElements.length - 1;
- } else {
- for(pos = 0; pos<fileElements.length-1; pos++) {
- if (name.localeCompare($(fileElements[pos]).attr('data-file')) > 0
- && name.localeCompare($(fileElements[pos+1]).attr('data-file')) < 0)
- {
- break;
- }
- }
- }
- if (fileElements.exists()) {
- if (pos === -1) {
- $(fileElements[0]).before(element);
- } else {
- $(fileElements[pos]).after(element);
- }
- } else if (type === 'dir' && !FileList.isEmpty) {
- this.$fileList.find('tr[data-file]:first').before(element);
- } else if (type === 'file' && !FileList.isEmpty) {
- this.$fileList.find('tr[data-file]:last').before(element);
- } else {
- this.$fileList.append(element);
+ /**
+ * Finds the index of the row before which the given
+ * fileData should be inserted, considering the current
+ * sorting
+ */
+ _findInsertionIndex: function(fileData) {
+ var index = 0;
+ while (index < this.files.length && this._fileInfoCompare(fileData, this.files[index]) > 0) {
+ index++;
}
- FileList.isEmpty = false;
- FileList.updateEmptyContent();
- FileList.updateFileSummary();
+ return index;
},
+ /**
+ * Moves a file to a given target folder.
+ *
+ * @param fileNames array of file names to move
+ * @param targetPath absolute target path
+ */
+ move: function(fileNames, targetPath) {
+ var self = this;
+ var dir = this.getCurrentDirectory();
+ var target = OC.basename(targetPath);
+ if (!_.isArray(fileNames)) {
+ fileNames = [fileNames];
+ }
+ _.each(fileNames, function(fileName) {
+ var $tr = self.findFileEl(fileName);
+ var $td = $tr.children('td.filename');
+ var oldBackgroundImage = $td.css('background-image');
+ $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+ // TODO: improve performance by sending all file names in a single call
+ $.post(
+ OC.filePath('files', 'ajax', 'move.php'),
+ {
+ dir: dir,
+ file: fileName,
+ target: targetPath
+ },
+ function(result) {
+ if (result) {
+ if (result.status === 'success') {
+ // if still viewing the same directory
+ if (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);
+ }
+ } else {
+ OC.Notification.hide();
+ if (result.status === 'error' && result.data.message) {
+ OC.Notification.show(result.data.message);
+ }
+ else {
+ OC.Notification.show(t('files', 'Error moving file.'));
+ }
+ // hide notification after 10 sec
+ setTimeout(function() {
+ OC.Notification.hide();
+ }, 10000);
+ }
+ } else {
+ OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
+ }
+ $td.css('background-image', oldBackgroundImage);
+ });
+ });
+
+ },
+
+ /**
+ * Triggers file rename input field for the given file name.
+ * If the user enters a new name, the file will be renamed.
+ *
+ * @param oldname file name of the file to rename
+ */
rename: function(oldname) {
var tr, td, input, form;
tr = FileList.findFileEl(oldname);
+ var oldFileInfo = this.files[tr.index()];
tr.data('renaming',true);
td = tr.children('td.filename');
input = $('<input type="text" class="filename"/>').val(oldname);
@@ -604,86 +970,50 @@ window.FileList = {
event.stopPropagation();
event.preventDefault();
try {
- var newname = input.val();
- var directory = FileList.getCurrentDirectory();
- if (newname !== oldname) {
+ var newName = input.val();
+ if (newName !== oldname) {
checkInput();
- // save background image, because it's replaced by a spinner while async request
- var oldBackgroundImage = td.css('background-image');
// mark as loading
td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
$.ajax({
url: OC.filePath('files','ajax','rename.php'),
data: {
dir : $('#dir').val(),
- newname: newname,
+ newname: newName,
file: oldname
},
success: function(result) {
+ var fileInfo;
if (!result || result.status === 'error') {
OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
- // revert changes
- newname = oldname;
- tr.attr('data-file', newname);
- var path = td.children('a.name').attr('href');
- td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(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);
- if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') {
- if ( ! td.find('a.name span.extension').exists() ) {
- td.find('a.name span.nametext').append('<span class="extension"></span>');
- }
- td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.')));
- }
- tr.find('.fileactions').effect('highlight', {}, 5000);
- tr.effect('highlight', {}, 5000);
- // remove loading mark and recover old image
- td.css('background-image', oldBackgroundImage);
+ fileInfo = oldFileInfo;
}
else {
- var fileInfo = result.data;
- tr.attr('data-mime', fileInfo.mime);
- tr.attr('data-etag', fileInfo.etag);
- if (fileInfo.isPreviewAvailable) {
- Files.lazyLoadPreview(directory + '/' + fileInfo.name, result.data.mime, function(previewpath) {
- tr.find('td.filename').attr('style','background-image:url('+previewpath+')');
- }, null, null, result.data.etag);
- }
- else {
- tr.find('td.filename')
- .removeClass('preview')
- .attr('style','background-image:url('
- + OC.Util.replaceSVGIcon(fileInfo.icon)
- + ')');
- }
+ fileInfo = result.data;
}
// reinsert row
- tr.detach();
- FileList.insertElement( tr.attr('data-file'), tr.attr('data-type'),tr );
- // update file actions in case the extension changed
- FileActions.display( tr.find('td.filename'), true);
+ FileList.files.splice(tr.index(), 1);
+ tr.remove();
+ FileList.add(fileInfo);
}
});
}
input.tipsy('hide');
tr.data('renaming',false);
- tr.attr('data-file', newname);
+ tr.attr('data-file', newName);
var path = td.children('a.name').attr('href');
// FIXME this will fail if the path contains the filename.
- td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname)));
- var basename = newname;
- if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') {
- basename = newname.substr(0, newname.lastIndexOf('.'));
+ td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(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);
- if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') {
+ if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
if ( ! td.find('a.name span.extension').exists() ) {
td.find('a.name span.nametext').append('<span class="extension"></span>');
}
- td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.')));
+ td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.')));
}
form.remove();
FileActions.display( tr.find('td.filename'), true);
@@ -764,20 +1094,22 @@ window.FileList = {
function(result) {
if (result.status === 'success') {
if (params.allfiles) {
- // clear whole list
- $('#fileList tr').remove();
+ FileList.setFiles([]);
}
else {
$.each(files,function(index,file) {
var fileEl = FileList.remove(file, {updateSummary: false});
+ // FIXME: not sure why we need this after the
+ // element isn't even in the DOM any more
fileEl.find('input[type="checkbox"]').prop('checked', false);
fileEl.removeClass('selected');
+ FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
});
}
- procesSelection();
checkTrashStatus();
- FileList.updateFileSummary();
FileList.updateEmptyContent();
+ FileList.fileSummary.update();
+ FileList.updateSelectionSummary();
Files.updateStorageStatistics();
} else {
if (result.status === 'error' && result.data.message) {
@@ -804,108 +1136,14 @@ window.FileList = {
}
});
},
- createFileSummary: function() {
- if ( !FileList.isEmpty ) {
- var summary = this._calculateFileSummary();
-
- // Get translations
- var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
- var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
-
- var infoVars = {
- dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
- files: '</span><span class="fileinfo">'+fileInfo+'</span>'
- };
-
- var info = t('files', '{dirs} and {files}', infoVars);
-
- // don't show the filesize column, if filesize is NaN (e.g. in trashbin)
- var fileSize = '';
- if (!isNaN(summary.totalSize)) {
- fileSize = '<td class="filesize">'+humanFileSize(summary.totalSize)+'</td>';
- }
-
- var $summary = $('<tr class="summary" data-file="undefined"><td><span class="info">'+info+'</span></td>'+fileSize+'<td></td></tr>');
- this.$fileList.append($summary);
-
- var $dirInfo = $summary.find('.dirinfo');
- var $fileInfo = $summary.find('.fileinfo');
- var $connector = $summary.find('.connector');
+ /**
+ * Creates the file summary section
+ */
+ _createSummary: function() {
+ var $tr = $('<tr class="summary"></tr>');
+ this.$el.find('tfoot').append($tr);
- // Show only what's necessary, e.g.: no files: don't show "0 files"
- if (summary.totalDirs === 0) {
- $dirInfo.addClass('hidden');
- $connector.addClass('hidden');
- }
- if (summary.totalFiles === 0) {
- $fileInfo.addClass('hidden');
- $connector.addClass('hidden');
- }
- }
- },
- _calculateFileSummary: function() {
- var result = {
- totalDirs: 0,
- totalFiles: 0,
- totalSize: 0
- };
- $.each($('tr[data-file]'), function(index, value) {
- var $value = $(value);
- if ($value.data('type') === 'dir') {
- result.totalDirs++;
- } else if ($value.data('type') === 'file') {
- result.totalFiles++;
- }
- if ($value.data('size') !== undefined && $value.data('id') !== -1) {
- //Skip shared as it does not count toward quota
- result.totalSize += parseInt($value.data('size'));
- }
- });
- return result;
- },
- updateFileSummary: function() {
- var $summary = this.$el.find('.summary');
-
- // always make it the last element
- this.$fileList.append($summary.detach());
-
- // Check if we should remove the summary to show "Upload something"
- if (this.isEmpty && $summary.length === 1) {
- $summary.remove();
- }
- // If there's no summary create one (createFileSummary checks if there's data)
- else if ($summary.length === 0) {
- FileList.createFileSummary();
- }
- // There's a summary and data -> Update the summary
- else if (!this.isEmpty && $summary.length === 1) {
- var fileSummary = this._calculateFileSummary();
- var $dirInfo = $('.summary .dirinfo');
- var $fileInfo = $('.summary .fileinfo');
- var $connector = $('.summary .connector');
-
- // Substitute old content with new translations
- $dirInfo.html(n('files', '%n folder', '%n folders', fileSummary.totalDirs));
- $fileInfo.html(n('files', '%n file', '%n files', fileSummary.totalFiles));
- $('.summary .filesize').html(humanFileSize(fileSummary.totalSize));
-
- // Show only what's necessary (may be hidden)
- if (fileSummary.totalDirs === 0) {
- $dirInfo.addClass('hidden');
- $connector.addClass('hidden');
- } else {
- $dirInfo.removeClass('hidden');
- }
- if (fileSummary.totalFiles === 0) {
- $fileInfo.addClass('hidden');
- $connector.addClass('hidden');
- } else {
- $fileInfo.removeClass('hidden');
- }
- if (fileSummary.totalDirs > 0 && fileSummary.totalFiles > 0) {
- $connector.removeClass('hidden');
- }
- }
+ return new FileSummary($tr);
},
updateEmptyContent: function() {
var permissions = $('#permissions').val();
@@ -956,7 +1194,7 @@ window.FileList = {
}
},
filter:function(query) {
- $('#fileList tr:not(.summary)').each(function(i,e) {
+ $('#fileList tr').each(function(i,e) {
if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
$(e).addClass("searchresult");
} else {
@@ -975,11 +1213,51 @@ window.FileList = {
});
},
/**
+ * Update UI based on the current selection
+ */
+ updateSelectionSummary: function() {
+ var summary = this._selectionSummary.summary;
+ if (summary.totalFiles === 0 && summary.totalDirs === 0) {
+ $('#headerName span.name').text(t('files','Name'));
+ $('#headerSize').text(t('files','Size'));
+ $('#modified').text(t('files','Modified'));
+ $('table').removeClass('multiselect');
+ $('.selectedActions').addClass('hidden');
+ }
+ else {
+ $('.selectedActions').removeClass('hidden');
+ $('#headerSize').text(OC.Util.humanFileSize(summary.totalSize));
+ var selection = '';
+ if (summary.totalDirs > 0) {
+ selection += n('files', '%n folder', '%n folders', summary.totalDirs);
+ if (summary.totalFiles > 0) {
+ selection += ' & ';
+ }
+ }
+ if (summary.totalFiles > 0) {
+ selection += n('files', '%n file', '%n files', summary.totalFiles);
+ }
+ $('#headerName span.name').text(selection);
+ $('#modified').text('');
+ $('table').addClass('multiselect');
+ }
+ },
+
+ /**
* Returns whether all files are selected
* @return true if all files are selected, false otherwise
*/
isAllSelected: function() {
- return $('#select_all').prop('checked');
+ return this.$el.find('#select_all').prop('checked');
+ },
+
+ /**
+ * Returns the file info of the selected files
+ *
+ * @return array of file names
+ */
+ getSelectedFiles: function() {
+ return _.values(this._selectedFiles);
}
};
@@ -1146,7 +1424,7 @@ $(document).ready(function() {
FileList.remove(file.name);
// create new file context
- data.context = FileList.add(file, {insert: true});
+ data.context = FileList.add(file, {animate: true});
}
}
});
@@ -1255,12 +1533,12 @@ $(document).ready(function() {
}
};
+ $(window).scroll(function(e) {FileList._onScroll(e);});
+
var dir = parseCurrentDirFromUrl();
// trigger ajax load, deferred to let sub-apps do their overrides first
setTimeout(function() {
FileList.changeDirectory(dir, false, true);
}, 0);
-
- FileList.createFileSummary();
});
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 5e669a796a9..6d167851e64 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -8,8 +8,8 @@
*
*/
-/* global OC, t, n, FileList, FileActions */
-/* global getURLParameter, isPublic */
+/* global OC, t, FileList */
+/* global getURLParameter */
var Files = {
// file space size sync
_updateStorageStatistics: function() {
@@ -96,10 +96,10 @@ var Files = {
throw t('files', 'File name cannot be empty.');
}
// check for invalid characters
- var invalid_characters =
+ var invalidCharacters =
['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n'];
- for (var i = 0; i < invalid_characters.length; i++) {
- if (trimmedName.indexOf(invalid_characters[i]) !== -1) {
+ for (var i = 0; i < invalidCharacters.length; i++) {
+ if (trimmedName.indexOf(invalidCharacters[i]) !== -1) {
throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.");
}
}
@@ -116,7 +116,8 @@ var Files = {
return;
}
if (usedSpacePercent > 90) {
- OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', {usedSpacePercent: usedSpacePercent}));
+ OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)',
+ {usedSpacePercent: usedSpacePercent}));
}
},
@@ -142,6 +143,7 @@ var Files = {
}
},
+ // TODO: move to FileList class
setupDragAndDrop: function() {
var $fileList = $('#fileList');
@@ -209,7 +211,7 @@ $(document).ready(function() {
// Trigger cancelling of file upload
$('#uploadprogresswrapper .stop').on('click', function() {
OC.Upload.cancelUploads();
- procesSelection();
+ FileList.updateSelectionSummary();
});
// Show trash bin
@@ -217,135 +219,11 @@ $(document).ready(function() {
window.location=OC.filePath('files_trashbin', '', 'index.php');
});
- var lastChecked;
-
- // Sets the file link behaviour :
- $('#fileList').on('click','td.filename a',function(event) {
- if (event.ctrlKey || event.shiftKey) {
- event.preventDefault();
- if (event.shiftKey) {
- var last = $(lastChecked).parent().parent().prevAll().length;
- var first = $(this).parent().parent().prevAll().length;
- var start = Math.min(first, last);
- var end = Math.max(first, last);
- var rows = $(this).parent().parent().parent().children('tr');
- for (var i = start; i < end; i++) {
- $(rows).each(function(index) {
- if (index === i) {
- var checkbox = $(this).children().children('input:checkbox');
- $(checkbox).attr('checked', 'checked');
- $(checkbox).parent().parent().addClass('selected');
- }
- });
- }
- }
- var checkbox = $(this).parent().children('input:checkbox');
- lastChecked = checkbox;
- if ($(checkbox).attr('checked')) {
- $(checkbox).removeAttr('checked');
- $(checkbox).parent().parent().removeClass('selected');
- $('#select_all').removeAttr('checked');
- } else {
- $(checkbox).attr('checked', 'checked');
- $(checkbox).parent().parent().toggleClass('selected');
- var selectedCount = $('td.filename input:checkbox:checked').length;
- if (selectedCount === $('td.filename input:checkbox').length) {
- $('#select_all').attr('checked', 'checked');
- }
- }
- procesSelection();
- } else {
- var filename=$(this).parent().parent().attr('data-file');
- var tr = FileList.findFileEl(filename);
- var renaming=tr.data('renaming');
- if (!renaming) {
- FileActions.currentFile = $(this).parent();
- var mime=FileActions.getCurrentMimeType();
- var type=FileActions.getCurrentType();
- var permissions = FileActions.getCurrentPermissions();
- var action=FileActions.getDefault(mime,type, permissions);
- if (action) {
- event.preventDefault();
- action(filename);
- }
- }
- }
-
- });
-
- // Sets the select_all checkbox behaviour :
- $('#select_all').click(function() {
- if ($(this).attr('checked')) {
- // Check all
- $('td.filename input:checkbox').attr('checked', true);
- $('td.filename input:checkbox').parent().parent().addClass('selected');
- } else {
- // Uncheck all
- $('td.filename input:checkbox').attr('checked', false);
- $('td.filename input:checkbox').parent().parent().removeClass('selected');
- }
- procesSelection();
- });
-
- $('#fileList').on('change', 'td.filename input:checkbox',function(event) {
- if (event.shiftKey) {
- var last = $(lastChecked).parent().parent().prevAll().length;
- var first = $(this).parent().parent().prevAll().length;
- var start = Math.min(first, last);
- var end = Math.max(first, last);
- var rows = $(this).parent().parent().parent().children('tr');
- for (var i = start; i < end; i++) {
- $(rows).each(function(index) {
- if (index === i) {
- var checkbox = $(this).children().children('input:checkbox');
- $(checkbox).attr('checked', 'checked');
- $(checkbox).parent().parent().addClass('selected');
- }
- });
- }
- }
- var selectedCount=$('td.filename input:checkbox:checked').length;
- $(this).parent().parent().toggleClass('selected');
- if (!$(this).attr('checked')) {
- $('#select_all').attr('checked',false);
- } else {
- if (selectedCount===$('td.filename input:checkbox').length) {
- $('#select_all').attr('checked',true);
- }
- }
- procesSelection();
- });
-
- $('.download').click('click',function(event) {
- var files;
- var dir = FileList.getCurrentDirectory();
- if (FileList.isAllSelected()) {
- files = OC.basename(dir);
- dir = OC.dirname(dir) || '/';
- }
- else {
- files = Files.getSelectedFiles('name');
- }
- OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
- OC.redirect(Files.getDownloadUrl(files, dir));
- return false;
- });
-
- $('.delete-selected').click(function(event) {
- var files = Files.getSelectedFiles('name');
- event.preventDefault();
- if (FileList.isAllSelected()) {
- files = null;
- }
- FileList.do_delete(files);
- return false;
- });
-
// drag&drop support using jquery.fileupload
// TODO use OC.dialogs
$(document).bind('drop dragover', function (e) {
e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
- });
+ });
//do a background scan if needed
scanFiles();
@@ -422,34 +300,22 @@ function scanFiles(force, dir, users) {
}
scanFiles.scanning=false;
-function boolOperationFinished(data, callback) {
- result = jQuery.parseJSON(data.responseText);
- Files.updateMaxUploadFilesize(result);
- if (result.status === 'success') {
- callback.call();
- } else {
- alert(result.data.message);
- }
-}
-
+// TODO: move to FileList
var createDragShadow = function(event) {
//select dragged file
var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
if (!isDragSelected) {
//select dragged file
- $(event.target).parents('tr').find('td input:first').prop('checked',true);
+ FileList._selectFileEl($(event.target).parents('tr:first'), true);
}
- var selectedFiles = Files.getSelectedFiles();
+ // do not show drag shadow for too many files
+ var selectedFiles = _.first(FileList.getSelectedFiles(), FileList.pageSize);
+ selectedFiles.sort(FileList._fileInfoCompare);
if (!isDragSelected && selectedFiles.length === 1) {
//revert the selection
- $(event.target).parents('tr').find('td input:first').prop('checked',false);
- }
-
- //also update class when we dragged more than one file
- if (selectedFiles.length > 1) {
- $(event.target).parents('tr').addClass('selected');
+ FileList._selectFileEl($(event.target).parents('tr:first'), false);
}
// build dragshadow
@@ -460,9 +326,12 @@ var createDragShadow = function(event) {
var dir=$('#dir').val();
$(selectedFiles).each(function(i,elem) {
- var newtr = $('<tr/>').attr('data-dir', dir).attr('data-filename', elem.name).attr('data-origin', elem.origin);
+ var newtr = $('<tr/>')
+ .attr('data-dir', dir)
+ .attr('data-file', elem.name)
+ .attr('data-origin', elem.origin);
newtr.append($('<td/>').addClass('filename').text(elem.name));
- newtr.append($('<td/>').addClass('size').text(humanFileSize(elem.size)));
+ newtr.append($('<td/>').addClass('size').text(OC.Util.humanFileSize(elem.size)));
tbody.append(newtr);
if (elem.type === 'dir') {
newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')');
@@ -479,154 +348,57 @@ var createDragShadow = function(event) {
//options for file drag/drop
//start&stop handlers needs some cleaning up
+// TODO: move to FileList class
var dragOptions={
revert: 'invalid', revertDuration: 300,
opacity: 0.7, zIndex: 100, appendTo: 'body', cursorAt: { left: 24, top: 18 },
helper: createDragShadow, cursor: 'move',
- start: function(event, ui){
- var $selectedFiles = $('td.filename input:checkbox:checked');
- if($selectedFiles.length > 1){
- $selectedFiles.parents('tr').fadeTo(250, 0.2);
- }
- else{
- $(this).fadeTo(250, 0.2);
- }
- },
- stop: function(event, ui) {
- var $selectedFiles = $('td.filename input:checkbox:checked');
- if($selectedFiles.length > 1){
- $selectedFiles.parents('tr').fadeTo(250, 1);
- }
- else{
- $(this).fadeTo(250, 1);
- }
- $('#fileList tr td.filename').addClass('ui-draggable');
+ start: function(event, ui){
+ var $selectedFiles = $('td.filename input:checkbox:checked');
+ if($selectedFiles.length > 1){
+ $selectedFiles.parents('tr').fadeTo(250, 0.2);
+ }
+ else{
+ $(this).fadeTo(250, 0.2);
+ }
+ },
+ stop: function(event, ui) {
+ var $selectedFiles = $('td.filename input:checkbox:checked');
+ if($selectedFiles.length > 1){
+ $selectedFiles.parents('tr').fadeTo(250, 1);
+ }
+ else{
+ $(this).fadeTo(250, 1);
}
+ $('#fileList tr td.filename').addClass('ui-draggable');
+ }
};
// sane browsers support using the distance option
if ( $('html.ie').length === 0) {
dragOptions['distance'] = 20;
}
-var folderDropOptions={
+// TODO: move to FileList class
+var folderDropOptions = {
hoverClass: "canDrop",
drop: function( event, ui ) {
- //don't allow moving a file into a selected folder
+ // don't allow moving a file into a selected folder
if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
return false;
}
- var target = $(this).closest('tr').data('file');
-
- var files = ui.helper.find('tr');
- $(files).each(function(i,row) {
- var dir = $(row).data('dir');
- var file = $(row).data('filename');
- //slapdash selector, tracking down our original element that the clone budded off of.
- var origin = $('tr[data-id=' + $(row).data('origin') + ']');
- var td = origin.children('td.filename');
- var oldBackgroundImage = td.css('background-image');
- td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
- $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: dir+'/'+target }, function(result) {
- if (result) {
- if (result.status === 'success') {
- //recalculate folder size
- var oldFile = FileList.findFileEl(target);
- var newFile = FileList.findFileEl(file);
- var oldSize = oldFile.data('size');
- var newSize = oldSize + newFile.data('size');
- oldFile.data('size', newSize);
- oldFile.find('td.filesize').text(humanFileSize(newSize));
-
- FileList.remove(file);
- procesSelection();
- $('#notification').hide();
- } else {
- $('#notification').hide();
- $('#notification').text(result.data.message);
- $('#notification').fadeIn();
- }
- } else {
- OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
- }
- td.css('background-image', oldBackgroundImage);
- });
- });
- },
- tolerance: 'pointer'
-};
+ var targetPath = FileList.getCurrentDirectory() + '/' + $(this).closest('tr').data('file');
-function procesSelection() {
- var selected = Files.getSelectedFiles();
- var selectedFiles = selected.filter(function(el) {
- return el.type==='file';
- });
- var selectedFolders = selected.filter(function(el) {
- return el.type==='dir';
- });
- if (selectedFiles.length === 0 && selectedFolders.length === 0) {
- $('#headerName span.name').text(t('files','Name'));
- $('#headerSize').text(t('files','Size'));
- $('#modified').text(t('files','Modified'));
- $('table').removeClass('multiselect');
- $('.selectedActions').hide();
- $('#select_all').removeAttr('checked');
- }
- else {
- $('.selectedActions').show();
- var totalSize = 0;
- for(var i=0; i<selectedFiles.length; i++) {
- totalSize+=selectedFiles[i].size;
- }
- for(var i=0; i<selectedFolders.length; i++) {
- totalSize+=selectedFolders[i].size;
- }
- $('#headerSize').text(humanFileSize(totalSize));
- var selection = '';
- if (selectedFolders.length > 0) {
- selection += n('files', '%n folder', '%n folders', selectedFolders.length);
- if (selectedFiles.length > 0) {
- selection += ' & ';
- }
+ var files = FileList.getSelectedFiles();
+ if (files.length === 0) {
+ // single one selected without checkbox?
+ files = _.map(ui.helper.find('tr'), FileList.elementToFile);
}
- if (selectedFiles.length>0) {
- selection += n('files', '%n file', '%n files', selectedFiles.length);
- }
- $('#headerName span.name').text(selection);
- $('#modified').text('');
- $('table').addClass('multiselect');
- }
-}
-/**
- * @brief get a list of selected files
- * @param {string} property (option) the property of the file requested
- * @return {array}
- *
- * possible values for property: name, mime, size and type
- * if property is set, an array with that property for each file is returnd
- * if it's ommited an array of objects with all properties is returned
- */
-Files.getSelectedFiles = function(property) {
- var elements=$('td.filename input:checkbox:checked').parent().parent();
- var files=[];
- elements.each(function(i,element) {
- var file={
- name:$(element).attr('data-file'),
- mime:$(element).data('mime'),
- type:$(element).data('type'),
- size:$(element).data('size'),
- etag:$(element).data('etag'),
- origin: $(element).data('id')
- };
- if (property) {
- files.push(file[property]);
- } else {
- files.push(file);
- }
- });
- return files;
-}
+ FileList.move(_.pluck(files, 'name'), targetPath);
+ },
+ tolerance: 'pointer'
+};
Files.getMimeIcon = function(mime, ready) {
if (Files.getMimeIcon.cache[mime]) {
@@ -665,7 +437,7 @@ Files.generatePreviewUrl = function(urlSpec) {
urlSpec.x *= window.devicePixelRatio;
urlSpec.forceIcon = 0;
return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
-}
+};
Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
// get mime icon url
diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js
new file mode 100644
index 00000000000..b5130247cc9
--- /dev/null
+++ b/apps/files/js/filesummary.js
@@ -0,0 +1,195 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/* global OC, n, t */
+
+(function() {
+ /**
+ * The FileSummary class encapsulates the file summary values and
+ * the logic to render it in the given container
+ * @param $tr table row element
+ * $param summary optional initial summary value
+ */
+ var FileSummary = function($tr) {
+ this.$el = $tr;
+ this.clear();
+ this.render();
+ };
+
+ FileSummary.prototype = {
+ summary: {
+ totalFiles: 0,
+ totalDirs: 0,
+ totalSize: 0
+ },
+
+ /**
+ * Adds file
+ * @param file file to add
+ * @param update whether to update the display
+ */
+ add: function(file, update) {
+ if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+ this.summary.totalDirs++;
+ }
+ else {
+ this.summary.totalFiles++;
+ }
+ this.summary.totalSize += parseInt(file.size, 10) || 0;
+ if (!!update) {
+ this.update();
+ }
+ },
+ /**
+ * Removes file
+ * @param file file to remove
+ * @param update whether to update the display
+ */
+ remove: function(file, update) {
+ if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+ this.summary.totalDirs--;
+ }
+ else {
+ this.summary.totalFiles--;
+ }
+ this.summary.totalSize -= parseInt(file.size, 10) || 0;
+ if (!!update) {
+ this.update();
+ }
+ },
+ /**
+ * Returns the total of files and directories
+ */
+ getTotal: function() {
+ return this.summary.totalDirs + this.summary.totalFiles;
+ },
+ /**
+ * Recalculates the summary based on the given files array
+ * @param files array of files
+ */
+ calculate: function(files) {
+ var file;
+ var summary = {
+ totalDirs: 0,
+ totalFiles: 0,
+ totalSize: 0
+ };
+
+ for (var i = 0; i < files.length; i++) {
+ file = files[i];
+ if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+ summary.totalDirs++;
+ }
+ else {
+ summary.totalFiles++;
+ }
+ summary.totalSize += parseInt(file.size, 10) || 0;
+ }
+ this.setSummary(summary);
+ },
+ /**
+ * Clears the summary
+ */
+ clear: function() {
+ this.calculate([]);
+ },
+ /**
+ * Sets the current summary values
+ * @param summary map
+ */
+ setSummary: function(summary) {
+ this.summary = summary;
+ this.update();
+ },
+
+ /**
+ * Renders the file summary element
+ */
+ update: function() {
+ if (!this.$el) {
+ return;
+ }
+ if (!this.summary.totalFiles && !this.summary.totalDirs) {
+ this.$el.addClass('hidden');
+ return;
+ }
+ // There's a summary and data -> Update the summary
+ this.$el.removeClass('hidden');
+ var $dirInfo = this.$el.find('.dirinfo');
+ var $fileInfo = this.$el.find('.fileinfo');
+ var $connector = this.$el.find('.connector');
+
+ // Substitute old content with new translations
+ $dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
+ $fileInfo.html(n('files', '%n file', '%n files', this.summary.totalFiles));
+ this.$el.find('.filesize').html(OC.Util.humanFileSize(this.summary.totalSize));
+
+ // Show only what's necessary (may be hidden)
+ if (this.summary.totalDirs === 0) {
+ $dirInfo.addClass('hidden');
+ $connector.addClass('hidden');
+ } else {
+ $dirInfo.removeClass('hidden');
+ }
+ if (this.summary.totalFiles === 0) {
+ $fileInfo.addClass('hidden');
+ $connector.addClass('hidden');
+ } else {
+ $fileInfo.removeClass('hidden');
+ }
+ if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
+ $connector.removeClass('hidden');
+ }
+ },
+ render: function() {
+ if (!this.$el) {
+ return;
+ }
+ // TODO: ideally this should be separate to a template or something
+ var summary = this.summary;
+ var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
+ var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
+
+ var infoVars = {
+ dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
+ files: '</span><span class="fileinfo">'+fileInfo+'</span>'
+ };
+
+ // don't show the filesize column, if filesize is NaN (e.g. in trashbin)
+ var fileSize = '';
+ if (!isNaN(summary.totalSize)) {
+ fileSize = '<td class="filesize">' + OC.Util.humanFileSize(summary.totalSize) + '</td>';
+ }
+
+ var info = t('files', '{dirs} and {files}', infoVars);
+
+ var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td></td>');
+
+ if (!this.summary.totalFiles && !this.summary.totalDirs) {
+ this.$el.addClass('hidden');
+ }
+
+ this.$el.append($summary);
+ }
+ };
+ window.FileSummary = FileSummary;
+})();
+
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index a8437835d95..42263c880a7 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -91,6 +91,8 @@
</thead>
<tbody id="fileList">
</tbody>
+ <tfoot>
+ </tfoot>
</table>
<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index 3c22c84b866..f5eafba509f 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -30,6 +30,7 @@ describe('FileActions tests', function() {
$body.append('<input type="hidden" id="permissions" value="31"></input>');
// dummy files table
$filesTable = $body.append('<table id="filestable"></table>');
+ FileList.files = [];
});
afterEach(function() {
$('#dir, #permissions, #filestable').remove();
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index ca85a360cf5..eab364644cd 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -24,6 +24,33 @@ describe('FileList tests', function() {
var testFiles, alertStub, notificationStub,
pushStateStub;
+ /**
+ * Generate test file data
+ */
+ function generateFiles(startIndex, endIndex) {
+ var files = [];
+ var name;
+ for (var i = startIndex; i <= endIndex; i++) {
+ name = 'File with index ';
+ if (i < 10) {
+ // do not rely on localeCompare here
+ // and make the sorting predictable
+ // cross-browser
+ name += '0';
+ }
+ name += i + '.txt';
+ files.push({
+ id: i,
+ type: 'file',
+ name: name,
+ mimetype: 'text/plain',
+ size: i * 2,
+ etag: 'abc'
+ });
+ }
+ return files;
+ }
+
beforeEach(function() {
// init horrible parameters
var $body = $('body');
@@ -48,9 +75,17 @@ describe('FileList tests', function() {
' <div class="notCreatable"></div>' +
'</div>' +
// dummy table
+ // TODO: at some point this will be rendered by the FileList class itself!
'<table id="filestable">' +
- '<thead><tr><th class="hidden">Name</th></tr></thead>' +
+ '<thead><tr><th id="headerName" class="hidden">' +
+ '<input type="checkbox" id="select_all">' +
+ '<span class="name">Name</span>' +
+ '<span class="selectedActions hidden">' +
+ '<a href class="download">Download</a>' +
+ '<a href class="delete-selected">Delete</a></span>' +
+ '</th></tr></thead>' +
'<tbody id="fileList"></tbody>' +
+ '<tfoot></tfoot>' +
'</table>' +
'<div id="emptycontent">Empty content message</div>'
);
@@ -60,25 +95,29 @@ describe('FileList tests', function() {
type: 'file',
name: 'One.txt',
mimetype: 'text/plain',
- size: 12
+ size: 12,
+ etag: 'abc'
}, {
id: 2,
type: 'file',
name: 'Two.jpg',
mimetype: 'image/jpeg',
- size: 12049
+ size: 12049,
+ etag: 'def',
}, {
id: 3,
type: 'file',
name: 'Three.pdf',
mimetype: 'application/pdf',
- size: 58009
+ size: 58009,
+ etag: '123',
}, {
id: 4,
type: 'dir',
name: 'somedir',
mimetype: 'httpd/unix-directory',
- size: 250
+ size: 250,
+ etag: '456'
}];
FileList.initialize();
@@ -220,25 +259,65 @@ describe('FileList tests', function() {
var $tr = FileList.add(fileData);
expect($tr.find('.filesize').text()).toEqual('0 B');
});
- it('adds new file to the end of the list before the summary', function() {
+ it('adds new file to the end of the list', function() {
+ var $tr;
var fileData = {
type: 'file',
- name: 'P comes after O.txt'
+ name: 'ZZZ.txt'
};
FileList.setFiles(testFiles);
$tr = FileList.add(fileData);
expect($tr.index()).toEqual(4);
- expect($tr.next().hasClass('summary')).toEqual(true);
});
- it('adds new file at correct position in insert mode', function() {
+ it('inserts files in a sorted manner when insert option is enabled', function() {
+ var $tr;
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i]);
+ }
+ expect(FileList.files[0].name).toEqual('somedir');
+ expect(FileList.files[1].name).toEqual('One.txt');
+ expect(FileList.files[2].name).toEqual('Three.pdf');
+ expect(FileList.files[3].name).toEqual('Two.jpg');
+ });
+ it('inserts new file at correct position', function() {
+ var $tr;
var fileData = {
type: 'file',
name: 'P comes after O.txt'
};
- FileList.setFiles(testFiles);
- $tr = FileList.add(fileData, {insert: true});
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i]);
+ }
+ $tr = FileList.add(fileData);
// after "One.txt"
+ expect($tr.index()).toEqual(2);
+ expect(FileList.files[2]).toEqual(fileData);
+ });
+ it('inserts new folder at correct position in insert mode', function() {
+ var $tr;
+ var fileData = {
+ type: 'dir',
+ name: 'somedir2 comes after somedir'
+ };
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i]);
+ }
+ $tr = FileList.add(fileData);
expect($tr.index()).toEqual(1);
+ expect(FileList.files[1]).toEqual(fileData);
+ });
+ it('inserts new file at the end correctly', function() {
+ var $tr;
+ var fileData = {
+ type: 'file',
+ name: 'zzz.txt'
+ };
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i]);
+ }
+ $tr = FileList.add(fileData);
+ expect($tr.index()).toEqual(4);
+ expect(FileList.files[4]).toEqual(fileData);
});
it('removes empty content message and shows summary when adding first file', function() {
var fileData = {
@@ -249,8 +328,8 @@ describe('FileList tests', function() {
FileList.setFiles([]);
expect(FileList.isEmpty).toEqual(true);
FileList.add(fileData);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(1);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(false);
// yes, ugly...
expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
@@ -268,11 +347,12 @@ describe('FileList tests', function() {
$removedEl = FileList.remove('One.txt');
expect($removedEl).toBeDefined();
expect($removedEl.attr('data-file')).toEqual('One.txt');
- expect($('#fileList tr:not(.summary)').length).toEqual(3);
+ expect($('#fileList tr').length).toEqual(3);
+ expect(FileList.files.length).toEqual(3);
expect(FileList.findFileEl('One.txt').length).toEqual(0);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(1);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(false);
expect($summary.find('.info').text()).toEqual('1 folder and 2 files');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
@@ -282,11 +362,12 @@ describe('FileList tests', function() {
it('Shows empty content when removing last file', function() {
FileList.setFiles([testFiles[0]]);
FileList.remove('One.txt');
- expect($('#fileList tr:not(.summary)').length).toEqual(0);
+ expect($('#fileList tr').length).toEqual(0);
+ expect(FileList.files.length).toEqual(0);
expect(FileList.findFileEl('One.txt').length).toEqual(0);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(0);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(true);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(false);
expect(FileList.isEmpty).toEqual(true);
@@ -318,10 +399,10 @@ describe('FileList tests', function() {
expect(FileList.findFileEl('One.txt').length).toEqual(0);
expect(FileList.findFileEl('Two.jpg').length).toEqual(0);
expect(FileList.findFileEl('Three.pdf').length).toEqual(1);
- expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(2);
+ expect(FileList.$fileList.find('tr').length).toEqual(2);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(1);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(false);
expect($summary.find('.info').text()).toEqual('1 folder and 1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
@@ -342,11 +423,12 @@ describe('FileList tests', function() {
JSON.stringify({status: 'success'})
);
- expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(0);
+ expect(FileList.$fileList.find('tr').length).toEqual(0);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(0);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(true);
expect(FileList.isEmpty).toEqual(true);
+ expect(FileList.files.length).toEqual(0);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(false);
});
@@ -363,7 +445,7 @@ describe('FileList tests', function() {
// files are still in the list
expect(FileList.findFileEl('One.txt').length).toEqual(1);
expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
- expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(4);
+ expect(FileList.$fileList.find('tr').length).toEqual(4);
expect(notificationStub.calledOnce).toEqual(true);
});
@@ -372,37 +454,41 @@ describe('FileList tests', function() {
function doRename() {
var $input, request;
- FileList.setFiles(testFiles);
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i]);
+ }
// trigger rename prompt
FileList.rename('One.txt');
$input = FileList.$fileList.find('input.filename');
- $input.val('One_renamed.txt').blur();
+ $input.val('Tu_after_three.txt').blur();
expect(fakeServer.requests.length).toEqual(1);
- var request = fakeServer.requests[0];
+ request = fakeServer.requests[0];
expect(request.url.substr(0, request.url.indexOf('?'))).toEqual(OC.webroot + '/index.php/apps/files/ajax/rename.php');
- expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'One_renamed.txt', file: 'One.txt'});
+ expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'Tu_after_three.txt', file: 'One.txt'});
// element is renamed before the request finishes
expect(FileList.findFileEl('One.txt').length).toEqual(0);
- expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1);
+ expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
// input is gone
expect(FileList.$fileList.find('input.filename').length).toEqual(0);
}
- it('Keeps renamed file entry if rename ajax call suceeded', function() {
+ it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() {
doRename();
fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
status: 'success',
data: {
- name: 'One_renamed.txt'
+ name: 'Tu_after_three.txt',
+ type: 'file'
}
}));
// element stays renamed
expect(FileList.findFileEl('One.txt').length).toEqual(0);
- expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1);
+ expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
+ expect(FileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt
expect(alertStub.notCalled).toEqual(true);
});
@@ -418,7 +504,8 @@ describe('FileList tests', function() {
// element was reverted
expect(FileList.findFileEl('One.txt').length).toEqual(1);
- expect(FileList.findFileEl('One_renamed.txt').length).toEqual(0);
+ expect(FileList.findFileEl('One.txt').index()).toEqual(1); // after somedir
+ expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(0);
expect(alertStub.calledOnce).toEqual(true);
});
@@ -429,12 +516,12 @@ describe('FileList tests', function() {
fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
status: 'success',
data: {
- name: 'One_renamed.txt'
+ name: 'Tu_after_three.txt'
}
}));
- $tr = FileList.findFileEl('One_renamed.txt');
- expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One_renamed.txt');
+ $tr = FileList.findFileEl('Tu_after_three.txt');
+ expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=Tu_after_three.txt');
});
// FIXME: fix this in the source code!
xit('Correctly updates file link after rename when path has same name', function() {
@@ -446,27 +533,122 @@ describe('FileList tests', function() {
fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
status: 'success',
data: {
- name: 'One_renamed.txt'
+ name: 'Tu_after_three.txt'
}
}));
- $tr = FileList.findFileEl('One_renamed.txt');
+ $tr = FileList.findFileEl('Tu_after_three.txt');
expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One.txt');
});
});
+ describe('Moving files', function() {
+ beforeEach(function() {
+ FileList.setFiles(testFiles);
+ });
+ it('Moves single file to target folder', function() {
+ var request;
+ FileList.move('One.txt', '/somedir');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+ expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+
+ fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ status: 'success',
+ data: {
+ name: 'One.txt',
+ type: 'file'
+ }
+ }));
+
+ expect(FileList.findFileEl('One.txt').length).toEqual(0);
+
+ // folder size has increased
+ expect(FileList.findFileEl('somedir').data('size')).toEqual(262);
+ expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
+
+ expect(notificationStub.notCalled).toEqual(true);
+ });
+ it('Moves list of files to target folder', function() {
+ var request;
+ FileList.move(['One.txt', 'Two.jpg'], '/somedir');
+
+ expect(fakeServer.requests.length).toEqual(2);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+ expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+
+ request = fakeServer.requests[1];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+ expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'Two.jpg', target: '/somedir'});
+
+ fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ status: 'success',
+ data: {
+ name: 'One.txt',
+ type: 'file'
+ }
+ }));
+
+ expect(FileList.findFileEl('One.txt').length).toEqual(0);
+
+ // folder size has increased
+ expect(FileList.findFileEl('somedir').data('size')).toEqual(262);
+ expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
+
+ fakeServer.requests[1].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ status: 'success',
+ data: {
+ name: 'Two.jpg',
+ type: 'file'
+ }
+ }));
+
+ expect(FileList.findFileEl('Two.jpg').length).toEqual(0);
+
+ // folder size has increased
+ expect(FileList.findFileEl('somedir').data('size')).toEqual(12311);
+ expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 kB');
+
+ expect(notificationStub.notCalled).toEqual(true);
+ });
+ it('Shows notification if a file could not be moved', function() {
+ var request;
+ FileList.move('One.txt', '/somedir');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+ expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+
+ fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ status: 'error',
+ data: {
+ message: 'Error while moving file',
+ }
+ }));
+
+ expect(FileList.findFileEl('One.txt').length).toEqual(1);
+
+ expect(notificationStub.calledOnce).toEqual(true);
+ expect(notificationStub.getCall(0).args[0]).toEqual('Error while moving file');
+ });
+ });
describe('List rendering', function() {
it('renders a list of files using add()', function() {
- var addSpy = sinon.spy(FileList, 'add');
+ expect(FileList.files.length).toEqual(0);
+ expect(FileList.files).toEqual([]);
FileList.setFiles(testFiles);
- expect(addSpy.callCount).toEqual(4);
- expect($('#fileList tr:not(.summary)').length).toEqual(4);
- addSpy.restore();
+ expect($('#fileList tr').length).toEqual(4);
+ expect(FileList.files.length).toEqual(4);
+ expect(FileList.files).toEqual(testFiles);
});
it('updates summary using the file sizes', function() {
var $summary;
FileList.setFiles(testFiles);
- $summary = $('#fileList .summary');
- expect($summary.length).toEqual(1);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(false);
expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
expect($summary.find('.filesize').text()).toEqual('69 kB');
});
@@ -474,20 +656,20 @@ describe('FileList tests', function() {
FileList.setFiles(testFiles);
expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
expect($('#emptycontent').hasClass('hidden')).toEqual(true);
- expect(FileList.$fileList.find('.summary').length).toEqual(1);
+ expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(false);
});
it('hides headers, summary and show empty content message after setting empty file list', function(){
FileList.setFiles([]);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(false);
- expect(FileList.$fileList.find('.summary').length).toEqual(0);
+ expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
});
it('hides headers, empty content message, and summary when list is empty and user has no creation permission', function(){
$('#permissions').val(0);
FileList.setFiles([]);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(true);
- expect(FileList.$fileList.find('.summary').length).toEqual(0);
+ expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
});
it('calling findFileEl() can find existing file element', function() {
FileList.setFiles(testFiles);
@@ -519,6 +701,110 @@ describe('FileList tests', function() {
FileList.setFiles(testFiles);
expect(handler.calledOnce).toEqual(true);
});
+ it('does not update summary when removing non-existing files', function() {
+ // single file
+ FileList.setFiles([testFiles[0]]);
+ $summary = $('#filestable .summary');
+ expect($summary.hasClass('hidden')).toEqual(false);
+ expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ FileList.remove('unexist.txt');
+ expect($summary.hasClass('hidden')).toEqual(false);
+ expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ });
+ });
+ describe('Rendering next page on scroll', function() {
+ beforeEach(function() {
+ FileList.setFiles(generateFiles(0, 64));
+ });
+ it('renders only the first page', function() {
+ expect(FileList.files.length).toEqual(65);
+ expect($('#fileList tr').length).toEqual(20);
+ });
+ it('renders the second page when scrolling down (trigger nextPage)', function() {
+ // TODO: can't simulate scrolling here, so calling nextPage directly
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(40);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(60);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(65);
+ FileList._nextPage(true);
+ // stays at 65
+ expect($('#fileList tr').length).toEqual(65);
+ });
+ it('inserts into the DOM if insertion point is in the visible page ', function() {
+ FileList.add({
+ id: 2000,
+ type: 'file',
+ name: 'File with index 15b.txt'
+ });
+ expect($('#fileList tr').length).toEqual(21);
+ expect(FileList.findFileEl('File with index 15b.txt').index()).toEqual(16);
+ });
+ it('does not inserts into the DOM if insertion point is not the visible page ', function() {
+ FileList.add({
+ id: 2000,
+ type: 'file',
+ name: 'File with index 28b.txt'
+ });
+ expect($('#fileList tr').length).toEqual(20);
+ expect(FileList.findFileEl('File with index 28b.txt').length).toEqual(0);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(40);
+ expect(FileList.findFileEl('File with index 28b.txt').index()).toEqual(29);
+ });
+ it('appends into the DOM when inserting a file after the last visible element', function() {
+ FileList.add({
+ id: 2000,
+ type: 'file',
+ name: 'File with index 19b.txt'
+ });
+ expect($('#fileList tr').length).toEqual(21);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(41);
+ });
+ it('appends into the DOM when inserting a file on the last page when visible', function() {
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(40);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(60);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(65);
+ FileList._nextPage(true);
+ FileList.add({
+ id: 2000,
+ type: 'file',
+ name: 'File with index 88.txt'
+ });
+ expect($('#fileList tr').length).toEqual(66);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(66);
+ });
+ it('shows additional page when appending a page of files and scrolling down', function() {
+ var newFiles = generateFiles(66, 81);
+ for (var i = 0; i < newFiles.length; i++) {
+ FileList.add(newFiles[i]);
+ }
+ expect($('#fileList tr').length).toEqual(20);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(40);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(60);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(80);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(81);
+ FileList._nextPage(true);
+ expect($('#fileList tr').length).toEqual(81);
+ });
+ it('automatically renders next page when there are not enough elements visible', function() {
+ // delete the 15 first elements
+ for (var i = 0; i < 15; i++) {
+ FileList.remove(FileList.files[0].name);
+ }
+ // still makes sure that there are 20 elements visible, if any
+ expect($('#fileList tr').length).toEqual(25);
+ });
});
describe('file previews', function() {
var previewLoadStub;
@@ -642,7 +928,7 @@ describe('FileList tests', function() {
var query = url.substr(url.indexOf('?') + 1);
expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'});
fakeServer.respond();
- expect($('#fileList tr:not(.summary)').length).toEqual(4);
+ expect($('#fileList tr').length).toEqual(4);
expect(FileList.findFileEl('One.txt').length).toEqual(1);
});
it('switches dir and fetches file list when calling changeDirectory()', function() {
@@ -740,14 +1026,12 @@ describe('FileList tests', function() {
}
};
// returns a list of tr that were dragged
- // FIXME: why are their attributes different than the
- // regular file trs ?
ui.helper.find.returns([
- $('<tr data-filename="One.txt" data-dir="' + testDir + '"></tr>'),
- $('<tr data-filename="Two.jpg" data-dir="' + testDir + '"></tr>')
+ $('<tr data-file="One.txt" data-dir="' + testDir + '"></tr>'),
+ $('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>')
]);
// simulate drop event
- FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui);
+ FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
// will trigger two calls to move.php (first one was previous list.php)
expect(fakeServer.requests.length).toEqual(3);
@@ -784,14 +1068,12 @@ describe('FileList tests', function() {
}
};
// returns a list of tr that were dragged
- // FIXME: why are their attributes different than the
- // regular file trs ?
ui.helper.find.returns([
- $('<tr data-filename="One.txt" data-dir="' + testDir + '"></tr>'),
- $('<tr data-filename="Two.jpg" data-dir="' + testDir + '"></tr>')
+ $('<tr data-file="One.txt" data-dir="' + testDir + '"></tr>'),
+ $('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>')
]);
// simulate drop event
- FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui);
+ FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
// no extra server request
expect(fakeServer.requests.length).toEqual(1);
@@ -811,4 +1093,329 @@ describe('FileList tests', function() {
expect(Files.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
});
});
+ describe('File selection', function() {
+ beforeEach(function() {
+ FileList.setFiles(testFiles);
+ });
+ it('Selects a file when clicking its checkbox', function() {
+ var $tr = FileList.findFileEl('One.txt');
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(false);
+ $tr.find('td.filename input:checkbox').click();
+
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ });
+ it('Selects/deselect a file when clicking on the name while holding Ctrl', function() {
+ var $tr = FileList.findFileEl('One.txt');
+ var $tr2 = FileList.findFileEl('Three.pdf');
+ var e;
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(false);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(false);
+ e = new $.Event('click');
+ e.ctrlKey = true;
+ $tr.find('td.filename .name').trigger(e);
+
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(false);
+
+ // click on second entry, does not clear the selection
+ e = new $.Event('click');
+ e.ctrlKey = true;
+ $tr2.find('td.filename .name').trigger(e);
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
+
+ expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt', 'Three.pdf']);
+
+ // deselect now
+ e = new $.Event('click');
+ e.ctrlKey = true;
+ $tr2.find('td.filename .name').trigger(e);
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(false);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt']);
+ });
+ it('Selects a range when clicking on one file then Shift clicking on another one', function() {
+ var $tr = FileList.findFileEl('One.txt');
+ var $tr2 = FileList.findFileEl('Three.pdf');
+ var e;
+ $tr.find('td.filename input:checkbox').click();
+ e = new $.Event('click');
+ e.shiftKey = true;
+ $tr2.find('td.filename .name').trigger(e);
+
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
+ expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
+ var selection = _.pluck(FileList.getSelectedFiles(), 'name');
+ expect(selection.length).toEqual(3);
+ expect(selection).toContain('One.txt');
+ expect(selection).toContain('Two.jpg');
+ expect(selection).toContain('Three.pdf');
+ });
+ it('Selects a range when clicking on one file then Shift clicking on another one that is above the first one', function() {
+ var $tr = FileList.findFileEl('One.txt');
+ var $tr2 = FileList.findFileEl('Three.pdf');
+ var e;
+ $tr2.find('td.filename input:checkbox').click();
+ e = new $.Event('click');
+ e.shiftKey = true;
+ $tr.find('td.filename .name').trigger(e);
+
+ expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
+ expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
+ expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
+ var selection = _.pluck(FileList.getSelectedFiles(), 'name');
+ expect(selection.length).toEqual(3);
+ expect(selection).toContain('One.txt');
+ expect(selection).toContain('Two.jpg');
+ expect(selection).toContain('Three.pdf');
+ });
+ it('Selecting all files will automatically check "select all" checkbox', function() {
+ expect($('#select_all').prop('checked')).toEqual(false);
+ $('#fileList tr td.filename input:checkbox').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+ });
+ it('Selecting all files on the first visible page will not automatically check "select all" checkbox', function() {
+ FileList.setFiles(generateFiles(0, 41));
+ expect($('#select_all').prop('checked')).toEqual(false);
+ $('#fileList tr td.filename input:checkbox').click();
+ expect($('#select_all').prop('checked')).toEqual(false);
+ });
+ it('Clicking "select all" will select/deselect all files', function() {
+ FileList.setFiles(generateFiles(0, 41));
+ $('#select_all').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+ $('#fileList tr input:checkbox').each(function() {
+ expect($(this).prop('checked')).toEqual(true);
+ });
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
+
+ $('#select_all').click();
+ expect($('#select_all').prop('checked')).toEqual(false);
+
+ $('#fileList tr input:checkbox').each(function() {
+ expect($(this).prop('checked')).toEqual(false);
+ });
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(0);
+ });
+ it('Clicking "select all" then deselecting a file will uncheck "select all"', function() {
+ $('#select_all').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+
+ var $tr = FileList.findFileEl('One.txt');
+ $tr.find('input:checkbox').click();
+
+ expect($('#select_all').prop('checked')).toEqual(false);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+ });
+ it('Updates the selection summary when doing a few manipulations with "Select all"', function() {
+ $('#select_all').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+
+ var $tr = FileList.findFileEl('One.txt');
+ // unselect one
+ $tr.find('input:checkbox').click();
+
+ expect($('#select_all').prop('checked')).toEqual(false);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+
+ // select all
+ $('#select_all').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4);
+
+ // unselect one
+ $tr.find('input:checkbox').click();
+ expect($('#select_all').prop('checked')).toEqual(false);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+
+ // re-select it
+ $tr.find('input:checkbox').click();
+ expect($('#select_all').prop('checked')).toEqual(true);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4);
+ });
+ it('Auto-selects files on next page when "select all" is checked', function() {
+ FileList.setFiles(generateFiles(0, 41));
+ $('#select_all').click();
+
+ expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20);
+ FileList._nextPage(true);
+ expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(40);
+ FileList._nextPage(true);
+ expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(42);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
+ });
+ it('Selecting files updates selection summary', function() {
+ var $summary = $('#headerName span.name');
+ expect($summary.text()).toEqual('Name');
+ FileList.findFileEl('One.txt').find('input:checkbox').click();
+ FileList.findFileEl('Three.pdf').find('input:checkbox').click();
+ FileList.findFileEl('somedir').find('input:checkbox').click();
+ expect($summary.text()).toEqual('1 folder & 2 files');
+ });
+ it('Unselecting files hides selection summary', function() {
+ var $summary = $('#headerName span.name');
+ FileList.findFileEl('One.txt').find('input:checkbox').click().click();
+ expect($summary.text()).toEqual('Name');
+ });
+ it('Select/deselect files shows/hides file actions', function() {
+ var $actions = $('#headerName .selectedActions');
+ var $checkbox = FileList.findFileEl('One.txt').find('input:checkbox');
+ expect($actions.hasClass('hidden')).toEqual(true);
+ $checkbox.click();
+ expect($actions.hasClass('hidden')).toEqual(false);
+ $checkbox.click();
+ expect($actions.hasClass('hidden')).toEqual(true);
+ });
+ it('Selection is cleared when switching dirs', function() {
+ $('#select_all').click();
+ var data = {
+ status: 'success',
+ data: {
+ files: testFiles,
+ permissions: 31
+ }
+ };
+ fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php/, [
+ 200, {
+ "Content-Type": "application/json"
+ },
+ JSON.stringify(data)
+ ]);
+ FileList.changeDirectory('/');
+ fakeServer.respond();
+ expect($('#select_all').prop('checked')).toEqual(false);
+ expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual([]);
+ });
+ it('getSelectedFiles returns the selected files even when they are on the next page', function() {
+ var selectedFiles;
+ FileList.setFiles(generateFiles(0, 41));
+ $('#select_all').click();
+ // unselect one to not have the "allFiles" case
+ FileList.$fileList.find('tr input:checkbox:first').click();
+
+ // only 20 files visible, must still return all the selected ones
+ selectedFiles = _.pluck(FileList.getSelectedFiles(), 'name');
+
+ expect(selectedFiles.length).toEqual(41);
+ });
+ describe('Actions', function() {
+ beforeEach(function() {
+ FileList.findFileEl('One.txt').find('input:checkbox').click();
+ FileList.findFileEl('Three.pdf').find('input:checkbox').click();
+ FileList.findFileEl('somedir').find('input:checkbox').click();
+ });
+ it('getSelectedFiles returns the selected file data', function() {
+ var files = FileList.getSelectedFiles();
+ expect(files.length).toEqual(3);
+ expect(files[0]).toEqual({
+ id: 1,
+ name: 'One.txt',
+ mimetype: 'text/plain',
+ type: 'file',
+ size: 12,
+ etag: 'abc'
+ });
+ expect(files[1]).toEqual({
+ id: 3,
+ type: 'file',
+ name: 'Three.pdf',
+ mimetype: 'application/pdf',
+ size: 58009,
+ etag: '123'
+ });
+ expect(files[2]).toEqual({
+ id: 4,
+ type: 'dir',
+ name: 'somedir',
+ mimetype: 'httpd/unix-directory',
+ size: 250,
+ etag: '456'
+ });
+ });
+ it('Removing a file removes it from the selection', function() {
+ FileList.remove('Three.pdf');
+ var files = FileList.getSelectedFiles();
+ expect(files.length).toEqual(2);
+ expect(files[0]).toEqual({
+ id: 1,
+ name: 'One.txt',
+ mimetype: 'text/plain',
+ type: 'file',
+ size: 12,
+ etag: 'abc'
+ });
+ expect(files[1]).toEqual({
+ id: 4,
+ type: 'dir',
+ name: 'somedir',
+ mimetype: 'httpd/unix-directory',
+ size: 250,
+ etag: '456'
+ });
+ });
+ describe('Download', function() {
+ it('Opens download URL when clicking "Download"', function() {
+ var redirectStub = sinon.stub(OC, 'redirect');
+ $('.selectedActions .download').click();
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22One.txt%22%2C%22Three.pdf%22%2C%22somedir%22%5D');
+ redirectStub.restore();
+ });
+ it('Downloads root folder when all selected in root folder', function() {
+ $('#dir').val('/');
+ $('#select_all').click();
+ var redirectStub = sinon.stub(OC, 'redirect');
+ $('.selectedActions .download').click();
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=');
+ redirectStub.restore();
+ });
+ it('Downloads parent folder when all selected in subfolder', function() {
+ $('#select_all').click();
+ var redirectStub = sinon.stub(OC, 'redirect');
+ $('.selectedActions .download').click();
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=subdir');
+ redirectStub.restore();
+ });
+ });
+ describe('Delete', function() {
+ it('Deletes selected files when "Delete" clicked', function() {
+ var request;
+ $('.selectedActions .delete-selected').click();
+ expect(fakeServer.requests.length).toEqual(1);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
+ expect(OC.parseQueryString(request.requestBody))
+ .toEqual({'dir': '/subdir', files: '["One.txt","Three.pdf","somedir"]'});
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({status: 'success'})
+ );
+ expect(FileList.findFileEl('One.txt').length).toEqual(0);
+ expect(FileList.findFileEl('Three.pdf').length).toEqual(0);
+ expect(FileList.findFileEl('somedir').length).toEqual(0);
+ expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
+ });
+ it('Deletes all files when all selected when "Delete" clicked', function() {
+ var request;
+ $('#select_all').click();
+ $('.selectedActions .delete-selected').click();
+ expect(fakeServer.requests.length).toEqual(1);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
+ expect(OC.parseQueryString(request.requestBody))
+ .toEqual({'dir': '/subdir', allfiles: 'true'});
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({status: 'success'})
+ );
+ expect(FileList.isEmpty).toEqual(true);
+ });
+ });
+ });
+ });
});
diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js
index 018c8ef0f3c..7f8848619f5 100644
--- a/apps/files/tests/js/filesSpec.js
+++ b/apps/files/tests/js/filesSpec.js
@@ -19,7 +19,7 @@
*
*/
-/* global Files */
+/* global OC, Files */
describe('Files tests', function() {
describe('File name validation', function() {
it('Validates correct file names', function() {
@@ -82,4 +82,30 @@ describe('Files tests', function() {
}
});
});
+ describe('getDownloadUrl', function() {
+ var curDirStub;
+ beforeEach(function() {
+ curDirStub = sinon.stub(FileList, 'getCurrentDirectory');
+ });
+ afterEach(function() {
+ curDirStub.restore();
+ });
+ it('returns the ajax download URL when only filename specified', function() {
+ curDirStub.returns('/subdir');
+ var url = Files.getDownloadUrl('test file.txt');
+ expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
+ });
+ it('returns the ajax download URL when filename and dir specified', function() {
+ var url = Files.getDownloadUrl('test file.txt', '/subdir');
+ expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
+ });
+ it('returns the ajax download URL when filename and root dir specific', function() {
+ var url = Files.getDownloadUrl('test file.txt', '/');
+ expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=test%20file.txt');
+ });
+ it('returns the ajax download URL when multiple files specified', function() {
+ var url = Files.getDownloadUrl(['test file.txt', 'abc.txt'], '/subdir');
+ expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22test%20file.txt%22%2C%22abc.txt%22%5D');
+ });
+ });
});
diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js
new file mode 100644
index 00000000000..c493700de38
--- /dev/null
+++ b/apps/files/tests/js/filesummarySpec.js
@@ -0,0 +1,87 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/* global FileSummary */
+describe('FileSummary tests', function() {
+ var $container;
+
+ beforeEach(function() {
+ $container = $('<table><tr></tr></table>').find('tr');
+ });
+ afterEach(function() {
+ $container = null;
+ });
+
+ it('renders summary as text', function() {
+ var s = new FileSummary($container);
+ s.setSummary({
+ totalDirs: 5,
+ totalFiles: 2,
+ totalSize: 256000
+ });
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.info').text()).toEqual('5 folders and 2 files');
+ expect($container.find('.filesize').text()).toEqual('250 kB');
+ });
+ it('hides summary when no files or folders', function() {
+ var s = new FileSummary($container);
+ s.setSummary({
+ totalDirs: 0,
+ totalFiles: 0,
+ totalSize: 0
+ });
+ expect($container.hasClass('hidden')).toEqual(true);
+ });
+ it('increases summary when adding files', function() {
+ var s = new FileSummary($container);
+ s.setSummary({
+ totalDirs: 5,
+ totalFiles: 2,
+ totalSize: 256000
+ });
+ s.add({type: 'file', size: 256000});
+ s.add({type: 'dir', size: 100});
+ s.update();
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.info').text()).toEqual('6 folders and 3 files');
+ expect($container.find('.filesize').text()).toEqual('500 kB');
+ expect(s.summary.totalDirs).toEqual(6);
+ expect(s.summary.totalFiles).toEqual(3);
+ expect(s.summary.totalSize).toEqual(512100);
+ });
+ it('decreases summary when removing files', function() {
+ var s = new FileSummary($container);
+ s.setSummary({
+ totalDirs: 5,
+ totalFiles: 2,
+ totalSize: 256000
+ });
+ s.remove({type: 'file', size: 128000});
+ s.remove({type: 'dir', size: 100});
+ s.update();
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.info').text()).toEqual('4 folders and 1 file');
+ expect($container.find('.filesize').text()).toEqual('125 kB');
+ expect(s.summary.totalDirs).toEqual(4);
+ expect(s.summary.totalFiles).toEqual(1);
+ expect(s.summary.totalSize).toEqual(127900);
+ });
+});
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index ce51eca6ddb..3abcbf291ff 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -138,6 +138,7 @@ if (isset($path)) {
OCP\Util::addStyle('files', 'files');
OCP\Util::addStyle('files', 'upload');
+ OCP\Util::addScript('files', 'filesummary');
OCP\Util::addScript('files', 'breadcrumb');
OCP\Util::addScript('files', 'files');
OCP\Util::addScript('files', 'filelist');
diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php
index e63fe1e4188..6e6a8a38307 100644
--- a/apps/files_trashbin/index.php
+++ b/apps/files_trashbin/index.php
@@ -11,6 +11,7 @@ $tmpl = new OCP\Template('files_trashbin', 'index', 'user');
OCP\Util::addStyle('files', 'files');
OCP\Util::addStyle('files_trashbin', 'trash');
+OCP\Util::addScript('files', 'filesummary');
OCP\Util::addScript('files', 'breadcrumb');
OCP\Util::addScript('files', 'filelist');
// filelist overrides
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index 7795daf2775..3bb3a92b60d 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/apps/files_trashbin/js/filelist.js
@@ -49,8 +49,8 @@
}
};
- var oldAdd = FileList.add;
- FileList.add = function(fileData, options) {
+ var oldRenderRow = FileList._renderRow;
+ FileList._renderRow = function(fileData, options) {
options = options || {};
var dir = FileList.getCurrentDirectory();
var dirListing = dir !== '' && dir !== '/';
@@ -62,7 +62,7 @@
fileData.displayName = fileData.name;
fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
}
- return oldAdd.call(this, fileData, options);
+ return oldRenderRow.call(this, fileData, options);
};
FileList.linkTo = function(dir){
@@ -75,4 +75,130 @@
$('#emptycontent').toggleClass('hidden', exists);
$('#filestable th').toggleClass('hidden', !exists);
};
+
+ var oldInit = FileList.initialize;
+ FileList.initialize = function() {
+ var result = oldInit.apply(this, arguments);
+ $('.undelete').click('click', FileList._onClickRestoreSelected);
+ return result;
+ };
+
+ FileList._removeCallback = function(result) {
+ if (result.status !== 'success') {
+ OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
+ }
+
+ var files = result.data.success;
+ var $el;
+ for (var i = 0; i < files.length; i++) {
+ $el = FileList.remove(OC.basename(files[i].filename), {updateSummary: false});
+ FileList.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
+ }
+ FileList.fileSummary.update();
+ FileList.updateEmptyContent();
+ enableActions();
+ }
+
+ FileList._onClickRestoreSelected = function(event) {
+ event.preventDefault();
+ var allFiles = $('#select_all').is(':checked');
+ var files = [];
+ var params = {};
+ disableActions();
+ if (allFiles) {
+ FileList.showMask();
+ params = {
+ allfiles: true,
+ dir: FileList.getCurrentDirectory()
+ };
+ }
+ else {
+ files = _.pluck(FileList.getSelectedFiles(), 'name');
+ for (var i = 0; i < files.length; i++) {
+ var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
+ deleteAction.removeClass('delete-icon').addClass('progress-icon');
+ }
+ params = {
+ files: JSON.stringify(files),
+ dir: FileList.getCurrentDirectory()
+ };
+ }
+
+ $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'),
+ params,
+ function(result) {
+ if (allFiles) {
+ if (result.status !== 'success') {
+ OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
+ }
+ FileList.hideMask();
+ // simply remove all files
+ FileList.setFiles([]);
+ enableActions();
+ }
+ else {
+ FileList._removeCallback(result);
+ }
+ }
+ );
+ };
+
+ FileList._onClickDeleteSelected = function(event) {
+ event.preventDefault();
+ var allFiles = $('#select_all').is(':checked');
+ var files = [];
+ var params = {};
+ if (allFiles) {
+ params = {
+ allfiles: true,
+ dir: FileList.getCurrentDirectory()
+ };
+ }
+ else {
+ files = _.pluck(FileList.getSelectedFiles(), 'name');
+ params = {
+ files: JSON.stringify(files),
+ dir: FileList.getCurrentDirectory()
+ };
+ }
+
+ disableActions();
+ if (allFiles) {
+ FileList.showMask();
+ }
+ else {
+ for (var i = 0; i < files.length; i++) {
+ var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
+ deleteAction.removeClass('delete-icon').addClass('progress-icon');
+ }
+ }
+
+ $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
+ params,
+ function(result) {
+ if (allFiles) {
+ if (result.status !== 'success') {
+ OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
+ }
+ FileList.hideMask();
+ // simply remove all files
+ FileList.setFiles([]);
+ enableActions();
+ }
+ else {
+ FileList._removeCallback(result);
+ }
+ }
+ );
+ };
+
+ var oldClickFile = FileList._onClickFile;
+ FileList._onClickFile = function(event) {
+ var mime = $(this).parent().parent().data('mime');
+ if (mime !== 'httpd/unix-directory') {
+ event.preventDefault();
+ }
+ return oldClickFile.apply(this, arguments);
+ };
+
})();
diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js
index f7724d07d2b..5f2436de809 100644
--- a/apps/files_trashbin/js/trash.js
+++ b/apps/files_trashbin/js/trash.js
@@ -28,20 +28,6 @@ $(document).ready(function() {
return name;
}
- function removeCallback(result) {
- if (result.status !== 'success') {
- OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
- }
-
- var files = result.data.success;
- for (var i = 0; i < files.length; i++) {
- FileList.remove(OC.basename(files[i].filename), {updateSummary: false});
- }
- FileList.updateFileSummary();
- FileList.updateEmptyContent();
- enableActions();
- }
-
Files.updateStorageStatistics = function() {
// no op because the trashbin doesn't have
// storage info like free space / used space
@@ -57,7 +43,7 @@ $(document).ready(function() {
files: JSON.stringify([filename]),
dir: FileList.getCurrentDirectory()
},
- removeCallback
+ FileList._removeCallback
);
}, t('files_trashbin', 'Restore'));
};
@@ -74,153 +60,10 @@ $(document).ready(function() {
files: JSON.stringify([filename]),
dir: FileList.getCurrentDirectory()
},
- removeCallback
+ FileList._removeCallback
);
});
- // Sets the select_all checkbox behaviour :
- $('#select_all').click(function() {
- if ($(this).attr('checked')) {
- // Check all
- $('td.filename input:checkbox').attr('checked', true);
- $('td.filename input:checkbox').parent().parent().addClass('selected');
- } else {
- // Uncheck all
- $('td.filename input:checkbox').attr('checked', false);
- $('td.filename input:checkbox').parent().parent().removeClass('selected');
- }
- procesSelection();
- });
- $('.undelete').click('click', function(event) {
- event.preventDefault();
- var allFiles = $('#select_all').is(':checked');
- var files = [];
- var params = {};
- disableActions();
- if (allFiles) {
- FileList.showMask();
- params = {
- allfiles: true,
- dir: FileList.getCurrentDirectory()
- };
- }
- else {
- files = Files.getSelectedFiles('name');
- for (var i = 0; i < files.length; i++) {
- var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
- deleteAction.removeClass('delete-icon').addClass('progress-icon');
- }
- params = {
- files: JSON.stringify(files),
- dir: FileList.getCurrentDirectory()
- };
- }
-
- $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'),
- params,
- function(result) {
- if (allFiles) {
- if (result.status !== 'success') {
- OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
- }
- FileList.hideMask();
- // simply remove all files
- FileList.update('');
- enableActions();
- }
- else {
- removeCallback(result);
- }
- }
- );
- });
-
- $('.delete').click('click', function(event) {
- event.preventDefault();
- var allFiles = $('#select_all').is(':checked');
- var files = [];
- var params = {};
- if (allFiles) {
- params = {
- allfiles: true,
- dir: FileList.getCurrentDirectory()
- };
- }
- else {
- files = Files.getSelectedFiles('name');
- params = {
- files: JSON.stringify(files),
- dir: FileList.getCurrentDirectory()
- };
- }
-
- disableActions();
- if (allFiles) {
- FileList.showMask();
- }
- else {
- for (var i = 0; i < files.length; i++) {
- var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
- deleteAction.removeClass('delete-icon').addClass('progress-icon');
- }
- }
-
- $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
- params,
- function(result) {
- if (allFiles) {
- if (result.status !== 'success') {
- OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
- }
- FileList.hideMask();
- // simply remove all files
- FileList.setFiles([]);
- enableActions();
- }
- else {
- removeCallback(result);
- }
- }
- );
-
- });
-
- $('#fileList').on('click', 'td.filename input', function() {
- var checkbox = $(this).parent().children('input:checkbox');
- $(checkbox).parent().parent().toggleClass('selected');
- if ($(checkbox).is(':checked')) {
- var selectedCount = $('td.filename input:checkbox:checked').length;
- if (selectedCount === $('td.filename input:checkbox').length) {
- $('#select_all').prop('checked', true);
- }
- } else {
- $('#select_all').prop('checked',false);
- }
- procesSelection();
- });
-
- $('#fileList').on('click', 'td.filename a', function(event) {
- var mime = $(this).parent().parent().data('mime');
- if (mime !== 'httpd/unix-directory') {
- event.preventDefault();
- }
- var filename = $(this).parent().parent().attr('data-file');
- var tr = FileList.findFileEl(filename);
- var renaming = tr.data('renaming');
- if(!renaming){
- if(mime.substr(0, 5) === 'text/'){ //no texteditor for now
- return;
- }
- var type = $(this).parent().parent().data('type');
- var permissions = $(this).parent().parent().data('permissions');
- var action = FileActions.getDefault(mime, type, permissions);
- if(action){
- event.preventDefault();
- action(filename);
- }
- }
- });
-
/**
* Override crumb URL maker (hacky!)
*/
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index b6c61c9b1c3..323e7495535 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -29,7 +29,7 @@
<th id="headerDate">
<span id="modified"><?php p($l->t( 'Deleted' )); ?></span>
<span class="selectedActions">
- <a href="" class="delete">
+ <a href="" class="delete-selected">
<?php p($l->t('Delete'))?>
<img class="svg" alt="<?php p($l->t('Delete'))?>"
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
@@ -40,4 +40,6 @@
</thead>
<tbody id="fileList">
</tbody>
+ <tfoot>
+ </tfoot>
</table>
diff --git a/core/css/apps.css b/core/css/apps.css
index a8dfc5b7ed1..a0bb262854d 100644
--- a/core/css/apps.css
+++ b/core/css/apps.css
@@ -243,7 +243,6 @@ button.loading {
padding-right: 30px;
}
-
/* general styles for the content area */
.section {
display: block;
@@ -264,3 +263,14 @@ button.loading {
vertical-align: -2px;
margin-right: 4px;
}
+.appear {
+ opacity: 1;
+ transition: opacity 500ms ease 0s;
+ -moz-transition: opacity 500ms ease 0s;
+ -ms-transition: opacity 500ms ease 0s;
+ -o-transition: opacity 500ms ease 0s;
+ -webkit-transition: opacity 500ms ease 0s;
+}
+.appear.transparent {
+ opacity: 0;
+}
diff --git a/core/js/js.js b/core/js/js.js
index 0aa8d12b3d6..27bc3c651e3 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1250,9 +1250,12 @@ function relative_modified_date(timestamp) {
}
/**
- * @todo Write documentation
+ * Utility functions
*/
OC.Util = {
+ // TODO: remove original functions from global namespace
+ humanFileSize: humanFileSize,
+ formatDate: formatDate,
/**
* Returns whether the browser supports SVG
* @return {boolean} true if the browser supports SVG, false otherwise
diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js
index ccd9f7a1288..65f768fbc51 100644
--- a/core/js/tests/specs/coreSpec.js
+++ b/core/js/tests/specs/coreSpec.js
@@ -474,5 +474,22 @@ describe('Core base tests', function() {
);
});
});
+ describe('Util', function() {
+ describe('humanFileSize', function() {
+ it('renders file sizes with the correct unit', function() {
+ var data = [
+ [0, '0 B'],
+ [125, '125 B'],
+ [128000, '125 kB'],
+ [128000000, '122.1 MB'],
+ [128000000000, '119.2 GB'],
+ [128000000000000, '116.4 TB']
+ ];
+ for (var i = 0; i < data.length; i++) {
+ expect(OC.Util.humanFileSize(data[i][0])).toEqual(data[i][1]);
+ }
+ });
+ });
+ });
});