Merge pull request #8041 from owncloud/files-sortcolumns

File list sorting by clicking on column headers
This commit is contained in:
Vincent Petry 2014-05-12 12:50:27 +02:00
commit 9a9665f361
12 changed files with 507 additions and 74 deletions

View File

@ -17,8 +17,11 @@ $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir=';
$permissions = $dirInfo->getPermissions();
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
// make filelist
$files = \OCA\Files\Helper::getFiles($dir);
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
$data['directory'] = $dir;
$data['files'] = \OCA\Files\Helper::formatFileInfos($files);

View File

@ -116,10 +116,29 @@ tr:hover span.extension {
table tr.mouseOver td { background-color:#eee; }
table th { height:24px; padding:0 8px; color:#999; }
table th .name {
position: absolute;
left: 55px;
top: 15px;
table th .columntitle {
display: inline-block;
padding: 15px;
width: 100%;
height: 50px;
box-sizing: border-box;
-moz-box-sizing: border-box;
vertical-align: middle;
}
table th .columntitle.name {
padding-left: 5px;
margin-left: 50px;
max-width: 300px;
}
/* hover effect on sortable column */
table th a.columntitle:hover {
background-color: #F0F0F0;
}
table th .sort-indicator {
width: 10px;
height: 8px;
margin-left: 10px;
display: inline-block;
}
table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; }
table td {
@ -139,8 +158,11 @@ table th#headerName {
}
table th#headerSize, table td.filesize {
min-width: 48px;
padding: 0 16px;
text-align: right;
padding: 0;
}
table table td.filesize {
padding: 0 16px;
}
table th#headerDate, table td.date {
-moz-box-sizing: border-box;
@ -197,10 +219,6 @@ table td.filename input.filename {
table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:3px 8px 8px 3px; }
table td.filename .nametext, .uploadtext, .modified { float:left; padding:14px 0; }
#modified {
position: absolute;
top: 15px;
}
.modified {
position: relative;
padding-left: 8px;

View File

@ -11,6 +11,9 @@
/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
/* global dragOptions, folderDropOptions */
window.FileList = {
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
appName: t('files', 'Files'),
isEmpty: true,
useUndo:true,
@ -45,18 +48,19 @@ window.FileList = {
_selectionSummary: null,
/**
* Compare two file info objects, sorting by
* folders first, then by name.
* Sort attribute
*/
_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);
},
_sort: 'name',
/**
* Sort direction: 'asc' or 'desc'
*/
_sortDirection: 'asc',
/**
* Sort comparator function for the current sort
*/
_sortComparator: null,
/**
* Initialize the file list and its components
@ -76,6 +80,8 @@ window.FileList = {
this.fileSummary = this._createSummary();
this.setSort('name', 'asc');
this.breadcrumb = new BreadCrumb({
onClick: this._onClickBreadCrumb,
onDrop: _.bind(this._onDropOnBreadCrumb, this),
@ -86,6 +92,8 @@ window.FileList = {
$('#controls').prepend(this.breadcrumb.$el);
this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
$(window).resize(function() {
// TODO: debounce this ?
var width = $(this).width();
@ -236,6 +244,27 @@ window.FileList = {
return false;
},
/**
* Event handler when clicking on a table header
*/
_onClickHeader: function(e) {
var $target = $(e.target);
var sort;
if (!$target.is('a')) {
$target = $target.closest('a');
}
sort = $target.attr('data-sort');
if (sort) {
if (this._sort === sort) {
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
}
else {
this.setSort(sort, 'asc');
}
this.reload();
}
},
/**
* Event handler when clicking on a bread crumb
*/
@ -685,8 +714,6 @@ window.FileList = {
previousDir: currentDir
}
));
this._selectedFiles = {};
this._selectionSummary.clear();
this.reload();
},
linkTo: function(dir) {
@ -722,10 +749,34 @@ window.FileList = {
}
this.breadcrumb.setDirectory(this.getCurrentDirectory());
},
/**
* Sets the current sorting and refreshes the list
*
* @param sort sort attribute name
* @param direction sort direction, one of "asc" or "desc"
*/
setSort: function(sort, direction) {
var comparator = this.Comparators[sort] || this.Comparators.name;
this._sort = sort;
this._sortDirection = (direction === 'desc')?'desc':'asc';
this._sortComparator = comparator;
if (direction === 'desc') {
this._sortComparator = function(fileInfo1, fileInfo2) {
return -comparator(fileInfo1, fileInfo2);
};
}
this.$el.find('thead th .sort-indicator')
.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
this.$el.find('thead th.column-' + sort + ' .sort-indicator')
.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
},
/**
* @brief Reloads the file list using ajax call
*/
reload: function() {
this._selectedFiles = {};
this._selectionSummary.clear();
this.$el.find('#select_all').prop('checked', false);
FileList.showMask();
if (FileList._reloadCall) {
FileList._reloadCall.abort();
@ -733,7 +784,9 @@ window.FileList = {
FileList._reloadCall = $.ajax({
url: Files.getAjaxUrl('list'),
data: {
dir : $('#dir').val()
dir: $('#dir').val(),
sort: FileList._sort,
sortdirection: FileList._sortDirection
},
error: function(result) {
FileList.reloadCallback(result);
@ -859,7 +912,7 @@ window.FileList = {
*/
_findInsertionIndex: function(fileData) {
var index = 0;
while (index < this.files.length && this._fileInfoCompare(fileData, this.files[index]) > 0) {
while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) {
index++;
}
return index;
@ -924,7 +977,7 @@ window.FileList = {
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
}
$td.css('background-image', oldBackgroundImage);
});
});
});
},
@ -1221,15 +1274,15 @@ window.FileList = {
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'));
$('#headerName a.name>span:first').text(t('files','Name'));
$('#headerSize a>span:first').text(t('files','Size'));
$('#modified a>span:first').text(t('files','Modified'));
$('table').removeClass('multiselect');
$('.selectedActions').addClass('hidden');
}
else {
$('.selectedActions').removeClass('hidden');
$('#headerSize').text(OC.Util.humanFileSize(summary.totalSize));
$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
var selection = '';
if (summary.totalDirs > 0) {
selection += n('files', '%n folder', '%n folders', summary.totalDirs);
@ -1240,8 +1293,8 @@ window.FileList = {
if (summary.totalFiles > 0) {
selection += n('files', '%n file', '%n files', summary.totalFiles);
}
$('#headerName span.name').text(selection);
$('#modified').text('');
$('#headerName a.name>span:first').text(selection);
$('#modified a>span:first').text('');
$('table').addClass('multiselect');
}
},
@ -1545,3 +1598,49 @@ $(document).ready(function() {
}, 0);
});
/**
* Sort comparators.
*/
FileList.Comparators = {
/**
* Compares two file infos by name, making directories appear
* first.
*
* @param fileInfo1 file info
* @param fileInfo2 file info
* @return -1 if the first file must appear before the second one,
* 0 if they are identify, 1 otherwise.
*/
name: 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);
},
/**
* Compares two file infos by size.
*
* @param fileInfo1 file info
* @param fileInfo2 file info
* @return -1 if the first file must appear before the second one,
* 0 if they are identify, 1 otherwise.
*/
size: function(fileInfo1, fileInfo2) {
return fileInfo1.size - fileInfo2.size;
},
/**
* Compares two file infos by timestamp.
*
* @param fileInfo1 file info
* @param fileInfo2 file info
* @return -1 if the first file must appear before the second one,
* 0 if they are identify, 1 otherwise.
*/
mtime: function(fileInfo1, fileInfo2) {
return fileInfo1.mtime - fileInfo2.mtime;
}
};

