xmpp.chapril.org-conversejs/src/plugins/rosterview/rosterview.js

239 lines
8.3 KiB
JavaScript
Raw Normal View History

import RosterGroupView from './groupview.js';
import log from "@converse/headless/log";
import tpl_roster from "./templates/roster.html";
import { Model } from '@converse/skeletor/src/model.js';
import { OrderedListView } from "@converse/skeletor/src/overview";
import { __ } from 'i18n';
import { _converse, api, converse } from "@converse/headless/core";
import { debounce, has } from "lodash-es";
const u = converse.env.utils;
/**
* @class
* @namespace _converse.RosterView
* @memberOf _converse
*/
const RosterView = OrderedListView.extend({
tagName: 'div',
id: 'converse-roster',
className: 'controlbox-section',
ItemView: RosterGroupView,
listItems: 'model',
listSelector: '.roster-contacts',
sortEvent: null, // Groups are immutable, so they don't get re-sorted
subviewIndex: 'name',
sortImmediatelyOnAdd: true,
events: {
'click a.controlbox-heading__btn.add-contact': 'showAddContactModal',
'click a.controlbox-heading__btn.sync-contacts': 'syncContacts'
},
initialize () {
OrderedListView.prototype.initialize.apply(this, arguments);
this.listenTo(_converse.roster, "add", this.onContactAdded);
this.listenTo(_converse.roster, 'change:groups', this.onContactAdded);
this.listenTo(_converse.roster, 'change', this.onContactChange);
this.listenTo(_converse.roster, "destroy", this.update);
this.listenTo(_converse.roster, "remove", this.update);
_converse.presences.on('change:show', () => {
this.update();
this.updateFilter();
});
this.listenTo(this.model, "reset", this.reset);
// This event gets triggered once *all* contacts (i.e. not
// just this group's) have been fetched from browser
// storage or the XMPP server and once they've been
// assigned to their various groups.
api.listen.on('rosterGroupsFetched', this.sortAndPositionAllItems.bind(this));
api.listen.on('rosterContactsFetched', () => {
_converse.roster.each(contact => this.addRosterContact(contact, {'silent': true}));
this.update();
this.updateFilter();
this.trigger('rosterContactsFetchedAndProcessed');
});
this.createRosterFilter();
},
render () {
this.el.innerHTML = tpl_roster({
'allow_contact_requests': _converse.allow_contact_requests,
'heading_contacts': __('Contacts'),
'title_add_contact': __('Add a contact'),
'title_sync_contacts': __('Re-sync your contacts')
});
const form = this.el.querySelector('.roster-filter-form');
this.el.replaceChild(this.filter_view.render().el, form);
this.roster_el = this.el.querySelector('.roster-contacts');
return this;
},
showAddContactModal (ev) {
api.modal.show(_converse.AddContactModal, {'model': new Model()}, ev);
},
createRosterFilter () {
// Create a model on which we can store filter properties
const model = new _converse.RosterFilter();
model.id = `_converse.rosterfilter-${_converse.bare_jid}`;
model.browserStorage = _converse.createStore(model.id);
this.filter_view = new _converse.RosterFilterView({model});
this.listenTo(this.filter_view.model, 'change', this.updateFilter);
this.filter_view.model.fetch();
},
/**
* Called whenever the filter settings have been changed or
* when contacts have been added, removed or changed.
*
* Debounced for 100ms so that it doesn't get called for every
* contact fetched from browser storage.
*/
updateFilter: debounce(function () {
const type = this.filter_view.model.get('filter_type');
if (type === 'state') {
this.filter(this.filter_view.model.get('chat_state'), type);
} else {
this.filter(this.filter_view.model.get('filter_text'), type);
}
}, 100),
update () {
if (!u.isVisible(this.roster_el)) {
u.showElement(this.roster_el);
}
this.filter_view.render();
return this;
},
filter (query, type) {
const views = Object.values(this.getAll());
// First ensure the filter is restored to its original state
views.forEach(v => (v.model.contacts.length > 0) && v.show().filter(''));
// Now we can filter
query = query.toLowerCase();
if (type === 'groups') {
views.forEach(view => {
if (!view.model.get('name').toLowerCase().includes(query)) {
u.slideIn(view.el);
} else if (view.model.contacts.length > 0) {
u.slideOut(view.el);
}
});
} else {
views.forEach(v => v.filter(query, type));
}
},
async syncContacts (ev) {
ev.preventDefault();
u.addClass('fa-spin', ev.target);
_converse.roster.data.save('version', null);
await _converse.roster.fetchFromServer();
api.user.presence.send();
u.removeClass('fa-spin', ev.target);
},
reset () {
this.removeAll();
this.render().update();
return this;
},
onContactAdded (contact) {
this.addRosterContact(contact)
this.update();
this.updateFilter();
},
onContactChange (contact) {
this.update();
if (has(contact.changed, 'subscription')) {
if (contact.changed.subscription === 'from') {
this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS);
} else if (['both', 'to'].includes(contact.get('subscription'))) {
this.addExistingContact(contact);
}
}
if (has(contact.changed, 'num_unread') && contact.get('num_unread')) {
this.addContactToGroup(contact, _converse.HEADER_UNREAD);
}
if (has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') {
this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS);
}
if (has(contact.changed, 'subscription') && contact.changed.requesting === 'true') {
this.addContactToGroup(contact, _converse.HEADER_REQUESTING_CONTACTS);
}
this.updateFilter();
},
/**
* Returns the group as specified by name.
* Creates the group if it doesn't exist.
* @method _converse.RosterView#getGroup
* @private
* @param {string} name
*/
getGroup (name) {
const view = this.get(name);
if (view) {
return view.model;
}
return this.model.create({name});
},
addContactToGroup (contact, name, options) {
this.getGroup(name).contacts.add(contact, options);
this.sortAndPositionAllItems();
},
addExistingContact (contact, options) {
let groups;
if (api.settings.get('roster_groups')) {
groups = contact.get('groups');
groups = (groups.length === 0) ? [_converse.HEADER_UNGROUPED] : groups;
} else {
groups = [_converse.HEADER_CURRENT_CONTACTS];
}
if (contact.get('num_unread')) {
groups.push(_converse.HEADER_UNREAD);
}
groups.forEach(g => this.addContactToGroup(contact, g, options));
},
isSelf (jid) {
return u.isSameBareJID(jid, _converse.connection.jid);
},
addRosterContact (contact, options) {
const jid = contact.get('jid');
if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to' || this.isSelf(jid)) {
this.addExistingContact(contact, options);
} else {
if (!_converse.allow_contact_requests) {
log.debug(
`Not adding requesting or pending contact ${jid} `+
`because allow_contact_requests is false`
);
return;
}
if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) {
this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS, options);
} else if (contact.get('requesting') === true) {
this.addContactToGroup(contact, _converse.HEADER_REQUESTING_CONTACTS, options);
}
}
return this;
}
});
export default RosterView;