diff --git a/clientapp/helpers/xmppEventHandlers.js b/clientapp/helpers/xmppEventHandlers.js index 7610de2..18cc54e 100644 --- a/clientapp/helpers/xmppEventHandlers.js +++ b/clientapp/helpers/xmppEventHandlers.js @@ -316,6 +316,22 @@ module.exports = function (client, app) { original.correct(msg); }); + client.on('receipt', function (msg) { + msg = msg.toJSON(); + console.log(msg); + + var contact = me.getContact(msg.from, msg.to); + console.log(contact); + if (!contact) return; + + var original = Message.idLookup(msg.to[msg.type === 'groupchat' ? 'full' : 'bare'], msg.receipt); + console.log(original); + + if (!original) return; + + original.receiptReceived = true; + }); + client.on('carbon:received', function (carbon) { if (!me.isMe(carbon.from)) return; diff --git a/clientapp/models/contact.js b/clientapp/models/contact.js index 5d8d5e2..705bb5b 100644 --- a/clientapp/models/contact.js +++ b/clientapp/models/contact.js @@ -165,6 +165,22 @@ module.exports = HumanModel.define({ return 'gone'; } }, + supportsReceipts: { + deps: ['lockedResource', '_forceUpdate'], + fn: function () { + if (!this.lockedResource) return false; + var res = this.resources.get(this.lockedResource); + return res.supportsReceipts; + } + }, + supportsChatStates: { + deps: ['lockedResource', '_forceUpdate'], + fn: function () { + if (!this.lockedResource) return false; + var res = this.resources.get(this.lockedResource); + return res.supportsChatStates; + } + }, hasUnread: { deps: ['unreadCount'], fn: function () { diff --git a/clientapp/models/message.js b/clientapp/models/message.js index 3001aaf..e3982e0 100644 --- a/clientapp/models/message.js +++ b/clientapp/models/message.js @@ -22,6 +22,7 @@ var Message = module.exports = HumanModel.define({ body: ['string', true, ''], type: ['string', true, 'normal'], acked: ['bool', true, false], + receipt: ['bool', true, false], archivedId: ['string', true, ''], oobURIs: ['array', false, []] }, @@ -141,6 +142,7 @@ var Message = module.exports = HumanModel.define({ if (this.pending) res.push('pending'); if (this.delayed) res.push('delayed'); if (this.edited) res.push('edited'); + if (this.receiptReceived) res.push('delivered'); if (this.meAction) res.push('meAction'); return res.join(' '); diff --git a/clientapp/models/resource.js b/clientapp/models/resource.js index 64aa748..1a3bed9 100644 --- a/clientapp/models/resource.js +++ b/clientapp/models/resource.js @@ -30,6 +30,14 @@ module.exports = HumanModel.define({ return !!this.idleSince; } }, + supportsReceipts: { + deps: ['discoInfo'], + fn: function () { + if (!this.discoInfo) return false; + var features = this.discoInfo.features || []; + return features.indexOf('urn:xmpp:receipts') >= 0; + } + }, supportsChatStates: { deps: ['discoInfo'], fn: function () { diff --git a/clientapp/pages/chat.js b/clientapp/pages/chat.js index c2141fc..7cd075e 100644 --- a/clientapp/pages/chat.js +++ b/clientapp/pages/chat.js @@ -49,17 +49,11 @@ module.exports = BasePage.extend({ }, show: function (animation) { BasePage.prototype.show.apply(this, [animation]); - client.sendMessage({ - to: this.model.lockedResource || this.model.jid, - chatState: 'active' - }); + this.sendChatState('active'); }, hide: function () { BasePage.prototype.hide.apply(this); - client.sendMessage({ - to: this.model.lockedResource || this.model.jid, - chatState: 'inactive' - }); + this.sendChatState('inactive'); }, render: function () { if (this.rendered) return this; @@ -126,10 +120,7 @@ module.exports = BasePage.extend({ this.typing = true; this.paused = false; this.$chatInput.addClass('typing'); - client.sendMessage({ - to: this.model.lockedResource || this.model.jid, - chatState: 'composing' - }); + this.sendChatState('composing'); } } }, @@ -138,10 +129,7 @@ module.exports = BasePage.extend({ if (this.typing && this.$chatInput.val().length === 0) { this.typing = false; this.$chatInput.removeClass('typing'); - client.sendMessage({ - to: this.model.lockedResource || this.model.jid, - chatState: 'active' - }); + this.sendChatState('active'); } else if (this.typing) { this.pausedTyping(); } @@ -149,12 +137,16 @@ module.exports = BasePage.extend({ pausedTyping: _.debounce(function () { if (this.typing && !this.paused) { this.paused = true; - client.sendMessage({ - to: this.model.lockedResource || this.model.jid, - chatState: 'paused' - }); + this.sendChatState('paused'); } }, 3000), + sendChatState: function (state) { + if (!this.model.supportsChatStates) return; + client.sendMessage({ + to: this.model.lockedResource || this.model.jid, + chatState: state + }); + }, sendChat: function () { var message; var val = this.$chatInput.val(); @@ -170,9 +162,12 @@ module.exports = BasePage.extend({ to: this.model.lockedResource || this.model.jid, type: 'chat', body: val, - chatState: 'active', + requestReceipt: true, oobURIs: links }; + if (this.model.supportsChatStates) { + message.chatState = 'active'; + } if (this.editMode) { message.replace = this.model.lastSentMessage.id; } diff --git a/package.json b/package.json index d0ea577..5c7b427 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "oembed": "0.1.0", "semi-static": "0.0.4", "sound-effect-manager": "0.0.5", - "stanza.io": "2.12.1", + "stanza.io": "2.13.0", "staydown": "1.0.3", "templatizer": "0.1.2", "underscore": "1.5.1", diff --git a/public/css/otalk.css b/public/css/otalk.css index 17e3acf..85ab37a 100644 --- a/public/css/otalk.css +++ b/public/css/otalk.css @@ -1148,7 +1148,7 @@ button.secondary:hover:not(:disabled) { font-size: 12px; margin: 2px; display: inline-block; - padding-right: 11px; + padding-right: 3px; min-width: 20px; width: 100%; -moz-box-sizing: border-box; @@ -1158,18 +1158,23 @@ button.secondary:hover:not(:disabled) { .messages .message:not(.mine) { color: #2d2d2d; } -.messages .message.mine { - background: #fff; -} .messages .message.mine .timestamp { color: #bebebe; } +.messages .message.mine:not(.delayed):not(.delivered) .timestamp:after { + content: '\26A0'; + color: #f18902; +} .messages .message.delayed .timestamp:before { content: '@ '; } .messages .message.edited .timestamp:before { content: 'edited '; } +.messages .message.delivered .timestamp:after { + content: '\2713'; + color: #43bb6e; +} .messages .message.pending { color: #ababab; } @@ -1179,6 +1184,7 @@ button.secondary:hover:not(:disabled) { background-color: #e7f7fd; padding: 2px; padding-left: 8px; + padding-right: 3px; border-radius: 2px; } .messages .message.meAction:before { @@ -1186,8 +1192,6 @@ button.secondary:hover:not(:disabled) { font-style: normal; } .messages .message.meAction .timestamp { - position: relative; - left: -10px; font-style: normal; color: #88d5f7; } @@ -1209,6 +1213,12 @@ button.secondary:hover:not(:disabled) { float: right; display: block; } +.messages .message .timestamp:after { + width: 15px; + content: ''; + display: inline-block; + text-align: right; +} .messages .message .sender { display: block; } diff --git a/public/css/pages/chat.styl b/public/css/pages/chat.styl index c5db809..68c7d1b 100644 --- a/public/css/pages/chat.styl +++ b/public/css/pages/chat.styl @@ -255,7 +255,7 @@ font-size: $font-size-small margin: 2px display: inline-block - padding-right: 11px + padding-right: 3px min-width: 20px width: 100% borderbox() @@ -264,11 +264,14 @@ color: $gray-dark &.mine - background: white - .timestamp color: darken($gray-lighter, 20%) + &.mine:not(.delayed):not(.delivered) + .timestamp:after + content: '\26A0' + color: $orange + &.delayed .timestamp:before content: '@ ' @@ -277,6 +280,11 @@ .timestamp:before content: 'edited ' + &.delivered + .timestamp:after + content: '\2713' + color: $green + &.pending color: lighten($gray, 50%) @@ -286,6 +294,7 @@ background-color: $blue-lighter padding: 2px padding-left: 8px + padding-right: 3px border-radius: 2px &:before @@ -293,8 +302,6 @@ font-style: normal .timestamp - position: relative - left: -10px font-style: normal color: $blue-light @@ -316,6 +323,12 @@ float: right display: block + &:after + width: 15px + content: '' + display: inline-block + text-align: right + .sender display: block diff --git a/public/x-manifest.cache b/public/x-manifest.cache index 6a7f1ea..30c9173 100644 --- a/public/x-manifest.cache +++ b/public/x-manifest.cache @@ -1,5 +1,5 @@ CACHE MANIFEST -# 0.0.1 1388898132000 +# 0.0.1 1388911775258 CACHE: /app.js