View File

@ -1,7 +1,16 @@
<?php
/**
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files;
/**
* Helper class for manipulating file information
*/
class Helper
{
public static function buildFileStorageStatistics($dir) {
@ -9,12 +18,12 @@ class Helper
$storageInfo = \OC_Helper::getStorageInfo($dir);
$l = new \OC_L10N('files');
$maxUploadFilesize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']);
$maxHumanFilesize = \OCP\Util::humanFileSize($maxUploadFilesize);
$maxHumanFilesize = $l->t('Upload (max. %s)', array($maxHumanFilesize));
$maxUploadFileSize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']);
$maxHumanFileSize = \OCP\Util::humanFileSize($maxUploadFileSize);
$maxHumanFileSize = $l->t('Upload (max. %s)', array($maxHumanFileSize));
return array('uploadMaxFilesize' => $maxUploadFilesize,
'maxHumanFilesize' => $maxHumanFilesize,
return array('uploadMaxFilesize' => $maxUploadFileSize,
'maxHumanFilesize' => $maxHumanFileSize,
'freeSpace' => $storageInfo['free'],
'usedSpacePercent' => (int)$storageInfo['relative']);
}
@ -27,7 +36,6 @@ class Helper
*/
public static function determineIcon($file) {
if($file['type'] === 'dir') {
$dir = $file['directory'];
$icon = \OC_Helper::mimetypeIcon('dir');
$absPath = $file->getPath();
$mount = \OC\Files\Filesystem::getMountManager()->find($absPath);
@ -57,7 +65,7 @@ class Helper
* @param \OCP\Files\FileInfo $b file
* @return int -1 if $a must come before $b, 1 otherwise
*/
public static function fileCmp($a, $b) {
public static function compareFileNames($a, $b) {
$aType = $a->getType();
$bType = $b->getType();
if ($aType === 'dir' and $bType !== 'dir') {
@ -69,6 +77,32 @@ class Helper
}
}
/**
* Comparator function to sort files by date
*
* @param \OCP\Files\FileInfo $a file
* @param \OCP\Files\FileInfo $b file
* @return int -1 if $a must come before $b, 1 otherwise
*/
public static function compareTimestamp($a, $b) {
$aTime = $a->getMTime();
$bTime = $b->getMTime();
return $aTime - $bTime;
}
/**
* Comparator function to sort files by size
*
* @param \OCP\Files\FileInfo $a file
* @param \OCP\Files\FileInfo $b file
* @return int -1 if $a must come before $b, 1 otherwise
*/
public static function compareSize($a, $b) {
$aSize = $a->getSize();
$bSize = $b->getSize();
return $aSize - $bSize;
}
/**
* Formats the file info to be returned as JSON to the client.
*
@ -120,12 +154,35 @@ class Helper
* returns it as a sorted array of FileInfo.
*
* @param string $dir path to the directory
* @param string $sortAttribute attribute to sort on
* @param bool $sortDescending true for descending sort, false otherwise
* @return \OCP\Files\FileInfo[] files
*/
public static function getFiles($dir) {
public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) {
$content = \OC\Files\Filesystem::getDirectoryContent($dir);
usort($content, array('\OCA\Files\Helper', 'fileCmp'));
return $content;
return self::sortFiles($content, $sortAttribute, $sortDescending);
}
/**
* Sort the given file info array
*
* @param \OCP\Files\FileInfo[] files to sort
* @param string $sortAttribute attribute to sort on
* @param bool $sortDescending true for descending sort, false otherwise
* @return \OCP\Files\FileInfo[] sorted files
*/
public static function sortFiles($files, $sortAttribute = 'name', $sortDescending = false) {
$sortFunc = 'compareFileNames';
if ($sortAttribute === 'mtime') {
$sortFunc = 'compareTimestamp';
} else if ($sortAttribute === 'size') {
$sortFunc = 'compareSize';
}
usort($files, array('\OCA\Files\Helper', $sortFunc));
if ($sortDescending) {
$files = array_reverse($files);
}
return $files;
}
}

View File

@ -1,3 +1,4 @@
<?php /** @var $l OC_L10N */ ?>
<div id="controls">
<div class="actions creatable hidden">
<?php if(!isset($_['dirToken'])):?>
@ -60,11 +61,11 @@
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
<thead>
<tr>
<th class="hidden" id='headerName'>
<th id='headerName' class="hidden column-name">
<div id="headerName-container">
<input type="checkbox" id="select_all" />
<label for="select_all"></label>
<span class="name"><?php p($l->t( 'Name' )); ?></span>
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
<span id="selectedActionsList" class="selectedActions">
<?php if($_['allowZipDownload']) : ?>
<a href="" class="download">
@ -76,9 +77,11 @@
</span>
</div>
</th>
<th class="hidden" id="headerSize"><?php p($l->t('Size')); ?></th>
<th class="hidden" id="headerDate">
<span id="modified"><?php p($l->t( 'Modified' )); ?></span>
<th id="headerSize" class="hidden column-size">
<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a>
</th>
<th id="headerDate" class="hidden column-mtime">
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
<span class="selectedActions"><a href="" class="delete-selected">
<?php p($l->t('Delete'))?>

View File

@ -0,0 +1,98 @@
<?php
/**
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
require_once __DIR__ . '/../lib/helper.php';
use OCA\Files;
/**
* Class Test_Files_Helper
*/
class Test_Files_Helper extends \PHPUnit_Framework_TestCase {
private function makeFileInfo($name, $size, $mtime, $isDir = false) {
return new \OC\Files\FileInfo(
'/',
null,
'/',
array(
'name' => $name,
'size' => $size,
'mtime' => $mtime,
'type' => $isDir ? 'dir' : 'file',
'mimetype' => $isDir ? 'httpd/unix-directory' : 'application/octet-stream'
)
);
}
/**
* Returns a file list for testing
*/
private function getTestFileList() {
return array(
self::makeFileInfo('a.txt', 4, 1000),
self::makeFileInfo('q.txt', 5, 150),
self::makeFileInfo('subdir2', 87, 128, true),
self::makeFileInfo('b.txt', 166, 800),
self::makeFileInfo('o.txt', 12, 100),
self::makeFileInfo('subdir', 88, 125, true),
);
}
function sortDataProvider() {
return array(
array(
'name',
false,
array('subdir', 'subdir2', 'a.txt', 'b.txt', 'o.txt', 'q.txt'),
),
array(
'name',
true,
array('q.txt', 'o.txt', 'b.txt', 'a.txt', 'subdir2', 'subdir'),
),
array(
'size',
false,
array('a.txt', 'q.txt', 'o.txt', 'subdir2', 'subdir', 'b.txt'),
),
array(
'size',
true,
array('b.txt', 'subdir', 'subdir2', 'o.txt', 'q.txt', 'a.txt'),
),
array(
'mtime',
false,
array('o.txt', 'subdir', 'subdir2', 'q.txt', 'b.txt', 'a.txt'),
),
array(
'mtime',
true,
array('a.txt', 'b.txt', 'q.txt', 'subdir2', 'subdir', 'o.txt'),
),
);
}
/**
* @dataProvider sortDataProvider
*/
public function testSortByName($sort, $sortDescending, $expectedOrder) {
$files = self::getTestFileList();
$files = \OCA\Files\Helper::sortFiles($files, $sort, $sortDescending);
$fileNames = array();
foreach ($files as $fileInfo) {
$fileNames[] = $fileInfo->getName();
}
$this->assertEquals(
$expectedOrder,
$fileNames
);
}
}

View File

@ -77,13 +77,17 @@ describe('FileList tests', function() {
// dummy table
// TODO: at some point this will be rendered by the FileList class itself!
'<table id="filestable">' +
'<thead><tr><th id="headerName" class="hidden">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all">' +
'<span class="name">Name</span>' +
'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +
'<span class="selectedActions hidden">' +
'<a href class="download">Download</a>' +
'<a href class="delete-selected">Delete</a></span>' +
'</th></tr></thead>' +
'</th>' +
'<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' +
'<th class="hidden column-mtime"><a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a></th>' +
'</tr></thead>' +
'<tbody id="fileList"></tbody>' +
'<tfoot></tfoot>' +
'</table>' +
@ -940,7 +944,7 @@ describe('FileList tests', function() {
expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url;
var query = url.substr(url.indexOf('?') + 1);
expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'});
expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', sort: 'name', sortdirection: 'asc'});
fakeServer.respond();
expect($('#fileList tr').length).toEqual(4);
expect(FileList.findFileEl('One.txt').length).toEqual(1);
@ -951,7 +955,7 @@ describe('FileList tests', function() {
expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url;
var query = url.substr(url.indexOf('?') + 1);
expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir'});
expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir', sort: 'name', sortdirection: 'asc'});
fakeServer.respond();
});
it('switches to root dir when current directory does not exist', function() {
@ -1260,7 +1264,7 @@ describe('FileList tests', function() {
expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
});
it('Selecting files updates selection summary', function() {
var $summary = $('#headerName span.name');
var $summary = $('#headerName a.name>span:first');
expect($summary.text()).toEqual('Name');
FileList.findFileEl('One.txt').find('input:checkbox').click();
FileList.findFileEl('Three.pdf').find('input:checkbox').click();
@ -1268,7 +1272,7 @@ describe('FileList tests', function() {
expect($summary.text()).toEqual('1 folder & 2 files');
});
it('Unselecting files hides selection summary', function() {
var $summary = $('#headerName span.name');
var $summary = $('#headerName a.name>span:first');
FileList.findFileEl('One.txt').find('input:checkbox').click().click();
expect($summary.text()).toEqual('Name');
});
@ -1431,5 +1435,150 @@ describe('FileList tests', function() {
});
});
});
it('resets the file selection on reload', function() {
FileList.$el.find('#select_all').click();
FileList.reload();
expect(FileList.$el.find('#select_all').prop('checked')).toEqual(false);
expect(FileList.getSelectedFiles()).toEqual([]);
});
});
describe('Sorting files', function() {
it('Sorts by name by default', function() {
FileList.reload();
expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url;
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
expect(query.sort).toEqual('name');
expect(query.sortdirection).toEqual('asc');
});
it('Reloads file list with a different sort when clicking on column header of unsorted column', function() {
FileList.$el.find('.column-size .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url;
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
expect(query.sort).toEqual('size');
expect(query.sortdirection).toEqual('asc');
});
it('Toggles sort direction when clicking on already sorted column', function() {
FileList.$el.find('.column-name .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url;
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
expect(query.sort).toEqual('name');
expect(query.sortdirection).toEqual('desc');
});
it('Toggles the sort indicator when clicking on a column header', function() {
var ASC_CLASS = FileList.SORT_INDICATOR_ASC_CLASS;
var DESC_CLASS = FileList.SORT_INDICATOR_DESC_CLASS;
FileList.$el.find('.column-size .columntitle').click();
// moves triangle to size column
expect(
FileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
).toEqual(false);
expect(
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
).toEqual(true);
// click again on size column, reverses direction
FileList.$el.find('.column-size .columntitle').click();
expect(
FileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
).toEqual(true);
// click again on size column, reverses direction
FileList.$el.find('.column-size .columntitle').click();
expect(
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
).toEqual(true);
// click on mtime column, moves indicator there
FileList.$el.find('.column-mtime .columntitle').click();
expect(
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
).toEqual(false);
expect(
FileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS)
).toEqual(true);
});
it('Uses correct sort comparator when inserting files', function() {
testFiles.sort(FileList.Comparators.size);
// this will make it reload the testFiles with the correct sorting
FileList.$el.find('.column-size .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
files: testFiles,
permissions: 31
}
})
);
var newFileData = {
id: 999,
type: 'file',
name: 'new file.txt',
mimetype: 'text/plain',
size: 40001,
etag: '999'
};
FileList.add(newFileData);
expect(FileList.files.length).toEqual(5);
expect(FileList.$fileList.find('tr').length).toEqual(5);
expect(FileList.findFileEl('One.txt').index()).toEqual(0);
expect(FileList.findFileEl('somedir').index()).toEqual(1);
expect(FileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(FileList.findFileEl('new file.txt').index()).toEqual(3);
expect(FileList.findFileEl('Three.pdf').index()).toEqual(4);
});
it('Uses correct reversed sort comparator when inserting files', function() {
testFiles.sort(FileList.Comparators.size);
testFiles.reverse();
// this will make it reload the testFiles with the correct sorting
FileList.$el.find('.column-size .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
files: testFiles,
permissions: 31
}
})
);
// reverse sort
FileList.$el.find('.column-size .columntitle').click();
fakeServer.requests[1].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
files: testFiles,
permissions: 31
}
})
);
var newFileData = {
id: 999,
type: 'file',
name: 'new file.txt',
mimetype: 'text/plain',
size: 40001,
etag: '999'
};
FileList.add(newFileData);
expect(FileList.files.length).toEqual(5);
expect(FileList.$fileList.find('tr').length).toEqual(5);
expect(FileList.findFileEl('One.txt').index()).toEqual(4);
expect(FileList.findFileEl('somedir').index()).toEqual(3);
expect(FileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(FileList.findFileEl('new file.txt').index()).toEqual(1);
expect(FileList.findFileEl('Three.pdf').index()).toEqual(0);
});
});
});

