Browse Source

Merge pull request #8041 from owncloud/files-sortcolumns

File list sorting by clicking on column headers
tags/v7.0.0alpha2
Vincent Petry 10 years ago
parent
commit
9a9665f361

+ 4
- 1
apps/files/ajax/list.php View File



$permissions = $dirInfo->getPermissions(); $permissions = $dirInfo->getPermissions();


$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;

// make filelist // make filelist
$files = \OCA\Files\Helper::getFiles($dir);
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);


$data['directory'] = $dir; $data['directory'] = $dir;
$data['files'] = \OCA\Files\Helper::formatFileInfos($files); $data['files'] = \OCA\Files\Helper::formatFileInfos($files);

+ 27
- 9
apps/files/css/files.css View File



table tr.mouseOver td { background-color:#eee; } table tr.mouseOver td { background-color:#eee; }
table th { height:24px; padding:0 8px; color:#999; } 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 th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; }
table td { table td {
} }
table th#headerSize, table td.filesize { table th#headerSize, table td.filesize {
min-width: 48px; min-width: 48px;
padding: 0 16px;
text-align: right; text-align: right;
padding: 0;
}
table table td.filesize {
padding: 0 16px;
} }
table th#headerDate, table td.date { table th#headerDate, table td.date {
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
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 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; } table td.filename .nametext, .uploadtext, .modified { float:left; padding:14px 0; }


#modified {
position: absolute;
top: 15px;
}
.modified { .modified {
position: relative; position: relative;
padding-left: 8px; padding-left: 8px;

+ 121
- 22
apps/files/js/filelist.js View File

/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ /* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
/* global dragOptions, folderDropOptions */ /* global dragOptions, folderDropOptions */
window.FileList = { window.FileList = {
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',

appName: t('files', 'Files'), appName: t('files', 'Files'),
isEmpty: true, isEmpty: true,
useUndo:true, useUndo:true,
_selectionSummary: null, _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 * Initialize the file list and its components


this.fileSummary = this._createSummary(); this.fileSummary = this._createSummary();


this.setSort('name', 'asc');

this.breadcrumb = new BreadCrumb({ this.breadcrumb = new BreadCrumb({
onClick: this._onClickBreadCrumb, onClick: this._onClickBreadCrumb,
onDrop: _.bind(this._onDropOnBreadCrumb, this), onDrop: _.bind(this._onDropOnBreadCrumb, this),


$('#controls').prepend(this.breadcrumb.$el); $('#controls').prepend(this.breadcrumb.$el);


this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));

$(window).resize(function() { $(window).resize(function() {
// TODO: debounce this ? // TODO: debounce this ?
var width = $(this).width(); var width = $(this).width();
return false; 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 * Event handler when clicking on a bread crumb
*/ */
previousDir: currentDir previousDir: currentDir
} }
)); ));
this._selectedFiles = {};
this._selectionSummary.clear();
this.reload(); this.reload();
}, },
linkTo: function(dir) { linkTo: function(dir) {
} }
this.breadcrumb.setDirectory(this.getCurrentDirectory()); 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 * @brief Reloads the file list using ajax call
*/ */
reload: function() { reload: function() {
this._selectedFiles = {};
this._selectionSummary.clear();
this.$el.find('#select_all').prop('checked', false);
FileList.showMask(); FileList.showMask();
if (FileList._reloadCall) { if (FileList._reloadCall) {
FileList._reloadCall.abort(); FileList._reloadCall.abort();
FileList._reloadCall = $.ajax({ FileList._reloadCall = $.ajax({
url: Files.getAjaxUrl('list'), url: Files.getAjaxUrl('list'),
data: { data: {
dir : $('#dir').val()
dir: $('#dir').val(),
sort: FileList._sort,
sortdirection: FileList._sortDirection
}, },
error: function(result) { error: function(result) {
FileList.reloadCallback(result); FileList.reloadCallback(result);
*/ */
_findInsertionIndex: function(fileData) { _findInsertionIndex: function(fileData) {
var index = 0; 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++; index++;
} }
return index; return index;
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
} }
$td.css('background-image', oldBackgroundImage); $td.css('background-image', oldBackgroundImage);
});
});
}); });


}, },
updateSelectionSummary: function() { updateSelectionSummary: function() {
var summary = this._selectionSummary.summary; var summary = this._selectionSummary.summary;
if (summary.totalFiles === 0 && summary.totalDirs === 0) { 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'); $('table').removeClass('multiselect');
$('.selectedActions').addClass('hidden'); $('.selectedActions').addClass('hidden');
} }
else { else {
$('.selectedActions').removeClass('hidden'); $('.selectedActions').removeClass('hidden');
$('#headerSize').text(OC.Util.humanFileSize(summary.totalSize));
$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
var selection = ''; var selection = '';
if (summary.totalDirs > 0) { if (summary.totalDirs > 0) {
selection += n('files', '%n folder', '%n folders', summary.totalDirs); selection += n('files', '%n folder', '%n folders', summary.totalDirs);
if (summary.totalFiles > 0) { if (summary.totalFiles > 0) {
selection += n('files', '%n file', '%n files', summary.totalFiles); 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'); $('table').addClass('multiselect');
} }
}, },
}, 0); }, 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;
}
};


