parent
be58e2b9c8
commit
2929647e16
|
@ -7,6 +7,7 @@
|
|||
- #161 XEP-0363: HTTP File Upload
|
||||
- #194 Include entity capabilities in outgoing presence stanzas
|
||||
- #337 API call to update a VCard
|
||||
- #421 XEP-0308: Last Message Correction
|
||||
- #968 Use nickname from VCard when joining a room
|
||||
- #1091 There's now only one CSS file for all view modes.
|
||||
- #1094 Show room members who aren't currently online
|
||||
|
@ -22,7 +23,6 @@
|
|||
If the device is trusted, localStorage is used and user data is cached indefinitely.
|
||||
- Initial support for XEP-0357 Push Notifications, specifically registering an "App Server".
|
||||
- Add support for logging in via OAuth (see the [oauth_providers](https://conversejs.org/docs/html/configurations.html#oauth-providers) setting)
|
||||
- XEP-0308: Render message corrections
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ which shows you how to use the CDN (content delivery network) to quickly get a d
|
|||
- Server-side archiving of messages [XEP 313](http://xmpp.org/extensions/xep-0313.html)
|
||||
- Hidden Messages (aka Spoilers) [XEP 382](http://xmpp.org/extensions/xep-0382.html)
|
||||
- Client state indication [XEP 352](http://xmpp.org/extensions/xep-0352.html)
|
||||
- Last Message Correction [XEP 308](http://xmpp.org/extensions/xep-0308.html)
|
||||
- Off-the-record encryption
|
||||
- Translated into 16 languages
|
||||
|
||||
|
|
|
@ -8051,36 +8051,6 @@ body.reset {
|
|||
#conversejs .toggle-controlbox span {
|
||||
color: white; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
#conversejs:not(.converse-embedded) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right); }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes {
|
||||
margin: 0 !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: space-between; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes .converse-chatroom {
|
||||
font-size: 14px; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes .chatbox .box-flyout {
|
||||
margin-left: 15px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox {
|
||||
width: 100vw !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox .box-flyout {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox .sidebar {
|
||||
display: block; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes.sidebar-open .chatbox:not(#controlbox) {
|
||||
display: none; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes.sidebar-open #controlbox .controlbox-pane {
|
||||
display: block; } }
|
||||
#conversejs.converse-overlayed #controlbox {
|
||||
order: -1;
|
||||
min-width: 250px !important;
|
||||
|
@ -8257,6 +8227,39 @@ body.reset {
|
|||
#conversejs.converse-mobile #controlbox #converse-login input[type=button] {
|
||||
width: auto; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
#conversejs:not(.converse-embedded) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right); }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes {
|
||||
margin: 0 !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: space-between; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes .converse-chatroom {
|
||||
font-size: 14px; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes .chatbox .box-flyout {
|
||||
margin-left: 15px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox {
|
||||
width: 100vw !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox .box-flyout {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes #controlbox .sidebar {
|
||||
display: block; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes.sidebar-open .chatbox:not(#controlbox) {
|
||||
display: none; }
|
||||
#conversejs:not(.converse-embedded) .converse-chatboxes.sidebar-open #controlbox .controlbox-pane {
|
||||
display: block; }
|
||||
|
||||
#conversejs.converse-overlayed .converse-chatboxes .chatbox .box-flyout {
|
||||
margin-left: 30px; } }
|
||||
#conversejs #converse-roster {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
|
68
dist/converse.js
vendored
68
dist/converse.js
vendored
|
@ -68305,6 +68305,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
utils = _converse$env.utils,
|
||||
_ = _converse$env._;
|
||||
const u = converse.env.utils;
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
converse.plugins.add('converse-chatboxes', {
|
||||
dependencies: ["converse-roster", "converse-vcard"],
|
||||
overrides: {
|
||||
|
@ -68615,7 +68616,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
'from': _converse.connection.jid,
|
||||
'to': this.get('jid'),
|
||||
'type': this.get('message_type'),
|
||||
'id': message.get('msgid')
|
||||
'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();
|
||||
|
@ -68638,6 +68639,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}).c('url').t(message.get('message')).up();
|
||||
}
|
||||
|
||||
if (message.get('edited')) {
|
||||
stanza.c('replace', {
|
||||
'xmlns': Strophe.NS.MESSAGE_CORRECT,
|
||||
'id': message.get('msgid')
|
||||
}).up();
|
||||
}
|
||||
|
||||
return stanza;
|
||||
},
|
||||
|
||||
|
@ -68667,6 +68675,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
|
||||
return {
|
||||
'fullname': fullname,
|
||||
'replace': this.correction,
|
||||
'from': _converse.bare_jid,
|
||||
'sender': 'me',
|
||||
'time': moment().format(),
|
||||
|
@ -68682,7 +68691,24 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
* Parameters:
|
||||
* (Message) message - The chat message
|
||||
*/
|
||||
this.sendMessageStanza(this.messages.create(attrs));
|
||||
if (attrs.replace) {
|
||||
const message = this.messages.findWhere({
|
||||
'id': attrs.replace
|
||||
});
|
||||
|
||||
if (message) {
|
||||
const older_versions = message.get('older_versions') || [];
|
||||
older_versions.push(message.get('message'));
|
||||
message.save({
|
||||
'message': attrs.message,
|
||||
'older_versions': older_versions,
|
||||
'edited': true
|
||||
});
|
||||
return this.sendMessageStanza(message);
|
||||
}
|
||||
}
|
||||
|
||||
return this.sendMessageStanza(this.messages.create(attrs));
|
||||
},
|
||||
|
||||
sendChatState() {
|
||||
|
@ -69177,6 +69203,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
});
|
||||
|
||||
_converse.on('addClientFeatures', () => {
|
||||
_converse.api.disco.own.features.add(Strophe.NS.MESSAGE_CORRECT);
|
||||
|
||||
_converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD);
|
||||
|
||||
_converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND);
|
||||
|
@ -69315,6 +69343,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
const u = converse.env.utils;
|
||||
const KEY = {
|
||||
ENTER: 13,
|
||||
UP_ARROW: 38,
|
||||
FORWARD_SLASH: 47
|
||||
};
|
||||
converse.plugins.add('converse-chatview', {
|
||||
|
@ -69592,7 +69621,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
|
||||
'click .toggle-smiley': 'toggleEmojiMenu',
|
||||
'click .upload-file': 'toggleFileUpload',
|
||||
'keypress .chat-textarea': 'keyPressed',
|
||||
'keyup .chat-textarea': 'keyPressed',
|
||||
'input .chat-textarea': 'inputChanged'
|
||||
},
|
||||
|
||||
|
@ -70113,6 +70142,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}
|
||||
|
||||
const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
|
||||
delete this.model.correction;
|
||||
this.model.sendMessage(attrs);
|
||||
},
|
||||
|
||||
|
@ -70175,6 +70205,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
*/
|
||||
if (ev.keyCode === KEY.ENTER && !ev.shiftKey) {
|
||||
this.onFormSubmitted(ev);
|
||||
} else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) {
|
||||
this.editPreviousMessage();
|
||||
} else if (ev.keyCode !== KEY.FORWARD_SLASH && this.model.get('chat_state') !== _converse.COMPOSING) {
|
||||
// Set chat state to composing if keyCode is not a forward-slash
|
||||
// (which would imply an internal command and not a message).
|
||||
|
@ -70182,6 +70214,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}
|
||||
},
|
||||
|
||||
editPreviousMessage() {
|
||||
const msg = _.findLast(this.model.messages.models, msg => msg.get('message'));
|
||||
|
||||
if (msg) {
|
||||
const textbox_el = this.el.querySelector('.chat-textarea');
|
||||
textbox_el.value = msg.get('message');
|
||||
textbox_el.focus(); // We don't set "correcting" the Backbone-way, because
|
||||
// we don't want it to persist to storage.
|
||||
|
||||
this.model.correction = msg.get('id');
|
||||
}
|
||||
},
|
||||
|
||||
inputChanged(ev) {
|
||||
ev.target.style.height = 'auto'; // Fixes weirdness
|
||||
|
||||
|
@ -71193,7 +71238,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
|
||||
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
|
||||
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
|
||||
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
|
||||
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
|
||||
|
@ -87771,6 +87815,14 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
return result;
|
||||
};
|
||||
|
||||
u.getUniqueId = function () {
|
||||
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
return u;
|
||||
});
|
||||
|
||||
|
@ -87839,14 +87891,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}));
|
||||
};
|
||||
|
||||
u.getUniqueId = function () {
|
||||
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
u.xForm2webForm = function (field, stanza, domain) {
|
||||
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
||||
* and turns it into an HTML field.
|
||||
|
|
|
@ -174,6 +174,7 @@
|
|||
<li>Server-side archiving of messages (<a href="http://xmpp.org/extensions/xep-0313.html" target="_blank" rel="noopener">XEP 313</a>)</li>
|
||||
<li>Hidden messages (aka Spoilers) (<a href="http://xmpp.org/extensions/xep-0382.html" target="_blank" rel="noopener">XEP 382</a>)</li>
|
||||
<li>Client state indication (<a href="http://xmpp.org/extensions/xep-0352.html" target="_blank" rel="noopener">XEP 352</a>)</li>
|
||||
<li>Last Message Correction (<a href="http://xmpp.org/extensions/xep-0308.html" target="_blank" rel="noopener">XEP 308</a>)</li>
|
||||
<li>Off-the-record encryption</li>
|
||||
<li>Supports anonymous logins, see the <a href="https://conversejs.org/demo/anonymous.html" target="_blank" rel="noopener">anonymous login demo</a>.</li>
|
||||
<li>Translated into 17 languages</li>
|
||||
|
|
|
@ -350,59 +350,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
|
||||
#conversejs:not(.converse-embedded) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
|
||||
.converse-chatboxes {
|
||||
margin: 0 !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: space-between;
|
||||
|
||||
.converse-chatroom {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chatbox {
|
||||
.box-flyout {
|
||||
margin-left: 15px; // Counteracts Bootstrap margins, but
|
||||
// not clear why needed...
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
#controlbox {
|
||||
width: 100vw !important;
|
||||
.box-flyout {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
.sidebar {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.sidebar-open {
|
||||
.chatbox:not(#controlbox) {
|
||||
display: none;
|
||||
}
|
||||
#controlbox {
|
||||
.controlbox-pane {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#conversejs.converse-overlayed {
|
||||
#controlbox {
|
||||
|
@ -563,3 +510,67 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
|
||||
#conversejs:not(.converse-embedded) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
|
||||
.converse-chatboxes {
|
||||
margin: 0 !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: space-between;
|
||||
|
||||
.converse-chatroom {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chatbox {
|
||||
.box-flyout {
|
||||
margin-left: 15px; // Counteracts Bootstrap margins, but
|
||||
// not clear why needed...
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
#controlbox {
|
||||
width: 100vw !important;
|
||||
.box-flyout {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
.sidebar {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.sidebar-open {
|
||||
.chatbox:not(#controlbox) {
|
||||
display: none;
|
||||
}
|
||||
#controlbox {
|
||||
.controlbox-pane {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#conversejs.converse-overlayed {
|
||||
.converse-chatboxes {
|
||||
.chatbox {
|
||||
.box-flyout {
|
||||
margin-left: 30px; // Counteracts Bootstrap margins, but
|
||||
// not clear why needed...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -635,7 +635,7 @@
|
|||
spyOn(_converse.connection, 'send');
|
||||
spyOn(_converse, 'emit');
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
|
@ -648,7 +648,7 @@
|
|||
|
||||
// The notification is not sent again
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
|
@ -776,7 +776,7 @@
|
|||
spyOn(view, 'setChatState').and.callThrough();
|
||||
expect(view.model.get('chat_state')).toBe('active');
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
|
@ -803,14 +803,14 @@
|
|||
// out if the user simply types longer than the
|
||||
// timeout.
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
expect(view.setChatState).toHaveBeenCalled();
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
|
@ -921,33 +921,25 @@
|
|||
contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
test_utils.openChatBoxFor(_converse, contact_jid);
|
||||
view = _converse.chatboxviews.get(contact_jid);
|
||||
return test_utils.waitUntil(function () {
|
||||
return view.model.get('chat_state') === 'active';
|
||||
}, 500);
|
||||
return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 500);
|
||||
}).then(function () {
|
||||
console.log('chat_state set to active');
|
||||
view = _converse.chatboxviews.get(contact_jid);
|
||||
expect(view.model.get('chat_state')).toBe('active');
|
||||
view.keyPressed({
|
||||
target: $(view.el).find('textarea.chat-textarea'),
|
||||
target: view.el.querySelector('textarea.chat-textarea'),
|
||||
keyCode: 1
|
||||
});
|
||||
return test_utils.waitUntil(function () {
|
||||
return view.model.get('chat_state') === 'composing';
|
||||
}, 500);
|
||||
return test_utils.waitUntil(() => view.model.get('chat_state') === 'composing', 500);
|
||||
}).then(function () {
|
||||
console.log('chat_state set to composing');
|
||||
view = _converse.chatboxviews.get(contact_jid);
|
||||
expect(view.model.get('chat_state')).toBe('composing');
|
||||
spyOn(_converse.connection, 'send');
|
||||
return test_utils.waitUntil(function () {
|
||||
return view.model.get('chat_state') === 'paused';
|
||||
}, 500);
|
||||
return test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500);
|
||||
}).then(function () {
|
||||
console.log('chat_state set to paused');
|
||||
return test_utils.waitUntil(function () {
|
||||
return view.model.get('chat_state') === 'inactive';
|
||||
}, 500);
|
||||
return test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 500);
|
||||
}).then(function () {
|
||||
console.log('chat_state set to inactive');
|
||||
expect(_converse.connection.send).toHaveBeenCalled();
|
||||
|
|
|
@ -136,6 +136,70 @@
|
|||
});
|
||||
}));
|
||||
|
||||
it("can be sent as a correction",
|
||||
mock.initConverseWithPromises(
|
||||
null, ['rosterGroupsFetched'], {},
|
||||
function (done, _converse) {
|
||||
|
||||
test_utils.createContacts(_converse, 'current', 1);
|
||||
test_utils.openControlBox();
|
||||
const message = 'This is a received message';
|
||||
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
test_utils.openChatBoxFor(_converse, contact_jid);
|
||||
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
const textarea = view.el.querySelector('textarea.chat-textarea');
|
||||
expect(textarea.value).toBe('');
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
keyCode: 38
|
||||
});
|
||||
expect(textarea.value).toBe('');
|
||||
|
||||
textarea.value = 'But soft, what light through yonder airlock breaks?';
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
preventDefault: _.noop,
|
||||
keyCode: 13
|
||||
});
|
||||
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
|
||||
expect(view.el.querySelector('.chat-msg-text').textContent)
|
||||
.toBe('But soft, what light through yonder airlock breaks?');
|
||||
|
||||
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
|
||||
expect(textarea.value).toBe('');
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
keyCode: 38
|
||||
});
|
||||
expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
|
||||
|
||||
spyOn(_converse.connection, 'send');
|
||||
textarea.value = 'But soft, what light through yonder window breaks?';
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
preventDefault: _.noop,
|
||||
keyCode: 13
|
||||
});
|
||||
expect(_converse.connection.send).toHaveBeenCalled();
|
||||
|
||||
const msg = _converse.connection.send.calls.all()[0].args[0];
|
||||
expect(msg.toLocaleString())
|
||||
.toBe(`<message from='dummy@localhost/resource' `+
|
||||
`to='max.frankfurter@localhost' type='chat' id='${msg.nodeTree.getAttribute('id')}' `+
|
||||
`xmlns='jabber:client'>`+
|
||||
`<body>But soft, what light through yonder window breaks?</body>`+
|
||||
`<active xmlns='http://jabber.org/protocol/chatstates'/>`+
|
||||
`<replace xmlns='urn:xmpp:message-correct:0' id='${first_msg.get('msgid')}'/>`+
|
||||
`</message>`);
|
||||
expect(view.model.messages.models.length).toBe(1);
|
||||
const corrected_message = view.model.messages.at(0);
|
||||
expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
|
||||
expect(corrected_message.get('older_versions').length).toBe(1);
|
||||
expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
|
||||
done();
|
||||
}));
|
||||
|
||||
describe("when a chatbox is opened for someone who is not in the roster", function () {
|
||||
|
||||
it("the VCard for that user is fetched and the chatbox updated with the results",
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"<presence xmlns='jabber:client'>"+
|
||||
"<status>Hello world</status>"+
|
||||
"<priority>0</priority>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
|
||||
"</presence>"
|
||||
);
|
||||
_converse.priority = 2;
|
||||
|
@ -57,7 +57,7 @@
|
|||
"<show>away</show>"+
|
||||
"<status>Going jogging</status>"+
|
||||
"<priority>2</priority>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
|
||||
"</presence>"
|
||||
);
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
"<show>dnd</show>"+
|
||||
"<status>Doing taxes</status>"+
|
||||
"<priority>0</priority>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
|
||||
"</presence>"
|
||||
);
|
||||
}));
|
||||
|
@ -97,7 +97,7 @@
|
|||
.toBe("<presence xmlns='jabber:client'>"+
|
||||
"<status>My custom status</status>"+
|
||||
"<priority>0</priority>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
|
||||
"</presence>")
|
||||
|
||||
return test_utils.waitUntil(function () {
|
||||
|
@ -113,7 +113,7 @@
|
|||
modal.el.querySelector('[type="submit"]').click();
|
||||
expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString())
|
||||
.toBe("<presence xmlns='jabber:client'><show>dnd</show><status>My custom status</status><priority>0</priority>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
|
||||
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
|
||||
"</presence>")
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
|
||||
|
||||
converse.plugins.add('converse-chatboxes', {
|
||||
|
||||
|
@ -43,7 +45,7 @@
|
|||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
const { _converse } = this,
|
||||
{ __ } = _converse;
|
||||
{ __ } = _converse;
|
||||
|
||||
// Configuration values for this plugin
|
||||
// ====================================
|
||||
|
@ -314,7 +316,7 @@
|
|||
'from': _converse.connection.jid,
|
||||
'to': this.get('jid'),
|
||||
'type': this.get('message_type'),
|
||||
'id': message.get('msgid')
|
||||
'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();
|
||||
|
||||
|
@ -328,6 +330,12 @@
|
|||
if (message.get('file')) {
|
||||
stanza.c('x', {'xmlns': Strophe.NS.OUTOFBAND}).c('url').t(message.get('message')).up();
|
||||
}
|
||||
if (message.get('edited')) {
|
||||
stanza.c('replace', {
|
||||
'xmlns': Strophe.NS.MESSAGE_CORRECT,
|
||||
'id': message.get('msgid')
|
||||
}).up();
|
||||
}
|
||||
return stanza;
|
||||
},
|
||||
|
||||
|
@ -357,6 +365,7 @@
|
|||
|
||||
return {
|
||||
'fullname': fullname,
|
||||
'replace': this.correction,
|
||||
'from': _converse.bare_jid,
|
||||
'sender': 'me',
|
||||
'time': moment().format(),
|
||||
|
@ -372,7 +381,20 @@
|
|||
* Parameters:
|
||||
* (Message) message - The chat message
|
||||
*/
|
||||
this.sendMessageStanza(this.messages.create(attrs));
|
||||
if (attrs.replace) {
|
||||
const message = this.messages.findWhere({'id': attrs.replace})
|
||||
if (message) {
|
||||
const older_versions = message.get('older_versions') || [];
|
||||
older_versions.push(message.get('message'));
|
||||
message.save({
|
||||
'message': attrs.message,
|
||||
'older_versions': older_versions,
|
||||
'edited': true
|
||||
});
|
||||
return this.sendMessageStanza(message);
|
||||
}
|
||||
}
|
||||
return this.sendMessageStanza(this.messages.create(attrs));
|
||||
},
|
||||
|
||||
sendChatState () {
|
||||
|
@ -826,6 +848,7 @@
|
|||
|
||||
|
||||
_converse.on('addClientFeatures', () => {
|
||||
_converse.api.disco.own.features.add(Strophe.NS.MESSAGE_CORRECT);
|
||||
_converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD);
|
||||
_converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND);
|
||||
});
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
const u = converse.env.utils;
|
||||
const KEY = {
|
||||
ENTER: 13,
|
||||
UP_ARROW: 38,
|
||||
FORWARD_SLASH: 47
|
||||
};
|
||||
|
||||
|
@ -333,7 +334,7 @@
|
|||
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
|
||||
'click .toggle-smiley': 'toggleEmojiMenu',
|
||||
'click .upload-file': 'toggleFileUpload',
|
||||
'keypress .chat-textarea': 'keyPressed',
|
||||
'keyup .chat-textarea': 'keyPressed',
|
||||
'input .chat-textarea': 'inputChanged'
|
||||
},
|
||||
|
||||
|
@ -847,6 +848,7 @@
|
|||
return;
|
||||
}
|
||||
const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
|
||||
delete this.model.correction;
|
||||
this.model.sendMessage(attrs);
|
||||
},
|
||||
|
||||
|
@ -912,6 +914,8 @@
|
|||
*/
|
||||
if (ev.keyCode === KEY.ENTER && !ev.shiftKey) {
|
||||
this.onFormSubmitted(ev);
|
||||
} else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) {
|
||||
this.editPreviousMessage();
|
||||
} else if (ev.keyCode !== KEY.FORWARD_SLASH && this.model.get('chat_state') !== _converse.COMPOSING) {
|
||||
// Set chat state to composing if keyCode is not a forward-slash
|
||||
// (which would imply an internal command and not a message).
|
||||
|
@ -919,6 +923,18 @@
|
|||
}
|
||||
},
|
||||
|
||||
editPreviousMessage () {
|
||||
const msg = _.findLast(this.model.messages.models, (msg) => msg.get('message'));
|
||||
if (msg) {
|
||||
const textbox_el = this.el.querySelector('.chat-textarea');
|
||||
textbox_el.value = msg.get('message');
|
||||
textbox_el.focus()
|
||||
// We don't set "correcting" the Backbone-way, because
|
||||
// we don't want it to persist to storage.
|
||||
this.model.correction = msg.get('id');
|
||||
}
|
||||
},
|
||||
|
||||
inputChanged (ev) {
|
||||
ev.target.style.height = 'auto'; // Fixes weirdness
|
||||
ev.target.style.height = (ev.target.scrollHeight) + 'px';
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
|
||||
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
|
||||
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
|
||||
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
|
||||
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
|
||||
|
|
|
@ -844,5 +844,12 @@
|
|||
return result;
|
||||
};
|
||||
|
||||
u.getUniqueId = function () {
|
||||
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
return u;
|
||||
}));
|
||||
|
|
|
@ -73,14 +73,6 @@
|
|||
);
|
||||
};
|
||||
|
||||
u.getUniqueId = function () {
|
||||
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
u.xForm2webForm = function (field, stanza, domain) {
|
||||
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
||||
* and turns it into an HTML field.
|
||||
|
|
Loading…
Reference in New Issue
Block a user