Переглянути джерело

Make roster contacts toggleable

JC Brand 2 роки тому
батько
коміт
1a8ae3dcbe

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

@@ -77,13 +77,6 @@
             font-size: 100%;
         }
 
-        .open-rooms-toggle, .open-rooms-toggle .fa {
-            color: var(--groupchats-header-color) !important;
-            &:hover {
-                color: var(--chatroom-head-bg-color-dark) !important;
-            }
-        }
-
         .box-flyout {
             background-color: var(--controlbox-pane-background-color);
         }

+ 11 - 0
src/plugins/muc-views/styles/controlbox.scss

@@ -13,5 +13,16 @@
                 padding: 0;
             }
         }
+
+        .open-rooms-toggle, .open-rooms-toggle .fa {
+            color: var(--groupchats-header-color) !important;
+            &:hover {
+                color: var(--chatroom-head-bg-color-dark) !important;
+            }
+        }
+
+        .open-rooms-toggle {
+            white-space: nowrap;
+        }
     }
 }

+ 22 - 11
src/plugins/rosterview/rosterview.js

@@ -2,6 +2,8 @@ import tpl_roster from "./templates/roster.js";
 import { CustomElement } from 'shared/components/element.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/utils/storage.js';
+import { slideIn, slideOut } from 'utils/html.js';
 
 
 /**
@@ -12,7 +14,13 @@ import { _converse, api } from "@converse/headless/core";
 export default class RosterView extends CustomElement {
 
     async initialize () {
+        const id = `converse.contacts-panel${_converse.bare_jid}`;
+        this.model = new Model({ id });
+        initStorage(this.model, id);
+        this.model.fetch();
+
         await api.waitUntil('rosterInitialized')
+
         const { presences, roster } = _converse;
         this.listenTo(_converse, 'rosterContactsFetched', () => this.requestUpdate());
         this.listenTo(presences, 'change:show', () => this.requestUpdate());
@@ -21,6 +29,7 @@ export default class RosterView extends CustomElement {
         this.listenTo(roster, 'remove', () => this.requestUpdate());
         this.listenTo(roster, 'change', () => this.requestUpdate());
         this.listenTo(roster.state, 'change', () => this.requestUpdate());
+        this.listenTo(this.model, 'change', () => this.requestUpdate());
         /**
          * Triggered once the _converse.RosterView instance has been created and initialized.
          * @event _converse#rosterViewInitialized
@@ -29,35 +38,37 @@ export default class RosterView extends CustomElement {
         api.trigger('rosterViewInitialized');
     }
 
-    firstUpdated () {
-        this.listenToRosterFilter();
-    }
-
     render () {
         return tpl_roster(this);
     }
 
-    listenToRosterFilter () {
-        this.filter_view = this.querySelector('converse-roster-filter');
-        this.filter_view.addEventListener('update', () => this.requestUpdate());
-    }
-
     showAddContactModal (ev) { // eslint-disable-line class-methods-use-this
         api.modal.show('converse-add-contact-modal', {'model': new Model()}, ev);
     }
 
     async syncContacts (ev) { // eslint-disable-line class-methods-use-this
         ev.preventDefault();
+        const { roster } = _converse;
         this.syncing_contacts = true;
         this.requestUpdate();
 
-        _converse.roster.data.save('version', null);
-        await _converse.roster.fetchFromServer();
+        roster.data.save('version', null);
+        await roster.fetchFromServer();
         api.user.presence.send();
 
         this.syncing_contacts = false;
         this.requestUpdate();
     }
+
+    toggleRoster (ev) {
+        ev?.preventDefault?.();
+        const list_el = this.querySelector('.list-container.roster-contacts');
+        if (this.model.get('toggle_state') === _converse.CLOSED) {
+            slideOut(list_el).then(() => this.model.save({'toggle_state': _converse.OPENED}));
+        } else {
+            slideIn(list_el).then(() => this.model.save({'toggle_state': _converse.CLOSED}));
+        }
+    }
 }
 
 api.elements.define('converse-roster', RosterView);

+ 148 - 131
src/plugins/rosterview/styles/roster.scss

@@ -1,174 +1,191 @@
-.conversejs #converse-roster {
-    text-align: left;
-    width: 100%;
-    position: relative;
-    margin: 0;
-    height: var(--roster-height);
-    padding: 0;
-    overflow: hidden;
-    // XXX: FIXME
-    height: calc(100% - 70px);
-
-
-    /* Custom addition for CSP */
-    #online-count {
-        display: none;
-    }
+.conversejs {
 
-    .search-xmpp {
-        ul {
-            li.chat-info {
-                padding-left: 10px;
+    #controlbox {
+        .open-contacts-toggle, .open-contacts-toggle .fa {
+            color: var(--chat-color) !important;
+            &:hover {
+                color: var(--chat-color) !important;
             }
         }
-    }
-
-    .roster-filter-form {
-        width: 100%;
-
-        .button-group {
-            padding: 0.2em;
-        }
 
-        converse-icon {
-            padding: 0.25em;
-        }
-
-        .roster-filter {
-            width: 100%;
-            margin: 0.2em;
-            font-size: calc(var(--font-size) - 2px);
+        .open-contacts-toggle {
+            white-space: nowrap;
         }
 
-        .state-type {
-            font-size: calc(var(--font-size) - 2px);
-            width: 100%;
-        }
     }
 
-    .roster-contacts {
+    #converse-roster {
+        text-align: left;
+        width: 100%;
+        position: relative;
+        margin: 0;
+        height: var(--roster-height);
         padding: 0;
-        margin: 0 0 0.2em 0;
-        height: 100%;
-        overflow-x: hidden;
-        overflow-y: auto;
-        color: var(--text-color);
+        overflow: hidden;
+        // XXX: FIXME
+        height: calc(100% - 70px);
 
-        .roster-group-contacts {
-            .list-item {
-                &:hover {
-                    .list-item-action {
-                        opacity: 1;
-                    }
+
+        /* Custom addition for CSP */
+        #online-count {
+            display: none;
+        }
+
+        .search-xmpp {
+            ul {
+                li.chat-info {
+                    padding-left: 10px;
                 }
             }
         }
 
-        converse-roster-contact {
+        .roster-filter-form {
             width: 100%;
-            overflow: hidden;
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            display: flex;
-            justify-content: space-between;
 
-            .list-item-action {
-                line-height: 2em;
+            .button-group {
+                padding: 0.2em;
             }
 
-            &:hover {
-                .list-item-action {
-                    opacity: 1;
-                }
+            converse-icon {
+                padding: 0.25em;
             }
-        }
 
-        .group-toggle {
-            font-family: var(--heading-font);
-            display: block;
-            width: 100%;
-            margin: 0.75em 0 0.25em 0;
-        }
+            .roster-filter {
+                width: 100%;
+                margin: 0.2em;
+                font-size: calc(var(--font-size) - 2px);
+            }
 
-        .group-toggle, .group-toggle .fa {
-            color: var(--chat-head-color-dark) !important;
-            &:hover {
-                color: var(--chat-head-color-darker) !important;
+            .state-type {
+                font-size: calc(var(--font-size) - 2px);
+                width: 100%;
             }
         }
 
-        .current-xmpp-contact {
-            margin: 0.25em 0;
-        }
+        .roster-contacts {
+            padding: 0;
+            margin: 0 0 0.2em 0;
+            height: 100%;
+            overflow-x: hidden;
+            overflow-y: auto;
+            color: var(--text-color);
+
+            .roster-group-contacts {
+                .list-item {
+                    &:hover {
+                        .list-item-action {
+                            opacity: 1;
+                        }
+                    }
+                }
+            }
 
-        .list-item {
-            &.requesting-xmpp-contact {
-                a {
-                    line-height: var(--line-height);
+            converse-roster-contact {
+                width: 100%;
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                display: flex;
+                justify-content: space-between;
+
+                .list-item-action {
+                    line-height: 2em;
                 }
-                .req-contact-name {
-                    padding: 0 0.2em 0 0;
+
+                &:hover {
+                    .list-item-action {
+                        opacity: 1;
+                    }
                 }
             }
 
-            .open-chat {
-                margin: 0;
-                padding: 0;
-                &.unread-msgs {
-                    font-weight: bold;
-                    color: var(--unread-msgs-color);
-                    .contact-name {
-                        width: 70%;
-                    }
+            .group-toggle {
+                font-family: var(--heading-font);
+                display: block;
+                width: 100%;
+                margin: 0.75em 0 0.25em 0;
+            }
+
+            .group-toggle, .group-toggle .fa {
+                color: var(--chat-head-color-dark) !important;
+                &:hover {
+                    color: var(--chat-head-color-darker) !important;
                 }
+            }
 
-                .msgs-indicator {
-                    color: var(--text-color-invert);
-                    background-color: var(--chat-color);
-                    opacity: 1;
-                    border-radius: 10%;
-                    padding: 0.2em 0.4em;
-                    font-size: var(--font-size-small);
-                    margin-right: 0;
+            .current-xmpp-contact {
+                margin: 0.25em 0;
+            }
+
+            .list-item {
+                &.requesting-xmpp-contact {
+                    a {
+                        line-height: var(--line-height);
+                    }
+                    .req-contact-name {
+                        padding: 0 0.2em 0 0;
+                    }
                 }
 
-                .contact-name {
-                    padding: 0;
+                .open-chat {
                     margin: 0;
-                    max-width: 85%;
-                    float: none;
-                    height: 100%;
+                    padding: 0;
                     &.unread-msgs {
-                        max-width: 60%;
+                        font-weight: bold;
+                        color: var(--unread-msgs-color);
+                        .contact-name {
+                            width: 70%;
+                        }
                     }
-                    &.contact-name--offline {
-                        margin-left: 0.25em;
+
+                    .msgs-indicator {
+                        color: var(--text-color-invert);
+                        background-color: var(--chat-color);
+                        opacity: 1;
+                        border-radius: 10%;
+                        padding: 0.2em 0.4em;
+                        font-size: var(--font-size-small);
+                        margin-right: 0;
+                    }
+
+                    .contact-name {
+                        padding: 0;
+                        margin: 0;
+                        max-width: 85%;
+                        float: none;
+                        height: 100%;
+                        &.unread-msgs {
+                            max-width: 60%;
+                        }
+                        &.contact-name--offline {
+                            margin-left: 0.25em;
+                        }
                     }
                 }
-            }
-            &.odd {
-                background-color: #DCEAC5;
-                /* Make this difference */
-            }
-            a, span {
-                white-space: nowrap;
-                text-overflow: ellipsis;
-            }
-            .span {
-                display: inline-block;
-            }
-            .decline-xmpp-request {
-                margin-left: 5px;
-            }
-            &:hover {
-                background-color: var(--controlbox-pane-bg-hover-color);
+                &.odd {
+                    background-color: #DCEAC5;
+                    /* Make this difference */
+                }
+                a, span {
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                }
+                .span {
+                    display: inline-block;
+                }
+                .decline-xmpp-request {
+                    margin-left: 5px;
+                }
+                &:hover {
+                    background-color: var(--controlbox-pane-bg-hover-color);
+                }
             }
         }
-    }
-    span {
-        &.pending-contact-name {
-            line-height: var(--line-height);
-            width: 100%;
+        span {
+            &.pending-contact-name {
+                line-height: var(--line-height);
+                width: 100%;
+            }
         }
     }
 }

+ 17 - 4
src/plugins/rosterview/templates/roster.js

@@ -9,17 +9,30 @@ import { shouldShowContact, shouldShowGroup, populateContactsMap } from '../util
 
 export default (el) => {
     const i18n_heading_contacts = __('Contacts');
+    const i18n_toggle_contacts = __('Click to toggle contacts');
     const i18n_title_add_contact = __('Add a contact');
     const i18n_title_sync_contacts = __('Re-sync your contacts');
     const roster = _converse.roster || [];
     const contacts_map = roster.reduce((acc, contact) => populateContactsMap(acc, contact), {});
     const groupnames = Object.keys(contacts_map).filter(shouldShowGroup);
+    const is_closed = el.model.get('toggle_state') === _converse.CLOSED;
     groupnames.sort(groupsComparator);
 
     return html`
         <div class="d-flex controlbox-padded">
-            <span class="w-100 controlbox-heading controlbox-heading--contacts">${i18n_heading_contacts}</span>
-            <a class="controlbox-heading__btn sync-contacts" @click=${ev => el.syncContacts(ev)} title="${i18n_title_sync_contacts}">
+            <span class="w-100 controlbox-heading controlbox-heading--contacts">
+                <a class="list-toggle open-contacts-toggle" title="${i18n_toggle_contacts}" @click=${el.toggleRoster}>
+                    <converse-icon
+                        class="fa ${ is_closed ? 'fa-caret-right' : 'fa-caret-down' }"
+                        size="1em"
+                        color="var(--chat-color)"></converse-icon>
+                    ${i18n_heading_contacts}
+                </a>
+            </span>
+            <a class="controlbox-heading__btn sync-contacts"
+               @click=${ev => el.syncContacts(ev)}
+               title="${i18n_title_sync_contacts}">
+
                 <converse-icon class="fa fa-sync right ${el.syncing_contacts ? 'fa-spin' : ''}" size="1em"></converse-icon>
             </a>
             ${ api.settings.get('allow_contact_requests') ? html`
@@ -31,8 +44,8 @@ export default (el) => {
                     <converse-icon class="fa fa-user-plus right" size="1.25em"></converse-icon>
                 </a>` : '' }
         </div>
-        <converse-roster-filter></converse-roster-filter>
-        <div class="list-container roster-contacts">
+        <div class="list-container roster-contacts ${ is_closed ? 'hidden' : '' }">
+            <converse-roster-filter @update=${() => el.requestUpdate()}></converse-roster-filter>
             ${ repeat(groupnames, n => n, name => {
                 const contacts = contacts_map[name].filter(c => shouldShowContact(c, name));
                 contacts.sort(contactsComparator);

+ 12 - 8
src/utils/html.js

@@ -284,12 +284,11 @@ u.slideToggleElement = function (el, duration) {
 
 /**
  * Shows/expands an element by sliding it out of itself
- * @private
- * @method u#slideOut
+ * @method slideOut
  * @param { HTMLElement } el - The HTML string
  * @param { Number } duration - The duration amount in milliseconds
  */
-u.slideOut = function (el, duration = 200) {
+export function slideOut (el, duration = 200) {
     return new Promise((resolve, reject) => {
         if (!el) {
             const err = 'An element needs to be passed in to slideOut';
@@ -340,10 +339,15 @@ u.slideOut = function (el, duration = 200) {
         el.classList.remove('collapsed');
         el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
     });
-};
+}
 
-u.slideIn = function (el, duration = 200) {
-    /* Hides/collapses an element by sliding it into itself. */
+/**
+ * Hides/contracts an element by sliding it into itself
+ * @method slideIn
+ * @param { HTMLElement } el - The HTML string
+ * @param { Number } duration - The duration amount in milliseconds
+ */
+export function slideIn (el, duration = 200) {
     return new Promise((resolve, reject) => {
         if (!el) {
             const err = 'An element needs to be passed in to slideIn';
@@ -382,7 +386,7 @@ u.slideIn = function (el, duration = 200) {
         }
         el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
     });
-};
+}
 
 function afterAnimationEnds (el, callback) {
     el.classList.remove('visible');
@@ -514,6 +518,6 @@ u.xForm2TemplateResult = function (field, stanza, options) {
     }
 };
 
-Object.assign(u, { getOOBURLMarkup, ancestor });
+Object.assign(u, { getOOBURLMarkup, ancestor, slideIn, slideOut });
 
 export default u;