diff options
author | Vincent Petry <pvince81@owncloud.com> | 2014-05-12 12:50:27 +0200 |
---|---|---|
committer | Vincent Petry <pvince81@owncloud.com> | 2014-05-12 12:50:27 +0200 |
commit | 9a9665f361c42764f5ae9f5f3ce63f71fdfcad5c (patch) | |
tree | ca04fac8b2ea1ca922a11fd17a7d4b913117a92e /apps | |
parent | f4e8de3cbbf426e7b22d5100085626b2e4fefc29 (diff) | |
parent | 61db16321f1bf7ceea22672b33cd165fc3620a4a (diff) | |
download | nextcloud-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.php | 5 | ||||
-rw-r--r-- | apps/files/css/files.css | 36 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 143 | ||||
-rw-r--r-- | apps/files/lib/helper.php | 77 | ||||
-rw-r--r-- | apps/files/templates/index.php | 13 | ||||
-rw-r--r-- | apps/files/tests/helper.php | 98 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 163 | ||||
-rw-r--r-- | apps/files_sharing/ajax/list.php | 10 | ||||
-rw-r--r-- | apps/files_trashbin/ajax/list.php | 4 | ||||
-rw-r--r-- | apps/files_trashbin/js/filelist.js | 1 | ||||
-rw-r--r-- | apps/files_trashbin/lib/helper.php | 10 | ||||
-rw-r--r-- | apps/files_trashbin/templates/index.php | 21 |
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'))?> |