浏览代码

Separate occupants view from MUC sidebar.

We want to use the sidebar to show other things besides the occupants.
JC Brand 9 月之前
父节点
当前提交
459d585e9f

+ 2 - 2
src/plugins/muc-views/chatarea.js

@@ -1,7 +1,7 @@
 import { api, converse } from '@converse/headless';
-import tplMUCChatarea from './templates/muc-chatarea.js';
-import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
+import { CustomElement } from 'shared/components/element.js';
+import tplMUCChatarea from './templates/muc-chatarea.js';
 
 const { u } = converse.env;
 

+ 88 - 0
src/plugins/muc-views/occupants.js

@@ -0,0 +1,88 @@
+import debounce from 'lodash-es/debounce.js';
+import { Model } from '@converse/skeletor';
+import { _converse, api, u, RosterFilter } from "@converse/headless";
+import { CustomElement } from 'shared/components/element.js';
+import tplMUCOccupants from "./templates/muc-occupants.js";
+import './modals/muc-invite.js';
+import 'shared/autocomplete/index.js';
+
+import 'shared/styles/status.scss';
+import './styles/muc-occupants.scss';
+
+const { initStorage } = u;
+
+export default class MUCOccupants extends CustomElement {
+
+    constructor () {
+        super();
+        this.jid = null;
+    }
+
+    static get properties () {
+        return {
+            jid: { type: String }
+        }
+    }
+
+    initialize() {
+        const filter_id = `_converse.occupants-filter-${this.jid}`;
+        this.filter = new RosterFilter();
+        this.filter.id = filter_id;
+        initStorage(this.filter, filter_id);
+        this.filter.fetch();
+
+        const { chatboxes } = _converse.state;
+        this.model = chatboxes.get(this.jid);
+
+        // To avoid rendering continuously the participant list in case of massive joins/leaves:
+        const debouncedRequestUpdate = debounce(() => this.requestUpdate(), 200, {
+            maxWait: 1000
+        });
+
+        this.listenTo(this.model, 'change', () => this.requestUpdate());
+        this.listenTo(this.model.occupants, 'add', debouncedRequestUpdate);
+        this.listenTo(this.model.occupants, 'remove', debouncedRequestUpdate);
+        this.listenTo(this.model.occupants, 'change', debouncedRequestUpdate);
+        this.listenTo(this.model.occupants, 'sort', debouncedRequestUpdate);
+        this.listenTo(this.model.occupants, 'vcard:change', debouncedRequestUpdate);
+        this.listenTo(this.model.occupants, 'vcard:add', debouncedRequestUpdate);
+        this.listenTo(this.model.features, 'change:open', () => this.requestUpdate());
+
+        this.model.initialized.then(() => this.requestUpdate());
+    }
+
+    render () {
+        return tplMUCOccupants(this);
+    }
+
+    /**
+     * @param {MouseEvent} ev
+     */
+    showInviteModal (ev) {
+        ev.preventDefault();
+        api.modal.show('converse-muc-invite-modal', { model: new Model(), muc: this.model }, ev);
+    }
+
+    /** @param {MouseEvent} ev */
+    toggleFilter (ev) {
+        ev?.preventDefault?.();
+        u.safeSave(this.model, { 'filter_visible': !this.model.get('filter_visible') });
+    }
+
+    /** @param {MouseEvent} ev */
+    closeSidebar (ev) {
+        ev?.preventDefault?.();
+        u.safeSave(this.model, { 'hidden_occupants': true });
+    }
+
+    /** @param {MouseEvent} ev */
+    onOccupantClicked (ev) {
+        ev?.preventDefault?.();
+        const { chatboxviews } = _converse.state;
+        const view = chatboxviews.get(this.getAttribute('jid'));
+        const occ_el = /** @type {HTMLElement} */(ev.target);
+        view?.getMessageForm().insertIntoTextArea(`@${occ_el.textContent}`);
+    }
+}
+
+api.elements.define('converse-muc-occupants', MUCOccupants);

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

