瀏覽代碼

muc-views: replace VDOMView with HTMLView

JC Brand 5 年之前
父節點
當前提交
d32c4c1f61

+ 5 - 6
spec/muc.js

@@ -1528,9 +1528,8 @@
                             .c('value').t('cauldronburn');
                 _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
 
-                await u.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length)
-                expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1);
-                expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
+                const form = await u.waitUntil(() => view.el.querySelector('.muc-config-form'));
+                expect(form.querySelectorAll('fieldset').length).toBe(2);
                 const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
                 expect(membersonly.length).toBe(1);
                 expect(membersonly[0].getAttribute('type')).toBe('checkbox');
@@ -2336,7 +2335,7 @@
 
                 await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
                 let occupants = view.el.querySelector('.occupant-list');
-                expect(occupants.childNodes.length).toBe(1);
+                expect(occupants.childElementCount).toBe(1);
                 expect(occupants.firstElementChild.querySelector('.occupant-nick').textContent.trim()).toBe("oldnick");
 
                 expect(chat_content.querySelectorAll('div.chat-info').length).toBe(1);
@@ -2368,7 +2367,7 @@
                 expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
 
                 occupants = view.el.querySelector('.occupant-list');
-                expect(occupants.childNodes.length).toBe(1);
+                expect(occupants.childElementCount).toBe(1);
 
                 presence = $pres().attrs({
                         from:'lounge@montague.lit/newnick',
@@ -2394,7 +2393,7 @@
                     __(_converse.muc.new_nickname_messages["303"], "newnick")
                 );
                 occupants = view.el.querySelector('.occupant-list');
-                expect(occupants.childNodes.length).toBe(1);
+                expect(occupants.childElementCount).toBe(1);
                 expect(sizzle('.occupant-nick:first', occupants).pop().textContent.trim()).toBe("newnick");
                 done();
             }));

+ 45 - 92
src/converse-muc-views.js

@@ -5,12 +5,11 @@
  * @license Mozilla Public License (MPLv2)
  */
 import "converse-modal";
-import "backbone.vdomview";
 import "formdata-polyfill";
 import "@converse/headless/utils/muc";
-import { get, head, isString, isUndefined, pick } from "lodash";
+import { get, head, isString, isUndefined } from "lodash";
+import { HTMLView } from 'skeletor.js/src/htmlview.js';
 import { Model } from 'skeletor.js/src/model.js';
-import { OrderedListView } from "skeletor.js/src/overview";
 import { View } from "skeletor.js/src/view";
 import { __ } from '@converse/headless/i18n';
 import converse from "@converse/headless/converse-core";
@@ -22,17 +21,15 @@ import tpl_chatroom_bottom_panel from "templates/chatroom_bottom_panel.html";
 import tpl_chatroom_destroyed from "templates/chatroom_destroyed.html";
 import tpl_chatroom_details_modal from "templates/chatroom_details_modal.js";
 import tpl_chatroom_disconnect from "templates/chatroom_disconnect.html";
-import tpl_chatroom_features from "templates/chatroom_features.html";
-import tpl_chatroom_form from "templates/chatroom_form.html";
+import tpl_muc_config_form from "templates/muc_config_form.js";
 import tpl_chatroom_head from "templates/chatroom_head.html";
 import tpl_chatroom_invite from "templates/chatroom_invite.html";
 import tpl_chatroom_nickname_form from "templates/chatroom_nickname_form.html";
-import tpl_chatroom_password_form from "templates/chatroom_password_form.html";
-import tpl_chatroom_sidebar from "templates/chatroom_sidebar.html";
+import tpl_muc_password_form from "templates/muc_password_form.js";
+import tpl_muc_sidebar from "templates/muc_sidebar.js";
 import tpl_info from "templates/info.html";
 import tpl_list_chatrooms_modal from "templates/list_chatrooms_modal.js";
 import tpl_moderator_tools_modal from "templates/moderator_tools_modal.js";
-import tpl_occupant from "templates/occupant.html";
 import tpl_room_description from "templates/room_description.html";
 import tpl_room_item from "templates/room_item.html";
 import tpl_room_panel from "templates/room_panel.html";
@@ -40,7 +37,7 @@ import tpl_rooms_results from "templates/rooms_results.html";
 import tpl_spinner from "templates/spinner.html";
 import xss from "xss/dist/xss";
 
