Ver código fonte

Show avatars in MUC occupants sidebar

Fixes #1322

(Also clean up some loose threads)
JC Brand 3 anos atrás
pai
commit
35947e3d62

+ 1 - 0
CHANGES.md

@@ -8,6 +8,7 @@
 - Fix trimming of chats in overlayed view mode
 - OMEMO bugfix: Always create device session based on real JID.
 - If `auto_register_muc_nickname` is set, make sure to register when the user changes current nick.
+- #1322: Display occupants’ avatars in the occupants list
 - #1419: Clicking on avatar should show bigger version
 - #2647: Singleton mode doesn't work
 - #2704: Send button doesn't work in a multi-user chat

+ 8 - 4
src/headless/plugins/vcard/utils.js

@@ -82,8 +82,10 @@ function getVCardForChatroomOccupant (message) {
 export async function setVCardOnOccupant (occupant) {
     await api.waitUntil('VCardsInitialized');
     occupant.vcard = getVCardForChatroomOccupant(occupant);
-    occupant.vcard.on('change', () => occupant.trigger('vcard:change'));
-    occupant.trigger('vcard:add');
+    if (occupant.vcard) {
+        occupant.vcard.on('change', () => occupant.trigger('vcard:change'));
+        occupant.trigger('vcard:add');
+    }
 }
 
 export async function setVCardOnMUCMessage (message) {
@@ -92,8 +94,10 @@ export async function setVCardOnMUCMessage (message) {
     } else {
         await api.waitUntil('VCardsInitialized');
         message.vcard = getVCardForChatroomOccupant(message);
-        message.vcard.on('change', () => message.trigger('vcard:change'));
-        message.trigger('vcard:add');
+        if (message.vcard) {
+            message.vcard.on('change', () => message.trigger('vcard:change'));
+            message.trigger('vcard:add');
+        }
     }
 }
 

+ 1 - 1
src/modals/templates/occupant.js

@@ -16,7 +16,7 @@ export default (o) => {
                     <div class="row">
                         <div class="col-auto">
                             <converse-avatar
-                                class="avatar chat-msg__avatar"
+                                class="avatar modal-avatar"
                                 .data=${o.vcard?.attributes}
                                 nonce=${o.vcard?.get('vcard_updated')}
                                 height="120" width="120"></converse-avatar>

+ 5 - 0
src/plugins/controlbox/styles/_controlbox.scss

@@ -75,6 +75,11 @@
         order: -1;
         color: var(--controlbox-text-color);
 
+        .chat-status--avatar {
+            border: 1px solid var(--controlbox-pane-background-color);
+            background: var(--controlbox-pane-background-color);
+        }
+
         converse-brand-logo {
             width: 100%;
             display: block;

+ 9 - 0
src/plugins/muc-views/constants.js

@@ -0,0 +1,9 @@
+export const PRETTY_CHAT_STATUS = {
+    'offline':      'Offline',
+    'unavailable':  'Unavailable',
+    'xa':           'Extended Away',
+    'away':         'Away',
+    'dnd':          'Do not disturb',
+    'chat':         'Chattty',
+    'online':       'Online'
+};

+ 3 - 0
src/plugins/muc-views/sidebar.js

@@ -3,6 +3,7 @@ import tpl_muc_sidebar from "./templates/muc-sidebar.js";
 import { CustomElement } from 'shared/components/element.js';
 import { _converse, api, converse } from "@converse/headless/core";
 
+import 'shared/styles/status.scss';
 import './styles/muc-occupants.scss';
 
 const { u } = converse.env;
@@ -21,6 +22,8 @@ export default class MUCSidebar extends CustomElement {
         this.listenTo(this.model.occupants, 'add', this.requestUpdate);
         this.listenTo(this.model.occupants, 'remove', this.requestUpdate);
         this.listenTo(this.model.occupants, 'change', this.requestUpdate);
+        this.listenTo(this.model.occupants, 'vcard:change', this.requestUpdate);
+        this.listenTo(this.model.occupants, 'vcard:add', this.requestUpdate);
         this.model.initialized.then(() => this.requestUpdate());
     }
 

+ 0 - 3
src/plugins/muc-views/styles/index.scss

@@ -67,9 +67,6 @@ converse-muc-destroyed {
                                             display: none;
                                         }
                                     }
-                                    .occupant-status {
-                                        margin-top: 6px;
-                                    }
                                 }
                             }
                         }