View File

@ -20,11 +20,6 @@
*
*/
// only need filesystem apps
$RUNTIME_APPTYPES=array('filesystem');
// Init owncloud
if(!\OC_App::isEnabled('files_sharing')){
exit;
}
@ -47,6 +42,9 @@ if (isset($_GET['dir'])) {
$relativePath = $_GET['dir'];
}
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password);
$linkItem = $data['linkItem'];
@ -64,7 +62,7 @@ $data = array();
$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir=';
// make filelist
$files = \OCA\Files\Helper::getFiles($dir);
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
$formattedFiles = array();
foreach ($files as $file) {

View File

@ -4,11 +4,13 @@ OCP\JSON::checkLoggedIn();
// Load the files
$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : '';
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
$data = array();
// make filelist
try {
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir);
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, $sortAttribute, $sortDirection);
} catch (Exception $e) {
header("HTTP/1.0 404 Not Found");
exit();

View File

@ -80,6 +80,7 @@
FileList.initialize = function() {
var result = oldInit.apply(this, arguments);
$('.undelete').click('click', FileList._onClickRestoreSelected);
this.setSort('mtime', 'desc');
return result;
};

View File

@ -8,11 +8,14 @@ class Helper
{
/**
* Retrieves the contents of a trash bin directory.
*
* @param string $dir path to the directory inside the trashbin
* or empty to retrieve the root of the trashbin
* @param string $sortAttribute attribute to sort on or empty to disable sorting
* @param bool $sortDescending true for descending sort, false otherwise
* @return \OCP\Files\FileInfo[]
*/
public static function getTrashFiles($dir){
public static function getTrashFiles($dir, $sortAttribute = '', $sortDescending = false){
$result = array();
$timestamp = null;
$user = \OCP\User::getUser();
@ -57,8 +60,9 @@ class Helper
closedir($dirContent);
}
usort($result, array('\OCA\Files\Helper', 'fileCmp'));
if ($sortAttribute !== '') {
return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending);
}
return $result;
}

View File

@ -1,3 +1,4 @@
<?php /** @var $l OC_L10N */ ?>
<div id="controls">
<div id="file_action_panel"></div>
</div>
@ -5,29 +6,29 @@
<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div>
<input type="hidden" id="permissions" value="0"></input>
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"></input>
<input type="hidden" id="permissions" value="0">
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>">
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
<table id="filestable">
<thead>
<tr>
<th id='headerName'>
<th id='headerName' class="hidden column-name">
<div id="headerName-container">
<input type="checkbox" id="select_all" />
<label for="select_all"></label>
<span class='name'><?php p($l->t( 'Name' )); ?></span>
<span class='selectedActions'>
<input type="checkbox" id="select_all" />
<label for="select_all"></label>
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
<span id="selectedActionsList" class='selectedActions'>
<a href="" class="undelete">
<img class="svg" alt="<?php p($l->t( 'Restore' )); ?>"
src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" />
<?php p($l->t('Restore'))?>
</a>
</span>
</span>
</div>
</th>
<th id="headerDate">
<span id="modified"><?php p($l->t( 'Deleted' )); ?></span>
<th id="headerDate" class="hidden column-mtime">
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Deleted' )); ?></span><span class="sort-indicator"></span></a>
<span class="selectedActions">
<a href="" class="delete-selected">
<?php p($l->t('Delete'))?>