diff options
-rw-r--r-- | css/style.scss | 75 | ||||
-rw-r--r-- | js/views/callinfoview.js | 31 | ||||
-rw-r--r-- | js/views/templates.js | 45 | ||||
-rw-r--r-- | js/views/templates/callinfoview.handlebars | 29 | ||||
-rw-r--r-- | tests/acceptance/features/bootstrap/ConversationInfoContext.php | 53 |
5 files changed, 135 insertions, 98 deletions
diff --git a/css/style.scss b/css/style.scss index 493f12761..7533e8a98 100644 --- a/css/style.scss +++ b/css/style.scss @@ -999,30 +999,59 @@ body:not(#body-public) .participantWithList > li > span:not(.currentUser):not(.g .share-link-options { display: flex; align-items: center; - .password-button { - position: relative; + } + + .room-moderation-button .menu { + /* Values copied from apps.scss in server. */ + $popoveritem-height: 44px; + $popovericon-size: 16px; + $outter-margin: ($popoveritem-height - $popovericon-size) / 2; + + /* The server sets a top/bottom margin to forms and inputs of the + * first/last item in a menu, but that misaligns the left icon; this + * sets the top/bottom margin to the whole item instead of only to the + * forms or inputs. */ + li:first-of-type { + > .menuitem.password-option { + + margin-top: $outter-margin - 2px; // minus the input margin + + > form, > input { + margin-top: 0; + } + } + } + li:last-of-type { + > .menuitem.password-option { + + margin-bottom: $outter-margin - 2px; // minus the input margin + + > form, > input { + margin-bottom: 0; + } + } + } + + .menuitem.password-option { + /* Override rule for menu items from server, as in this case + * only the button in the password field is clickable, so the + * pointer cursor should not be used for the whole item. */ + cursor: default; + + .password-form { + position: relative; + + .password-confirm, + .password-loading { + /* Inputs in menu items do not have a right margin, so + * it does not need to be compensated. */ + right: 0; + } - .menuitem { - /* Override rule for menu items from server, as in this case - * only the button in the password field is clickable, so the - * pointer cursor should not be used for the whole item. */ - cursor: default; - - .password-form { - position: relative; - - .password-confirm, - .password-loading { - /* Inputs in menu items do not have a right margin, so - * it does not need to be compensated. */ - right: 0; - } - - .password-confirm { - /* Needed to override an important rule set in the - * server. */ - background-color: transparent !important; - } + .password-confirm { + /* Needed to override an important rule set in the + * server. */ + background-color: transparent !important; } } } diff --git a/js/views/callinfoview.js b/js/views/callinfoview.js index 0e1cd3fcf..6fd06b728 100644 --- a/js/views/callinfoview.js +++ b/js/views/callinfoview.js @@ -46,17 +46,18 @@ templateContext: function() { var canModerate = this._canModerate(); var canFullModerate = this._canFullModerate(); + var isPublic = this.model.get('type') === OCA.SpreedMe.app.ROOM_TYPE_PUBLIC; return $.extend(this.model.toJSON(), { isRoomForFile: this.model.get('objectType') === 'file', fileLink: OC.generateUrl('/f/{fileId}', { fileId: this.model.get('objectId') }), fileLinkTitle: t('spreed', 'Go to file'), - showRoomModerationMenu: canModerate && canFullModerate, + showRoomModerationMenu: canModerate && (canFullModerate || isPublic), canModerate: canModerate, canFullModerate: canFullModerate, linkCheckboxLabel: t('spreed', 'Share link'), - isPublic: this.model.get('type') === 3, + isPublic: isPublic, passwordInputPlaceholder: this.model.get('hasPassword')? t('spreed', 'Change password'): t('spreed', 'Set password'), - showShareLink: !canModerate && this.model.get('type') === 3, + showShareLink: !canModerate && isPublic, isDeletable: canModerate && (Object.keys(this.model.get('participants')).length > 2 || this.model.get('numGuests') > 0) }); }, @@ -70,7 +71,6 @@ 'callButton': 'div.call-button', - 'passwordButton': '.password-button .button', 'passwordForm': '.password-form', 'passwordInput': '.password-input', 'passwordConfirm': '.password-confirm', @@ -88,7 +88,6 @@ events: { 'change @ui.linkCheckbox': 'toggleLinkCheckbox', - 'keyup @ui.passwordInput': 'keyUpPassword', 'click @ui.passwordConfirm': 'confirmPassword', 'submit @ui.passwordForm': 'confirmPassword', }, @@ -181,21 +180,10 @@ }); this.initClipboard(); - this.ui.passwordButton.tooltip({ - placement: 'bottom', - trigger: 'hover', - title: (this.model.get('hasPassword')) ? t('spreed', 'Change password') : t('spreed', 'Set password') - }); - // Set the body as the container to show the tooltip in front of the // header. this.ui.fileLink.tooltip({container: $('body')}); - var self = this; - OC.registerMenu($(this.ui.passwordButton), $(this.ui.passwordMenu), function() { - $(self.ui.passwordInput).focus(); - }); - OC.registerMenu(this.ui.roomModerationButton, this.ui.roomModerationMenu); }, @@ -287,7 +275,7 @@ this.ui.passwordInput.val(''); restoreState(); OC.hideMenus(); - this.ui.passwordButton.focus(); + this.ui.roomModerationButton.focus(); }.bind(this), error: function() { restoreState(); @@ -297,15 +285,6 @@ }); }, - keyUpPassword: function(e) { - e.preventDefault(); - if (e.keyCode === 27) { - // ESC - OC.hideMenus(); - this.ui.passwordButton.focus(); - } - }, - /** * Clipboard */ diff --git a/js/views/templates.js b/js/views/templates.js index 426f116ff..17e87bee2 100644 --- a/js/views/templates.js +++ b/js/views/templates.js @@ -54,37 +54,38 @@ templates['callinfoview'] = template({"1":function(container,depth0,helpers,part + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isPublic : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " </div>\n"; },"4":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return " <div class=\"clipboard-button\"><button class=\"button icon-clippy\"></button></div>\n <div class=\"password-button\">\n <div class=\"menutoggle\"><button class=\"button " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasPassword : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(7, data, 0),"data":data})) != null ? stack1 : "") - + "\"></button></div>\n <div class=\"popovermenu password-menu menu-right\">\n <ul>\n <li>\n <span class=\"menuitem " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasPassword : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(7, data, 0),"data":data})) != null ? stack1 : "") - + " password-option\">\n <form class=\"password-form\">\n <input class=\"password-input\" maxlength=\"200\" type=\"password\"\n placeholder=\"" - + container.escapeExpression(((helper = (helper = helpers.passwordInputPlaceholder || (depth0 != null ? depth0.passwordInputPlaceholder : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"passwordInputPlaceholder","hash":{},"data":data}) : helper))) - + "\">\n <input type=\"submit\" value=\"\" autocomplete=\"new-password\" class=\"icon icon-confirm password-confirm\"></input>\n <span class=\"icon icon-loading-small password-loading hidden\"/>\n </form>\n </span>\n </li>\n </ul>\n </div>\n </div>\n"; -},"5":function(container,depth0,helpers,partials,data) { - return "icon-password"; -},"7":function(container,depth0,helpers,partials,data) { - return "icon-no-password"; -},"9":function(container,depth0,helpers,partials,data) { + return " <div class=\"clipboard-button\"><button class=\"button icon-clippy\"></button></div>\n"; +},"6":function(container,depth0,helpers,partials,data) { return " <div class=\"share-link-options\">\n <div class=\"clipboard-button\"><button class=\"button icon-clippy\"></button></div>\n </div>\n"; -},"11":function(container,depth0,helpers,partials,data) { - var stack1; +},"8":function(container,depth0,helpers,partials,data) { + var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); return " <div class=\"room-moderation-button\">\n <div class=\"menutoggle\">\n <button class=\"button icon-more\"></button>\n </div>\n <div class=\"popovermenu bubble menu\">\n <ul>\n" - + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.canFullModerate : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.canFullModerate : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPublic : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " </ul>\n </div>\n </div>\n"; -},"12":function(container,depth0,helpers,partials,data) { +},"9":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); return " <li>\n <span class=\"menuitem\">\n <input name=\"link-checkbox\" id=\"link-checkbox\" class=\"checkbox link-checkbox\" value=\"1\" " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPublic : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPublic : depth0),{"name":"if","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " type=\"checkbox\">\n <label for=\"link-checkbox\" class=\"link-checkbox-label\">" + container.escapeExpression(((helper = (helper = helpers.linkCheckboxLabel || (depth0 != null ? depth0.linkCheckboxLabel : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"linkCheckboxLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n"; -},"13":function(container,depth0,helpers,partials,data) { +},"10":function(container,depth0,helpers,partials,data) { return " checked=\"checked\""; +},"12":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); + + return " <li>\n <span class=\"menuitem " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasPassword : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "") + + " password-option\">\n <form class=\"password-form\">\n <input class=\"password-input\" maxlength=\"200\" type=\"password\"\n placeholder=\"" + + container.escapeExpression(((helper = (helper = helpers.passwordInputPlaceholder || (depth0 != null ? depth0.passwordInputPlaceholder : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"passwordInputPlaceholder","hash":{},"data":data}) : helper))) + + "\">\n <input type=\"submit\" value=\"\" autocomplete=\"new-password\" class=\"icon icon-confirm password-confirm\"></input>\n <span class=\"icon icon-loading-small password-loading hidden\"/>\n </form>\n </span>\n </li>\n"; +},"13":function(container,depth0,helpers,partials,data) { + return "icon-password"; +},"15":function(container,depth0,helpers,partials,data) { + return "icon-no-password"; },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); @@ -92,8 +93,8 @@ templates['callinfoview'] = template({"1":function(container,depth0,helpers,part + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isRoomForFile : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "</div>\n<div class=\"call-controls-container\">\n <div class=\"call-button\"></div>\n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.canModerate : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showShareLink : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showRoomModerationMenu : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showShareLink : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showRoomModerationMenu : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "</div>\n"; },"useData":true}); templates['chatview'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { diff --git a/js/views/templates/callinfoview.handlebars b/js/views/templates/callinfoview.handlebars index 6d9551207..968bfe829 100644 --- a/js/views/templates/callinfoview.handlebars +++ b/js/views/templates/callinfoview.handlebars @@ -12,23 +12,6 @@ <div class="share-link-options"> {{#if isPublic}} <div class="clipboard-button"><button class="button icon-clippy"></button></div> - <div class="password-button"> - <div class="menutoggle"><button class="button {{#if hasPassword}}icon-password{{else}}icon-no-password{{/if}}"></button></div> - <div class="popovermenu password-menu menu-right"> - <ul> - <li> - <span class="menuitem {{#if hasPassword}}icon-password{{else}}icon-no-password{{/if}} password-option"> - <form class="password-form"> - <input class="password-input" maxlength="200" type="password" - placeholder="{{passwordInputPlaceholder}}"> - <input type="submit" value="" autocomplete="new-password" class="icon icon-confirm password-confirm"></input> - <span class="icon icon-loading-small password-loading hidden"/> - </form> - </span> - </li> - </ul> - </div> - </div> {{/if}} </div> {{/if}} @@ -52,6 +35,18 @@ </span> </li> {{/if}} + {{#if isPublic}} + <li> + <span class="menuitem {{#if hasPassword}}icon-password{{else}}icon-no-password{{/if}} password-option"> + <form class="password-form"> + <input class="password-input" maxlength="200" type="password" + placeholder="{{passwordInputPlaceholder}}"> + <input type="submit" value="" autocomplete="new-password" class="icon icon-confirm password-confirm"></input> + <span class="icon icon-loading-small password-loading hidden"/> + </form> + </span> + </li> + {{/if}} </ul> </div> </div> diff --git a/tests/acceptance/features/bootstrap/ConversationInfoContext.php b/tests/acceptance/features/bootstrap/ConversationInfoContext.php index 29a7e4d34..3186bc7de 100644 --- a/tests/acceptance/features/bootstrap/ConversationInfoContext.php +++ b/tests/acceptance/features/bootstrap/ConversationInfoContext.php @@ -84,10 +84,19 @@ class ConversationInfoContext implements Context, ActorAwareInterface { /** * @return Locator */ - public static function passwordButton() { - return Locator::forThe()->css(".password-button")-> + public static function roomModerationButton() { + return Locator::forThe()->css(".room-moderation-button")-> descendantOf(self::conversationInfoContainer())-> - describedAs("Password button in conversation info"); + describedAs("Room moderation button in conversation info"); + } + + /** + * @return Locator + */ + public static function roomModerationMenu() { + return Locator::forThe()->css(".menu")-> + descendantOf(self::roomModerationButton())-> + describedAs("Room moderation menu in conversation info"); } /** @@ -95,8 +104,8 @@ class ConversationInfoContext implements Context, ActorAwareInterface { */ public static function passwordIcon() { return Locator::forThe()->css(".icon-password")-> - descendantOf(self::passwordButton())-> - describedAs("Password icon in conversation info"); + descendantOf(self::roomModerationMenu())-> + describedAs("Password icon in room moderation menu in conversation info"); } /** @@ -104,8 +113,8 @@ class ConversationInfoContext implements Context, ActorAwareInterface { */ public static function noPasswordIcon() { return Locator::forThe()->css(".icon-no-password")-> - descendantOf(self::passwordButton())-> - describedAs("No password icon in conversation info"); + descendantOf(self::roomModerationMenu())-> + describedAs("No password icon in room moderation menu in conversation info"); } /** @@ -113,8 +122,8 @@ class ConversationInfoContext implements Context, ActorAwareInterface { */ public static function passwordField() { return Locator::forThe()->css(".password-input")-> - descendantOf(self::conversationInfoContainer())-> - describedAs("Password field in conversation info"); + descendantOf(self::roomModerationMenu())-> + describedAs("Password field in room moderation menu in conversation info"); } /** @@ -142,7 +151,7 @@ class ConversationInfoContext implements Context, ActorAwareInterface { * @When I protect the conversation with the password :password */ public function iProtectTheConversationWithThePassword($password) { - $this->actor->find(self::passwordButton(), 2)->click(); + $this->showRoomModerationMenu(); $this->actor->find(self::passwordField(), 2)->setValue($password . "\r"); } @@ -151,14 +160,38 @@ class ConversationInfoContext implements Context, ActorAwareInterface { * @Then I see that the conversation is password protected */ public function iSeeThatTheConversationIsPasswordProtected() { + $this->showRoomModerationMenu(); + PHPUnit_Framework_Assert::assertTrue($this->actor->find(self::passwordIcon(), 10)->isVisible(), "Password icon is visible"); + + // Hide menu again after checking the icon. + $this->actor->find(self::roomModerationButton(), 2)->click(); } /** * @Then I see that the conversation is not password protected */ public function iSeeThatTheConversationIsNotPasswordProtected() { + $this->showRoomModerationMenu(); + PHPUnit_Framework_Assert::assertTrue($this->actor->find(self::noPasswordIcon(), 10)->isVisible(), "No password icon is visible"); + + // Hide menu again after checking the icon. + $this->actor->find(self::roomModerationButton(), 2)->click(); + } + + private function showRoomModerationMenu() { + // The room moderation menu is hidden after clicking on an action of the + // menu. Therefore, if the menu is visible, wait a little just in case + // it is in the process of being hidden due to a previous action. + if (!WaitFor::elementToBeEventuallyNotShown( + $this->actor, + self::roomModerationMenu(), + $timeout = 5 * $this->actor->getFindTimeoutMultiplier())) { + PHPUnit_Framework_Assert::fail("The room moderation menu is still shown after $timeout seconds"); + } + + $this->actor->find(self::roomModerationButton(), 10)->click(); } } |