Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
da5ca0b5
Commit
da5ca0b5
authored
Nov 03, 2018
by
Christoph Scholz
Committed by
JC Brand
Nov 13, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement XEP-0184: Message Delivery Receipts
parent
e3a5bf7e
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
226 additions
and
13 deletions
+226
-13
CHANGES.md
CHANGES.md
+1
-0
css/converse.css
css/converse.css
+3
-0
dist/converse.js
dist/converse.js
+68
-7
sass/_messages.scss
sass/_messages.scss
+4
-0
sass/_variables.scss
sass/_variables.scss
+1
-0
spec/http-file-upload.js
spec/http-file-upload.js
+2
-0
spec/messages.js
spec/messages.js
+96
-0
spec/omemo.js
spec/omemo.js
+1
-0
src/converse-message-view.js
src/converse-message-view.js
+1
-1
src/converse-omemo.js
src/converse-omemo.js
+1
-0
src/headless/converse-chatboxes.js
src/headless/converse-chatboxes.js
+36
-2
src/headless/converse-muc.js
src/headless/converse-muc.js
+11
-3
src/templates/message.html
src/templates/message.html
+1
-0
No files found.
CHANGES.md
View file @
da5ca0b5
...
...
@@ -5,6 +5,7 @@
-
Error
`FATAL: TypeError: Cannot read property 'extend' of undefined`
when using
`embedded`
view mode.
-
Default paths in converse-notifications.js are now relative
-
Add a button to regenerate OMEMO keys
-
#141 XEP-0184: Message Delivery Receipts
-
#1188 Feature request: drag and drop file to HTTP Upload
-
#1268 Switch from SASS variables to CSS custom properties
-
#1278 Replace the default avatar with a SVG version
...
...
css/converse.css
View file @
da5ca0b5
...
...
@@ -9303,6 +9303,7 @@ readers do not read off random characters that represent icons */
--text-color: #666;
--text-color-lighten-15-percent: #8c8c8c;
--message-text-color: #555;
--message-receipt-color: #3AA569;
--save-button-color: #3AA569;
--chat-textarea-color: #666;
--chat-textarea-height: 60px;
...
...
@@ -11796,6 +11797,8 @@ body.reset {
display
:
none
;
}
#conversejs
.message.chat-msg.chat-msg--followup
.chat-msg__content
{
margin-left
:
2.75rem
;
}
#conversejs
.message.chat-msg
.chat-msg__receipt
{
color
:
var
(
--message-receipt-color
);
}
#conversejs
.chatroom-body
.message.onload
{
animation
:
colorchange-chatmessage-muc
1s
;
...
...
dist/converse.js
View file @
da5ca0b5
...
...
@@ -61682,7 +61682,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
return this.renderFileUploadProgresBar();
}
if (_.filter(['correcting', 'message', 'type', 'upload'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
if (_.filter(['correcting', 'message', 'type', 'upload'
, 'received'
], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
await this.render();
}
...
...
@@ -65704,7 +65704,9 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
'to': this.get('jid'),
'type': this.get('message_type'),
'id': message.get('msgid')
}).c('body').t(body).up() // An encrypted header is added to the message for
}).c('body').t(body).up().c('request', {
'xmlns': Strophe.NS.RECEIPTS
}).up() // An encrypted header is added to the message for
// each device that is supposed to receive it.
// These headers simply contain the key that the
// payload message is encrypted with,
...
...
@@ -70630,6 +70632,7 @@ const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env
_ = _converse$env._;
const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils;
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0');
_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-chatboxes', {
dependencies: ["converse-roster", "converse-vcard"],
...
...
@@ -70940,6 +70943,31 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
return false;
},
handleReceipt(stanza) {
const to_bare_jid = Strophe.getBareJidFromJid(stanza.getAttribute('to'));
if (to_bare_jid === _converse.bare_jid) {
const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop();
if (receipt) {
const msgid = receipt && receipt.getAttribute('id'),
message = msgid && this.messages.findWhere({
msgid
});
if (message && !message.get('received')) {
message.save({
'received': moment().format()
});
}
return true;
}
}
return false;
},
createMessageStanza(message) {
/* Given a _converse.Message Backbone.Model, return the XML
* stanza that represents it.
...
...
@@ -70954,6 +70982,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid')
}).c('body').t(message.get('message')).up().c(_converse.ACTIVE, {
'xmlns': Strophe.NS.CHATSTATES
}).up().c('request', {
'xmlns': Strophe.NS.RECEIPTS
}).up();
if (message.get('is_spoiler')) {
...
...
@@ -71344,6 +71374,19 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
}
},
sendReceiptStanza(to_jid, id) {
const receipt_stanza = $msg({
'from': _converse.connection.jid,
'id': _converse.connection.getUniqueId(),
'to': to_jid
}).c('received', {
'xmlns': Strophe.NS.RECEIPTS,
'id': id
}).up();
_converse.api.send(receipt_stanza);
},
onMessage(stanza) {
/* Handler method for all incoming single-user chat "message"
* stanzas.
...
...
@@ -71387,6 +71430,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
to_jid = stanza.getAttribute('to');
}
const requests_receipt = !_.isUndefined(sizzle(`request[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop());
if (requests_receipt) {
this.sendReceiptStanza(from_jid, stanza.getAttribute('id'));
}
const from_bare_jid = Strophe.getBareJidFromJid(from_jid),
from_resource = Strophe.getResourceFromJid(from_jid),
is_me = from_bare_jid === _converse.bare_jid;
...
...
@@ -71410,7 +71459,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`).length > 0;
const chatbox = this.getChatBox(contact_jid, attrs, has_body);
if (chatbox && !chatbox.handleMessageCorrection(stanza)) {
if (chatbox && !chatbox.handleMessageCorrection(stanza)
&& !chatbox.handleReceipt(stanza)
) {
const msgid = stanza.getAttribute('id'),
message = msgid && chatbox.messages.findWhere({
msgid
...
...
@@ -76065,15 +76114,23 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
return data;
},
isDuplicate(message
, original_stanza
) {
isDuplicate(message) {
const msgid = message.getAttribute('id'),
jid = message.getAttribute('from');
if (msgid) {
return this.messages.w
here({
const msg = this.messages.findW
here({
'msgid': msgid,
'from': jid
}).length;
});
if (msg && msg.get('sender') === 'me' && !msg.get('received')) {
msg.save({
'received': moment().format()
});
}
return msg;
}
return false;
...
...
@@ -76106,7 +76163,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
stanza = forwarded.querySelector('message');
}
if (this.isDuplicate(stanza
, original_stanza
)) {
if (this.isDuplicate(stanza)) {
return;
}
...
...
@@ -102447,6 +102504,10 @@ __p += '\n </span>\n ';
if (!o.is_me_message) { ;
__p += '<div class="chat-msg__body">';
} ;
__p += '\n ';
if (o.received) { ;
__p += ' <span class="fa fa-check chat-msg__receipt"> </span> ';
} ;
__p += '\n ';
if (o.edited) { ;
__p += ' <i title="' +
sass/_messages.scss
View file @
da5ca0b5
...
...
@@ -256,6 +256,10 @@
margin-left
:
2
.75rem
;
}
}
.chat-msg__receipt
{
color
:
var
(
--
message-receipt-color
);
}
}
}
...
...
sass/_variables.scss
View file @
da5ca0b5
...
...
@@ -28,6 +28,7 @@ $font-path: "webfonts/icomoon/fonts/" !default;
--text-color
:
#666
;
--text-color-lighten-15-percent
:
#8c8c8c
;
// lighten(#666, 15%)
--message-text-color
:
#555
;
--message-receipt-color
:
#3AA569
;
// $green
--save-button-color
:
#3AA569
;
// $green
--chat-textarea-color
:
#666
;
...
...
spec/http-file-upload.js
View file @
da5ca0b5
...
...
@@ -357,6 +357,7 @@
`xmlns="jabber:client">`
+
`<body>
${
message
}
</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<x xmlns="jabber:x:oob">`
+
`<url>
${
message
}
</url>`
+
`</x>`
+
...
...
@@ -459,6 +460,7 @@
`xmlns="jabber:client">`
+
`<body>
${
message
}
</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<x xmlns="jabber:x:oob">`
+
`<url>
${
message
}
</url>`
+
`</x>`
+
...
...
spec/messages.js
View file @
da5ca0b5
...
...
@@ -77,6 +77,7 @@
`xmlns="jabber:client">`
+
`<body>But soft, what light through yonder window breaks?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<replace id="
${
first_msg
.
get
(
"
msgid
"
)}
" xmlns="urn:xmpp:message-correct:0"/>`
+
`</message>`
);
expect
(
view
.
model
.
messages
.
models
.
length
).
toBe
(
1
);
...
...
@@ -181,6 +182,7 @@
`xmlns="jabber:client">`
+
`<body>But soft, what light through yonder window breaks?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<replace id="
${
first_msg
.
get
(
"
msgid
"
)}
" xmlns="urn:xmpp:message-correct:0"/>`
+
`</message>`
);
expect
(
view
.
model
.
messages
.
models
.
length
).
toBe
(
1
);
...
...
@@ -1200,6 +1202,64 @@
done
();
}));
it
(
"
received may emit a message delivery receipt
"
,
mock
.
initConverseWithPromises
(
null
,
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
function
(
done
,
_converse
)
{
test_utils
.
createContacts
(
_converse
,
'
current
'
,
1
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@localhost
'
;
const
msg_id
=
u
.
getUniqueId
();
const
sent_stanzas
=
[];
spyOn
(
_converse
.
connection
,
'
send
'
).
and
.
callFake
(
function
(
stanza
)
{
sent_stanzas
.
push
(
stanza
);
});
const
msg
=
$msg
({
'
from
'
:
sender_jid
,
'
to
'
:
_converse
.
connection
.
jid
,
'
type
'
:
'
chat
'
,
'
id
'
:
msg_id
,
}).
c
(
'
body
'
).
t
(
'
Message!
'
).
up
()
.
c
(
'
request
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
RECEIPTS
}).
tree
();
_converse
.
chatboxes
.
onMessage
(
msg
);
const
receipt
=
sizzle
(
`received[xmlns="
${
Strophe
.
NS
.
RECEIPTS
}
"]`
,
sent_stanzas
[
0
].
tree
()).
pop
();
expect
(
receipt
.
outerHTML
).
toBe
(
`<received xmlns="
${
Strophe
.
NS
.
RECEIPTS
}
" id="
${
msg_id
}
"/>`
);
done
();
}));
it
(
"
delivery can be acknowledged by a receipt
"
,
mock
.
initConverseWithPromises
(
null
,
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
test_utils
.
createContacts
(
_converse
,
'
current
'
,
1
);
_converse
.
emit
(
'
rosterContactsFetched
'
);
const
contact_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@localhost
'
;
await
test_utils
.
openChatBoxFor
(
_converse
,
contact_jid
);
const
view
=
_converse
.
chatboxviews
.
get
(
contact_jid
);
const
textarea
=
view
.
el
.
querySelector
(
'
textarea.chat-textarea
'
);
textarea
.
value
=
'
But soft, what light through yonder airlock breaks?
'
;
view
.
keyPressed
({
target
:
textarea
,
preventDefault
:
_
.
noop
,
keyCode
:
13
// Enter
});
await
test_utils
.
waitUntil
(()
=>
_converse
.
api
.
chats
.
get
().
length
);
const
chatbox
=
_converse
.
chatboxes
.
get
(
contact_jid
);
expect
(
chatbox
).
toBeDefined
();
await
new
Promise
((
resolve
,
reject
)
=>
view
.
once
(
'
messageInserted
'
,
resolve
));
const
msg_obj
=
chatbox
.
messages
.
models
[
0
];
const
msg_id
=
msg_obj
.
get
(
'
msgid
'
);
const
msg
=
$msg
({
'
from
'
:
contact_jid
,
'
to
'
:
_converse
.
connection
.
jid
,
'
id
'
:
u
.
getUniqueId
(),
}).
c
(
'
received
'
,
{
'
id
'
:
msg_id
,
xmlns
:
Strophe
.
NS
.
RECEIPTS
}).
up
().
tree
();
_converse
.
chatboxes
.
onMessage
(
msg
);
await
new
Promise
((
resolve
,
reject
)
=>
view
.
model
.
messages
.
once
(
'
rendered
'
,
resolve
));
expect
(
view
.
el
.
querySelectorAll
(
'
.chat-msg__receipt
'
).
length
).
toBe
(
1
);
done
();
}));
describe
(
"
when received from someone else
"
,
function
()
{
...
...
@@ -2010,6 +2070,7 @@
`xmlns="jabber:client">`
+
`<body>But soft, what light through yonder window breaks?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<replace id="
${
first_msg
.
get
(
"
msgid
"
)}
" xmlns="urn:xmpp:message-correct:0"/>`
+
`</message>`
);
...
...
@@ -2056,6 +2117,38 @@
done
();
}));
it
(
"
delivery can be acknowledged by a receipt
"
,
mock
.
initConverseWithPromises
(
null
,
[
'
rosterGroupsFetched
'
],
{},
async
function
(
done
,
_converse
)
{
test_utils
.
createContacts
(
_converse
,
'
current
'
);
await
test_utils
.
openAndEnterChatRoom
(
_converse
,
'
lounge
'
,
'
localhost
'
,
'
dummy
'
);
const
view
=
_converse
.
chatboxviews
.
get
(
'
lounge@localhost
'
);
const
textarea
=
view
.
el
.
querySelector
(
'
textarea.chat-textarea
'
);
textarea
.
value
=
'
But soft, what light through yonder airlock breaks?
'
;
view
.
keyPressed
({
target
:
textarea
,
preventDefault
:
_
.
noop
,
keyCode
:
13
// Enter
});
await
new
Promise
((
resolve
,
reject
)
=>
view
.
once
(
'
messageInserted
'
,
resolve
));
const
msg_obj
=
view
.
model
.
messages
.
at
(
0
);
const
msg_id
=
msg_obj
.
get
(
'
msgid
'
);
const
from
=
msg_obj
.
get
(
'
from
'
);
const
body
=
msg_obj
.
get
(
'
message
'
);
const
msg
=
$msg
({
'
from
'
:
from
,
'
id
'
:
msg_id
,
'
to
'
:
'
dummy@localhost
'
,
'
type
'
:
'
groupchat
'
,
}).
c
(
'
body
'
).
t
(
body
).
up
().
tree
();
view
.
model
.
onMessage
(
msg
);
await
new
Promise
((
resolve
,
reject
)
=>
view
.
model
.
messages
.
once
(
'
rendered
'
,
resolve
));
expect
(
view
.
el
.
querySelectorAll
(
'
.chat-msg__receipt
'
).
length
).
toBe
(
1
);
done
();
}));
describe
(
"
when received
"
,
function
()
{
it
(
"
highlights all users mentioned via XEP-0372 references
"
,
...
...
@@ -2201,6 +2294,7 @@
`xmlns="jabber:client">`
+
`<body>hello z3r0 gibson mr.robot, how are you?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`
+
...
...
@@ -2226,6 +2320,7 @@
`xmlns="jabber:client">`
+
`<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`
+
...
...
@@ -2274,6 +2369,7 @@
`xmlns="jabber:client">`
+
`<body>hello z3r0 gibson mr.robot, how are you?</body>`
+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`
+
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`
+
...
...
spec/omemo.js
View file @
da5ca0b5
...
...
@@ -172,6 +172,7 @@
`to="max.frankfurter@localhost" `
+
`type="chat" xmlns="jabber:client">`
+
`<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`
+
`<request xmlns="urn:xmpp:receipts"/>`
+
`<encrypted xmlns="eu.siacs.conversations.axolotl">`
+
`<header sid="123456789">`
+
`<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`
+
...
...
src/converse-message-view.js
View file @
da5ca0b5
...
...
@@ -86,7 +86,7 @@ converse.plugins.add('converse-message-view', {
if
(
this
.
model
.
changed
.
progress
)
{
return
this
.
renderFileUploadProgresBar
();
}
if
(
_
.
filter
([
'
correcting
'
,
'
message
'
,
'
type
'
,
'
upload
'
],
if
(
_
.
filter
([
'
correcting
'
,
'
message
'
,
'
type
'
,
'
upload
'
,
'
received
'
],
prop
=>
Object
.
prototype
.
hasOwnProperty
.
call
(
this
.
model
.
changed
,
prop
)).
length
)
{
await
this
.
render
();
}
...
...
src/converse-omemo.js
View file @
da5ca0b5
...
...
@@ -394,6 +394,7 @@ converse.plugins.add('converse-omemo', {
'
type
'
:
this
.
get
(
'
message_type
'
),
'
id
'
:
message
.
get
(
'
msgid
'
)
}).
c
(
'
body
'
).
t
(
body
).
up
()
.
c
(
'
request
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
RECEIPTS
}).
up
()
// An encrypted header is added to the message for
// each device that is supposed to receive it.
// These headers simply contain the key that the
...
...
src/headless/converse-chatboxes.js
View file @
da5ca0b5
...
...
@@ -13,6 +13,7 @@ const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } =
const
u
=
converse
.
env
.
utils
;
Strophe
.
addNamespace
(
'
MESSAGE_CORRECT
'
,
'
urn:xmpp:message-correct:0
'
);
Strophe
.
addNamespace
(
'
RECEIPTS
'
,
'
urn:xmpp:receipts
'
);
Strophe
.
addNamespace
(
'
REFERENCE
'
,
'
urn:xmpp:reference:0
'
);
...
...
@@ -297,6 +298,24 @@ converse.plugins.add('converse-chatboxes', {
return
false
;
},
handleReceipt
(
stanza
)
{
const
to_bare_jid
=
Strophe
.
getBareJidFromJid
(
stanza
.
getAttribute
(
'
to
'
));
if
(
to_bare_jid
===
_converse
.
bare_jid
)
{
const
receipt
=
sizzle
(
`received[xmlns="
${
Strophe
.
NS
.
RECEIPTS
}
"]`
,
stanza
).
pop
();
if
(
receipt
)
{
const
msgid
=
receipt
&&
receipt
.
getAttribute
(
'
id
'
),
message
=
msgid
&&
this
.
messages
.
findWhere
({
msgid
});
if
(
message
&&
!
message
.
get
(
'
received
'
))
{
message
.
save
({
'
received
'
:
moment
().
format
()
});
}
return
true
;
}
}
return
false
;
},
createMessageStanza
(
message
)
{
/* Given a _converse.Message Backbone.Model, return the XML
* stanza that represents it.
...
...
@@ -310,7 +329,8 @@ converse.plugins.add('converse-chatboxes', {
'
type
'
:
this
.
get
(
'
message_type
'
),
'
id
'
:
message
.
get
(
'
edited
'
)
&&
_converse
.
connection
.
getUniqueId
()
||
message
.
get
(
'
msgid
'
),
}).
c
(
'
body
'
).
t
(
message
.
get
(
'
message
'
)).
up
()
.
c
(
_converse
.
ACTIVE
,
{
'
xmlns
'
:
Strophe
.
NS
.
CHATSTATES
}).
up
();
.
c
(
_converse
.
ACTIVE
,
{
'
xmlns
'
:
Strophe
.
NS
.
CHATSTATES
}).
up
()
.
c
(
'
request
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
RECEIPTS
}).
up
();
if
(
message
.
get
(
'
is_spoiler
'
))
{
if
(
message
.
get
(
'
spoiler_hint
'
))
{
...
...
@@ -663,6 +683,15 @@ converse.plugins.add('converse-chatboxes', {
}
},
sendReceiptStanza
(
to_jid
,
id
)
{
const
receipt_stanza
=
$msg
({
'
from
'
:
_converse
.
connection
.
jid
,
'
id
'
:
_converse
.
connection
.
getUniqueId
(),
'
to
'
:
to_jid
,
}).
c
(
'
received
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
RECEIPTS
,
'
id
'
:
id
}).
up
();
_converse
.
api
.
send
(
receipt_stanza
);
},
onMessage
(
stanza
)
{
/* Handler method for all incoming single-user chat "message"
* stanzas.
...
...
@@ -709,6 +738,11 @@ converse.plugins.add('converse-chatboxes', {
to_jid
=
stanza
.
getAttribute
(
'
to
'
);
}
const
requests_receipt
=
!
_
.
isUndefined
(
sizzle
(
`request[xmlns="
${
Strophe
.
NS
.
RECEIPTS
}
"]`
,
stanza
).
pop
());
if
(
requests_receipt
)
{
this
.
sendReceiptStanza
(
from_jid
,
stanza
.
getAttribute
(
'
id
'
));
}
const
from_bare_jid
=
Strophe
.
getBareJidFromJid
(
from_jid
),
from_resource
=
Strophe
.
getResourceFromJid
(
from_jid
),
is_me
=
from_bare_jid
===
_converse
.
bare_jid
;
...
...
@@ -732,7 +766,7 @@ converse.plugins.add('converse-chatboxes', {
// Get chat box, but only create a new one when the message has a body.
const
has_body
=
sizzle
(
`body, encrypted[xmlns="
${
Strophe
.
NS
.
OMEMO
}
"]`
).
length
>
0
;
const
chatbox
=
this
.
getChatBox
(
contact_jid
,
attrs
,
has_body
);
if
(
chatbox
&&
!
chatbox
.
handleMessageCorrection
(
stanza
))
{
if
(
chatbox
&&
!
chatbox
.
handleMessageCorrection
(
stanza
)
&&
!
chatbox
.
handleReceipt
(
stanza
)
)
{
const
msgid
=
stanza
.
getAttribute
(
'
id
'
),
message
=
msgid
&&
chatbox
.
messages
.
findWhere
({
msgid
});
if
(
!
message
)
{
...
...
src/headless/converse-muc.js
View file @
da5ca0b5
...
...
@@ -913,13 +913,21 @@ converse.plugins.add('converse-muc', {
return
data
;
},
isDuplicate
(
message
,
original_stanza
)
{
isDuplicate
(
message
)
{
const
msgid
=
message
.
getAttribute
(
'
id
'
),
jid
=
message
.
getAttribute
(
'
from
'
);
if
(
msgid
)
{
return
this
.
messages
.
where
({
'
msgid
'
:
msgid
,
'
from
'
:
jid
}).
length
;
const
msg
=
this
.
messages
.
findWhere
({
'
msgid
'
:
msgid
,
'
from
'
:
jid
});
if
(
msg
&&
msg
.
get
(
'
sender
'
)
===
'
me
'
&&
!
msg
.
get
(
'
received
'
))
{
msg
.
save
({
'
received
'
:
moment
().
format
()
});
}
return
msg
;
}
return
false
;
},
fetchFeaturesIfConfigurationChanged
(
stanza
)
{
...
...
@@ -949,7 +957,7 @@ converse.plugins.add('converse-muc', {
if
(
!
_
.
isNull
(
forwarded
))
{
stanza
=
forwarded
.
querySelector
(
'
message
'
);
}
if
(
this
.
isDuplicate
(
stanza
,
original_stanza
))
{
if
(
this
.
isDuplicate
(
stanza
))
{
return
;
}
const
jid
=
stanza
.
getAttribute
(
'
from
'
),
...
...
src/templates/message.html
View file @
da5ca0b5
...
...
@@ -12,6 +12,7 @@
{[ if (o.is_encrypted) { ]}
<span
class=
"fa fa-lock"
></span>
{[ } ]}
</span>
{[ if (!o.is_me_message) { ]}
<div
class=
"chat-msg__body"
>
{[ } ]}
{[ if (o.received) { ]}
<span
class=
"fa fa-check chat-msg__receipt"
>
</span>
{[ } ]}
{[ if (o.edited) { ]}
<i
title=
"{{{o.__('This message has been edited')}}}"
class=
"fa fa-edit chat-msg__edit-modal"
></i>
{[ } ]}
{[ if (!o.is_me_message) { ]}
<div
class=
"chat-msg__message"
>
{[ } ]}
{[ if (o.is_spoiler) { ]}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment