Merge pull request #6709 from nextcloud/show-checkbox-where-the-favourite-icon-is-now

Show checkbox where the favourite icon is now
This commit is contained in:
Morris Jobke 2017-10-25 01:07:51 +02:00 committed by GitHub
commit 1978867a11
14 changed files with 292 additions and 160 deletions

View file

@ -230,9 +230,6 @@ table th#headerName {
position: relative;
height: 50px;
}
.has-favorites #headerName-container {
padding-left: 50px;
}
table th#headerSize, table td.filesize {
text-align: right;
@ -294,7 +291,12 @@ table td.filename a.name {
line-height: 50px;
padding: 0;
}
table td.filename label.icon-loading-small {
table td.filename .thumbnail-wrapper {
position: absolute;
width: 50px;
height: 50px;
}
table td.filename .thumbnail-wrapper.icon-loading-small {
&:after {
z-index: 10;
}
@ -306,10 +308,10 @@ table td.filename .thumbnail {
display: inline-block;
width: 32px;
height: 32px;
background-size: 32px;
margin-left: 9px;
margin-top: 9px;
cursor: pointer;
float: left;
position: absolute;
z-index: 4;
}
@ -319,12 +321,9 @@ table td.filename input.filename {
margin-left: 48px;
cursor: text;
}
.has-favorites table td.filename input.filename {
margin-left: 52px;
}
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, .column-last>span:first-child { float:left; padding:15px 0; }
table td.filename .nametext, .modified, .column-last>span:first-child { float:left; padding:15px 0; }
.modified, .column-last>span:first-child {
position: relative;
@ -336,22 +335,23 @@ table td.filename .nametext, .uploadtext, .modified, .column-last>span:first-chi
/* TODO fix usability bug (accidental file/folder selection) */
table td.filename .nametext {
position: absolute;
left: 55px;
padding: 0;
padding-left: 55px;
overflow: hidden;
text-overflow: ellipsis;
width: 70%;
max-width: 800px;
height: 100%;
z-index: 10;
}
table td.filename .uploadtext {
position: absolute;
left: 55px;
}
/* ellipsis on file names */
table td.filename .nametext .innernametext {
max-width: calc(100% - 100px) !important;
}
.has-favorites #fileList td.filename a.name {
left: 50px;
margin-right: 50px;
}
.hide-hidden-files #fileList tr.hidden-file,
.hide-hidden-files #fileList tr.hidden-file.dragging {
@ -437,49 +437,27 @@ table td.filename .uploadtext {
opacity: .5;
}
/* File checkboxes */
#fileList tr td.filename>.selectCheckBox + label:before {
opacity: 0;
position: absolute;
bottom: 4px;
right: 0;
z-index: 10;
table td.selection {
padding: 0;
}
/* Show checkbox when hovering, checked, or selected */
#fileList tr:hover td.filename>.selectCheckBox + label:before,
#fileList tr:focus td.filename>.selectCheckBox + label:before,
#fileList tr td.filename>.selectCheckBox:checked + label:before,
#fileList tr.selected td.filename>.selectCheckBox + label:before {
/* File checkboxes */
#fileList tr td.selection>.selectCheckBox + label:before {
opacity: 0.3;
}
/* Show checkbox with full opacity when hovering, checked, or selected */
#fileList tr:hover td.selection>.selectCheckBox + label:before,
#fileList tr:focus td.selection>.selectCheckBox + label:before,
#fileList tr td.selection>.selectCheckBox:checked + label:before,
#fileList tr.selected td.selection>.selectCheckBox + label:before {
opacity: 1;
}
/* Use label to have bigger clickable size for checkbox */
#fileList tr td.filename>.selectCheckBox + label,
#fileList tr td.selection>.selectCheckBox + label,
.select-all + label {
background-position: 30px 30px;
height: 50px;
position: absolute;
width: 50px;
z-index: 5;
}
#fileList tr td.filename>.selectCheckBox {
/* sometimes checkbox height is bigger (KDE/Qt), so setting to absolute
* to prevent it to increase the height */
position: absolute;
z-index: 10;
}
.select-all + label {
top: 0;
}
.select-all + label:before {
position: absolute;
top: 18px;
left: 18px;
z-index: 10;
}
.has-favorites .select-all {
left: 68px;
padding: 16px;
}
#fileList tr td.filename {
@ -500,10 +478,11 @@ table td.filename .uploadtext {
display: inline-block;
float: left;
}
#fileList tr td.filename .action-favorite {
#fileList tr td.filename .favorite-mark {
position: absolute;
display: block;
float: left;
width: 30px;
top: -6px;
right: -6px;
line-height: 100%;
text-align: center;
}
@ -615,7 +594,7 @@ a.action > img {
padding-left: 6px;
}
#fileList .action.action-favorite.permanent {
#fileList .favorite-mark.permanent {
opacity: 1;
}
@ -659,9 +638,6 @@ table tr.summary td {
.summary .info {
margin-left: 40px;
}
.has-favorites .summary .info {
margin-left: 90px;
}
table.dragshadow {
width:auto;
@ -714,12 +690,24 @@ table.dragshadow td.size {
#filestable .filename .action .icon,
#filestable .selectedActions a .icon,
#filestable .filename .favorite-mark .icon,
#controls .actions .button .icon {
display: inline-block;
vertical-align: middle;
background-size: 16px 16px;
}
#filestable .filename .favorite-mark {
// Override default icons to always hide the star icon and always show the
// starred icon even when hovered or focused.
& .icon-star {
background-image: none;
}
& .icon-starred {
background-image: url('../../../core/img/actions/starred.svg?v=1');
}
}
#filestable .filename .action .icon.hidden,
#filestable .selectedActions a .icon.hidden,
#controls .actions .button .icon.hidden {

View file

@ -24,10 +24,6 @@ table td.date {
table td {
padding: 0;
}
/* and accordingly fix left margin of file list summary on mobile */
.summary .info {
margin-left: 105px;
}
/* remove shift for multiselect bar to account for missing navigation */
table.multiselect thead {

View file

@ -706,7 +706,7 @@
* @property {String} mime mime type
* @property {int} permissions permissions
* @property {(Function|String)} icon icon path to the icon or function that returns it (deprecated, use iconClass instead)
* @property {(Function|String)} iconClass class name of the icon (recommended for theming)
* @property {(String|OCA.Files.FileActions~iconClassFunction)} iconClass class name of the icon (recommended for theming)
* @property {OCA.Files.FileActions~renderActionFunction} [render] optional rendering function
* @property {OCA.Files.FileActions~actionHandler} actionHandler action handler function
*/
@ -745,6 +745,17 @@
* @return {String} display name
*/
/**
* Icon class function for actions.
* The function returns the icon class of the action using
* the given context information.
*
* @callback OCA.Files.FileActions~iconClassFunction
* @param {String} fileName name of the file on which the action must be performed
* @param {OCA.Files.FileActionContext} context action context
* @return {String} icon class
*/
/**
* Action handler function for file actions
*

View file

@ -115,6 +115,11 @@
item = _.extend({}, item);
item.displayName = item.displayName(self._context);
}
if (_.isFunction(item.iconClass)) {
var fileName = self._context.$file.attr('data-file');
item = _.extend({}, item);
item.iconClass = item.iconClass(fileName, self._context);
}
return item;
});
items = items.sort(function(actionA, actionB) {

View file

@ -332,7 +332,7 @@
this.$fileList.on('click','td.filename>a.name, td.filesize, td.date', _.bind(this._onClickFile, this));
this.$fileList.on('change', 'td.filename>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
this.$fileList.on('change', 'td.selection>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
this.$el.on('show', _.bind(this._onShow, this));
this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this));
@ -593,7 +593,7 @@
* @param {bool} state true to select, false to deselect
*/
_selectFileEl: function($tr, state, showDetailsView) {
var $checkbox = $tr.find('td.filename>.selectCheckBox');
var $checkbox = $tr.find('td.selection>.selectCheckBox');
var oldData = !!this._selectedFiles[$tr.data('id')];
var data;
$checkbox.prop('checked', state);
@ -649,7 +649,7 @@
else {
this._lastChecked = $tr;
}
var $checkbox = $tr.find('td.filename>.selectCheckBox');
var $checkbox = $tr.find('td.selection>.selectCheckBox');
this._selectFileEl($tr, !$checkbox.prop('checked'));
this.updateSelectionSummary();
} else {
@ -704,7 +704,7 @@
*/
_onClickSelectAll: function(e) {
var checked = $(e.target).prop('checked');
this.$fileList.find('td.filename>.selectCheckBox').prop('checked', checked)
this.$fileList.find('td.selection>.selectCheckBox').prop('checked', checked)
.closest('tr').toggleClass('selected', checked);
this._selectedFiles = {};
this._selectionSummary.clear();
@ -1063,6 +1063,13 @@
this.$fileList.empty();
if (this._allowSelection) {
// The results table, which has no selection column, checks
// whether the main table has a selection column or not in order
// to align its contents with those of the main table.
this.$el.addClass('has-selection');
}
// clear "Select all" checkbox
this.$el.find('.select-all').prop('checked', false);
@ -1192,6 +1199,20 @@
path = this.getCurrentDirectory();
}
// selection td
if (this._allowSelection) {
td = $('<td class="selection"></td>');
td.append(
'<input id="select-' + this.id + '-' + fileData.id +
'" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
'<span class="hidden-visually">' + t('files', 'Select') + '</span>' +
'</label>'
);
tr.append(td);
}
// filename td
td = $('<td class="filename"></td>');
@ -1203,22 +1224,13 @@
else {
linkUrl = this.getDownloadUrl(name, path, type === 'dir');
}
if (this._allowSelection) {
td.append(
'<input id="select-' + this.id + '-' + fileData.id +
'" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
'<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>' +
'<span class="hidden-visually">' + t('files', 'Select') + '</span>' +
'</label>'
);
} else {
td.append('<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>');
}
var linkElem = $('<a></a>').attr({
"class": "name",
"href": linkUrl
});
linkElem.append('<div class="thumbnail-wrapper"><div class="thumbnail" style="background-image:url(' + icon + ');"></div></div>');
// from here work on the display name
name = fileData.displayName || name;
@ -2614,6 +2626,13 @@
*/
_createSummary: function() {
var $tr = $('<tr class="summary"></tr>');
if (this._allowSelection) {
// Dummy column for selection, as all rows must have the same
// number of columns.
$tr.append('<td></td>');
}
this.$el.find('tfoot').append($tr);
return new OCA.Files.FileSummary($tr, {config: this._filesConfig});

View file

@ -17,12 +17,12 @@
PROPERTY_FAVORITE: '{' + OC.Files.Client.NS_OWNCLOUD + '}favorite'
});
var TEMPLATE_FAVORITE_ACTION =
'<a href="#" ' +
'class="action action-favorite {{#isFavorite}}permanent{{/isFavorite}}">' +
var TEMPLATE_FAVORITE_MARK =
'<div ' +
'class="favorite-mark {{#isFavorite}}permanent{{/isFavorite}}">' +
'<span class="icon {{iconClass}}" />' +
'<span class="hidden-visually">{{altText}}</span>' +
'</a>';
'</div>';
/**
* Returns the icon class for the matching state
@ -42,24 +42,24 @@
*/
function renderStar(state) {
if (!this._template) {
this._template = Handlebars.compile(TEMPLATE_FAVORITE_ACTION);
this._template = Handlebars.compile(TEMPLATE_FAVORITE_MARK);
}
return this._template({
isFavorite: state,
altText: state ? t('files', 'Favorited') : t('files', 'Favorite'),
altText: state ? t('files', 'Favorited') : t('files', 'Not favorited'),
iconClass: getStarIconClass(state)
});
}
/**
* Toggle star icon on action element
* Toggle star icon on favorite mark element
*
* @param {Object} action element
* @param {Object} $favoriteMarkEl favorite mark element
* @param {boolean} state true if starred, false otherwise
*/
function toggleStar($actionEl, state) {
$actionEl.removeClass('icon-star icon-starred').addClass(getStarIconClass(state));
$actionEl.toggleClass('permanent', state);
function toggleStar($favoriteMarkEl, state) {
$favoriteMarkEl.removeClass('icon-star icon-starred').addClass(getStarIconClass(state));
$favoriteMarkEl.toggleClass('permanent', state);
}
OCA.Files = OCA.Files || {};
@ -67,8 +67,9 @@
/**
* @namespace OCA.Files.TagsPlugin
*
* Extends the file actions and file list to include a favorite action icon
* and addition "data-tags" and "data-favorite" attributes.
* Extends the file actions and file list to include a favorite mark icon
* and a favorite action in the file actions menu; it also adds "data-tags"
* and "data-favorite" attributes to file elements.
*/
OCA.Files.TagsPlugin = {
name: 'Tags',
@ -84,22 +85,38 @@
_extendFileActions: function(fileActions) {
var self = this;
// register "star" action
fileActions.registerAction({
name: 'Favorite',
displayName: t('files', 'Favorite'),
mime: 'all',
permissions: OC.PERMISSION_READ,
type: OCA.Files.FileActions.TYPE_INLINE,
render: function(actionSpec, isDefault, context) {
displayName: function(context) {
var $file = context.$file;
var isFavorite = $file.data('favorite') === true;
var $icon = $(renderStar(isFavorite));
$file.find('td:first>.favorite').replaceWith($icon);
return $icon;
if (isFavorite) {
return t('files', 'Remove from favorites');
}
// As it is currently not possible to provide a context for
// the i18n strings "Add to favorites" was used instead of
// "Favorite" to remove the ambiguity between verb and noun
// when it is translated.
return t('files', 'Add to favorites');
},
mime: 'all',
order: -23,
permissions: OC.PERMISSION_READ,
iconClass: function(fileName, context) {
var $file = context.$file;
var isFavorite = $file.data('favorite') === true;
if (isFavorite) {
return 'icon-starred';
}
return 'icon-star';
},
actionHandler: function(fileName, context) {
var $actionEl = context.$file.find('.action-favorite');
var $favoriteMarkEl = context.$file.find('.favorite-mark');
var $file = context.$file;
var fileInfo = context.fileList.files[$file.index()];
var dir = context.dir || context.fileList.getCurrentDirectory();
@ -118,14 +135,14 @@
}
// pre-toggle the star
toggleStar($actionEl, !isFavorite);
toggleStar($favoriteMarkEl, !isFavorite);
context.fileInfoModel.trigger('busy', context.fileInfoModel, true);
self.applyFileTags(
dir + '/' + fileName,
tags,
$actionEl,
$favoriteMarkEl,
isFavorite
).then(function(result) {
context.fileInfoModel.trigger('busy', context.fileInfoModel, false);
@ -145,17 +162,19 @@
_extendFileList: function(fileList) {
// extend row prototype
fileList.$el.addClass('has-favorites');
var oldCreateRow = fileList._createRow;
fileList._createRow = function(fileData) {
var $tr = oldCreateRow.apply(this, arguments);
var isFavorite = false;
if (fileData.tags) {
$tr.attr('data-tags', fileData.tags.join('|'));
if (fileData.tags.indexOf(OC.TAG_FAVORITE) >= 0) {
$tr.attr('data-favorite', true);
isFavorite = true;
}
}
$tr.find('td:first').prepend('<div class="favorite"></div>');
var $icon = $(renderStar(isFavorite));
$tr.find('td.filename .thumbnail').append($icon);
return $tr;
};
var oldElementToFile = fileList.elementToFile;
@ -215,10 +234,10 @@
*
* @param {String} fileName path to the file or folder to tag
* @param {Array.<String>} tagNames array of tag names
* @param {Object} $actionEl element
* @param {Object} $favoriteMarkEl favorite mark element
* @param {boolean} isFavorite Was the item favorited before
*/
applyFileTags: function(fileName, tagNames, $actionEl, isFavorite) {
applyFileTags: function(fileName, tagNames, $favoriteMarkEl, isFavorite) {
var encodedPath = OC.encodePath(fileName);
while (encodedPath[0] === '/') {
encodedPath = encodedPath.substr(1);
@ -238,7 +257,7 @@
message = ': ' + response.responseJSON.message;
}
OC.Notification.show(t('files', 'An error occurred while trying to update the tags' + message), {type: 'error'});
toggleStar($actionEl, isFavorite);
toggleStar($favoriteMarkEl, isFavorite);
});
}
};

View file

@ -41,12 +41,14 @@
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
<thead>
<tr>
<th id="headerSelection" class="hidden column-selection">
<input type="checkbox" id="select_all_files" class="select-all checkbox"/>
<label for="select_all_files">
<span class="hidden-visually"><?php p($l->t('Select all'))?></span>
</label>
</th>
<th id='headerName' class="hidden column-name">
<div id="headerName-container">
<input type="checkbox" id="select_all_files" class="select-all checkbox"/>
<label for="select_all_files">
<span class="hidden-visually"><?php p($l->t('Select all'))?></span>
</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="copy-move">

View file

@ -205,6 +205,34 @@ describe('OCA.Files.FileActionsMenu tests', function() {
expect(displayNameStub.calledWith(menuContext)).toEqual(true);
expect(menu.$el.find('a[data-action=Something]').text()).toEqual('Test');
});
it('uses plain iconClass', function() {
fileActions.registerAction({
name: 'Something',
mime: 'text/plain',
permissions: OC.PERMISSION_ALL,
iconClass: 'test'
});
menu.render();
expect(menu.$el.find('a[data-action=Something]').children('span.icon').hasClass('test')).toEqual(true);
});
it('calls iconClass function', function() {
var iconClassStub = sinon.stub().returns('test');
fileActions.registerAction({
name: 'Something',
mime: 'text/plain',
permissions: OC.PERMISSION_ALL,
iconClass: iconClassStub
});
menu.render();
expect(iconClassStub.calledOnce).toEqual(true);
expect(iconClassStub.calledWith(menuContext.$file.attr('data-file'), menuContext)).toEqual(true);
expect(menu.$el.find('a[data-action=Something]').children('span.icon').hasClass('test')).toEqual(true);
});
});
describe('action handler', function() {

View file

@ -712,8 +712,14 @@ describe('OCA.Files.FileList tests', function() {
fileList.add(testFiles[i], {silent: true});
}
$tr = fileList.findFileEl('One.txt');
expect($tr.find('a.name').css('display')).not.toEqual('none');
// trigger rename prompt
fileList.rename('One.txt');
expect($tr.find('a.name').css('display')).toEqual('none');
$input = fileList.$fileList.find('input.filename');
$input.val('Two.jpg');
@ -735,12 +741,12 @@ describe('OCA.Files.FileList tests', function() {
$tr = fileList.findFileEl('One.txt');
expect($tr.length).toEqual(1);
expect($tr.find('a .nametext').text().trim()).toEqual('One.txt');
expect($tr.find('a.name').is(':visible')).toEqual(true);
expect($tr.find('a.name').css('display')).not.toEqual('none');
$tr = fileList.findFileEl('Two.jpg');
expect($tr.length).toEqual(1);
expect($tr.find('a .nametext').text().trim()).toEqual('Two.jpg');
expect($tr.find('a.name').is(':visible')).toEqual(true);
expect($tr.find('a.name').css('display')).not.toEqual('none');
// input and form are gone
expect(fileList.$fileList.find('input.filename').length).toEqual(0);
@ -750,7 +756,7 @@ describe('OCA.Files.FileList tests', function() {
doRename();
expect(fileList.findFileEl('Tu_after_three.txt').find('.thumbnail').parent().attr('class'))
.toEqual('icon-loading-small');
.toContain('icon-loading-small');
deferredRename.reject(409);
@ -838,7 +844,7 @@ describe('OCA.Files.FileList tests', function() {
fileList.move('One.txt', '/somedir');
expect(fileList.findFileEl('One.txt').find('.thumbnail').parent().attr('class'))
.toEqual('icon-loading-small');
.toContain('icon-loading-small');
expect(moveStub.calledOnce).toEqual(true);
@ -935,7 +941,7 @@ describe('OCA.Files.FileList tests', function() {
fileList.copy('One.txt', '/somedir');
expect(fileList.findFileEl('One.txt').find('.thumbnail').parent().attr('class'))
.toEqual('icon-loading-small');
.toContain('icon-loading-small');
expect(copyStub.calledOnce).toEqual(true);
@ -1741,7 +1747,7 @@ describe('OCA.Files.FileList tests', function() {
it('Selects a file when clicking its checkbox', function() {
var $tr = fileList.findFileEl('One.txt');
expect($tr.find('input:checkbox').prop('checked')).toEqual(false);
$tr.find('td.filename input:checkbox').click();
$tr.find('td.selection input:checkbox').click();
expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
});
@ -1779,7 +1785,7 @@ describe('OCA.Files.FileList tests', function() {
var $tr = fileList.findFileEl('One.txt');
var $tr2 = fileList.findFileEl('Three.pdf');
var e;
$tr.find('td.filename input:checkbox').click();
$tr.find('td.selection input:checkbox').click();
e = new $.Event('click');
e.shiftKey = true;
$tr2.find('td.filename .name').trigger(e);
@ -1797,7 +1803,7 @@ describe('OCA.Files.FileList tests', function() {
var $tr = fileList.findFileEl('One.txt');
var $tr2 = fileList.findFileEl('Three.pdf');
var e;
$tr2.find('td.filename input:checkbox').click();
$tr2.find('td.selection input:checkbox').click();
e = new $.Event('click');
e.shiftKey = true;
$tr.find('td.filename .name').trigger(e);
@ -1813,13 +1819,13 @@ describe('OCA.Files.FileList tests', function() {
});
it('Selecting all files will automatically check "select all" checkbox', function() {
expect($('.select-all').prop('checked')).toEqual(false);
$('#fileList tr td.filename input:checkbox').click();
$('#fileList tr td.selection input:checkbox').click();
expect($('.select-all').prop('checked')).toEqual(true);
});
it('Selecting all files on the first visible page will not automatically check "select all" checkbox', function() {
fileList.setFiles(generateFiles(0, 41));
expect($('.select-all').prop('checked')).toEqual(false);
$('#fileList tr td.filename input:checkbox').click();
$('#fileList tr td.selection input:checkbox').click();
expect($('.select-all').prop('checked')).toEqual(false);
});
it('Selecting all files also selects hidden files when invisible', function() {
@ -1831,7 +1837,7 @@ describe('OCA.Files.FileList tests', function() {
size: 150
}));
$('.select-all').click();
expect($tr.find('td.filename input:checkbox').prop('checked')).toEqual(true);
expect($tr.find('td.selection input:checkbox').prop('checked')).toEqual(true);
expect(_.pluck(fileList.getSelectedFiles(), 'name')).toContain('.hidden');
});
it('Clicking "select all" will select/deselect all files', function() {
@ -3150,7 +3156,7 @@ describe('OCA.Files.FileList tests', function() {
fileList.showFileBusyState('Two.jpg', true);
expect($tr.hasClass('busy')).toEqual(true);
expect($tr.find('.thumbnail').parent().attr('class'))
.toEqual('icon-loading-small');
.toContain('icon-loading-small');
fileList.showFileBusyState('Two.jpg', false);

View file

@ -49,39 +49,39 @@ describe('OCA.Files.TagsPlugin tests', function() {
describe('Favorites icon', function() {
it('renders favorite icon and extra data', function() {
var $action, $tr;
var $favoriteMark, $tr;
fileList.setFiles(testFiles);
$tr = fileList.$el.find('tbody tr:first');
$action = $tr.find('.action-favorite');
expect($action.length).toEqual(1);
expect($action.hasClass('permanent')).toEqual(false);
$favoriteMark = $tr.find('.favorite-mark');
expect($favoriteMark.length).toEqual(1);
expect($favoriteMark.hasClass('permanent')).toEqual(false);
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2']);
expect($tr.attr('data-favorite')).not.toBeDefined();
});
it('renders permanent favorite icon and extra data', function() {
var $action, $tr;
var $favoriteMark, $tr;
testFiles[0].tags.push(OC.TAG_FAVORITE);
fileList.setFiles(testFiles);
$tr = fileList.$el.find('tbody tr:first');
$action = $tr.find('.action-favorite');
expect($action.length).toEqual(1);
expect($action.hasClass('permanent')).toEqual(true);
$favoriteMark = $tr.find('.favorite-mark');
expect($favoriteMark.length).toEqual(1);
expect($favoriteMark.hasClass('permanent')).toEqual(true);
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', OC.TAG_FAVORITE]);
expect($tr.attr('data-favorite')).toEqual('true');
});
it('adds has-favorites class on table', function() {
expect(fileList.$el.hasClass('has-favorites')).toEqual(true);
});
});
describe('Applying tags', function() {
it('sends request to server and updates icon', function() {
it('through FileActionsMenu sends request to server and updates icon', function() {
var request;
fileList.setFiles(testFiles);
var $tr = fileList.findFileEl('One.txt');
var $action = $tr.find('.action-favorite');
$action.click();
var $favoriteMark = $tr.find('.favorite-mark');
var $showMenuAction = $tr.find('.action-menu');
$showMenuAction.click();
var $favoriteActionInMenu = $tr.find('.fileActionsMenu .action-favorite');
$favoriteActionInMenu.click();
expect(fakeServer.requests.length).toEqual(1);
request = fakeServer.requests[0];
@ -94,15 +94,21 @@ describe('OCA.Files.TagsPlugin tests', function() {
// re-read the element as it was re-inserted
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-favorite');
$favoriteMark = $tr.find('.favorite-mark');
$showMenuAction = $tr.find('.action-menu');
expect($tr.attr('data-favorite')).toEqual('true');
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE]);
expect(fileList.files[0].tags).toEqual(['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE]);
expect($action.find('.icon').hasClass('icon-star')).toEqual(false);
expect($action.find('.icon').hasClass('icon-starred')).toEqual(true);
expect($favoriteMark.find('.icon').hasClass('icon-star')).toEqual(false);
expect($favoriteMark.find('.icon').hasClass('icon-starred')).toEqual(true);
$action.click();
// show again the menu and get the new action, as the menu was
// closed and removed (and with it, the previous action) when that
// action was clicked
$showMenuAction.click();
$favoriteActionInMenu = $tr.find('.fileActionsMenu .action-favorite');
$favoriteActionInMenu.click();
expect(fakeServer.requests.length).toEqual(2);
request = fakeServer.requests[1];
@ -115,13 +121,13 @@ describe('OCA.Files.TagsPlugin tests', function() {
// re-read the element as it was re-inserted
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-favorite');
$favoriteMark = $tr.find('.favorite-mark');
expect($tr.attr('data-favorite')).toBeFalsy();
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', 'tag3']);
expect(fileList.files[0].tags).toEqual(['tag1', 'tag2', 'tag3']);
expect($action.find('.icon').hasClass('icon-star')).toEqual(true);
expect($action.find('.icon').hasClass('icon-starred')).toEqual(false);
expect($favoriteMark.find('.icon').hasClass('icon-star')).toEqual(true);
expect($favoriteMark.find('.icon').hasClass('icon-starred')).toEqual(false);
});
});
describe('elementToFile', function() {

View file

@ -21,12 +21,14 @@
<table id="filestable">
<thead>
<tr>
<th id="headerSelection" class="hidden column-selection">
<input type="checkbox" id="select_all_trash" class="select-all checkbox"/>
<label for="select_all_trash">
<span class="hidden-visually"><?php p($l->t('Select all'))?></span>
</label>
</th>
<th id='headerName' class="hidden column-name">
<div id="headerName-container">
<input type="checkbox" id="select_all_trash" class="select-all checkbox"/>
<label for="select_all_trash">
<span class="hidden-visually"><?php p($l->t('Select all'))?></span>
</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">

View file

@ -30,8 +30,8 @@
padding: 28px 0 28px 56px;
font-size: 18px;
}
.has-favorites:not(.hidden) ~ #searchresults .status {
padding-left: 102px;
.has-selection:not(.hidden) ~ #searchresults .status {
padding-left: 105px;
}
#searchresults .status.fixed {
position: fixed;
@ -54,7 +54,7 @@
}
#searchresults td {
padding: 5px 19px;
padding: 5px 14px;
font-style: normal;
vertical-align: middle;
border-bottom: none;
@ -67,8 +67,8 @@
background-position: right center;
background-repeat: no-repeat;
}
.has-favorites:not(.hidden) ~ #searchresults td.icon {
width: 86px;
.has-selection:not(.hidden) ~ #searchresults td.icon {
width: 91px;
background-size: 32px;
}

View file

@ -145,6 +145,14 @@ Feature: app-files
Given I am logged in
And I create a new folder named "A name alphabetically lower than welcome.txt"
And I see that "A name alphabetically lower than welcome.txt" precedes "welcome.txt" in the file list
# To mark the file as favorite the file actions menu has to be shown but, as
# the details view is opened automatically when the folder is created,
# clicking on the menu trigger could fail if it is covered by the details
# view due to its opening animation. Instead of ensuring that the animations
# of the contents and the details view have both finished it is easier to
# close the details view and wait until it is closed before continuing.
And I close the details view
And I see that the details view is closed
When I mark "welcome.txt" as favorite
Then I see that "welcome.txt" is marked as favorite
And I see that "welcome.txt" precedes "A name alphabetically lower than welcome.txt" in the file list
@ -153,6 +161,14 @@ Feature: app-files
Given I am logged in
And I create a new folder named "A name alphabetically lower than welcome.txt"
And I see that "A name alphabetically lower than welcome.txt" precedes "welcome.txt" in the file list
# To mark the file as favorite the file actions menu has to be shown but, as
# the details view is opened automatically when the folder is created,
# clicking on the menu trigger could fail if it is covered by the details
# view due to its opening animation. Instead of ensuring that the animations
# of the contents and the details view have both finished it is easier to
# close the details view and wait until it is closed before continuing.
And I close the details view
And I see that the details view is closed
And I mark "welcome.txt" as favorite
And I see that "welcome.txt" is marked as favorite
And I see that "welcome.txt" precedes "A name alphabetically lower than welcome.txt" in the file list

View file

@ -77,6 +77,15 @@ class FilesAppContext implements Context, ActorAwareInterface {
describedAs("Current section details view in Files app");
}
/**
* @return Locator
*/
public static function closeDetailsViewButton() {
return Locator::forThe()->css(".icon-close")->
descendantOf(self::currentSectionDetailsView())->
describedAs("Close current section details view in Files app");
}
/**
* @return Locator
*/
@ -278,16 +287,16 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
public static function favoriteActionForFile($fileName) {
return Locator::forThe()->css(".action-favorite")->descendantOf(self::rowForFile($fileName))->
describedAs("Favorite action for file $fileName in Files app");
public static function favoriteMarkForFile($fileName) {
return Locator::forThe()->css(".favorite-mark")->descendantOf(self::rowForFile($fileName))->
describedAs("Favorite mark for file $fileName in Files app");
}
/**
* @return Locator
*/
public static function notFavoritedStateIconForFile($fileName) {
return Locator::forThe()->css(".icon-star")->descendantOf(self::favoriteActionForFile($fileName))->
return Locator::forThe()->css(".icon-star")->descendantOf(self::favoriteMarkForFile($fileName))->
describedAs("Not favorited state icon for file $fileName in Files app");
}
@ -295,7 +304,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function favoritedStateIconForFile($fileName) {
return Locator::forThe()->css(".icon-starred")->descendantOf(self::favoriteActionForFile($fileName))->
return Locator::forThe()->css(".icon-starred")->descendantOf(self::favoriteMarkForFile($fileName))->
describedAs("Favorited state icon for file $fileName in Files app");
}
@ -338,6 +347,20 @@ class FilesAppContext implements Context, ActorAwareInterface {
return self::fileActionsMenuItemFor("Details");
}
/**
* @return Locator
*/
public static function addToFavoritesMenuItem() {
return self::fileActionsMenuItemFor("Add to favorites");
}
/**
* @return Locator
*/
public static function removeFromFavoritesMenuItem() {
return self::fileActionsMenuItemFor("Remove from favorites");
}
/**
* @return Locator
*/
@ -373,6 +396,13 @@ class FilesAppContext implements Context, ActorAwareInterface {
$this->actor->find(self::detailsMenuItem(), 2)->click();
}
/**
* @Given I close the details view
*/
public function iCloseTheDetailsView() {
$this->actor->find(self::closeDetailsViewButton(), 10)->click();
}
/**
* @Given I open the input field for tags in the details view
*/
@ -393,7 +423,9 @@ class FilesAppContext implements Context, ActorAwareInterface {
public function iMarkAsFavorite($fileName) {
$this->iSeeThatIsNotMarkedAsFavorite($fileName);
$this->actor->find(self::favoriteActionForFile($fileName), 10)->click();
$this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click();
$this->actor->find(self::addToFavoritesMenuItem(), 2)->click();
}
/**
@ -402,7 +434,9 @@ class FilesAppContext implements Context, ActorAwareInterface {
public function iUnmarkAsFavorite($fileName) {
$this->iSeeThatIsMarkedAsFavorite($fileName);
$this->actor->find(self::favoriteActionForFile($fileName), 10)->click();
$this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click();
$this->actor->find(self::removeFromFavoritesMenuItem(), 2)->click();
}
/**