+ 11 - 24
src/plugins/muc-views/styles/muc-occupants.scss

@@ -1,5 +1,15 @@
 .conversejs {
     converse-muc.chatroom {
+
+        .chat-status--avatar {
+            background: var(--occupants-background-color);
+            border: 1px solid var(--occupants-background-color);
+        }
+
+        .badge-groupchat {
+            background-color: var(--groupchats-header-color);
+        }
+
         .box-flyout {
             .occupants {
                 display: flex;
@@ -90,6 +100,7 @@
                                     flex-direction: row;
 
                                     span {
+                                        height: 1.6em;
                                         margin-right: 0.25rem;
                                     }
                                 }
@@ -102,30 +113,6 @@
                             .badge {
                                 margin-bottom: 0.125rem;
                             }
-
-                            .occupant-status {
-                                display: inline-block;
-                                margin: 0 0.5em 0.125em 0;
-                                width: 0.5em;
-                                height: 0.5em;
-
-                                &.occupant-online,
-                                &.occupant-chat {
-                                    background-color: #1A9707;
-                                }
-                                &.occupant-dnd {
-                                    background-color: red;
-                                }
-                                &.occupant-away {
-                                    background-color: darkorange;
-                                }
-                                &.occupant-xa {
-                                    background-color: orange;
-                                }
-                                &.occupant-offline {
-                                    background-color: darkgrey;
-                                }
-                            }
                         }
                     }
                 }

+ 1 - 22
src/plugins/muc-views/templates/muc-sidebar.js

@@ -3,29 +3,8 @@ import { __ } from 'i18n';
 import tpl_occupant from "./occupant.js";
 
 
-const PRETTY_CHAT_STATUS = {
-    'offline':      'Offline',
-    'unavailable':  'Unavailable',
-    'xa':           'Extended Away',
-    'away':         'Away',
-    'dnd':          'Do not disturb',
-    'chat':         'Chattty',
-    'online':       'Online'
-};
-
-
 export default (o) => {
-    const i18n_occupant_hint = (occupant) => __('Click to mention %1$s in your message.', occupant.get('nick'))
     const i18n_participants = __('Participants');
-    const occupant_tpls = o.occupants.map(occupant => {
-        return tpl_occupant(Object.assign({
-            'jid': '',
-            'hint_show': PRETTY_CHAT_STATUS[occupant.get('show')],
-            'hint_occupant': i18n_occupant_hint(occupant),
-            'onOccupantClicked': o.onOccupantClicked
-        }, occupant.toJSON()));
-    });
-
     return html`
         <div class="occupants-header">
             <i class="hide-occupants" @click=${o.closeSidebar}>
@@ -36,6 +15,6 @@ export default (o) => {
             </div>
         </div>
         <div class="dragresize dragresize-occupants-left"></div>
-        <ul class="occupant-list">${occupant_tpls}</ul>
+        <ul class="occupant-list">${o.occupants.map(occ => tpl_occupant(occ, o))}</ul>
     `;
 }

+ 52 - 19
src/plugins/muc-views/templates/occupant.js

@@ -1,44 +1,77 @@
-import { html } from "lit";
+import { PRETTY_CHAT_STATUS } from '../constants.js';
 import { __ } from 'i18n';
+import { html } from "lit";
+import { showOccupantModal } from '../utils.js';
 
