Răsfoiți Sursa

Add dropdown to the MUC sidebar

Allows the sidebar filter to be toggled.
JC Brand 1 an în urmă
părinte
comite
fc8b0e8650

+ 1 - 1
.eslintrc.json

@@ -88,7 +88,7 @@
         "lines-around-comment": "off",
         "lines-around-directive": "off",
         "max-depth": "error",
-        "max-len": "off",
+        "max-len": ["error", { "code": 120 }],
         "max-lines": "off",
         "max-nested-callbacks": "off",
         "max-params": "off",

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

@@ -39,6 +39,7 @@ export default class MUCSidebar extends CustomElement {
             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);
@@ -59,17 +60,25 @@ export default class MUCSidebar extends CustomElement {
         return tpl;
     }
 
-    closeSidebar(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?.();
-        ev?.stopPropagation?.();
         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'));
-        view?.getMessageForm().insertIntoTextArea(`@${ev.target.textContent}`);
+        const occ_el = /** @type {HTMLElement} */(ev.target);
+        view?.getMessageForm().insertIntoTextArea(`@${occ_el.textContent}`);
     }
 }
 

+ 10 - 10
src/plugins/muc-views/styles/muc-occupants.scss

@@ -15,14 +15,13 @@
                 display: flex;
                 flex-direction: column;
                 justify-content: space-between;
-                overflow-x: hidden;
-                overflow-y: hidden;
+                overflow: visible;
                 vertical-align: top;
                 background-color: var(--occupants-background-color);
                 border-left: var(--occupants-border-left);
                 padding: 0.5em;
                 max-width: 75%;
