diff options
author | Morris Jobke <hey@morrisjobke.de> | 2017-10-25 01:07:51 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-25 01:07:51 +0200 |
commit | 1978867a11a9baf1ec50555264e65bcfd679c7e8 (patch) | |
tree | 6a41c13f05e5ebc4cb32250669e5a66f8f3fe52b | |
parent | 5d84daa167ca7d631175f54fc949d3f78e854978 (diff) | |
parent | 065ab6bfff47b4e869b76dacf3b077fd0aff8ad5 (diff) | |
download | nextcloud-server-1978867a11a9baf1ec50555264e65bcfd679c7e8.tar.gz nextcloud-server-1978867a11a9baf1ec50555264e65bcfd679c7e8.zip |
Merge pull request #6709 from nextcloud/show-checkbox-where-the-favourite-icon-is-now
Show checkbox where the favourite icon is now
-rw-r--r-- | apps/files/css/files.scss | 100 | ||||
-rw-r--r-- | apps/files/css/mobile.scss | 4 | ||||
-rw-r--r-- | apps/files/js/fileactions.js | 13 | ||||
-rw-r--r-- | apps/files/js/fileactionsmenu.js | 5 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 49 | ||||
-rw-r--r-- | apps/files/js/tagsplugin.js | 75 | ||||
-rw-r--r-- | apps/files/templates/list.php | 10 | ||||
-rw-r--r-- | apps/files/tests/js/fileactionsmenuSpec.js | 28 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 30 | ||||
-rw-r--r-- | apps/files/tests/js/tagspluginspec.js | 48 | ||||
-rw-r--r-- | apps/files_trashbin/templates/index.php | 10 | ||||
-rw-r--r-- | core/search/css/results.css | 10 | ||||
-rw-r--r-- | tests/acceptance/features/app-files.feature | 16 | ||||
-rw-r--r-- | tests/acceptance/features/bootstrap/FilesAppContext.php | 48 |
14 files changed, 289 insertions, 157 deletions
diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 8ae8fac6c21..c03b1ce6fad 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -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; } +table td.selection { + padding: 0; +} + /* File checkboxes */ -#fileList tr td.filename>.selectCheckBox + label:before { - opacity: 0; - position: absolute; - bottom: 4px; - right: 0; - z-index: 10; +#fileList tr td.selection>.selectCheckBox + label:before { + opacity: 0.3; } -/* 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 { +/* 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 { diff --git a/apps/files/css/mobile.scss b/apps/files/css/mobile.scss index eefc92c816b..e7b75910fa9 100644 --- a/apps/files/css/mobile.scss +++ b/apps/files/css/mobile.scss @@ -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 { diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 3da9b06b0d3..0f320c8b3c7 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -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 */ @@ -746,6 +746,17 @@ */ /** + * 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 * * @callback OCA.Files.FileActions~actionHandler diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js index 45d2bd83049..b8022f13734 100644 --- a/apps/files/js/fileactionsmenu.js +++ b/apps/files/js/fileactionsmenu.js @@ -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) { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index cc23ac73979..0d45c29b25a 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -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}); diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js index 9bd20be4bf8..2286477750c 100644 --- a/apps/files/js/tagsplugin.js +++ b/apps/files/js/tagsplugin.js @@ -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'), + displayName: function(context) { + var $file = context.$file; + var isFavorite = $file.data('favorite') === true; + + 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, - type: OCA.Files.FileActions.TYPE_INLINE, - render: function(actionSpec, isDefault, context) { + iconClass: function(fileName, 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 '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); }); } }; diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php index 761a67be6a2..f980081336f 100644 --- a/apps/files/templates/list.php +++ b/apps/files/templates/list.php @@ -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"> diff --git a/apps/files/tests/js/fileactionsmenuSpec.js b/apps/files/tests/js/fileactionsmenuSpec.js index 3028db2b3ac..926516b3043 100644 --- a/apps/files/tests/js/fileactionsmenuSpec.js +++ b/apps/files/tests/js/fileactionsmenuSpec.js @@ -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() { diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 836a5e5ce71..1f8d38c59ce 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -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); diff --git a/apps/files/tests/js/tagspluginspec.js b/apps/files/tests/js/tagspluginspec.js index a4efc08aa53..363a8bb0e19 100644 --- a/apps/files/tests/js/tagspluginspec.js +++ b/apps/files/tests/js/tagspluginspec.js @@ -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() { diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index c3f0304d408..b3ebca2867b 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -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"> diff --git a/core/search/css/results.css b/core/search/css/results.css index 2e3791a47f0..e2ccfe36ef8 100644 --- a/core/search/css/results.css +++ b/core/search/css/results.css @@ -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; } diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature index 37e01bcada2..ac2d05fac2c 100644 --- a/tests/acceptance/features/app-files.feature +++ b/tests/acceptance/features/app-files.feature @@ -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 diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index bb088c0a2c3..338823a9478 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -80,6 +80,15 @@ class FilesAppContext implements Context, ActorAwareInterface { /** * @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 + */ public static function fileDetailsInCurrentSectionDetailsViewWithText($fileDetailsText) { return Locator::forThe()->xpath("//span[normalize-space() = '$fileDetailsText']")-> descendantOf(self::fileDetailsInCurrentSectionDetailsView())-> @@ -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"); } @@ -341,6 +350,20 @@ class FilesAppContext implements Context, ActorAwareInterface { /** * @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 + */ public static function viewFileInFolderMenuItem() { return self::fileActionsMenuItemFor("View in folder"); } @@ -374,6 +397,13 @@ class FilesAppContext implements Context, ActorAwareInterface { } /** + * @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 */ public function iOpenTheInputFieldForTagsInTheDetailsView() { @@ -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(); } /** |