+const i18n_occupant_hint = (o) => __('Click to mention %1$s in your message.', o.get('nick'))
 
 const occupant_title = (o) => {
+    const role = o.get('role');
+    const hint_occupant = i18n_occupant_hint(o);
     const i18n_moderator_hint = __('This user is a moderator.');
     const i18n_participant_hint = __('This user can send messages in this groupchat.');
     const i18n_visitor_hint = __('This user can NOT send messages in this groupchat.')
-    const spaced_jid = `${o.jid} ` || '';
-    if (o.role === "moderator") {
-        return `${spaced_jid}${i18n_moderator_hint} ${o.hint_occupant}`;
-    } else if (o.role === "participant") {
-        return `${spaced_jid}${i18n_participant_hint} ${o.hint_occupant}`;
-    } else if (o.role === "visitor") {
-        return `${spaced_jid}${i18n_visitor_hint} ${o.hint_occupant}`;
-    } else if (!["visitor", "participant", "moderator"].includes(o.role)) {
-        return `${spaced_jid}${o.hint_occupant}`;
+    const spaced_jid = o.get('jid') ? `${o.get('jid')} ` : '';
+    if (role === "moderator") {
+        return `${spaced_jid}${i18n_moderator_hint} ${hint_occupant}`;
+    } else if (role === "participant") {
+        return `${spaced_jid}${i18n_participant_hint} ${hint_occupant}`;
+    } else if (role === "visitor") {
+        return `${spaced_jid}${i18n_visitor_hint} ${hint_occupant}`;
+    } else if (!["visitor", "participant", "moderator"].includes(role)) {
+        return `${spaced_jid}${hint_occupant}`;
     }
 }
 
 
-export default (o) => {
-    const i18n_owner = __('Owner');
+export default (o, chat) => {
+    const affiliation = o.get('affiliation');
+    const hint_show = PRETTY_CHAT_STATUS[o.get('show')];
     const i18n_admin = __('Admin');
     const i18n_member = __('Member');
     const i18n_moderator = __('Moderator');
+    const i18n_owner = __('Owner');
     const i18n_visitor = __('Visitor');
+    const role = o.get('role');
+
+    const show = o.get('show');
+    let classes, color;
+    if (show === 'online') {
+        [classes, color] = ['fa fa-circle', 'chat-status-online'];
+    } else if (show === 'dnd') {
+        [classes, color] =  ['fa fa-minus-circle', 'chat-status-busy'];
+    } else if (show === 'away') {
+        [classes, color] =  ['fa fa-circle', 'chat-status-away'];
+    } else {
+        [classes, color] = ['fa fa-circle', 'subdued-color'];
+    }
+
     return html`
         <li class="occupant" id="${o.id}" title="${occupant_title(o)}">
             <div class="row no-gutters">
                 <div class="col-auto">
-                    <div class="occupant-status occupant-${o.show} circle" title="${o.hint_show}"></div>
+                    <a class="show-msg-author-modal" @click=${(ev) => showOccupantModal(ev, o)}>
+                        <converse-avatar
+                            class="avatar chat-msg__avatar"
+                            .data=${o.vcard?.attributes}
+                            nonce=${o.vcard?.get('vcard_updated')}
+                            height="30" width="30"></converse-avatar>
+                        <converse-icon
+                           title="${hint_show}"
+                           color="var(--${color})"
+                           style="margin-top: -0.1em"
+                           size="0.82em"
+                           class="${classes} chat-status chat-status--avatar"></converse-icon>
+                    </a>
                 </div>
                 <div class="col occupant-nick-badge">
-                    <span class="occupant-nick" @click=${o.onOccupantClicked}>${o.nick || o.jid}</span>
+                    <span class="occupant-nick" @click=${chat.onOccupantClicked}>${o.getDisplayName()}</span>
                     <span class="occupant-badges">
-                        ${ (o.affiliation === "owner") ? html`<span class="badge badge-groupchat">${i18n_owner}</span>` : '' }
-                        ${ (o.affiliation === "admin") ? html`<span class="badge badge-info">${i18n_admin}</span>` : '' }
-                        ${ (o.affiliation === "member") ? html`<span class="badge badge-info">${i18n_member}</span>` : '' }
-                        ${ (o.role === "moderator") ? html`<span class="badge badge-info">${i18n_moderator}</span>` : '' }
-                        ${ (o.role === "visitor") ? html`<span class="badge badge-secondary">${i18n_visitor}</span>`  : '' }
+                        ${ (affiliation === "owner") ? html`<span class="badge badge-groupchat">${i18n_owner}</span>` : '' }
+                        ${ (affiliation === "admin") ? html`<span class="badge badge-info">${i18n_admin}</span>` : '' }
+                        ${ (affiliation === "member") ? html`<span class="badge badge-info">${i18n_member}</span>` : '' }
+                        ${ (role === "moderator") ? html`<span class="badge badge-info">${i18n_moderator}</span>` : '' }
+                        ${ (role === "visitor") ? html`<span class="badge badge-secondary">${i18n_visitor}</span>`  : '' }
                     </span>
                 </div>
             </div>

+ 6 - 0
src/plugins/muc-views/utils.js

@@ -1,4 +1,5 @@
 import ModeratorToolsModal from './modals/moderator-tools.js';
+import OccupantModal from 'modals/occupant.js';
 import log from "@converse/headless/log";
 import tpl_spinner from 'templates/spinner.js';
 import { __ } from 'i18n';
@@ -292,6 +293,11 @@ export function showModeratorToolsModal (muc, affiliation) {
 }
 
 
+export function showOccupantModal (ev, occupant) {
+    api.modal.show(OccupantModal, { 'model': occupant }, ev);
+}
+
+
 export function parseMessageForMUCCommands (muc, text) {
     if (
         api.settings.get('muc_disable_slash_commands') &&

+ 2 - 2
src/plugins/profile/statusview.js

@@ -4,7 +4,7 @@ import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api } from '@converse/headless/core';
 
-class ProfileView extends CustomElement {
+class Profile extends CustomElement {
 
     initialize () {
         this.model = _converse.xmppstatus;
@@ -41,4 +41,4 @@ class ProfileView extends CustomElement {
     }
 }
 
-api.elements.define('converse-user-profile', ProfileView);
+api.elements.define('converse-user-profile', Profile);

+ 1 - 1
src/plugins/profile/templates/profile.js

@@ -51,7 +51,7 @@ export default (el) => {
             <div class="d-flex xmpp-status">
                 <a class="change-status" title="${i18n_change_status}" data-toggle="modal" data-target="#changeStatusModal" @click=${el.showStatusChangeModal}>
                     <span class="${chat_status} w-100 align-self-center" data-value="${chat_status}">
-                    <converse-icon color="var(--${color})" size="1em" class="${classes}"></converse-icon> ${status_message}</span>
+                    <converse-icon color="var(--${color})" style="margin-top: -0.1em" size="0.82em" class="${classes}"></converse-icon> ${status_message}</span>
                 </a>
             </div>
         </div>`

+ 1 - 0
src/plugins/rosterview/index.js

@@ -12,6 +12,7 @@ import { RosterFilter, RosterFilterView } from './filterview.js';
 import { _converse, api, converse } from "@converse/headless/core";
 import { highlightRosterItem } from './utils.js';
 
+import 'shared/styles/status.scss';
 import './styles/roster.scss';
 
 

+ 0 - 29
src/plugins/rosterview/styles/roster.scss

@@ -81,35 +81,6 @@
 
         .current-xmpp-contact {
             margin: 0.25em 0;
-
-            .chat-status {
-                vertical-align: middle;
-                font-size: 0.6em;
-                margin-right: 0;
-                margin-left: -0.7em;
-                margin-bottom: -1.5em;
-                border-radius: 50%;
-                border: 2px solid var(--occupants-background-color);
-            }
-            .chat-status--offline {
-                margin-right: 0.8em;
-            }
-            .chat-status--online {
-                color: var(--chat-status-online);
-            }
-            .chat-status--busy {
-                color: var(--chat-status-busy);
-            }
-            .chat-status--away {
-                color: var(--chat-status-away);
-            }
-            .chat-status--offline {
-                display: none;
-            }
-            .far.fa-circle,
-            .fa-times-circle {
-                color: var(--subdued-color);
-            }
         }
 
         li {

+ 23 - 19
src/plugins/rosterview/templates/roster_item.js

@@ -6,31 +6,35 @@ import { STATUSES } from '../constants.js';
 
 export default  (el, item) => {
    const show = item.presence.get('show') || 'offline';
-   let status_icon;
-   if (show === 'online') {
-      status_icon = 'fa fa-circle chat-status chat-status--online';
-   } else if (show === 'away') {
-      status_icon = 'fa fa-circle chat-status chat-status--away';
-   } else if (show === 'xa') {
-      status_icon = 'far fa-circle chat-status chat-status-xa';
-   } else if (show === 'dnd') {
-      status_icon = 'fa fa-minus-circle chat-status chat-status--busy';
-   } else {
-      status_icon = 'fa fa-times-circle chat-status chat-status--offline';
-   }
+    let classes, color;
+    if (show === 'online') {
+        [classes, color] = ['fa fa-circle', 'chat-status-online'];
+    } else if (show === 'dnd') {
+        [classes, color] =  ['fa fa-minus-circle', 'chat-status-busy'];
+    } else if (show === 'away') {
+        [classes, color] =  ['fa fa-circle', 'chat-status-away'];
+    } else {
+        [classes, color] = ['fa fa-circle', 'subdued-color'];
+    }
    const display_name = item.getDisplayName();
    const desc_status = STATUSES[show];
    const num_unread = item.get('num_unread') || 0;
-   const i18n_chat = __('Click to chat with %1$s (XMPP address: %2$s)', display_name, el.jid);
+   const i18n_chat = __('Click to chat with %1$s (XMPP address: %2$s)', display_name, el.model.get('jid'));
    const i18n_remove = __('Click to remove %1$s as a contact', display_name);
    return html`
    <a class="list-item-link cbox-list-item open-chat ${ num_unread ? 'unread-msgs' : '' }" title="${i18n_chat}" href="#" @click=${el.openChat}>
-      <converse-avatar
-         class="avatar"
-         .data=${el.model.vcard?.attributes}
-         nonce=${el.model.vcard?.get('vcard_updated')}
-         height="30" width="30"></converse-avatar>
-      <span class="${status_icon}" title="${desc_status}"></span>
+      <span>
+         <converse-avatar
+            class="avatar"
+            .data=${el.model.vcard?.attributes}
+            nonce=${el.model.vcard?.get('vcard_updated')}
+            height="30" width="30"></converse-avatar>
+         <converse-icon
+            title="${desc_status}"
+            color="var(--${color})"
+            size="1em"
+            class="${classes} chat-status chat-status--avatar"></converse-icon>
+      </span>
       ${ num_unread ? html`<span class="msgs-indicator">${ num_unread }</span>` : '' }
       <span class="contact-name contact-name--${el.show} ${ num_unread ? 'unread-msgs' : ''}">${display_name}</span>
    </a>

+ 6 - 0
src/shared/avatar/avatar.scss

@@ -1,6 +1,12 @@
 converse-avatar {
     border: 0;
     background: transparent;
+
+    &.modal-avatar {
+        display: block;
+        margin-bottom: 1em;
+    }
+
     .avatar {
         border-radius: var(--avatar-border-radius);
     }

+ 3 - 3
src/shared/avatar/templates/avatar.js

@@ -7,9 +7,9 @@ const getImgHref = (image, image_type) => {
 export default  (o) => {
     if (o.image) {
         return html`
-                <svg xmlns="http://www.w3.org/2000/svg" class="avatar ${o.classes}" width="${o.width}" height="${o.height}">
-                    <image width="${o.width}" height="${o.height}" preserveAspectRatio="xMidYMid meet" href="${getImgHref(o.image, o.image_type)}"/>
-                </svg>`;
+            <svg xmlns="http://www.w3.org/2000/svg" class="avatar ${o.classes}" width="${o.width}" height="${o.height}">
+                <image width="${o.width}" height="${o.height}" preserveAspectRatio="xMidYMid meet" href="${getImgHref(o.image, o.image_type)}"/>
+            </svg>`;
     } else {
         return '';
     }

+ 0 - 1
src/shared/styles/messages.scss

@@ -252,7 +252,6 @@
 
             .chat-msg__heading {
                 width: 100%;
-                margin-top: 0.5em;
                 padding-right: 0.25rem;
                 padding-bottom: 0.25rem;
 

+ 34 - 0
src/shared/styles/status.scss

@@ -0,0 +1,34 @@
+.conversejs {
+    .chat-status {
+        vertical-align: middle;
+        margin-right: 0;
+        border-radius: 50%;
+        font-size: 1em;
+
+        &.chat-status--avatar {
+          font-size: 0.6rem;
+          margin-left: -0.7em;
+          margin-bottom: -1.9em;
+          border-radius: 50%;
+        }
+    }
+    .chat-status--offline {
+        margin-right: 0.8em;
+    }
+    .chat-status--online {
+        color: var(--chat-status-online);
+    }
+    .chat-status--busy {
+        color: var(--chat-status-busy);
+    }
+    .chat-status--away {
+        color: var(--chat-status-away);
+    }
+    .chat-status--offline {
+        display: none;
+    }
+    .far.fa-circle,
+    .fa-times-circle {
+        color: var(--subdued-color);
+    }
+}