-const { Backbone, Strophe, sizzle, $iq, $pres } = converse.env;
+const { Strophe, sizzle, $iq, $pres } = converse.env;
 const u = converse.env.utils;
 
 const ROLES = ['moderator', 'participant', 'visitor'];
@@ -1593,10 +1590,12 @@ converse.plugins.add('converse-muc-views', {
                 u.safeSave(this.model.session, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED});
             },
 
+            /**
+             * Remove the configuration form without submitting and return to the chat view.
+             * @private
+             * @method _converse.ChatRoomView#closeForm
+             */
             closeForm () {
-                /* Remove the configuration form without submitting and
-                 * return to the chat view.
-                 */
                 sizzle('.chatroom-form-container', this.el).forEach(e => u.addClass('hidden', e));
                 this.renderAfterTransition();
             },
@@ -2040,12 +2039,8 @@ converse.plugins.add('converse-muc-views', {
         });
 
 
-        _converse.MUCConfigForm = Backbone.VDOMView.extend({
-            className: 'muc-config-form',
-            events: {
-                'submit .chatroom-form': 'submitConfigForm',
-                'click .button-cancel': 'closeConfigForm'
-            },
+        _converse.MUCConfigForm = HTMLView.extend({
+            className: 'chatroom-form-container muc-config-form',
 
             initialize (attrs) {
                 this.chatroomview = attrs.chatroomview;
@@ -2066,11 +2061,12 @@ converse.plugins.add('converse-muc-views', {
                     'new_password': !password_protected,
                     'fixed_username': this.model.get('jid')
                 };
-                return tpl_chatroom_form({
-                    '__': __,
-                    'title': get(stanza.querySelector('title'), 'textContent'),
+                return tpl_muc_config_form({
+                    'closeConfigForm': ev => this.closeConfigForm(ev),
+                    'fields': fields.map(f => u.xForm2webForm(f, stanza, options)),
                     'instructions': get(stanza.querySelector('instructions'), 'textContent'),
-                    'fields': fields.map(f => u.xForm2webForm(f, stanza, options))
+                    'submitConfigForm': ev => this.submitConfigForm(ev),
+                    'title': get(stanza.querySelector('title'), 'textContent')
                 });
             },
 
@@ -2098,11 +2094,8 @@ converse.plugins.add('converse-muc-views', {
         });
 
 
-        _converse.MUCPasswordForm = Backbone.VDOMView.extend({
-            className: 'muc-password-form',
-            events: {
-                'submit form': 'submitPassword',
-            },
+        _converse.MUCPasswordForm = HTMLView.extend({
+            className: 'chatroom-form-container muc-password-form',
 
             initialize (attrs) {
                 this.chatroomview = attrs.chatroomview;
@@ -2111,14 +2104,10 @@ converse.plugins.add('converse-muc-views', {
             },
 
             toHTML () {
-                const err_msg = this.model.get('validation_message');
-                return tpl_chatroom_password_form({
+                return tpl_muc_password_form({
                     'jid': this.model.get('jid'),
-                    'heading': __('This groupchat requires a password'),
-                    'label_password': __('Password: '),
-                    'label_submit': __('Submit'),
-                    'error_class': err_msg ? 'error' : '',
-                    'validation_message': err_msg
+                    'submitPassword': ev => this.submitPassword(ev),
+                    'validation_message':  this.model.get('validation_message')
                 });
             },
 
@@ -2131,67 +2120,42 @@ converse.plugins.add('converse-muc-views', {
         });
 
 
-        _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({
-            tagName: 'li',
-            initialize () {
-                this.listenTo(this.model, 'change', this.render);
-            },
-
-            toHTML () {
-                const show = this.model.get('show');
-                return tpl_occupant(
-                    Object.assign({
-                        __,
-                        show,
-                        'jid': '',
-                        'hint_show': _converse.PRETTY_CHAT_STATUS[show],
-                        'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick'))
-                    }, this.model.toJSON())
-                );
-            },
-
-            destroy () {
-                this.el.parentElement.removeChild(this.el);
-            }
-        });
-
-
-        _converse.ChatRoomOccupantsView = OrderedListView.extend({
+        _converse.ChatRoomOccupantsView = HTMLView.extend({
             tagName: 'div',
             className: 'occupants col-md-3 col-4',
-            listItems: 'model',
-            sortEvent: 'change:role',
-            listSelector: '.occupant-list',
-
-            ItemView: _converse.ChatRoomOccupantView,
 
             async initialize () {
-                OrderedListView.prototype.initialize.apply(this, arguments);
-
+                this.chatroomview = this.model.chatroomview;
                 this.listenTo(this.model, 'add', this.maybeRenderInviteWidget);
+                this.listenTo(this.model, 'add', this.render);
+                this.listenTo(this.model, 'remove', this.render);
+                this.listenTo(this.model, 'change', this.render);
                 this.listenTo(this.model, 'change:affiliation', this.maybeRenderInviteWidget);
-
-                this.chatroomview = this.model.chatroomview;
-                this.listenTo(this.chatroomview.model.features, 'change', this.renderRoomFeatures);
+                this.listenTo(this.chatroomview.model.features, 'change', this.render);
                 this.listenTo(this.chatroomview.model.features, 'change:open', this.renderInviteWidget);
                 this.listenTo(this.chatroomview.model, 'change:hidden_occupants', this.setVisibility);
                 this.render();
                 await this.model.fetched;
-                this.sortAndPositionAllItems();
             },
 
-            render () {
-                this.el.innerHTML = tpl_chatroom_sidebar(
+            toHTML () {
+                return tpl_muc_sidebar(
                     Object.assign(this.chatroomview.model.toJSON(), {
                         'allow_muc_invitations': _converse.allow_muc_invitations,
-                        'label_occupants': __('Participants')
+                        'features': this.chatroomview.model.features,
+                        'label_occupants': __('Participants'),
+                        'occupants': this.model.models
                     })
                 );
+            },
+
+            afterRender () {
                 if (_converse.allow_muc_invitations) {
+                    // TODO: the invite widget needs to be rendered via a directive
                     _converse.api.waitUntil('rosterContactsFetched').then(() => this.renderInviteWidget());
                 }
                 this.setVisibility();
-                return this.renderRoomFeatures();
+                this.setOccupantsHeight();
             },
 
             setVisibility () {
@@ -2210,6 +2174,7 @@ converse.plugins.add('converse-muc-views', {
             },
 
             renderInviteWidget () {
+                // TODO: this needs to be rendered inside muc_sidebar.js
                 const widget = this.el.querySelector('.room-invite');
                 if (this.shouldInviteWidgetBeShown()) {
                     if (widget === null) {
@@ -2229,26 +2194,14 @@ converse.plugins.add('converse-muc-views', {
                 return this;
             },
 
-            renderRoomFeatures () {
-                const features = this.chatroomview.model.features,
-                      picks = pick(features.attributes, converse.ROOM_FEATURES),
-                      iteratee = (a, v) => a || v;
-
-                if (Object.values(picks).reduce(iteratee)) {
-                    const el = this.el.querySelector('.chatroom-features');
-                    el.innerHTML = tpl_chatroom_features(Object.assign(features.toJSON(), {__}));
-                    this.setOccupantsHeight();
-                }
-                return this;
-            },
-
             setOccupantsHeight () {
                 const el = this.el.querySelector('.chatroom-features');
-                this.el.querySelector('.occupant-list').style.cssText =
-                    `height: calc(100% - ${el.offsetHeight}px - 5em);`;
+                if (el) {
+                    this.el.querySelector('.occupant-list').style.cssText =
+                        `height: calc(100% - ${el.offsetHeight}px - 5em);`;
+                }
             },
 
-
             promptForInvite (suggestion) {
                 let reason = '';
                 if (!_converse.auto_join_on_invite) {
@@ -2417,7 +2370,7 @@ converse.plugins.add('converse-muc-views', {
                  * @method _converse.api.roomviews.get
                  * @param {String|string[]} name - e.g. 'coven@conference.shakespeare.lit' or
                  *  ['coven@conference.shakespeare.lit', 'cave@conference.shakespeare.lit']
-                 * @returns {Backbone.View} Backbone.View representing the groupchat
+                 * @returns {View} View representing the groupchat
                  *
                  * @example
                  * // To return a single view, provide the JID of the groupchat

+ 1 - 0
src/converse-rosterview.js

@@ -3,6 +3,7 @@
  * @copyright 2020, the Converse.js contributors
  * @license Mozilla Public License (MPLv2)
  */
+import "backbone.vdomview";
 import "@converse/headless/converse-chatboxes";
 import "@converse/headless/converse-roster";
 import "converse-modal";

+ 0 - 9
src/headless/converse-core.js

@@ -145,15 +145,6 @@ _converse.STATUS_WEIGHTS = {
     'chat':         1, // We currently don't differentiate between "chat" and "online"
     'online':       1
 };
-_converse.PRETTY_CHAT_STATUS = {
-    'offline':      'Offline',
-    'unavailable':  'Unavailable',
-    'xa':           'Extended Away',
-    'away':         'Away',
-    'dnd':          'Do not disturb',
-    'chat':         'Chattty',
-    'online':       'Online'
-};
 _converse.ANONYMOUS = 'anonymous';
 _converse.CLOSED = 'closed';
 _converse.EXTERNAL = 'external';

+ 0 - 42
src/templates/chatroom_features.html

@@ -1,42 +0,0 @@
-<p class="occupants-heading">{{{o.__('Features')}}}</p>
-<ul class="features-list">
-{[ if (o.passwordprotected) { ]}
-<li class="feature" title="{{{ o.__('This groupchat requires a password before entry') }}}"><span class="fa fa-lock"></span>{{{ o.__('Password protected') }}}</li>
-{[ } ]}
-{[ if (o.unsecured) { ]}
-<li class="feature" title="{{{ o.__('This groupchat does not require a password upon entry') }}}"><span class="fa fa-unlock"></span>{{{ o.__('No password') }}}</li>
-{[ } ]}
-{[ if (o.hidden) { ]}
-<li class="feature" title="{{{ o.__('This groupchat is not publicly searchable') }}}"><span class="fa fa-eye-slash"></span>{{{ o.__('Hidden') }}}</li>
-{[ } ]}
-{[ if (o.public_room) { ]}
-<li class="feature" title="{{{ o.__('This groupchat is publicly searchable') }}}"><span class="fa fa-eye"></span>{{{ o.__('Public') }}}</li>
-{[ } ]}
-{[ if (o.membersonly) { ]}
-<li class="feature" title="{{{ o.__('this groupchat is restricted to members only') }}}"><span class="fa fa-address-book"></span>{{{ o.__('Members only') }}}</li>
-{[ } ]}
-{[ if (o.open) { ]}
-<li class="feature" title="{{{ o.__('Anyone can join this groupchat') }}}"><span class="fa fa-globe"></span>{{{ o.__('Open') }}}</li>
-{[ } ]}
-{[ if (o.persistent) { ]}
-<li class="feature" title="{{{ o.__('This groupchat persists even if it\'s unoccupied') }}}"><span class="fa fa-save"></span>{{{ o.__('Persistent') }}}</li>
-{[ } ]}
-{[ if (o.temporary) { ]}
-<li class="feature" title="{{{ o.__('This groupchat will disappear once the last person leaves') }}}"><span class="fa fa-snowflake"></span>{{{ o.__('Temporary') }}}</li>
-{[ } ]}
-{[ if (o.nonanonymous) { ]}
-<li class="feature" title="{{{ o.__('All other groupchat participants can see your XMPP address') }}}"><span class="fa fa-id-card"></span>{{{ o.__('Not anonymous') }}}</li>
-{[ } ]}
-{[ if (o.semianonymous) { ]}
-<li class="feature" title="{{{ o.__('Only moderators can see your XMPP address') }}}"><span class="fa fa-user-secret"></span>{{{ o.__('Semi-anonymous') }}}</li>
-{[ } ]}
-{[ if (o.moderated) { ]}
-<li class="feature" title="{{{ o.__('Participants entering this groupchat need to request permission to write') }}}"><span class="fa fa-gavel"></span>{{{ o.__('Moderated') }}}</li>
-{[ } ]}
-{[ if (o.unmoderated) { ]}
-<li class="feature" title="{{{ o.__('Participants entering this groupchat can write right away') }}}"><span class="fa fa-info-circle"></span>{{{ o.__('Not moderated') }}}</li>
-{[ } ]}
-{[ if (o.mam_enabled) { ]}
-<li class="feature" title="{{{ o.__('Messages are archived on the server') }}}"><span class="fa fa-database"></span>{{{ o.__('Message archiving') }}}</li>
-{[ } ]}
-</ul>

+ 0 - 16
src/templates/chatroom_form.html

@@ -1,16 +0,0 @@
-<div class="chatroom-form-container muc-config-form">
-    <form class="converse-form chatroom-form" autocomplete="off">
-        <fieldset class="form-group">
-            <legend>{{{o.title}}}</legend>
-            {[ if (o.title !== o.instructions) { ]}
-                <p class="form-help">{{{o.instructions}}}</p>
-            {[ } ]}
-            <!-- Fields are generated internally, with xForm2webForm -->
-            {[ o.fields.forEach(function (field) { ]} {{ field }} {[ }) ]}
-        </fieldset>
-        <fieldset>
-            <input type="submit" class="btn btn-primary" value="{{{o.__('Save')}}}"/>
-            <input type="button" class="btn btn-secondary .button-cancel" value="{{{o.__('Cancel')}}}"/>
-        </fieldset>
-    </form>
-</div>

+ 0 - 14
src/templates/chatroom_password_form.html

@@ -1,14 +0,0 @@
-<div class="chatroom-form-container muc-password-form">
-    <form class="converse-form chatroom-form converse-centered-form">
-        <fieldset class="form-group">
-            <label>{{{o.heading}}}</label>
-            <p class="validation-message">{{{o.validation_message}}}</p>
-            <input class="hidden-username" type="text" autocomplete="username" value="{{{o.jid}}}"></input>
-            <input type="password" name="password" required="required"
-                   class="form-control {{o.error_class}}" placeholder="{{{o.label_password}}}"/>
-        </fieldset>
-        <fieldset class="form-group">
-            <input class="btn btn-primary" type="submit" value="{{{o.label_submit}}}"/>
-        </fieldset>
-    </form>
-</div>

+ 0 - 9
src/templates/chatroom_sidebar.html

@@ -1,9 +0,0 @@
-<!-- <div class="occupants"> -->
-<div class="occupants-header">
-    <i class="hide-occupants fa fa-times"></i>
-    <p class="occupants-heading">{{{o.label_occupants}}}</p>
-</div>
-<div class="dragresize dragresize-occupants-left"></div>
-<ul class="occupant-list"></ul>
-<div class="chatroom-features"></div>
-<!-- </div> -->

+ 21 - 0
src/templates/muc_config_form.js

@@ -0,0 +1,21 @@
+import { html } from "lit-html";
+import { __ } from '@converse/headless/i18n';
+import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
+
+const i18n_save = __('Save');
+const i18n_cancel = __('Cancel');
+
+export default (o) => html`
+    <form class="converse-form chatroom-form" autocomplete="off" @submit=${o.submitConfigForm}>
+        <fieldset class="form-group">
+            <legend>${o.title}</legend>
+            ${ (o.title !== o.instructions) ? html`<p class="form-help">${o.instructions}</p>` : '' }
+            <!-- Fields are generated internally, with xForm2webForm -->
+            ${ o.fields.map(field =>  unsafeHTML(field)) }
+        </fieldset>
+        <fieldset>
+            <input type="submit" class="btn btn-primary" value="${i18n_save}">
+            <input type="button" class="btn btn-secondary button-cancel" value="${i18n_cancel}" @click=${o.closeConfigForm}>
+        </fieldset>
+    </form>
+`;

+ 25 - 0
src/templates/muc_password_form.js

@@ -0,0 +1,25 @@
+import { html } from "lit-html";
+import { __ } from '@converse/headless/i18n';
+
+const i18n_heading = __('This groupchat requires a password');
+const i18n_password = __('Password: ');
+const i18n_submit = __('Submit');
+
+
+export default (o) => html`
+    <form class="converse-form chatroom-form converse-centered-form" @submit=${o.submitPassword}>
+        <fieldset class="form-group">
+            <label>${i18n_heading}</label>
+            <p class="validation-message">${o.validation_message}</p>
+            <input class="hidden-username" type="text" autocomplete="username" value="${o.jid}"></input>
+            <input type="password"
+                   name="password"
+                   required="required"
+                   class="form-control ${o.validation_message ? 'error': ''}"
+                   placeholder="${i18n_password}"/>
+        </fieldset>
+        <fieldset class="form-group">
+            <input class="btn btn-primary" type="submit" value="${i18n_submit}"/>
+        </fieldset>
+    </form>
+`;

+ 99 - 0
src/templates/muc_sidebar.js

@@ -0,0 +1,99 @@
+import { html } from "lit-html";
+import { __ } from '@converse/headless/i18n';
+import { pick } from "lodash";
+import converse from "@converse/headless/converse-core";
+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'
+};
+
+const occupant_hint = (occupant) => __('Click to mention %1$s in your message.', occupant.get('nick'))
+
+const i18n_archived = __('Message archiving');
+const i18n_archived_hint = __('Messages are archived on the server');
+const i18n_features = __('Features');
+const i18n_hidden = __('Hidden');
+const i18n_members_only = __('Members only');
+const i18n_members_only_hint = __('this groupchat is restricted to members only');
+const i18n_moderated = __('Moderated');
+const i18n_moderated_hint = __('Participants entering this groupchat need to request permission to write');
+const i18n_no_password = __('No password');
+const i18n_no_password_hint = __('This groupchat does not require a password upon entry');
+const i18n_non_anon_hint = __('All other groupchat participants can see your XMPP address');
+const i18n_not_anon = __('Not anonymous');
+const i18n_not_moderated = __('Not moderated');
+const i18n_not_searchable_hint = __('This groupchat is not publicly searchable');
+const i18n_open = __('Open');
+const i18n_open_hint = __('Anyone can join this groupchat');
+const i18n_password = __('Password protected')
+const i18n_password_hint = __('This groupchat requires a password before entry');
+const i18n_persistent = __('Persistent');
+const i18n_persistent_hint = __('This groupchat persists even if it\'s unoccupied');
+const i18n_public =  __('Public');
+const i18n_searchable_hint = __('This groupchat is publicly searchable');
+const i18n_semi_anon = __('Semi-anonymous');
+const i18n_semi_anon_hint = __('Only moderators can see your XMPP address');
+const i18n_temporary = __('Temporary');
+const i18n_temporary_hint = __('This groupchat will disappear once the last person leaves');
+const i18n_unmoderated_hint = __('Participants entering this groupchat can write right away');
+
+
+function renderFeatures (o) {
+    const picks = pick(o.features.attributes, converse.ROOM_FEATURES);
+    const iteratee = (a, v) => a || v;
+    if (Object.values(picks).reduce(iteratee)) {
+        return tpl_features(o.features.toJSON());
+    } else {
+        return '';
+    }
+}
+
+
+const tpl_features = (o) => html`
+    <div class="chatroom-features">
+        <p class="occupants-heading">${i18n_features}</p>
+        <ul class="features-list">
+            ${ (o.passwordprotected) ? html`<li class="feature" title="${ i18n_password_hint }"><span class="fa fa-lock"></span>${ i18n_password }</li>` : '' }
+            ${ (o.unsecured) ? html`<li class="feature" title="${ i18n_no_password_hint }"><span class="fa fa-unlock"></span>${ i18n_no_password }</li>` : '' }
+            ${ (o.hidden) ? html`<li class="feature" title="${ i18n_not_searchable_hint }"><span class="fa fa-eye-slash"></span>${ i18n_hidden }</li>` : '' }
+            ${ (o.public_room) ? html`<li class="feature" title="${ i18n_searchable_hint }"><span class="fa fa-eye"></span>${ i18n_public }</li>` : '' }
+            ${ (o.membersonly) ? html`<li class="feature" title="${ i18n_members_only_hint }"><span class="fa fa-address-book"></span>${ i18n_members_only }</li>` : '' }
+            ${ (o.open) ? html`<li class="feature" title="${ i18n_open_hint }"><span class="fa fa-globe"></span>${ i18n_open }</li>` : '' }
+            ${ (o.persistent) ? html`<li class="feature" title="${ i18n_persistent_hint }"><span class="fa fa-save"></span>${ i18n_persistent }</li>` : '' }
+            ${ (o.temporary) ? html`<li class="feature" title="${ i18n_temporary_hint }"><span class="fa fa-snowflake"></span>${ i18n_temporary }</li>` : '' }
+            ${ (o.nonanonymous) ? html`<li class="feature" title="${ i18n_non_anon_hint }"><span class="fa fa-id-card"></span>${ i18n_not_anon }</li>` : '' }
+            ${ (o.semianonymous) ? html`<li class="feature" title="${ i18n_semi_anon_hint }"><span class="fa fa-user-secret"></span>${ i18n_semi_anon }</li>` : '' }
+            ${ (o.moderated) ? html`<li class="feature" title="${ i18n_moderated_hint }"><span class="fa fa-gavel"></span>${ i18n_moderated }</li>` : '' }
+            ${ (o.unmoderated) ? html`<li class="feature" title="${ i18n_unmoderated_hint }"><span class="fa fa-info-circle"></span>${ i18n_not_moderated }</li>` : '' }
+            ${ (o.mam_enabled) ? html`<li class="feature" title="${ i18n_archived_hint }"><span class="fa fa-database"></span>${ i18n_archived }</li>` : '' }
+        </ul>
+    </div>
+`;
+
+
+export default (o) => html`
+    <div class="occupants-header">
+        <i class="hide-occupants fa fa-times"></i>
+        <p class="occupants-heading">${o.label_occupants}</p>
+    </div>
+    <div class="dragresize dragresize-occupants-left"></div>
+    <ul class="occupant-list">
+        ${ o.occupants.map(occupant => {
+            return tpl_occupant(
+                    Object.assign({
+                        'jid': '',
+                        'hint_show': PRETTY_CHAT_STATUS[occupant.get('show')],
+                        'hint_occupant': occupant_hint(occupant)
+                    }, occupant.toJSON())
+                );
+        }) }
+    </ul>
+    ${ renderFeatures(o) }
+`;

+ 0 - 40
src/templates/occupant.html

@@ -1,40 +0,0 @@
-<li class="occupant" id="{{{ o.id }}}"
-    {[ if (o.role === "moderator") { ]}
-       title="{{{ o.jid }}} {{{ o.__('This user is a moderator.') }}} {{{ o.hint_occupant }}}"
-    {[ } ]}
-    {[ if (o.role === "participant") { ]}
-       title="{{{ o.jid }}} {{{ o.__('This user can send messages in this groupchat.') }}} {{{ o.hint_occupant }}}"
-    {[ } ]}
-    {[ if (o.role === "visitor") { ]}
-       title="{{{ o.jid }}} {{{ o.__('This user can NOT send messages in this groupchat.') }}} {{{ o.hint_occupant }}}"
-    {[ } ]}
-    {[ if (!["visitor", "participant", "moderator"].includes(o.role)) { ]}
-       title="{{{ o.jid }}} {{{ o.hint_occupant }}}"
-    {[ } ]}>
-    <div class="row no-gutters">
-        <div class="col-auto">
-            <div class="occupant-status occupant-{{{o.show}}} circle" title="{{{o.hint_show}}}"></div>
-        </div>
-        <div class="col occupant-nick-badge">
-            <span class="occupant-nick">{{{o.nick || o.jid}}}</span>
-            <span class="occupant-badges">
-                {[ if (o.affiliation === "owner") { ]}
-                    <span class="badge badge-groupchat">{{{o.__('Owner')}}}</span>
-                {[ } ]}
-                {[ if (o.affiliation === "admin") { ]}
-                    <span class="badge badge-info">{{{o.__('Admin')}}}</span>
-                {[ } ]}
-                {[ if (o.affiliation === "member") { ]}
-                    <span class="badge badge-info">{{{o.__('Member')}}}</span>
-                {[ } ]}
-
-                {[ if (o.role === "moderator") { ]}
-                    <span class="badge badge-info">{{{o.__('Moderator')}}}</span>
-                {[ } ]}
-                {[ if (o.role === "visitor") { ]}
-                    <span class="badge badge-secondary">{{{o.__('Visitor')}}}</span>
-                {[ } ]}
-            </span>
-        </div>
-    </div>
-</li>

+ 45 - 0
src/templates/occupant.js

@@ -0,0 +1,45 @@
+import { html } from "lit-html";
+import { __ } from '@converse/headless/i18n';
+
+
+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 i18n_owner = __('Owner');
+const i18n_admin = __('Admin');
+const i18n_member = __('Member');
+const i18n_moderator = __('Moderator');
+const i18n_visitor = __('Visitor');
+
+const occupant_title = (o) => {
+    if (o.role === "moderator") {
+        return `${o.jid} ${i18n_moderator_hint} ${o.hint_occupant}`;
+    } else if (o.role === "participant") {
+        return `${o.jid} ${i18n_participant_hint} ${o.hint_occupant}`;
+    } else if (o.role === "visitor") {
+        return `${o.jid} ${i18n_visitor_hint} ${o.hint_occupant}`;
+    } else if (!["visitor", "participant", "moderator"].includes(o.role)) {
+        return `${o.jid} ${o.hint_occupant}`;
+    }
+}
+
+
+export default (o) => 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>
+            </div>
+            <div class="col occupant-nick-badge">
+                <span class="occupant-nick">${o.nick || o.jid}</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>`  : '' }
+                </span>
+            </div>
+        </div>
+    </li>
+`;