summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2014-05-12 12:50:27 +0200
committerVincent Petry <pvince81@owncloud.com>2014-05-12 12:50:27 +0200
commit9a9665f361c42764f5ae9f5f3ce63f71fdfcad5c (patch)
treeca04fac8b2ea1ca922a11fd17a7d4b913117a92e /apps
parentf4e8de3cbbf426e7b22d5100085626b2e4fefc29 (diff)
parent61db16321f1bf7ceea22672b33cd165fc3620a4a (diff)
downloadnextcloud-server-9a9665f361c42764f5ae9f5f3ce63f71fdfcad5c.tar.gz
nextcloud-server-9a9665f361c42764f5ae9f5f3ce63f71fdfcad5c.zip
Merge pull request #8041 from owncloud/files-sortcolumns
File list sorting by clicking on column headers
Diffstat (limited to 'apps')
-rw-r--r--apps/files/ajax/list.php5
-rw-r--r--apps/files/css/files.css36
-rw-r--r--apps/files/js/filelist.js143
-rw-r--r--apps/files/lib/helper.php77
-rw-r--r--apps/files/templates/index.php13
-rw-r--r--apps/files/tests/helper.php98
-rw-r--r--apps/files/tests/js/filelistSpec.js163
-rw-r--r--apps/files_sharing/ajax/list.php10
-rw-r--r--apps/files_trashbin/ajax/list.php4
-rw-r--r--apps/files_trashbin/js/filelist.js1
-rw-r--r--apps/files_trashbin/lib/helper.php10
-rw-r--r--apps/files_trashbin/templates/index.php21
12 files changed, 507 insertions, 74 deletions
diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php
index 2d76b685018..bae3628402f 100644
--- a/apps/files/ajax/list.php
+++ b/apps/files/ajax/list.php
@@ -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);
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 533050691d5..2aae73f8bd0 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -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;
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 8896a8e23a8..73a441368bb 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -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();
@@ -237,6 +245,27 @@ window.FileList = {
},
/**
+ * 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
*/
_onClickBreadCrumb: function(e) {
@@ -685,8 +714,6 @@ window.FileList = {
previousDir: currentDir
}
));
- this._selectedFiles = {};
- this._selectionSummary.clear();
this.reload();
},
linkTo: function(dir) {
@@ -723,9 +750,33 @@ 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;
+ }
+};
+
diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php
index 0ae87d12fbf..7e0f47bf84f 100644
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/helper.php
@@ -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') {
@@ -70,6 +78,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.
*
* @param \OCP\Files\FileInfo $i
@@ -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;
}
}
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index 42263c880a7..9c593a9341d 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -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'))?>
diff --git a/apps/files/tests/helper.php b/apps/files/tests/helper.php
new file mode 100644
index 00000000000..9b3603cd563
--- /dev/null
+++ b/apps/files/tests/helper.php
@@ -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
+ );
+ }
+
+}
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 7a2b56d559a..edb9e15e9bc 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -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);
+ });
});
});
diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php
index 4b645496253..82bacb3d38d 100644
--- a/apps/files_sharing/ajax/list.php
+++ b/apps/files_sharing/ajax/list.php
@@ -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) {
diff --git a/apps/files_trashbin/ajax/list.php b/apps/files_trashbin/ajax/list.php
index 89a55114524..e1f52e814bb 100644
--- a/apps/files_trashbin/ajax/list.php
+++ b/apps/files_trashbin/ajax/list.php
@@ -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();
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index 3bb3a92b60d..00fc7e16b88 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/apps/files_trashbin/js/filelist.js
@@ -80,6 +80,7 @@
FileList.initialize = function() {
var result = oldInit.apply(this, arguments);
$('.undelete').click('click', FileList._onClickRestoreSelected);
+ this.setSort('mtime', 'desc');
return result;
};
diff --git a/apps/files_trashbin/lib/helper.php b/apps/files_trashbin/lib/helper.php
index e6ca73520a6..c98d57586d3 100644
--- a/apps/files_trashbin/lib/helper.php
+++ b/apps/files_trashbin/lib/helper.php
@@ -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;
}
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index 323e7495535..7c673c317e0 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -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'))?>