Commit b8679063 authored by JC Brand's avatar JC Brand

Use the VCards collection for roster contacts

Instead of saving the vcard data on the contact model itself
parent e5cfdca3
...@@ -10,6 +10,12 @@ ...@@ -10,6 +10,12 @@
- Support for rendering URLs sent according to XEP-0066 Out of Band Data. - Support for rendering URLs sent according to XEP-0066 Out of Band Data.
- Geo-URIs (e.g. from Conversations) are now replaced by links to openstreetmap (works in reverse also) - Geo-URIs (e.g. from Conversations) are now replaced by links to openstreetmap (works in reverse also)
### Bugfixes
- Spoiler messages didn't include the message author's name.
- Documentation includes utf-8 charset to make minfied versions compatible across platforms. #1017
- #1026 Typing in MUC shows "Typing from another device"
### API changes ### API changes
- `_converse.api.vcard.get` now also accepts a `Backbone.Model` instance and - `_converse.api.vcard.get` now also accepts a `Backbone.Model` instance and
has an additional `force` parameter to force fetching the vcard even if it has an additional `force` parameter to force fetching the vcard even if it
...@@ -40,12 +46,10 @@ ...@@ -40,12 +46,10 @@
- Extracted the views from `converse-muc.js` into `converse-muc-views.js` and - Extracted the views from `converse-muc.js` into `converse-muc-views.js` and
where appropriate moved methods from the views into the models/collections. where appropriate moved methods from the views into the models/collections.
This makes MUC possible in headless mode. This makes MUC possible in headless mode.
- Created a new core plugin `converse-roster.js` which contains the models for
roster-related data. Previously this code was in `converse-core.js`.
- VCards are now stored separately from chats and roster contacts.
### Bugfixes
- Spoiler messages didn't include the message author's name.
- Documentation includes utf-8 charset to make minfied versions compatible across platforms. #1017
- #1026 Typing in MUC shows "Typing from another device"
## 3.3.4 (2018-03-05) ## 3.3.4 (2018-03-05)
......
...@@ -245,7 +245,7 @@ ...@@ -245,7 +245,7 @@
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
chatbox = test_utils.openChatBoxFor(_converse, contact_jid); chatbox = test_utils.openChatBoxFor(_converse, contact_jid);
$el = $(_converse.rosterview.el).find('a.open-chat:contains("'+chatbox.get('fullname')+'")'); $el = $(_converse.rosterview.el).find('a.open-chat:contains("'+chatbox.getDisplayName()+'")');
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost'; jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(_converse, 'emit'); spyOn(_converse, 'emit');
......
...@@ -651,6 +651,8 @@ ...@@ -651,6 +651,8 @@
function (done, _converse) { function (done, _converse) {
_addContacts(_converse); _addContacts(_converse);
return test_utils.waitUntil(() => _converse.roster.at(0).vcard.get('fullname'))
.then(function () {
var name; var name;
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
for (var i=0; i<mock.pend_names.length; i++) { for (var i=0; i<mock.pend_names.length; i++) {
...@@ -660,6 +662,7 @@ ...@@ -660,6 +662,7 @@
} }
expect($(_converse.rosterview.el).find('#pending-xmpp-contacts').is(':visible')).toBeFalsy(); expect($(_converse.rosterview.el).find('#pending-xmpp-contacts').is(':visible')).toBeFalsy();
done(); done();
});
})); }));
it("can be added to the roster and they will be sorted alphabetically", it("can be added to the roster and they will be sorted alphabetically",
......
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
} }
roster_item = _converse.roster.get(from_jid); roster_item = _converse.roster.get(from_jid);
if (!_.isUndefined(roster_item)) { if (!_.isUndefined(roster_item)) {
title = __("%1$s says", roster_item.get('fullname')); title = __("%1$s says", roster_item.getDisplayName());
} else { } else {
if (_converse.allow_non_roster_messaging) { if (_converse.allow_non_roster_messaging) {
title = __("%1$s says", from_jid); title = __("%1$s says", from_jid);
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
if (message === null) { if (message === null) {
return; return;
} }
const n = new Notification(contact.fullname, { const n = new Notification(contact.getDisplayName(), {
body: message, body: message,
lang: _converse.locale, lang: _converse.locale,
icon: _converse.notification_icon icon: _converse.notification_icon
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
}; };
_converse.showContactRequestNotification = function (contact) { _converse.showContactRequestNotification = function (contact) {
const n = new Notification(contact.fullname, { const n = new Notification(contact.getDisplayName(), {
body: __('wants to be your contact'), body: __('wants to be your contact'),
lang: _converse.locale, lang: _converse.locale,
icon: _converse.notification_icon icon: _converse.notification_icon
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
converse.plugins.add('converse-roster', { converse.plugins.add('converse-roster', {
dependencies: ["converse-vcard"],
overrides: { overrides: {
clearSession () { clearSession () {
this.__super__.clearSession.apply(this, arguments); this.__super__.clearSession.apply(this, arguments);
...@@ -121,7 +123,6 @@ ...@@ -121,7 +123,6 @@
attributes.jid = bare_jid; attributes.jid = bare_jid;
this.set(_.assignIn({ this.set(_.assignIn({
'fullname': bare_jid,
'groups': [], 'groups': [],
'id': bare_jid, 'id': bare_jid,
'jid': bare_jid, 'jid': bare_jid,
...@@ -129,11 +130,24 @@ ...@@ -129,11 +130,24 @@
'user_id': Strophe.getNodeFromJid(jid) 'user_id': Strophe.getNodeFromJid(jid)
}, attributes)); }, attributes));
this.vcard = _converse.vcards.findWhere({'jid': bare_jid});
if (_.isNil(this.vcard)) {
this.vcard = _converse.vcards.create({'jid': bare_jid});
}
this.on('change:chat_status', function (item) { this.on('change:chat_status', function (item) {
_converse.emit('contactStatusChanged', item.attributes); _converse.emit('contactStatusChanged', item.attributes);
}); });
}, },
getDisplayName () {
return this.vcard.get('fullname') || this.get('jid');
},
getFullname () {
return this.vcard.get('fullname');
},
subscribe (message) { subscribe (message) {
/* Send a presence subscription request to this roster contact /* Send a presence subscription request to this roster contact
* *
...@@ -297,8 +311,8 @@ ...@@ -297,8 +311,8 @@
const status1 = contact1.get('chat_status') || 'offline'; const status1 = contact1.get('chat_status') || 'offline';
const status2 = contact2.get('chat_status') || 'offline'; const status2 = contact2.get('chat_status') || 'offline';
if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) { if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) {
const name1 = (contact1.get('fullname') || contact1.get('jid')).toLowerCase(); const name1 = (contact1.getDisplayName()).toLowerCase();
const name2 = (contact2.get('fullname') || contact2.get('jid')).toLowerCase(); const name2 = (contact2.getDisplayName()).toLowerCase();
return name1 < name2 ? -1 : (name1 > name2? 1 : 0); return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else { } else {
return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1; return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1;
...@@ -436,12 +450,11 @@ ...@@ -436,12 +450,11 @@
*/ */
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
groups = groups || []; groups = groups || [];
name = _.isEmpty(name)? jid: name;
this.sendContactAddIQ(jid, name, groups, this.sendContactAddIQ(jid, name, groups,
() => { () => {
const contact = this.create(_.assignIn({ const contact = this.create(_.assignIn({
'ask': undefined, 'ask': undefined,
'fullname': name, 'nickname': name,
groups, groups,
jid, jid,
'requesting': false, 'requesting': false,
...@@ -555,7 +568,7 @@ ...@@ -555,7 +568,7 @@
} }
this.create({ this.create({
'ask': ask, 'ask': ask,
'fullname': item.getAttribute("name") || jid, 'nickname': item.getAttribute("name"),
'groups': groups, 'groups': groups,
'jid': jid, 'jid': jid,
'subscription': subscription 'subscription': subscription
...@@ -585,7 +598,7 @@ ...@@ -585,7 +598,7 @@
'subscription': 'none', 'subscription': 'none',
'ask': null, 'ask': null,
'requesting': true, 'requesting': true,
'fullname': nickname 'nickname': nickname
}; };
this.create(user_data); this.create(user_data);
_converse.emit('contactRequest', user_data); _converse.emit('contactRequest', user_data);
......
...@@ -368,9 +368,10 @@ ...@@ -368,9 +368,10 @@
initialize () { initialize () {
this.model.on("change", this.render, this); this.model.on("change", this.render, this);
this.model.on("remove", this.remove, this);
this.model.on("destroy", this.remove, this); this.model.on("destroy", this.remove, this);
this.model.on("open", this.openChat, this); this.model.on("open", this.openChat, this);
this.model.on("remove", this.remove, this);
this.model.vcard.on('change:fullname', this.render, this);
}, },
render () { render () {
...@@ -412,19 +413,23 @@ ...@@ -412,19 +413,23 @@
* *
* So in both cases the user is a "pending" contact. * So in both cases the user is a "pending" contact.
*/ */
const display_name = item.getDisplayName();
this.el.classList.add('pending-xmpp-contact'); this.el.classList.add('pending-xmpp-contact');
this.el.innerHTML = tpl_pending_contact( this.el.innerHTML = tpl_pending_contact(
_.extend(item.toJSON(), { _.extend(item.toJSON(), {
'desc_remove': __('Click to remove %1$s as a contact', item.get('fullname') || item.get('jid')), 'display_name': display_name,
'desc_remove': __('Click to remove %1$s as a contact', display_name),
'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
}) })
); );
} else if (requesting === true) { } else if (requesting === true) {
const display_name = item.getDisplayName();
this.el.classList.add('requesting-xmpp-contact'); this.el.classList.add('requesting-xmpp-contact');
this.el.innerHTML = tpl_requesting_contact( this.el.innerHTML = tpl_requesting_contact(
_.extend(item.toJSON(), { _.extend(item.toJSON(), {
'desc_accept': __("Click to accept the contact request from %1$s", item.get('fullname') || item.get('jid')), 'display_name': display_name,
'desc_decline': __("Click to decline the contact request from %1$s", item.get('fullname') || item.get('jid')), 'desc_accept': __("Click to accept the contact request from %1$s", display_name),
'desc_decline': __("Click to decline the contact request from %1$s", display_name),
'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
}) })
); );
...@@ -449,12 +454,14 @@ ...@@ -449,12 +454,14 @@
} else if (chat_status === 'dnd') { } else if (chat_status === 'dnd') {
status_icon = 'fa-minus-circle'; status_icon = 'fa-minus-circle';
} }
const display_name = item.getDisplayName();
this.el.innerHTML = tpl_roster_item( this.el.innerHTML = tpl_roster_item(
_.extend(item.toJSON(), { _.extend(item.toJSON(), {
'display_name': display_name,
'desc_status': STATUSES[chat_status], 'desc_status': STATUSES[chat_status],
'status_icon': status_icon, 'status_icon': status_icon,
'desc_chat': __('Click to chat with %1$s (JID: %2$s)', item.get('fullname') || item.get('jid'), item.get('jid')), 'desc_chat': __('Click to chat with %1$s (JID: %2$s)', display_name, item.get('jid')),
'desc_remove': __('Click to remove %1$s as a contact', item.get('fullname') || item.get('jid')), 'desc_remove': __('Click to remove %1$s as a contact', display_name),
'allow_contact_removal': _converse.allow_contact_removal, 'allow_contact_removal': _converse.allow_contact_removal,
'num_unread': item.get('num_unread') || 0 'num_unread': item.get('num_unread') || 0
}) })
...@@ -511,7 +518,7 @@ ...@@ -511,7 +518,7 @@
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
_converse.roster.sendContactAddIQ( _converse.roster.sendContactAddIQ(
this.model.get('jid'), this.model.get('jid'),
this.model.get('fullname'), this.model.getFullname(),
[], [],
() => { this.model.authorize().subscribe(); } () => { this.model.authorize().subscribe(); }
); );
...@@ -633,8 +640,7 @@ ...@@ -633,8 +640,7 @@
} }
} else { } else {
matches = this.model.contacts.filter((contact) => { matches = this.model.contacts.filter((contact) => {
const value = contact.get('fullname') || contact.get('jid'); return !_.includes(contact.getDisplayName().toLowerCase(), q.toLowerCase());
return !_.includes(value.toLowerCase(), q.toLowerCase());
}); });
} }
return matches; return matches;
......
// Converse.js (A browser based XMPP chat client) // Converse.js
// http://conversejs.org // http://conversejs.org
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) { (function (root, factory) {
define(["converse-core", "crypto", "strophe.vcard"], factory); define(["converse-core", "crypto", "strophe.vcard"], factory);
...@@ -36,11 +34,9 @@ ...@@ -36,11 +34,9 @@
} }
function onVCardError (_converse, jid, iq, errback) { function onVCardError (_converse, jid, iq, errback) {
const contact = _converse.roster.get(jid); if (errback) {
if (contact) { errback({'stanza': iq, 'jid': jid});
contact.save({'vcard_updated': moment().format() });
} }
if (errback) { errback({'stanza': iq, 'jid': jid}); }
} }
function getVCard (_converse, jid) { function getVCard (_converse, jid) {
...@@ -63,34 +59,6 @@ ...@@ -63,34 +59,6 @@
converse.plugins.add('converse-vcard', { converse.plugins.add('converse-vcard', {
// FIXME: After refactoring, the dependency switches, from
// converse-roster to converse-vcard
dependencies: ["converse-roster"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
RosterContacts: {
createRequestingContact (presence) {
const { _converse } = this.__super__;
const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
_converse.api.vcard.get(bare_jid)
.then(_.partial(_converse.createRequestingContactFromVCard, presence))
.catch((vcard) => {
_converse.log(
`Error while retrieving vcard for ${vcard.jid}`,
Strophe.LogLevel.WARN);
_converse.createRequestingContactFromVCard(presence, vcard.stanza, vcard.jid);
});
}
}
},
initialize () { initialize () {
/* The initialize function gets called as soon as the plugin is /* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery. * loaded by converse.js's plugin machinery.
...@@ -106,29 +74,6 @@ ...@@ -106,29 +74,6 @@
}); });
_converse.createRequestingContactFromVCard = function (presence, vcard) {
const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
let fullname = vcard.fullname;
if (!fullname) {
const nick_el = sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence);
fullname = nick_el.length ? nick_el[0].textContent : bare_jid;
}
const user_data = {
'jid': bare_jid,
'subscription': 'none',
'ask': null,
'requesting': true,
'fullname': fullname,
'image': vcard.image,
'image_type': vcard.image_type,
'image_hash': vcard.image_hash,
'url': vcard.url,
'vcard_updated': moment().format()
};
_converse.roster.create(user_data);
_converse.emit('contactRequest', user_data);
};
/* Event handlers */ /* Event handlers */
_converse.initVCardCollection = function () { _converse.initVCardCollection = function () {
_converse.vcards = new _converse.VCards(); _converse.vcards = new _converse.VCards();
...@@ -142,10 +87,6 @@ ...@@ -142,10 +87,6 @@
_converse.connection.disco.addFeature(Strophe.NS.VCARD); _converse.connection.disco.addFeature(Strophe.NS.VCARD);
}); });
_converse.on('initialized', () => {
_converse.roster.on("add", (contact) => _converse.api.vcard.update(contact));
});
_converse.on('statusInitialized', function fetchOwnVCard () { _converse.on('statusInitialized', function fetchOwnVCard () {
_converse.api.disco.supports(Strophe.NS.VCARD, _converse.domain) _converse.api.disco.supports(Strophe.NS.VCARD, _converse.domain)
.then((result) => { .then((result) => {
......
{[ if (o.allow_chat_pending_contacts) { ]} {[ if (o.allow_chat_pending_contacts) { ]}
<a class="open-chat w-100" href="#"> <a class="open-chat w-100" href="#">
{[ } ]} {[ } ]}
<span class="pending-contact-name w-100" title="Name: {{{o.fullname}}} JID: {{{o.jid}}}">{{{o.fullname}}}</span> <span class="pending-contact-name w-100" title="JID: {{{o.jid}}}">{{{o.display_name}}}</span>
{[ if (o.allow_chat_pending_contacts) { ]}</a> {[ if (o.allow_chat_pending_contacts) { ]}</a>
{[ } ]} {[ } ]}
<a class="remove-xmpp-contact fa fa-trash" title="{{{o.desc_remove}}}" href="#"></a> <a class="remove-xmpp-contact fa fa-trash" title="{{{o.desc_remove}}}" href="#"></a>
{[ if (o.allow_chat_pending_contacts) { ]} {[ if (o.allow_chat_pending_contacts) { ]}
<a class="open-chat w-100"href="#"> <a class="open-chat w-100"href="#">
{[ } ]} {[ } ]}
<span class="req-contact-name w-100" title="Name: {{{o.fullname}}} <span class="req-contact-name w-100" title="JID: {{{o.jid}}}">{{{o.display_name}}}</span>
JID: {{{o.jid}}}">{{{o.fullname}}}</span>
{[ if (o.allow_chat_pending_contacts) { ]} {[ if (o.allow_chat_pending_contacts) { ]}
</a> </a>
{[ } ]} {[ } ]}
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
{[ if (o.num_unread) { ]} {[ if (o.num_unread) { ]}
<span class="msgs-indicator">{{{ o.num_unread }}}</span> <span class="msgs-indicator">{{{ o.num_unread }}}</span>
{[ } ]} {[ } ]}
<span class="contact-name {[ if (o.num_unread) { ]} unread-msgs {[ } ]}">{{{o.fullname || o.jid}}}</span></a> <span class="contact-name {[ if (o.num_unread) { ]} unread-msgs {[ } ]}">{{{o.display_name}}}</span></a>
{[ if (o.allow_contact_removal) { ]} {[ if (o.allow_contact_removal) { ]}
<a class="remove-xmpp-contact fa fa-trash" title="{{{o.desc_remove}}}" href="#"></a> <a class="remove-xmpp-contact fa fa-trash" title="{{{o.desc_remove}}}" href="#"></a>
{[ } ]} {[ } ]}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment