diff --git a/css/style.scss b/css/style.scss index 452f17185b6..8b9779fe68a 100644 --- a/css/style.scss +++ b/css/style.scss @@ -55,15 +55,6 @@ flex-grow: 1; } -#oca-spreedme-add-room { - border-bottom: 1px solid var(--color-border); -} - -.oca-spreedme-add-person { - border: 1px solid var(--color-border); - border-radius: var(--border-radius); -} - .contactsmenu-popover li > a > img { /* The app uses border-box sizing, so the size of the icon is the * width/height plus the padding set by default in the server @@ -155,11 +146,36 @@ input[type="password"] { #select2-drop { margin-top: -44px !important; } +#oca-spreedme-add-room .select2-container { + border-bottom: 1px solid var(--color-border); + + &.select2-container-active { + border-bottom-color: var(--color-border-dark); + border-right: 1px solid var(--color-border-dark); + + .select2-choice, + .select2-default { + color: var(--color-text-light) !important; + } + } +} +.oca-spreedme-add-person .select2-container { + border: 1px solid var(--color-border); + border-radius: var(--border-radius); + + &.select2-container-active { + border-color: var(--color-border-dark); + + .select2-choice, + .select2-default { + color: var(--color-text-light) !important; + } + } +} #oca-spreedme-add-room .select2-container, .oca-spreedme-add-person .select2-container { width: 100%; margin: 0 !important; - border: none; padding: 9px; } #oca-spreedme-add-room .select2-container .select2-choice, @@ -183,14 +199,12 @@ input[type="password"] { padding: 0; } -.participantWithList .avatar, #app-navigation .app-navigation-entry-link > .avatar { position: absolute; left: 6px; top: 6px; } -.participantWithList > li > a:first-child > img, #app-navigation li > a:first-child img { width: 32px !important; height: 32px !important; @@ -694,7 +708,7 @@ input[type="password"] { } .participantWithList .participant-offline { - & > a { + & > span { color: var(--color-text-maxcontrast); } .avatar { @@ -708,7 +722,7 @@ input[type="password"] { box-sizing: border-box; } -.participantWithList > li > a { +.participantWithList > li > span { display: block; width: 100%; line-height: 44px; @@ -722,6 +736,33 @@ input[type="password"] { background: no-repeat 14px center; } +.participantWithList > li > span > .avatar-wrapper { + position: absolute; + top: 6px; + left: 6px; + + &:focus, + &:active { + border: 1px solid var(--color-border-dark); + border-radius: 50%; + + /* Separate the border slightly from the avatar to make it more + * noticeable. */ + padding: 3px; + + /* Compensate position for the border and the increased size. */ + top: 2px; + left: 2px; + } +} + +body:not(#body-public) .participantWithList > li > span:not(.currentUser):not(.guestUser) { + .avatar, + .avatar img { + cursor: pointer; + } +} + /** @@ -771,7 +812,12 @@ input[type="password"] { .label-wrapper { display: flex; align-items: center; - .edit-button { + .edit-button .icon { + background-color: transparent; + border: none; + padding: 13px 22px; + margin: 0; + opacity: .3; &:hover, @@ -779,13 +825,6 @@ input[type="password"] { &:active { opacity: 1; } - - .icon { - background-color: transparent; - border: none; - padding: 13px 22px; - margin: 0; - } } } .input-wrapper { diff --git a/js/views/callinfoview.js b/js/views/callinfoview.js index 9108e09b857..43071ecc7aa 100644 --- a/js/views/callinfoview.js +++ b/js/views/callinfoview.js @@ -89,7 +89,6 @@ 'change @ui.linkCheckbox': 'toggleLinkCheckbox', 'keyup @ui.passwordInput': 'keyUpPassword', - 'click @ui.passwordButton': 'showPasswordInput', 'click @ui.passwordConfirm': 'confirmPassword', 'submit @ui.passwordForm': 'confirmPassword', }, @@ -296,6 +295,7 @@ this.ui.passwordInput.val(''); restoreState(); OC.hideMenus(); + this.ui.passwordButton.focus(); }.bind(this), error: function() { restoreState(); @@ -310,6 +310,7 @@ if (e.keyCode === 27) { // ESC OC.hideMenus(); + this.ui.passwordButton.focus(); } }, diff --git a/js/views/editabletextlabel.js b/js/views/editabletextlabel.js index 8031a401a31..d7dfb7414d6 100644 --- a/js/views/editabletextlabel.js +++ b/js/views/editabletextlabel.js @@ -73,7 +73,7 @@ ui: { labelWrapper: '.label-wrapper', label: '.label', - editButton: '.edit-button', + editButton: '.edit-button button', inputWrapper: '.input-wrapper', input: 'input.username', confirmButton: '.confirm-button', @@ -81,6 +81,7 @@ }, events: { + 'keydown @ui.editButton': 'preventConfirmEditOnNextInputKeyUp', 'click @ui.editButton': 'showInput', 'keyup @ui.input': 'handleInputKeyUp', 'click @ui.confirmButton': 'confirmEdit', @@ -181,6 +182,34 @@ this.getUI('label').text(this._getText()); }, + /** + * Prevents the edition to be confirmed on the next key up event on the + * input. + * + * When Enter is pressed in the edit button the default behaviour is to + * trigger a click event which, in turn, shows and focus the input. + * However, as the enter key is still pressed as soon as it is released + * a key up event is triggered, now on the focused input, which would + * confirm the edit and hide again the input. + * + * Note that confirming the edition is only prevented for the first key + * up event. If the Enter key is kept pressed on an input the browser + * periodically generates new key down and key up events; surprisingly + * the "repeat" property of the event is "false", so it can not be + * distinguished if the key is being kept pressed. Due to this it is not + * possible to prevent confirming the edition until the Enter key is + * actually released for the first time after showing the input. + */ + preventConfirmEditOnNextInputKeyUp: function(event) { + if (event.keyCode !== 13) { + return; + } + + this.getUI('input').one('keyup', function(event) { + event.stopPropagation(); + }.bind(this)); + }, + showInput: function() { this.getUI('input').val(this.model.get(this.modelAttribute)); @@ -193,6 +222,8 @@ hideInput: function() { this.getUI('labelWrapper').removeClass('hidden-important'); this.getUI('inputWrapper').addClass('hidden-important'); + + this.getUI('editButton').focus(); }, handleInputKeyUp: function(event) { diff --git a/js/views/participantlistview.js b/js/views/participantlistview.js index c42ab31c922..52a50818342 100644 --- a/js/views/participantlistview.js +++ b/js/views/participantlistview.js @@ -101,12 +101,18 @@ name = t('spreed', 'Guest'); } + var isGuestOrGuestModerator = this.model.get('participantType') === OCA.SpreedMe.app.GUEST || this.model.get('participantType') === OCA.SpreedMe.app.GUEST_MODERATOR; + var hasContactsMenu = OC.getCurrentUser().uid && !isSelf && !isGuestOrGuestModerator; + return { canModerate: canModerate, canBePromoted: this.model.get('participantType') === OCA.SpreedMe.app.USER || this.model.get('participantType') === OCA.SpreedMe.app.GUEST, canBeDemoted: this.model.get('participantType') === OCA.SpreedMe.app.MODERATOR || this.model.get('participantType') === OCA.SpreedMe.app.GUEST_MODERATOR, name: name, + participantHasContactsMenu: hasContactsMenu, + participantIsSelf: isSelf, participantIsUser: this.model.get('participantType') === OCA.SpreedMe.app.USER, + participantIsGuestOrGuestModerator: isGuestOrGuestModerator, participantIsGuestModerator: this.model.get('participantType') === OCA.SpreedMe.app.GUEST_MODERATOR, participantIsModerator: this.model.get('participantType') === OCA.SpreedMe.app.MODERATOR, participantIsOwner: this.model.get('participantType') === OCA.SpreedMe.app.OWNER, @@ -134,8 +140,14 @@ if (OC.getCurrentUser().uid && model.get('userId') && model.get('userId') !== OC.getCurrentUser().uid) { - this.$el.find('.participant-entry-link .avatar').contactsMenu( - model.get('userId'), 0, this.$el.find('.participant-entry-link')); + this.$el.find('.participant-entry .avatar').contactsMenu( + model.get('userId'), 0, this.$el.find('.participant-entry')); + + this.$el.find('.participant-entry .avatar-wrapper').on('keyup', function(event) { + if (event.key === ' ' || event.key === 'Enter') { + $(this).find('.avatar').click(); + } + }); } this.$el.attr('data-session-id', this.model.get('sessionId')); diff --git a/js/views/tabview.js b/js/views/tabview.js index 6e054c37aa6..21fddd4af32 100644 --- a/js/views/tabview.js +++ b/js/views/tabview.js @@ -34,6 +34,9 @@ tagName: 'li', className: 'tabHeader', + attributes: { + tabindex: 0, + }, template: function(context) { // OCA.Talk.Views.Templates may not have been initialized when this @@ -52,14 +55,21 @@ events: { 'click': function() { this.triggerMethod('click:tabHeader', this.getOption('tabId')); - } + }, + 'keyup': function(event) { + if (event.key === ' ' || event.key === 'Enter') { + this.triggerMethod('click:tabHeader', this.getOption('tabId')); + } + }, }, setSelected: function(selected) { if (selected) { this.$el.addClass('selected'); + this.$el.attr('tabindex', '-1'); } else { this.$el.removeClass('selected'); + this.$el.attr('tabindex', '0'); } } @@ -182,6 +192,10 @@ }, selectTabHeader: function(tabId) { + if (this._currentTabId === tabId) { + return; + } + this.triggerMethod('unselect:tabHeader', this._currentTabId); if (this._currentTabId !== undefined) { diff --git a/js/views/templates.js b/js/views/templates.js index a6295e8d3bc..2ff1e34a03b 100644 --- a/js/views/templates.js +++ b/js/views/templates.js @@ -67,9 +67,9 @@ templates['callinfoview'] = template({"1":function(container,depth0,helpers,part },"7":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - return "
\n
\n
\n
\n
\n
\n
\n"; + return " \n"; },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); @@ -192,9 +192,9 @@ templates['collectionsview'] = template({"compiler":[7,">= 4.0.0"],"main":functi templates['editabletextlabel'] = template({"1":function(container,depth0,helpers,partials,data) { var stack1; - return "
\n"; + + ">
\n"; },"2":function(container,depth0,helpers,partials,data) { var helper; @@ -260,32 +260,38 @@ templates['mediacontrolsview'] = template({"compiler":[7,">= 4.0.0"],"main":func + "\n \n \n \n\n"; },"useData":true}); templates['participantlistview'] = template({"1":function(container,depth0,helpers,partials,data) { + return "currentUser"; +},"3":function(container,depth0,helpers,partials,data) { + return "guestUser"; +},"5":function(container,depth0,helpers,partials,data) { + return "tabindex=\"0\""; +},"7":function(container,depth0,helpers,partials,data) { var helper; return "" + container.escapeExpression(((helper = (helper = helpers.moderatorIndicator || (depth0 != null ? depth0.moderatorIndicator : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"moderatorIndicator","hash":{},"data":data}) : helper))) + ""; -},"3":function(container,depth0,helpers,partials,data) { +},"9":function(container,depth0,helpers,partials,data) { return ""; -},"5":function(container,depth0,helpers,partials,data) { +},"11":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); return "
\n \n
\n
\n \n
\n"; -},"6":function(container,depth0,helpers,partials,data) { +},"12":function(container,depth0,helpers,partials,data) { var helper; return "
  • \n \n
  • \n"; -},"8":function(container,depth0,helpers,partials,data) { +},"14":function(container,depth0,helpers,partials,data) { var stack1; - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.canBePromoted : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"9":function(container,depth0,helpers,partials,data) { + return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.canBePromoted : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"15":function(container,depth0,helpers,partials,data) { var helper; return "
  • \n \n
    \n
    \n
    \n
    \n
    \n
    " + container.escapeExpression(((helper = (helper = helpers.closeLabel || (depth0 != null ? depth0.closeLabel : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"closeLabel","hash":{},"data":data}) : helper))) + "\n
    \n"; },"useData":true}); @@ -481,7 +493,7 @@ templates['tabview_header'] = template({"compiler":[7,">= 4.0.0"],"main":functio return "\n" + + "\">\n" + alias4(((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) + "\n"; },"useData":true}); diff --git a/js/views/templates/callinfoview.handlebars b/js/views/templates/callinfoview.handlebars index 0af592581fc..a4648ce6424 100644 --- a/js/views/templates/callinfoview.handlebars +++ b/js/views/templates/callinfoview.handlebars @@ -15,9 +15,9 @@ {{/if}} {{#if isPublic}} -
    +
    - + diff --git a/js/views/templates/editabletextlabel.handlebars b/js/views/templates/editabletextlabel.handlebars index 9ec40049787..bb015002707 100644 --- a/js/views/templates/editabletextlabel.handlebars +++ b/js/views/templates/editabletextlabel.handlebars @@ -1,7 +1,7 @@
    <{{labelTagName}} class="label">{{text}} {{#if editionEnabled}} -
    +
    {{/if}}
    {{#if editionEnabled}} diff --git a/js/views/templates/participantlistview.handlebars b/js/views/templates/participantlistview.handlebars index 86063eac108..d4956db050b 100644 --- a/js/views/templates/participantlistview.handlebars +++ b/js/views/templates/participantlistview.handlebars @@ -1,11 +1,11 @@ - -
    - {{name}} + +
    + {{name}} {{#if participantIsOwner}}{{moderatorIndicator}}{{/if}} {{#if participantIsModerator}}{{moderatorIndicator}}{{/if}} {{#if participantIsGuestModerator}}{{moderatorIndicator}}{{/if}} {{#if inCall}}{{/if}} -
    + {{#if canModerate}}
      diff --git a/js/views/templates/sidebarview.handlebars b/js/views/templates/sidebarview.handlebars index 13a877ac4b2..dc02ddf42de 100644 --- a/js/views/templates/sidebarview.handlebars +++ b/js/views/templates/sidebarview.handlebars @@ -1,5 +1,4 @@ -
      -
      +
      diff --git a/js/views/templates/tabview_header.handlebars b/js/views/templates/tabview_header.handlebars index e63e1e31e3a..e1efd41b8b2 100644 --- a/js/views/templates/tabview_header.handlebars +++ b/js/views/templates/tabview_header.handlebars @@ -1,2 +1,2 @@ -{{label}} +{{label}} diff --git a/tests/acceptance/features/bootstrap/ParticipantListContext.php b/tests/acceptance/features/bootstrap/ParticipantListContext.php index 111307a59ed..6e640684eaf 100644 --- a/tests/acceptance/features/bootstrap/ParticipantListContext.php +++ b/tests/acceptance/features/bootstrap/ParticipantListContext.php @@ -57,7 +57,7 @@ public static function participantsList() { * @return Locator */ public static function itemInParticipantsListFor($participantName) { - return Locator::forThe()->xpath("//a/text()[normalize-space() = '$participantName']/..")-> + return Locator::forThe()->xpath("//span/span[normalize-space() = '$participantName']/../..")-> descendantOf(self::participantsList())-> describedAs("Item for $participantName in the participants list"); }