+ 67
- 10
apps/files/lib/helper.php View File

<?php <?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; namespace OCA\Files;


/**
* Helper class for manipulating file information
*/
class Helper class Helper
{ {
public static function buildFileStorageStatistics($dir) { public static function buildFileStorageStatistics($dir) {
$storageInfo = \OC_Helper::getStorageInfo($dir); $storageInfo = \OC_Helper::getStorageInfo($dir);


$l = new \OC_L10N('files'); $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'], 'freeSpace' => $storageInfo['free'],
'usedSpacePercent' => (int)$storageInfo['relative']); 'usedSpacePercent' => (int)$storageInfo['relative']);
} }
*/ */
public static function determineIcon($file) { public static function determineIcon($file) {
if($file['type'] === 'dir') { if($file['type'] === 'dir') {
$dir = $file['directory'];
$icon = \OC_Helper::mimetypeIcon('dir'); $icon = \OC_Helper::mimetypeIcon('dir');
$absPath = $file->getPath(); $absPath = $file->getPath();
$mount = \OC\Files\Filesystem::getMountManager()->find($absPath); $mount = \OC\Files\Filesystem::getMountManager()->find($absPath);
* @param \OCP\Files\FileInfo $b file * @param \OCP\Files\FileInfo $b file
* @return int -1 if $a must come before $b, 1 otherwise * @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(); $aType = $a->getType();
$bType = $b->getType(); $bType = $b->getType();
if ($aType === 'dir' and $bType !== 'dir') { if ($aType === 'dir' and $bType !== 'dir') {
} }
} }


/**
* 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. * Formats the file info to be returned as JSON to the client.
* *
* returns it as a sorted array of FileInfo. * returns it as a sorted array of FileInfo.
* *
* @param string $dir path to the directory * @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 * @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); $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;
} }
} }

+ 8
- 5
apps/files/templates/index.php View File

<?php /** @var $l OC_L10N */ ?>
<div id="controls"> <div id="controls">
<div class="actions creatable hidden"> <div class="actions creatable hidden">
<?php if(!isset($_['dirToken'])):?> <?php if(!isset($_['dirToken'])):?>
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
<thead> <thead>
<tr> <tr>
<th class="hidden" id='headerName'>
<th id='headerName' class="hidden column-name">
<div id="headerName-container"> <div id="headerName-container">
<input type="checkbox" id="select_all" /> <input type="checkbox" id="select_all" />
<label for="select_all"></label> <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"> <span id="selectedActionsList" class="selectedActions">
<?php if($_['allowZipDownload']) : ?> <?php if($_['allowZipDownload']) : ?>
<a href="" class="download"> <a href="" class="download">
</span> </span>
</div> </div>
</th> </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): ?> <?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
<span class="selectedActions"><a href="" class="delete-selected"> <span class="selectedActions"><a href="" class="delete-selected">
<?php p($l->t('Delete'))?> <?php p($l->t('Delete'))?>

+ 98
- 0
apps/files/tests/helper.php View File

<?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
);
}

}

+ 156
- 7
apps/files/tests/js/filelistSpec.js View File

