Make roster contacts toggleable
This commit is contained in:
parent
34a4a70ae2
commit
1a8ae3dcbe
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user