Make roster contacts toggleable

This commit is contained in:
JC Brand 2022-10-02 14:30:16 +02:00
parent 34a4a70ae2
commit 1a8ae3dcbe
6 changed files with 220 additions and 171 deletions

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);
.conversejs {
/* Custom addition for CSP */
#online-count {
display: none;
}
.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;
}
}
.open-contacts-toggle {
white-space: nowrap;
}
}
.roster-filter-form {
#converse-roster {
text-align: left;
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);
}
.state-type {
font-size: calc(var(--font-size) - 2px);
width: 100%;
}
}
.roster-contacts {
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);
/* Custom addition for CSP */
#online-count {
display: none;
}
.search-xmpp {
ul {
li.chat-info {
padding-left: 10px;
}
}
}
.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);
}
.state-type {
font-size: calc(var(--font-size) - 2px);
width: 100%;
}
}
.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;
}
}
}
}
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;
}
.roster-group-contacts {
.list-item {
&:hover {
.list-item-action {
opacity: 1;
}
}
}
}
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;
.group-toggle {
font-family: var(--heading-font);
display: block;
width: 100%;
margin: 0.75em 0 0.25em 0;
}
&:hover {
.list-item-action {
opacity: 1;
}
}
}
.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;
}
}
.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;
.group-toggle, .group-toggle .fa {
color: var(--chat-head-color-dark) !important;
&:hover {
color: var(--chat-head-color-darker) !important;
}
}
.open-chat {
margin: 0;
padding: 0;
&.unread-msgs {
font-weight: bold;
color: var(--unread-msgs-color);
.contact-name {
width: 70%;
.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;
}
}
.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;
.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%;
}
}
}
}

View File

@ -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);

View File

@ -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;