diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 4a59502bc6..f5cb130cfe 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -502,7 +502,7 @@ table td.filename .uploadtext { display: inline-block; float: left; } -#fileList tr td.filename .action-favorite:not(.menuitem) { +#fileList tr td.filename .favorite-mark { display: block; float: left; width: 30px; @@ -569,7 +569,8 @@ a.action > img { margin-bottom: -1px; } -#fileList a.action { +#fileList a.action, +#fileList div.favorite-mark { display: inline; padding: 17px 8px; line-height: 50px; @@ -617,7 +618,7 @@ a.action > img { padding-left: 6px; } -#fileList .action.action-favorite.permanent { +#fileList .favorite-mark.permanent { opacity: 1; } @@ -716,12 +717,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/js/tagsplugin.js b/apps/files/js/tagsplugin.js index 22d04e9f19..e6724e371c 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 = - '' + + var TEMPLATE_FAVORITE_MARK = + '
' + '' + '{{altText}}' + - ''; + '
'; /** * 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,63 +85,6 @@ _extendFileActions: function(fileActions) { var self = this; - // register "star" action - fileActions.registerAction({ - name: 'FavoriteInline', - displayName: t('files', 'Favorite'), - mime: 'all', - permissions: OC.PERMISSION_READ, - type: OCA.Files.FileActions.TYPE_INLINE, - render: function(actionSpec, isDefault, context) { - var $file = context.$file; - var isFavorite = $file.data('favorite') === true; - var $icon = $(renderStar(isFavorite)); - $file.find('td:first>.favorite').replaceWith($icon); - return $icon; - }, - actionHandler: function(fileName, context) { - var $actionEl = context.$file.find('.action-favorite'); - var $file = context.$file; - var fileInfo = context.fileList.files[$file.index()]; - var dir = context.dir || context.fileList.getCurrentDirectory(); - var tags = $file.attr('data-tags'); - if (_.isUndefined(tags)) { - tags = ''; - } - tags = tags.split('|'); - tags = _.without(tags, ''); - var isFavorite = tags.indexOf(OC.TAG_FAVORITE) >= 0; - if (isFavorite) { - // remove tag from list - tags = _.without(tags, OC.TAG_FAVORITE); - } else { - tags.push(OC.TAG_FAVORITE); - } - - // pre-toggle the star - toggleStar($actionEl, !isFavorite); - - context.fileInfoModel.trigger('busy', context.fileInfoModel, true); - - self.applyFileTags( - dir + '/' + fileName, - tags, - $actionEl, - isFavorite - ).then(function(result) { - context.fileInfoModel.trigger('busy', context.fileInfoModel, false); - // response from server should contain updated tags - var newTags = result.tags; - if (_.isUndefined(newTags)) { - newTags = tags; - } - context.fileInfoModel.set({ - 'tags': newTags, - 'favorite': !isFavorite - }); - }); - } - }); fileActions.registerAction({ name: 'Favorite', @@ -172,7 +116,7 @@ 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(); @@ -191,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); @@ -222,13 +166,16 @@ 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('
'); + var $icon = $(renderStar(isFavorite)); + $tr.find('td:first').prepend($icon); return $tr; }; var oldElementToFile = fileList.elementToFile; @@ -288,10 +235,10 @@ * * @param {String} fileName path to the file or folder to tag * @param {Array.} 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); @@ -311,7 +258,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/tests/js/tagspluginspec.js b/apps/files/tests/js/tagspluginspec.js index e2cbaa2c4d..b76b3c02dd 100644 --- a/apps/files/tests/js/tagspluginspec.js +++ b/apps/files/tests/js/tagspluginspec.js @@ -49,24 +49,24 @@ 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'); @@ -76,58 +76,11 @@ describe('OCA.Files.TagsPlugin tests', function() { }); }); describe('Applying tags', function() { - it('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(); - - expect(fakeServer.requests.length).toEqual(1); - request = fakeServer.requests[0]; - expect(JSON.parse(request.requestBody)).toEqual({ - tags: ['tag1', 'tag2', OC.TAG_FAVORITE] - }); - request.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ - tags: ['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE] - })); - - // re-read the element as it was re-inserted - $tr = fileList.findFileEl('One.txt'); - $action = $tr.find('.action-favorite'); - - 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); - - $action.click(); - - expect(fakeServer.requests.length).toEqual(2); - request = fakeServer.requests[1]; - expect(JSON.parse(request.requestBody)).toEqual({ - tags: ['tag1', 'tag2', 'tag3'] - }); - request.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ - tags: ['tag1', 'tag2', 'tag3'] - })); - - // re-read the element as it was re-inserted - $tr = fileList.findFileEl('One.txt'); - $action = $tr.find('.action-favorite'); - - 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); - }); 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'); + var $favoriteMark = $tr.find('.favorite-mark'); var $showMenuAction = $tr.find('.action-menu'); $showMenuAction.click(); var $favoriteActionInMenu = $tr.find('.fileActionsMenu .action-favorite'); @@ -144,14 +97,14 @@ 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); // show again the menu and get the new action, as the menu was // closed and removed (and with it, the previous action) when that @@ -171,13 +124,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/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index a2b286330f..eee03973dc 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -278,16 +278,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 +295,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"); }