@@ -1,15 +1,11 @@
-import debounce from 'lodash-es/debounce.js';
-import { Model } from '@converse/skeletor';
-import { _converse, api, u, RosterFilter } from "@converse/headless";
+import { _converse, api, u } from "@converse/headless";
 import { CustomElement } from 'shared/components/element.js';
 import tplMUCSidebar from "./templates/muc-sidebar.js";
 import './modals/muc-invite.js';
+import './occupants.js';
 import 'shared/autocomplete/index.js';
 
 import 'shared/styles/status.scss';
-import './styles/muc-occupants.scss';
-
-const { initStorage } = u;
 
 export default class MUCSidebar extends CustomElement {
 
@@ -25,54 +21,17 @@ export default class MUCSidebar extends CustomElement {
     }
 
     initialize() {
-        const filter_id = `_converse.occupants-filter-${this.jid}`;
-        this.filter = new RosterFilter();
-        this.filter.id = filter_id;
-        initStorage(this.filter, filter_id);
-        this.filter.fetch();
-
         const { chatboxes } = _converse.state;
         this.model = chatboxes.get(this.jid);
 
-        // To avoid rendering continuously the participant list in case of massive joins/leaves:
-        const debouncedRequestUpdate = debounce(() => this.requestUpdate(), 200, {
-            maxWait: 1000
-        });
-
         this.listenTo(this.model, 'change', () => this.requestUpdate());
-        this.listenTo(this.model.occupants, 'add', debouncedRequestUpdate);
-        this.listenTo(this.model.occupants, 'remove', debouncedRequestUpdate);
-        this.listenTo(this.model.occupants, 'change', debouncedRequestUpdate);
-        this.listenTo(this.model.occupants, 'sort', debouncedRequestUpdate);
-        this.listenTo(this.model.occupants, 'vcard:change', debouncedRequestUpdate);
-        this.listenTo(this.model.occupants, 'vcard:add', debouncedRequestUpdate);
         this.listenTo(this.model.features, 'change:open', () => this.requestUpdate());
 
         this.model.initialized.then(() => this.requestUpdate());
     }
 
     render () {
-        const tpl = tplMUCSidebar(this, Object.assign(
-            this.model.toJSON(), {
-                'occupants': [...this.model.occupants.models],
-                'onOccupantClicked': ev => this.onOccupantClicked(ev)
-            }
-        ));
-        return tpl;
-    }
-
-    /**
-     * @param {MouseEvent} ev
-     */
-    showInviteModal (ev) {
-        ev.preventDefault();
-        api.modal.show('converse-muc-invite-modal', { model: new Model(), muc: this.model }, ev);
-    }
-
-    /** @param {MouseEvent} ev */
-    toggleFilter (ev) {
-        ev?.preventDefault?.();
-        u.safeSave(this.model, { 'filter_visible': !this.model.get('filter_visible') });
+        return tplMUCSidebar(this);
     }
 
     /** @param {MouseEvent} ev */
@@ -80,15 +39,6 @@ export default class MUCSidebar extends CustomElement {
         ev?.preventDefault?.();
         u.safeSave(this.model, { 'hidden_occupants': true });
     }
-
-    /** @param {MouseEvent} ev */
-    onOccupantClicked (ev) {
-        ev?.preventDefault?.();
-        const { chatboxviews } = _converse.state;
-        const view = chatboxviews.get(this.getAttribute('jid'));
-        const occ_el = /** @type {HTMLElement} */(ev.target);
-        view?.getMessageForm().insertIntoTextArea(`@${occ_el.textContent}`);
-    }
 }
 
 api.elements.define('converse-muc-sidebar', MUCSidebar);

+ 125 - 0
src/plugins/muc-views/templates/muc-occupants.js

