summaryrefslogtreecommitdiffstats
path: root/apps/files
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files')
-rw-r--r--apps/files/js/filelist.js172
-rw-r--r--apps/files/tests/js/filelistSpec.js84
2 files changed, 150 insertions, 106 deletions
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 02754d7acbb..53bb3a5c868 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -31,6 +31,20 @@ window.FileList = {
totalPages: 0,
/**
+ * 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
*/
initialize: function() {
@@ -42,6 +56,7 @@ window.FileList = {
// TODO: FileList should not know about global elements
this.$el = $('#filestable');
this.$fileList = $('#fileList');
+ this.files = [];
this.fileSummary = this._createSummary();
@@ -321,7 +336,8 @@ window.FileList = {
index = this.pageNumber * this.pageSize;
while (count > 0 && index < this.files.length) {
- tr = this.add(this.files[index], {updateSummary: false});
+ tr = this._renderRow(this.files[index], {updateSummary: false});
+ this.$fileList.append(tr);
if (selected) {
tr.addClass('selected');
tr.find('input:checkbox').prop('checked', true);
@@ -497,8 +513,10 @@ 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
+ *
* @param fileData map of file attributes
* @param options map of attributes:
* - "insert" true to insert in a sorted manner, false to append (default)
@@ -506,6 +524,47 @@ window.FileList = {
* @return new tr element (not appended to the table)
*/
add: function(fileData, options) {
+ var index = -1;
+ var $tr = this._renderRow(fileData, options);
+ options = options || {};
+
+ this.isEmpty = false;
+
+ if (options.insert) {
+ index = this._findInsertionIndex(fileData);
+ if (index < this.files.length) {
+ this.files.splice(index, 0, fileData);
+ this.$fileList.children().eq(index).before($tr);
+ }
+ else {
+ this.files.push(fileData);
+ this.$fileList.append($tr);
+ }
+ }
+ else {
+ this.files.push(fileData);
+ this.$fileList.append($tr);
+ }
+
+ // 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,
@@ -524,16 +583,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) {
@@ -569,12 +618,6 @@ window.FileList = {
filenameTd.css('background-image', 'url(' + previewUrl + ')');
}
}
-
- // defaults to true if not defined
- if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
- this.fileSummary.add(fileData, true);
- this.updateEmptyContent();
- }
return tr;
},
/**
@@ -742,9 +785,10 @@ window.FileList = {
* "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;
}
@@ -752,51 +796,32 @@ window.FileList = {
// 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').length;
+ FileList.isEmpty = !this.files.length;
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
FileList.updateEmptyContent();
this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, 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();
+ return index;
},
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);
@@ -844,51 +869,18 @@ window.FileList = {
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, {insert: true});
}
});
}
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 93e7c81cb1f..1b155f4f1df 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -233,6 +233,7 @@ describe('FileList tests', function() {
expect($tr.find('.filesize').text()).toEqual('0 B');
});
it('adds new file to the end of the list', function() {
+ var $tr;
var fileData = {
type: 'file',
name: 'P comes after O.txt'
@@ -241,15 +242,55 @@ describe('FileList tests', function() {
$tr = FileList.add(fileData);
expect($tr.index()).toEqual(4);
});
- 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], {insert: true});
+ }
+ 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);
+ for (var i = 0; i < testFiles.length; i++) {
+ FileList.add(testFiles[i], {insert: true});
+ }
$tr = FileList.add(fileData, {insert: true});
// 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], {insert: true});
+ }
+ $tr = FileList.add(fileData, {insert: true});
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], {insert: true});
+ }
+ $tr = FileList.add(fileData, {insert: true});
+ 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 = {
@@ -280,6 +321,7 @@ describe('FileList tests', function() {
expect($removedEl).toBeDefined();
expect($removedEl.attr('data-file')).toEqual('One.txt');
expect($('#fileList tr').length).toEqual(3);
+ expect(FileList.files.length).toEqual(3);
expect(FileList.findFileEl('One.txt').length).toEqual(0);
$summary = $('#filestable .summary');
@@ -294,6 +336,7 @@ describe('FileList tests', function() {
FileList.setFiles([testFiles[0]]);
FileList.remove('One.txt');
expect($('#fileList tr').length).toEqual(0);
+ expect(FileList.files.length).toEqual(0);
expect(FileList.findFileEl('One.txt').length).toEqual(0);
$summary = $('#filestable .summary');
@@ -358,6 +401,7 @@ describe('FileList tests', function() {
$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);
});
@@ -383,37 +427,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], {insert: true});
+ }
// 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);
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);
});
@@ -429,7 +477,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);
});
@@ -440,12 +489,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() {
@@ -457,20 +506,23 @@ 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('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').length).toEqual(4);
+ expect(FileList.files.length).toEqual(4);
+ expect(FileList.files).toEqual(testFiles);
addSpy.restore();
});
it('updates summary using the file sizes', function() {