-                min-width: 20%;
+                min-width: 25%;
                 flex: 0 0 25%;
 
                 .occupants-header--title {
@@ -30,6 +29,14 @@
                     flex-direction: row;
                     margin-bottom: 0.5em;
 
+                    .occupants-heading {
+                        width: 100%;
+                        font-family: var(--heading-font);
+                        color: var(--groupchats-header-color-dark);
+                        padding-left: 0;
+                        margin-right: 0.5em;
+                    }
+
                     .hide-occupants {
                         align-self: flex-end;
                         cursor: pointer;
@@ -41,13 +48,6 @@
                     margin-right: 0.25em;
                 }
 
-                .occupants-heading {
-                    width: 100%;
-                    font-family: var(--heading-font);
-                    color: var(--groupchats-header-color-dark);
-                    padding-left: 0;
-                    margin-right: 1em;
-                }
                 .suggestion-box{
                     ul {
                         padding: 0;

+ 50 - 14
src/plugins/muc-views/templates/muc-sidebar.js

@@ -14,6 +14,8 @@ import { repeat } from 'lit/directives/repeat.js';
  * @param {Occupant} 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();
 
@@ -41,26 +43,60 @@ function shouldShowOccupant (el, occ, o) {
  * @param {Object} o
  */
 export default (el, o) => {
-    const i18n_participants = o.occupants.length === 1 ? __('Participant') : __('Participants');
+    const i18n_participants = el.model.occupants === 1 ? __('Participant') : __('Participants');
+    const i18n_close = __('Hide sidebar');
+    const i18n_show_filter = __('Show filter');
+    const i18n_hide_filter = __('Hide filter');
+    const is_filter_visible = el.model.get('filter_visible');
+
+    const btns = /** @type {TemplateResult[]} */ [];
+    if (el.model.occupants < 6) {
+        // We don't show the filter
+        btns.push(
+            html` <i class="hide-occupants" @click=${(ev) => el.closeSidebar(ev)}>
+                <converse-icon class="fa fa-times" size="1em"></converse-icon>
+            </i>`
+        );
+    } else {
+        btns.push(html`
+            <a href="#" class="dropdown-item" @click=${(ev) => el.closeSidebar(ev)}>
+                <converse-icon size="1em" class="fa fa-times"></converse-icon>
+                ${i18n_close}
+            </a>
+        `);
+        btns.push(html`
+            <a href="#" class="dropdown-item" @click=${(ev) => el.toggleFilter(ev)}>
+                <converse-icon size="1em" class="fa fa-filter"></converse-icon>
+                ${is_filter_visible ? i18n_hide_filter : i18n_show_filter}
+            </a>
+        `);
+    }
+
     return html`
         <div class="occupants-header">
             <div class="occupants-header--title">
-                <span class="occupants-heading">${o.occupants.length} ${i18n_participants}</span>
-                <i class="hide-occupants" @click=${(ev) => el.closeSidebar(ev)}>
-                    <converse-icon class="fa fa-times" size="1em"></converse-icon>
-                </i>
+                <span class="occupants-heading">${el.model.occupants.length} ${i18n_participants}</span>
+                ${btns.length === 1
+                    ? btns[0]
+                    : html`<converse-dropdown class="chatbox-btn dropleft" .items=${btns}></converse-dropdown>`}
             </div>
         </div>
         <div class="dragresize dragresize-occupants-left"></div>
         <ul class="occupant-list">
-            <converse-list-filter
-                    @update=${() => el.requestUpdate()}
-                    .promise=${el.model.initialized}
-                    .items=${el.model.occupants}
-                    .template=${tplOccupantsFilter}
-                    .model=${el.filter}></converse-list-filter>
-
-            ${ repeat(o.occupants, (occ) => occ.get('jid'), (occ) => shouldShowOccupant(el, occ, o)) }
+            ${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>
     `;
-}
+};

+ 1 - 1
src/plugins/muc-views/tests/occupants-filter.js

@@ -24,7 +24,7 @@ describe("The MUC occupants filter", function () {
         await u.waitUntil(() => view.model.occupants.length === 3);
 
         let filter_el = view.querySelector('converse-list-filter');
-        expect(u.isVisible(filter_el.firstElementChild)).toBe(false);
+        expect(filter_el).toBe(null);
 
         for (let i=0; i<mock.chatroom_names.length; i++) {
             const name = mock.chatroom_names[i];

+ 12 - 8
src/shared/chat/utils.js

@@ -46,6 +46,9 @@ export async function getHeadingStandaloneButton (promise_or_data) {
     `;
 }
 
+/**
+ * @param {Promise} promise
+ */
 export function getStandaloneButtons (promise) {
     return promise.then(
         btns => btns
@@ -55,15 +58,16 @@ export function getStandaloneButtons (promise) {
             .map(b => until(b, '')));
 }
 
+/**
+ * @param {Promise} promise
+ */
 export function getDropdownButtons (promise) {
-    return promise.then(
-        btns => {
-            const dropdown_btns = btns
-                .filter(b => !b.standalone)
-                .map(b => getHeadingDropdownItem(b));
-            return dropdown_btns.length ? html`<converse-dropdown class="chatbox-btn dropleft" .items=${dropdown_btns}></converse-dropdown>` : '';
-        }
-    );
+    return promise.then((btns) => {
+        const dropdown_btns = btns.filter((b) => !b.standalone).map((b) => getHeadingDropdownItem(b));
+        return dropdown_btns.length
+            ? html`<converse-dropdown class="chatbox-btn dropleft" .items=${dropdown_btns}></converse-dropdown>`
+            : '';
+    });
 }
 
 

+ 3 - 0
src/shared/components/templates/icons.js

@@ -7,6 +7,9 @@ export default () => html`
     License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
     -->
     <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
+    <symbol id="icon-filter" viewBox="0 0 512 512">
+        <path d="M3.9 54.9C10.5 40.9 24.5 32 40 32H472c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9V448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6V320.9L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9z"/>
+    </symbol>
     <symbol id="icon-address-book" viewBox="0 0 448 512">
         <path d="M436 160c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20V48c0-26.5-21.5-48-48-48H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h320c26.5 0 48-21.5 48-48v-48h20c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20v-64h20c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20v-64h20zm-228-32c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm112 236.8c0 10.6-10 19.2-22.4 19.2H118.4C106 384 96 375.4 96 364.8v-19.2c0-31.8 30.1-57.6 67.2-57.6h5c12.3 5.1 25.7 8 39.8 8s27.6-2.9 39.8-8h5c37.1 0 67.2 25.8 67.2 57.6v19.2z"></path>
     </symbol>