@@ -0,0 +1,125 @@
+/**
+ * @typedef {import('@converse/headless').MUCOccupant} MUCOccupant
+ */
+import 'shared/components/list-filter.js';
+import tplOccupant from "./occupant.js";
+import tplOccupantsFilter from './occupants-filter.js';
+import { __ } from 'i18n';
+import { html } from "lit";
+import { repeat } from 'lit/directives/repeat.js';
+
+/**
+ * @param {import('../occupants').default} el
+ * @param {MUCOccupant} occ
+ */
+function isOccupantFiltered (el, occ) {
+    if (!el.model.get('filter_visible')) return false;
+
+    const type = el.filter.get('type');
+    const q = (type === 'state') ? el.filter.get('state').toLowerCase() : el.filter.get('text').toLowerCase();
+
+    if (!q) return false;
+
+    if (type === 'state') {
+        const show = occ.get('show');
+        return q === 'online' ? ["offline", "unavailable"].includes(show) : !show.includes(q);
+    } else if (type === 'items')  {
+        return !occ.getDisplayName().toLowerCase().includes(q);
+    }
+}
+
+/**
+ * @param {import('../occupants').default} el
+ * @param {MUCOccupant} occ
+ */
+function shouldShowOccupant (el, occ) {
+    return isOccupantFiltered(el, occ) ? '' : tplOccupant(el, occ);
+}
+
+/**
+ * @param {import('../occupants').default} el
+ */
+export default (el) => {
+    const i18n_participants = el.model.occupants === 1 ? __('Participant') : __('Participants');
+    const i18n_close = __('Hide');
+    const i18n_show_filter = __('Show filter');
+    const i18n_hide_filter = __('Hide filter');
+    const is_filter_visible = el.model.get('filter_visible');
+    const i18n_invite = __('Invite someone')
+    const i18n_invite_title = __('Invite someone to join this groupchat')
+
+    const btns = /** @type {TemplateResult[]} */ [];
+
+    if (el.model.invitesAllowed()) {
+        btns.push(html`
+            <a href="#"
+               class="dropdown-item open-invite-modal"
+               role="button"
+               title="${i18n_invite_title}"
+               @click=${(/** @type {MouseEvent} */ev) => el.showInviteModal(ev)}>
+                <converse-icon size="1em" class="fa fa-user-plus"></converse-icon>
+                ${i18n_invite}
+            </a>
+        `);
+    }
+
+    if (el.model.occupants.length > 5) {
+        btns.push(html`
+            <a href="#"
+               class="dropdown-item toggle-filter"
+               role="button"
+               @click=${(/** @type {MouseEvent} */ev) => el.toggleFilter(ev)}>
+                <converse-icon size="1em" class="fa fa-filter"></converse-icon>
+                ${is_filter_visible ? i18n_hide_filter : i18n_show_filter}
+            </a>
+        `);
+    }
+
+    if (btns.length) {
+        btns.push(html`
+            <a href="#" class="dropdown-item" role="button"
+                @click=${(/** @type {MouseEvent} */ev) => el.closeSidebar(ev)}>
+                <converse-icon size="1em" class="fa fa-times"></converse-icon>
+                ${i18n_close}
+            </a>
+        `);
+    } else {
+        // Only a single button is shown, not a dropdown.
+        btns.push(
+            html` <i class="hide-occupants" @click=${(/** @type {MouseEvent} */ev) => el.closeSidebar(ev)}>
+                <converse-icon class="fa fa-times" size="1em"></converse-icon>
+            </i>`
+        );
+    }
+
+    return html`
+        <div class="occupants">
+            <div class="occupants-header">
+                <div class="occupants-header--title">
+                    <span class="occupants-heading">${el.model.occupants.length} ${i18n_participants}</span>
+                    ${btns.length === 1
+                        ? btns[0]
+                            : html`<converse-dropdown
+                                class="chatbox-btn btn-group dropstart"
+                                .items=${btns}></converse-dropdown>`}
+                </div>
+            </div>
+            <ul class="occupant-list">
+                ${is_filter_visible
+                    ? html` <converse-list-filter
+                        @update=${() => el.requestUpdate()}
+                        .promise=${el.model.initialized}
+                        .items=${el.model.occupants}
+                        .template=${tplOccupantsFilter}
+                        .model=${el.filter}
+                    ></converse-list-filter>`
+                    : ''}
+                ${repeat(
+                    el.model.occupants.models,
+                    (occ) => occ.get('jid'),
+                    (occ) => shouldShowOccupant(el, occ)
+                )}
+            </ul>
+        </div>
+    `;
+};

+ 3 - 119
src/plugins/muc-views/templates/muc-sidebar.js