// dummy table // dummy table
// TODO: at some point this will be rendered by the FileList class itself! // TODO: at some point this will be rendered by the FileList class itself!
'<table id="filestable">' + '<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">' + '<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">' + '<span class="selectedActions hidden">' +
'<a href class="download">Download</a>' + '<a href class="download">Download</a>' +
'<a href class="delete-selected">Delete</a></span>' + '<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>' + '<tbody id="fileList"></tbody>' +
'<tfoot></tfoot>' + '<tfoot></tfoot>' +
'</table>' + '</table>' +
expect(fakeServer.requests.length).toEqual(1); expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url; var url = fakeServer.requests[0].url;
var query = url.substr(url.indexOf('?') + 1); 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(); fakeServer.respond();
expect($('#fileList tr').length).toEqual(4); expect($('#fileList tr').length).toEqual(4);
expect(FileList.findFileEl('One.txt').length).toEqual(1); expect(FileList.findFileEl('One.txt').length).toEqual(1);
expect(fakeServer.requests.length).toEqual(1); expect(fakeServer.requests.length).toEqual(1);
var url = fakeServer.requests[0].url; var url = fakeServer.requests[0].url;
var query = url.substr(url.indexOf('?') + 1); 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(); fakeServer.respond();
}); });
it('switches to root dir when current directory does not exist', function() { it('switches to root dir when current directory does not exist', function() {
expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
}); });
it('Selecting files updates selection summary', function() { it('Selecting files updates selection summary', function() {
var $summary = $('#headerName span.name');
var $summary = $('#headerName a.name>span:first');
expect($summary.text()).toEqual('Name'); expect($summary.text()).toEqual('Name');
FileList.findFileEl('One.txt').find('input:checkbox').click(); FileList.findFileEl('One.txt').find('input:checkbox').click();
FileList.findFileEl('Three.pdf').find('input:checkbox').click(); FileList.findFileEl('Three.pdf').find('input:checkbox').click();
expect($summary.text()).toEqual('1 folder & 2 files'); expect($summary.text()).toEqual('1 folder & 2 files');
}); });
it('Unselecting files hides selection summary', function() { 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(); FileList.findFileEl('One.txt').find('input:checkbox').click().click();
expect($summary.text()).toEqual('Name'); expect($summary.text()).toEqual('Name');
}); });
}); });
}); });
}); });
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);
});
}); });
}); });

+ 4
- 6
apps/files_sharing/ajax/list.php View File

* *
*/ */


// only need filesystem apps
$RUNTIME_APPTYPES=array('filesystem');

// Init owncloud

if(!\OC_App::isEnabled('files_sharing')){ if(!\OC_App::isEnabled('files_sharing')){
exit; exit;
} }
$relativePath = $_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); $data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password);


$linkItem = $data['linkItem']; $linkItem = $data['linkItem'];
$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; $baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir=';


// make filelist // make filelist
$files = \OCA\Files\Helper::getFiles($dir);
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);


$formattedFiles = array(); $formattedFiles = array();
foreach ($files as $file) { foreach ($files as $file) {

+ 3
- 1
apps/files_trashbin/ajax/list.php View File



// Load the files // Load the files
$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : '';
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
$data = array(); $data = array();


// make filelist // make filelist
try { try {
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir);
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, $sortAttribute, $sortDirection);
} catch (Exception $e) { } catch (Exception $e) {
header("HTTP/1.0 404 Not Found"); header("HTTP/1.0 404 Not Found");
exit(); exit();

+ 1
- 0
apps/files_trashbin/js/filelist.js View File

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



+ 7
- 3
apps/files_trashbin/lib/helper.php View File

{ {
/** /**
* Retrieves the contents of a trash bin directory. * Retrieves the contents of a trash bin directory.
*
* @param string $dir path to the directory inside the trashbin * @param string $dir path to the directory inside the trashbin
* or empty to retrieve the root of 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[] * @return \OCP\Files\FileInfo[]
*/ */
public static function getTrashFiles($dir){
public static function getTrashFiles($dir, $sortAttribute = '', $sortDescending = false){
$result = array(); $result = array();
$timestamp = null; $timestamp = null;
$user = \OCP\User::getUser(); $user = \OCP\User::getUser();
closedir($dirContent); closedir($dirContent);
} }


usort($result, array('\OCA\Files\Helper', 'fileCmp'));

if ($sortAttribute !== '') {
return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending);
}
return $result; return $result;
} }



+ 11
- 10
apps/files_trashbin/templates/index.php View File

<?php /** @var $l OC_L10N */ ?>
<div id="controls"> <div id="controls">
<div id="file_action_panel"></div> <div id="file_action_panel"></div>
</div> </div>


<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div> <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"> <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">


<table id="filestable"> <table id="filestable">
<thead> <thead>
<tr> <tr>
<th id='headerName'>
<th id='headerName' class="hidden column-name">
<div id="headerName-container"> <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"> <a href="" class="undelete">
<img class="svg" alt="<?php p($l->t( 'Restore' )); ?>" <img class="svg" alt="<?php p($l->t( 'Restore' )); ?>"
src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" /> src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" />
<?php p($l->t('Restore'))?> <?php p($l->t('Restore'))?>
</a> </a>
</span>
</span>
</div> </div>
</th> </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"> <span class="selectedActions">
<a href="" class="delete-selected"> <a href="" class="delete-selected">
<?php p($l->t('Delete'))?> <?php p($l->t('Delete'))?>

Loading…
Cancel
Save