Show unread messages counter next to roster contacts
This commit is contained in:
parent
ce16e1bcef
commit
f3d29e016e
@ -2227,7 +2227,7 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
opacity: 0; }
|
opacity: 0; }
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat.unread-msgs .avatar.avatar-online .pulse {
|
#conversejs #converse-roster .roster-contacts dd .open-chat.unread-msgs .avatar.avatar-online .pulse {
|
||||||
border: 0.7em solid #1A9707; }
|
border: 0.7em solid #2A9D8F; }
|
||||||
@-webkit-keyframes pulse {
|
@-webkit-keyframes pulse {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: scale(0);
|
-webkit-transform: scale(0);
|
||||||
@ -2304,7 +2304,7 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-left: -3em;
|
margin-left: -3em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding: 2px 4px;
|
padding: 0 4px;
|
||||||
text-shadow: none; }
|
text-shadow: none; }
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat .contact-name {
|
#conversejs #converse-roster .roster-contacts dd .open-chat .contact-name {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -2312,16 +2312,12 @@
|
|||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
float: none;
|
float: none;
|
||||||
height: 60px; }
|
height: 60px; }
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat .contact-name.unread-msgs {
|
|
||||||
max-width: 70%; }
|
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat .avatar {
|
#conversejs #converse-roster .roster-contacts dd .open-chat .avatar {
|
||||||
float: left;
|
float: left;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 60px; }
|
height: 60px; }
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat .avatar .status-icon {
|
#conversejs #converse-roster .roster-contacts dd .open-chat .avatar .status-icon {
|
||||||
color: #2A9D8F; }
|
color: #2A9D8F; }
|
||||||
#conversejs #converse-roster .roster-contacts dd .open-chat .avatar .status-icon.icon-online {
|
|
||||||
color: #1A9707; }
|
|
||||||
#conversejs #converse-roster .roster-contacts dd:hover {
|
#conversejs #converse-roster .roster-contacts dd:hover {
|
||||||
background-color: #DCF9F6; }
|
background-color: #DCF9F6; }
|
||||||
#conversejs #converse-roster .roster-contacts dd:hover .remove-xmpp-contact {
|
#conversejs #converse-roster .roster-contacts dd:hover .remove-xmpp-contact {
|
||||||
@ -2349,7 +2345,6 @@
|
|||||||
background-color: #DCEAC5;
|
background-color: #DCEAC5;
|
||||||
/* Make this difference */ }
|
/* Make this difference */ }
|
||||||
#conversejs #converse-roster .roster-contacts dd a, #conversejs #converse-roster .roster-contacts dd span {
|
#conversejs #converse-roster .roster-contacts dd a, #conversejs #converse-roster .roster-contacts dd span {
|
||||||
text-shadow: 0 1px 0 #FAFAFA;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 3.1.0 (Unreleased)
|
||||||
|
|
||||||
|
- Show unread messages next to roster contacts. [jcbrand]
|
||||||
|
- API change: the `message` event now returns a data object with `stanza` and
|
||||||
|
`chatbox` attributes, instead of just the stanza. [jcbrand]
|
||||||
|
|
||||||
## 3.0.2 (2017-04-23)
|
## 3.0.2 (2017-04-23)
|
||||||
|
|
||||||
*Dependency updates*:
|
*Dependency updates*:
|
||||||
|
@ -144,6 +144,19 @@ The user has logged out.
|
|||||||
|
|
||||||
``_converse.on('logout', function () { ... });``
|
``_converse.on('logout', function () { ... });``
|
||||||
|
|
||||||
|
messageAdded
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Once a message has been added to a chat box. The passed in data object contains
|
||||||
|
a `chatbox` attribute, referring to the chat box receiving the message, as well
|
||||||
|
as a `message` attribute which refers to the Message model.
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
_converse.on('messageAdded', function (data) {
|
||||||
|
// The message is at `data.message`
|
||||||
|
// The original chat box is at `data.chatbox`.
|
||||||
|
});
|
||||||
|
|
||||||
messageSend
|
messageSend
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
&.avatar-online {
|
&.avatar-online {
|
||||||
.pulse {
|
.pulse {
|
||||||
border: 0.7em solid $online-color;
|
border: 0.7em solid $link-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@include keyframes(pulse) {
|
@include keyframes(pulse) {
|
||||||
@ -176,7 +176,7 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-left: -3em;
|
margin-left: -3em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding: 2px 4px;
|
padding: 0 4px;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,9 +186,6 @@
|
|||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
float: none;
|
float: none;
|
||||||
height: $roster-item-height;
|
height: $roster-item-height;
|
||||||
&.unread-msgs {
|
|
||||||
max-width: 70%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
@ -198,9 +195,6 @@
|
|||||||
|
|
||||||
.status-icon {
|
.status-icon {
|
||||||
color: $link-color;
|
color: $link-color;
|
||||||
&.icon-online {
|
|
||||||
color: $online-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,7 +236,6 @@
|
|||||||
/* Make this difference */
|
/* Make this difference */
|
||||||
}
|
}
|
||||||
a, span {
|
a, span {
|
||||||
text-shadow: 0 1px 0 $link-shadow-color;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -471,6 +471,10 @@
|
|||||||
} else {
|
} else {
|
||||||
this.handleTextMessage(message);
|
this.handleTextMessage(message);
|
||||||
}
|
}
|
||||||
|
_converse.emit('messageAdded', {
|
||||||
|
'message': message,
|
||||||
|
'chatbox': this.model
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createMessageStanza: function (message) {
|
createMessageStanza: function (message) {
|
||||||
|
@ -183,9 +183,11 @@
|
|||||||
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
|
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
|
||||||
Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
|
Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
|
||||||
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
|
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
|
||||||
|
Strophe.addNamespace('MAM', 'urn:xmpp:mam:0');
|
||||||
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
|
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
|
||||||
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
|
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
|
||||||
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
|
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
|
||||||
|
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
|
||||||
Strophe.addNamespace('XFORM', 'jabber:x:data');
|
Strophe.addNamespace('XFORM', 'jabber:x:data');
|
||||||
|
|
||||||
// Instance level constants
|
// Instance level constants
|
||||||
@ -786,7 +788,8 @@
|
|||||||
'groups': [],
|
'groups': [],
|
||||||
'image_type': DEFAULT_IMAGE_TYPE,
|
'image_type': DEFAULT_IMAGE_TYPE,
|
||||||
'image': DEFAULT_IMAGE,
|
'image': DEFAULT_IMAGE,
|
||||||
'status': ''
|
'status': '',
|
||||||
|
'num_unread': 0
|
||||||
}, attributes));
|
}, attributes));
|
||||||
|
|
||||||
this.on('destroy', function () { this.removeFromRoster(); }.bind(this));
|
this.on('destroy', function () { this.removeFromRoster(); }.bind(this));
|
||||||
@ -1476,7 +1479,7 @@
|
|||||||
*/
|
*/
|
||||||
var original_stanza = message,
|
var original_stanza = message,
|
||||||
contact_jid, forwarded, delay, from_bare_jid,
|
contact_jid, forwarded, delay, from_bare_jid,
|
||||||
from_resource, is_me, msgid,
|
from_resource, is_me, msgid, messages,
|
||||||
chatbox, resource,
|
chatbox, resource,
|
||||||
from_jid = message.getAttribute('from'),
|
from_jid = message.getAttribute('from'),
|
||||||
to_jid = message.getAttribute('to'),
|
to_jid = message.getAttribute('to'),
|
||||||
@ -1517,7 +1520,6 @@
|
|||||||
from_bare_jid = Strophe.getBareJidFromJid(from_jid);
|
from_bare_jid = Strophe.getBareJidFromJid(from_jid);
|
||||||
from_resource = Strophe.getResourceFromJid(from_jid);
|
from_resource = Strophe.getResourceFromJid(from_jid);
|
||||||
is_me = from_bare_jid === _converse.bare_jid;
|
is_me = from_bare_jid === _converse.bare_jid;
|
||||||
msgid = message.getAttribute('id');
|
|
||||||
if (is_me) {
|
if (is_me) {
|
||||||
// I am the sender, so this must be a forwarded message...
|
// I am the sender, so this must be a forwarded message...
|
||||||
contact_jid = Strophe.getBareJidFromJid(to_jid);
|
contact_jid = Strophe.getBareJidFromJid(to_jid);
|
||||||
@ -1526,16 +1528,16 @@
|
|||||||
contact_jid = from_bare_jid;
|
contact_jid = from_bare_jid;
|
||||||
resource = from_resource;
|
resource = from_resource;
|
||||||
}
|
}
|
||||||
_converse.emit('message', original_stanza);
|
|
||||||
// Get chat box, but only create a new one when the message has a body.
|
// Get chat box, but only create a new one when the message has a body.
|
||||||
chatbox = this.getChatBox(contact_jid, !_.isNull(message.querySelector('body')));
|
chatbox = this.getChatBox(contact_jid, !_.isNull(message.querySelector('body')));
|
||||||
if (!chatbox) {
|
msgid = message.getAttribute('id');
|
||||||
return true;
|
messages = msgid && chatbox.messages.findWhere({msgid: msgid}) || [];
|
||||||
}
|
if (chatbox && _.isEmpty(messages)) {
|
||||||
if (msgid && chatbox.messages.findWhere({msgid: msgid})) {
|
// Only create the message when we're sure it's not a
|
||||||
return true; // We already have this message stored.
|
// duplicate
|
||||||
}
|
|
||||||
chatbox.createMessage(message, delay, original_stanza);
|
chatbox.createMessage(message, delay, original_stanza);
|
||||||
|
}
|
||||||
|
_converse.emit('message', {'stanza': original_stanza, 'chatbox': chatbox});
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -27,9 +27,6 @@
|
|||||||
// XEP-0313 Message Archive Management
|
// XEP-0313 Message Archive Management
|
||||||
var MAM_ATTRIBUTES = ['with', 'start', 'end'];
|
var MAM_ATTRIBUTES = ['with', 'start', 'end'];
|
||||||
|
|
||||||
Strophe.addNamespace('MAM', 'urn:xmpp:mam:0');
|
|
||||||
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
|
|
||||||
|
|
||||||
converse.plugins.add('converse-mam', {
|
converse.plugins.add('converse-mam', {
|
||||||
|
|
||||||
overrides: {
|
overrides: {
|
||||||
|
@ -1952,7 +1952,10 @@
|
|||||||
this.model.createMessage(message, delay, original_stanza);
|
this.model.createMessage(message, delay, original_stanza);
|
||||||
if (sender !== this.model.get('nick')) {
|
if (sender !== this.model.get('nick')) {
|
||||||
// We only emit an event if it's not our own message
|
// We only emit an event if it's not our own message
|
||||||
_converse.emit('message', original_stanza);
|
_converse.emit(
|
||||||
|
'message',
|
||||||
|
{'stanza': original_stanza, 'chatbox': this.model}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -231,10 +231,11 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_converse.handleMessageNotification = function (message) {
|
_converse.handleMessageNotification = function (data) {
|
||||||
/* Event handler for the on('message') event. Will call methods
|
/* Event handler for the on('message') event. Will call methods
|
||||||
* to play sounds and show HTML5 notifications.
|
* to play sounds and show HTML5 notifications.
|
||||||
*/
|
*/
|
||||||
|
var message = data.stanza;
|
||||||
if (!_converse.shouldNotifyOfMessage(message)) {
|
if (!_converse.shouldNotifyOfMessage(message)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
Strophe = converse.env.Strophe,
|
Strophe = converse.env.Strophe,
|
||||||
$iq = converse.env.$iq,
|
$iq = converse.env.$iq,
|
||||||
b64_sha1 = converse.env.b64_sha1,
|
b64_sha1 = converse.env.b64_sha1,
|
||||||
|
sizzle = converse.env.sizzle,
|
||||||
_ = converse.env._;
|
_ = converse.env._;
|
||||||
|
|
||||||
|
|
||||||
@ -156,6 +157,7 @@
|
|||||||
label_groups: LABEL_GROUPS,
|
label_groups: LABEL_GROUPS,
|
||||||
label_state: __('State'),
|
label_state: __('State'),
|
||||||
label_any: __('Any'),
|
label_any: __('Any'),
|
||||||
|
label_unread_messages: __('Unread'),
|
||||||
label_online: __('Online'),
|
label_online: __('Online'),
|
||||||
label_chatty: __('Chatty'),
|
label_chatty: __('Chatty'),
|
||||||
label_busy: __('Busy'),
|
label_busy: __('Busy'),
|
||||||
@ -279,6 +281,8 @@
|
|||||||
_converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
|
_converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
|
||||||
_converse.on('rosterContactsFetched', this.update, this);
|
_converse.on('rosterContactsFetched', this.update, this);
|
||||||
this.createRosterFilter();
|
this.createRosterFilter();
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
@ -622,17 +626,25 @@
|
|||||||
));
|
));
|
||||||
} else if (subscription === 'both' || subscription === 'to') {
|
} else if (subscription === 'both' || subscription === 'to') {
|
||||||
this.el.classList.add('current-xmpp-contact');
|
this.el.classList.add('current-xmpp-contact');
|
||||||
this.$el.removeClass(_.without(['both', 'to'], subscription)[0]).addClass(subscription);
|
this.el.classList.remove(_.without(['both', 'to'], subscription)[0])
|
||||||
|
this.el.classList.add(subscription);
|
||||||
|
this.renderRosterItem(item);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderRosterItem: function (item) {
|
||||||
|
var chat_status = item.get('chat_status');
|
||||||
this.$el.html(tpl_roster_item(
|
this.$el.html(tpl_roster_item(
|
||||||
_.extend(item.toJSON(), {
|
_.extend(item.toJSON(), {
|
||||||
'desc_status': STATUSES[chat_status||'offline'],
|
'desc_status': STATUSES[chat_status||'offline'],
|
||||||
'desc_chat': __('Click to chat with this contact'),
|
'desc_chat': __('Click to chat with this contact'),
|
||||||
'desc_remove': __('Click to remove this contact'),
|
'desc_remove': __('Click to remove this contact'),
|
||||||
'title_fullname': __('Name'),
|
'title_fullname': __('Name'),
|
||||||
'allow_contact_removal': _converse.allow_contact_removal
|
'allow_contact_removal': _converse.allow_contact_removal,
|
||||||
|
'num_unread': item.get('num_unread') || 0
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -677,6 +689,7 @@
|
|||||||
|
|
||||||
openChat: function (ev) {
|
openChat: function (ev) {
|
||||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||||
|
this.model.save({'num_unread': 0});
|
||||||
return _converse.chatboxviews.showChat(this.model.attributes);
|
return _converse.chatboxviews.showChat(this.model.attributes);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -829,6 +842,8 @@
|
|||||||
return utils.contains.not('chat_status', q)(contact) && !contact.get('requesting');
|
return utils.contains.not('chat_status', q)(contact) && !contact.get('requesting');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else if (q === 'unread_messages') {
|
||||||
|
matches = this.model.contacts.filter({'num_unread': 0});
|
||||||
} else {
|
} else {
|
||||||
matches = this.model.contacts.filter(
|
matches = this.model.contacts.filter(
|
||||||
utils.contains.not('chat_status', q)
|
utils.contains.not('chat_status', q)
|
||||||
@ -918,6 +933,32 @@
|
|||||||
|
|
||||||
/* -------- Event Handlers ----------- */
|
/* -------- Event Handlers ----------- */
|
||||||
|
|
||||||
|
var onMessageReceived = function (data) {
|
||||||
|
/* Given a newly received message, update the unread counter on
|
||||||
|
* the relevant roster contact (TODO: or chat room).
|
||||||
|
*/
|
||||||
|
var chatbox = data.chatbox;
|
||||||
|
if (_.isUndefined(chatbox)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_.isNull(data.stanza.querySelector('body'))) {
|
||||||
|
return; // The message has no text
|
||||||
|
}
|
||||||
|
var new_message = !(sizzle('result[xmlns="'+Strophe.NS.MAM+'"]', data.stanza).length);
|
||||||
|
var hidden_or_minimized_chatbox = chatbox.get('hidden') || chatbox.get('minimized');
|
||||||
|
|
||||||
|
if (hidden_or_minimized_chatbox && new_message) {
|
||||||
|
if (chatbox.get('type') === 'chatroom') {
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
var contact = _.head(_converse.roster.where({'jid': chatbox.get('jid')}));
|
||||||
|
if (!_.isUndefined(contact)) {
|
||||||
|
contact.save({'num_unread': contact.get('num_unread') + 1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var initRoster = function () {
|
var initRoster = function () {
|
||||||
/* Create an instance of RosterView once the RosterGroups
|
/* Create an instance of RosterView once the RosterGroups
|
||||||
* collection has been created (in converse-core.js)
|
* collection has been created (in converse-core.js)
|
||||||
@ -927,8 +968,9 @@
|
|||||||
});
|
});
|
||||||
_converse.rosterview.render();
|
_converse.rosterview.render();
|
||||||
};
|
};
|
||||||
_converse.on('rosterInitialized', initRoster);
|
_converse.api.listen.on('rosterInitialized', initRoster);
|
||||||
_converse.on('rosterReadyAfterReconnection', initRoster);
|
_converse.api.listen.on('rosterReadyAfterReconnection', initRoster);
|
||||||
|
_converse.api.listen.on('message', onMessageReceived);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
{[ if (filter_type === 'state') { ]} style="display: none" {[ } ]} >
|
{[ if (filter_type === 'state') { ]} style="display: none" {[ } ]} >
|
||||||
<select class="state-type" {[ if (filter_type !== 'state') { ]} style="display: none" {[ } ]} >
|
<select class="state-type" {[ if (filter_type !== 'state') { ]} style="display: none" {[ } ]} >
|
||||||
<option value="">{{label_any}}</option>
|
<option value="">{{label_any}}</option>
|
||||||
|
<option {[ if (chat_state === 'unread_messages') { ]} selected="selected" {[ } ]}
|
||||||
|
value="unread_messages">{{label_unread_messages}}</option>
|
||||||
<option {[ if (chat_state === 'online') { ]} selected="selected" {[ } ]}
|
<option {[ if (chat_state === 'online') { ]} selected="selected" {[ } ]}
|
||||||
value="online">{{label_online}}</option>
|
value="online">{{label_online}}</option>
|
||||||
<option {[ if (chat_state === 'chat') { ]} selected="selected" {[ } ]}
|
<option {[ if (chat_state === 'chat') { ]} selected="selected" {[ } ]}
|
||||||
|
Loading…
Reference in New Issue
Block a user