@@ -1,129 +1,13 @@
-/**
- * @typedef {import('plugins/muc-views/sidebar').default} MUCSidebar
- * @typedef {import('@converse/headless').MUCOccupant} MUCOccupant
- */
 import 'shared/components/list-filter.js';
-import tplOccupant from "./occupant.js";
-import tplOccupantsFilter from './occupants-filter.js';
 import { __ } from 'i18n';
 import { html } from "lit";
-import { repeat } from 'lit/directives/repeat.js';
-
-/**
- * @param {MUCSidebar} el
- * @param {MUCOccupant} occ
- */
-function isOccupantFiltered (el, occ) {
-    if (!el.model.get('filter_visible')) return false;
-
-    const type = el.filter.get('type');
-    const q = (type === 'state') ? el.filter.get('state').toLowerCase() : el.filter.get('text').toLowerCase();
-
-    if (!q) return false;
-
-    if (type === 'state') {
-        const show = occ.get('show');
-        return q === 'online' ? ["offline", "unavailable"].includes(show) : !show.includes(q);
-    } else if (type === 'items')  {
-        return !occ.getDisplayName().toLowerCase().includes(q);
-    }
-}
 
 /**
- * @param {MUCSidebar} el
- * @param {MUCOccupant} occ
- * @param {Object} o
+ * @param {import('../sidebar').default} el
  */
-function shouldShowOccupant (el, occ, o) {
-    return isOccupantFiltered(el, occ) ? '' : tplOccupant(occ, o);
-}
-
-/**
- * @param {MUCSidebar} el
- * @param {Object} o
- */
-export default (el, o) => {
-    const i18n_participants = el.model.occupants === 1 ? __('Participant') : __('Participants');
-    const i18n_close = __('Hide');
-    const i18n_show_filter = __('Show filter');
-    const i18n_hide_filter = __('Hide filter');
-    const is_filter_visible = el.model.get('filter_visible');
-    const i18n_invite = __('Invite someone')
-    const i18n_invite_title = __('Invite someone to join this groupchat')
-
-    const btns = /** @type {TemplateResult[]} */ [];
-
-    if (el.model.invitesAllowed()) {
-        btns.push(html`
-            <a href="#"
-               class="dropdown-item open-invite-modal"
-               role="button"
-               title="${i18n_invite_title}"
-               @click=${(/** @type {MouseEvent} */ev) => el.showInviteModal(ev)}>
-                <converse-icon size="1em" class="fa fa-user-plus"></converse-icon>
-                ${i18n_invite}
-            </a>
-        `);
-    }
-
-    if (el.model.occupants.length > 5) {
-        btns.push(html`
-            <a href="#"
-               class="dropdown-item toggle-filter"
-               role="button"
-               @click=${(/** @type {MouseEvent} */ev) => el.toggleFilter(ev)}>
-                <converse-icon size="1em" class="fa fa-filter"></converse-icon>
-                ${is_filter_visible ? i18n_hide_filter : i18n_show_filter}
-            </a>
-        `);
-    }
-
-    if (btns.length) {
-        btns.push(html`
-            <a href="#" class="dropdown-item" role="button"
-                @click=${(/** @type {MouseEvent} */ev) => el.closeSidebar(ev)}>
-                <converse-icon size="1em" class="fa fa-times"></converse-icon>
-                ${i18n_close}
-            </a>
-        `);
-    } else {
-        // Only a single button is shown, not a dropdown.
-        btns.push(
-            html` <i class="hide-occupants" @click=${(/** @type {MouseEvent} */ev) => el.closeSidebar(ev)}>
-                <converse-icon class="fa fa-times" size="1em"></converse-icon>
-            </i>`
-        );
-    }
-
+export default (el) => {
     return html`
         <div class="dragresize-occupants-left">&nbsp;</div>
-        <div class="occupants">
-            <div class="occupants-header">
-                <div class="occupants-header--title">
-                    <span class="occupants-heading">${el.model.occupants.length} ${i18n_participants}</span>
-                    ${btns.length === 1
-                        ? btns[0]
-                            : html`<converse-dropdown
-                                class="chatbox-btn btn-group dropstart"
-                                .items=${btns}></converse-dropdown>`}
-                </div>
-            </div>
-            <ul class="occupant-list">
-                ${is_filter_visible
-                    ? html` <converse-list-filter
-                        @update=${() => el.requestUpdate()}
-                        .promise=${el.model.initialized}
-                        .items=${el.model.occupants}
-                        .template=${tplOccupantsFilter}
-                        .model=${el.filter}
-                    ></converse-list-filter>`
-                    : ''}
-                ${repeat(
-                    el.model.occupants.models,
-                    (occ) => occ.get('jid'),
-                    (occ) => shouldShowOccupant(el, occ, o)
-                )}
-            </ul>
-        </div>
+        <converse-muc-occupants jid="${el.jid}"></converse-muc-occupants>
     `;
 };

+ 3 - 3
src/plugins/muc-views/templates/occupant.js

@@ -139,10 +139,10 @@ async function tplActionButtons (o) {
 }
 
 /**
+ * @param {import('../occupants').default} el
  * @param {MUCOccupant} o
- * @param {Object} chat
  */
-export default (o, chat) => {
+export default (el, o) => {
     const affiliation = o.get('affiliation');
     const hint_show = PRETTY_CHAT_STATUS[o.get('show')];
     const role = o.get('role');
@@ -184,7 +184,7 @@ export default (o, chat) => {
                 <div class="col occupant-nick-badge">
                     <span class="occupant-nick"
                           title="${occupant_title(o)}"
-                          @click=${chat.onOccupantClicked}
+                          @click=${(ev) => el.onOccupantClicked(ev)}
                           style="${getAuthorStyle(o)}">${o.getDisplayName()}</span>
                     <span class="occupant-badges">
                         ${ (affiliation === "owner") ? tplBadge('owner') : '' }

+ 4 - 0
src/shared/components/element.js

@@ -4,6 +4,10 @@ import { EventEmitter } from '@converse/skeletor';
 
 export class CustomElement extends EventEmitter(LitElement) {
 
+    constructor () {
+        super();
+    }
+
     createRenderRoot () {
         // Render without the shadow DOM
         return this;

+ 0 - 10
src/types/plugins/muc-views/sidebar.d.ts

@@ -6,20 +6,10 @@ export default class MUCSidebar extends CustomElement {
     };
     jid: any;
     initialize(): void;
-    filter: RosterFilter;
     model: any;
     render(): import("lit").TemplateResult<1>;
-    /**
-     * @param {MouseEvent} ev
-     */
-    showInviteModal(ev: MouseEvent): void;
-    /** @param {MouseEvent} ev */
-    toggleFilter(ev: MouseEvent): void;
     /** @param {MouseEvent} ev */
     closeSidebar(ev: MouseEvent): void;
-    /** @param {MouseEvent} ev */
-    onOccupantClicked(ev: MouseEvent): void;
 }
 import { CustomElement } from 'shared/components/element.js';
-import { RosterFilter } from "@converse/headless";
 //# sourceMappingURL=sidebar.d.ts.map

+ 1 - 3
src/types/plugins/muc-views/templates/muc-sidebar.d.ts

@@ -1,5 +1,3 @@
-declare function _default(el: MUCSidebar, o: any): import("lit").TemplateResult<1>;
+declare function _default(el: import("../sidebar").default): import("lit").TemplateResult<1>;
 export default _default;
-export type MUCSidebar = import("plugins/muc-views/sidebar").default;
-export type MUCOccupant = import("@converse/headless").MUCOccupant;
 //# sourceMappingURL=muc-sidebar.d.ts.map

+ 1 - 1
src/types/plugins/muc-views/templates/occupant.d.ts

@@ -1,4 +1,4 @@
-declare function _default(o: MUCOccupant, chat: any): import("lit").TemplateResult<1>;
+declare function _default(el: import("../occupants").default, o: MUCOccupant): import("lit").TemplateResult<1>;
 export default _default;
 export type MUCOccupant = import("@converse/headless").MUCOccupant;
 //# sourceMappingURL=occupant.d.ts.map