Commit 11cc41d3 authored by JC Brand's avatar JC Brand

Merge branch 'converse-omemo'

parents 56933508 da68ea9c
......@@ -9,12 +9,13 @@
"plugins": ["lodash"],
"extends": ["eslint:recommended", "plugin:lodash/canonical"],
"globals": {
"Uint8Array": true,
"Promise": true,
"converse": true,
"window": true,
"sinon": true,
"define": true,
"require": true
"require": true,
"sinon": true,
"window": true
},
"rules": {
"lodash/prefer-lodash-method": [2, {
......
......@@ -12,6 +12,7 @@
.idea
.su?
builds/*
3rdparty/libsignal-protocol-javascript/
*.map
dist/converse-no-dependencies-es2015.js
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -11,6 +11,7 @@
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
<link type="text/css" rel="stylesheet" media="screen" href="css/fullpage.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="3rdparty/libsignal-protocol-javascript/dist/libsignal-protocol.js"></script>
<script src="dist/converse.js"></script>
</head>
......
This diff is collapsed.
......@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: Converse.js 0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-22 11:17+0200\n"
"PO-Revision-Date: 2018-07-22 12:12+0200\n"
"PO-Revision-Date: 2018-07-22 15:37+0200\n"
"Last-Translator: JC Brand <jc@opkode.com>\n"
"Language-Team: Afrikaans <https://hosted.weblate.org/projects/conversejs/"
"translations/af/>\n"
......
......@@ -134,12 +134,33 @@
<span class="chat-msg__heading">
<span class="chat-msg__author">Juliet Capulet</span>
<span class="chat-msg__time">15:31</span>
<span class="fa fa-lock"></span>
</span>
<div class="chat-msg__body">
<div class="chat-msg__message">
<span class="chat-msg__text">
O Romeo, Romeo! wherefore art thou Romeo?
Deny thy father and refuse thy name;
</span>
</div>
</div>
</div>
<div class="chat-msg__actions">
<button class="chat-msg__action fa fa-pencil" title="Edit this message">&nbsp;</button>
</div>
</div>
<div class="message chat-msg chat-msg--followup">
<canvas height="36" width="36" class="avatar chat-msg__avatar"></canvas>
<div class="chat-msg__content">
<span class="chat-msg__heading">
<span class="chat-msg__author">Juliet Capulet</span>
<span class="chat-msg__time">15:31</span>
<span class="fa fa-lock"></span>
</span>
<div class="chat-msg__body">
<div class="chat-msg__message">
<span class="chat-msg__text">
Or, if thou wilt not, be but sworn my love,
And I'll no longer be a Capulet.
</span>
......
This diff is collapsed.
......@@ -61,6 +61,8 @@
}
.chatbox-buttons {
flex-direction: row-reverse;
@include make-col-ready();
@include make-col(3);
padding: 0;
}
......
......@@ -60,15 +60,6 @@
width: 100%;
}
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
}
#controlbox {
.box-flyout {
background-color: white;
......
......@@ -61,6 +61,10 @@ body.reset {
direction: ltr;
z-index: 1031; // One more than bootstrap navbar
.nopadding {
padding: 0 !important;
}
&.converse-overlayed {
> .row {
flex-direction: row-reverse;
......
#conversejs {
form {
.form-group {
margin-bottom: 2em;
}
.btn--small {
font-size: 80%;
font-weight: normal;
}
form {
.form-check-label {
margin-top: $form-check-input-margin-y;
}
......@@ -103,6 +104,11 @@
}
}
}
&.converse-form--modal {
padding-bottom: 0;
}
&.converse-centered-form {
text-align: center;
}
......
#conversejs {
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
#omemo-tabpanel {
margin-top: 1em;
}
.btn {
font-weight: normal;
}
#user-profile-modal {
.profile-form {
label {
font-weight: bold;
}
}
.fingerprint-removal {
label {
display: flex;
padding: 0.75rem 1.25rem;
}
}
.list-group-item {
display: flex;
justify-content: left;
font-size: 95%;
input[type="checkbox"] {
margin-right: 1em;
}
}
}
.fingerprints {
width: 100%;
margin-bottom: 1em;
}
.fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%;
.fingerprint {
margin-left: 1em;
}
}
}
}
#conversejs {
#user-profile-modal {
label {
font-weight: bold;
}
}
}
......@@ -34,6 +34,8 @@ $green: #3AA569;
$dark-green: #1E9652;
$darkest-green: #0E763B;
$info: $green !default;
$lightest-green: #E7FBF0;
$light-green: #5CBC86;
$green: #3AA569;
......
......@@ -26,6 +26,8 @@
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/input-group";
@import "bootstrap/scss/custom-forms";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/badge";
......@@ -40,9 +42,9 @@
}
@import "core";
@import "forms";
@import "profile";
@import "chatbox";
@import "controlbox";
@import "modal";
@import "roster";
@import "lists";
@import "chatrooms";
......
......@@ -314,52 +314,54 @@
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
).then(function () {
test_utils.waitUntil(function () {
return _converse.bookmarks;
}, 300).then(function () {
/* The stored data is automatically pushed to all of the user's
* connected resources.
*
* Publisher receives event notification
* -------------------------------------
* <message from='juliet@capulet.lit'
* to='juliet@capulet.lit/balcony'
* type='headline'
* id='rnfoo1'>
* <event xmlns='http://jabber.org/protocol/pubsub#event'>
* <items node='storage:bookmarks'>
* <item id='current'>
* <storage xmlns='storage:bookmarks'>
* <conference name='The Play&apos;s the Thing'
* autojoin='true'
* jid='theplay@conference.shakespeare.lit'>
* <nick>JC</nick>
* </conference>
* </storage>
* </item>
* </items>
* </event>
* </message>
*/
var stanza = $msg({
'from': 'dummy@localhost',
'to': 'dummy@localhost/resource',
'type': 'headline',
'id': 'rnfoo1'
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {'name': 'The Play&apos;s the Thing',
'autojoin': 'true',
'jid':'theplay@conference.shakespeare.lit'})
.c('nick').t('JC');
return test_utils.waitUntil(() => _converse.bookmarks);
}).then(function () {
// Emit here instead of mocking fetching of bookmarks.
_converse.emit('bookmarksInitialized');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.bookmarks.length).toBe(1);
expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
done();
});
/* The stored data is automatically pushed to all of the user's
* connected resources.
*
* Publisher receives event notification
* -------------------------------------
* <message from='juliet@capulet.lit'
* to='juliet@capulet.lit/balcony'
* type='headline'
* id='rnfoo1'>
* <event xmlns='http://jabber.org/protocol/pubsub#event'>
* <items node='storage:bookmarks'>
* <item id='current'>
* <storage xmlns='storage:bookmarks'>
* <conference name='The Play&apos;s the Thing'
* autojoin='true'
* jid='theplay@conference.shakespeare.lit'>
* <nick>JC</nick>
* </conference>
* </storage>
* </item>
* </items>
* </event>
* </message>
*/
var stanza = $msg({
'from': 'dummy@localhost',
'to': 'dummy@localhost/resource',
'type': 'headline',
'id': 'rnfoo1'
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {'name': 'The Play&apos;s the Thing',
'autojoin': 'true',
'jid':'theplay@conference.shakespeare.lit'})
.c('nick').t('JC');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.bookmarks.length);
}).then(function () {
expect(_converse.bookmarks.length).toBe(1);
expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
done();
});
}));
......
This diff is collapsed.
This diff is collapsed.
(function (root, factory) {
define(["jquery", "jasmine", "mock", "test-utils"], factory);
} (this, function ($, jasmine, mock, test_utils) {
var _ = converse.env._;
var $pres = converse.env.$pres;
var $msg = converse.env.$msg;
var $iq = converse.env.$iq;
var u = converse.env.utils;
const _ = converse.env._,
$pres = converse.env.$pres,
$msg = converse.env.$msg,
$iq = converse.env.$iq,
u = converse.env.utils,
Strophe = converse.env.Strophe;
describe("The Controlbox", function () {
......@@ -72,18 +73,18 @@
test_utils.createContacts(_converse, 'all').openControlBox();
_converse.emit('rosterContactsFetched');
let chatview;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, sender_jid);
return test_utils.waitUntil(() => _converse.chatboxes.length).then(() => {
const chatview = _converse.chatboxviews.get(sender_jid);
chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
var msg = $msg({
const msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
......@@ -91,10 +92,13 @@
}).c('body').t('hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator"));
}).then(() => {
spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
msg = $msg({
const msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
......@@ -102,14 +106,15 @@
}).c('body').t('hello again').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
}).then(() => {
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
chatview.model.set({'minimized': false});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}));
});
......
......@@ -311,7 +311,7 @@
}).catch(_.partial(console.error, _));
}));
it("has a method 'open' which opens and returns promise that resolves to a chat model", mock.initConverseWithPromises(
it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesInitialized'], {}, function (done, _converse) {
test_utils.openControlBox();
......
......@@ -20,15 +20,27 @@
null, ['discoInitialized'], {},
function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand').then(function () {
var chatroomview = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
var stanza = Strophe.xmlHtmlNode(
let view, stanza;
test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand')
.then(() => {
view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
stanza = Strophe.xmlHtmlNode(
`<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452" type="groupchat" from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)">
<body>negan</body>
<stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
</message>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length)
}).then(() => {
// XXX: we wait here until the first message appears before
// sending the duplicate. If we don't do that, then the
// duplicate appears before the promise for `createMessage`
// has been resolved, which means that the `isDuplicate`
// check fails because the first message doesn't exist yet.
//
// Not sure whether such a race-condition might pose a problem
// in "real-world" situations.
stanza = Strophe.xmlHtmlNode(
`<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452">
<result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
......@@ -39,10 +51,14 @@
</forwarded>
</result>
</message>`).firstElementChild;
chatroomview.model.onMessage(stanza);
expect(chatroomview.content.querySelectorAll('.chat-msg').length).toBe(1);
spyOn(view.model, 'isDuplicate').and.callThrough();
view.model.onMessage(stanza);
return test_utils.waitUntil(() => view.model.isDuplicate.calls.count());
}).then(() => {
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}))
});
......
This diff is collapsed.
......@@ -4,6 +4,7 @@
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
const Strophe = converse.env.Strophe;
describe("The Minimized Chats Widget", function () {
......@@ -43,7 +44,7 @@
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("can be toggled to hide or show minimized chats",
......@@ -74,7 +75,7 @@
}).then(() => {
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows the number messages received to minimized chats",
......@@ -99,7 +100,7 @@
contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
}
return test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
for (i=0; i<3; i++) {
chatview = _converse.chatboxviews.get(contact_jid);
chatview.model.set({'minimized': true});
......@@ -111,9 +112,11 @@
}).c('body').t('This message is sent to a minimized chatbox').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i+1).toString());
}
return test_utils.waitUntil(() => chatview.model.messages.length);
}).then(() => {
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
// Chat state notifications don't increment the unread messages counter
// <composing> state
_converse.chatboxes.onMessage($msg({
......@@ -122,7 +125,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <paused> state
_converse.chatboxes.onMessage($msg({
......@@ -131,7 +134,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <gone> state
_converse.chatboxes.onMessage($msg({
......@@ -140,7 +143,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <inactive> state
_converse.chatboxes.onMessage($msg({
......@@ -149,9 +152,9 @@
type: 'chat',
id: (new Date()).getTime()
}).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows the number messages received to minimized groupchats",
......@@ -159,7 +162,7 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
var room_jid = 'kitchen@conference.shakespeare.lit';
const room_jid = 'kitchen@conference.shakespeare.lit';
test_utils.openAndEnterChatRoom(
_converse, 'kitchen', 'conference.shakespeare.lit', 'fires').then(function () {
var view = _converse.chatboxviews.get(room_jid);
......@@ -175,11 +178,12 @@
type: 'groupchat'
}).c('body').t(message).tree();
view.model.onMessage(msg);
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe('1');
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
}));
This diff is collapsed.
......@@ -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='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</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='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</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='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</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='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</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='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>")
done();
});
......
......@@ -229,14 +229,16 @@
// have to mock stanza traffic.
}, function (done, _converse) {
let view, nick;
const room_jid = 'kitchen@conference.shakespeare.lit';
test_utils.waitUntil(() => !_.isUndefined(_converse.rooms_list_view), 500)
.then(() => test_utils.openAndEnterChatRoom(_converse, 'kitchen', 'conference.shakespeare.lit', 'romeo'))
.then(() => {
const room_jid = 'kitchen@conference.shakespeare.lit';
const view = _converse.chatboxviews.get(room_jid);
view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
const nick = mock.chatroom_names[0];
nick = mock.chatroom_names[0];
view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
......@@ -260,9 +262,11 @@
type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree()
);
var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator"));
}).then(() => {
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
const indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('1');
view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
......@@ -271,14 +275,16 @@
type: 'groupchat'
}).c('body').t('romeo: and another thing...').tree()
);
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
return test_utils.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
}).then(() => {
let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('2');
// When the chat gets maximized again, the unread indicators are removed
view.model.set({'minimized': false});
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(_.isNull(indicator_el));
room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
const room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
......
......@@ -389,7 +389,6 @@
function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render();
test_utils.openControlBox();
......@@ -430,7 +429,6 @@
function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render();
......@@ -477,7 +475,6 @@
_converse.roster_groups = true;
var groups = ['colleagues', 'friends'];
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox();
_converse.rosterview.render();
......@@ -576,7 +573,6 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox();
_converse.roster.create({
......@@ -726,7 +722,6 @@
var i, t;
test_utils.openControlBox();
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
for (i=0; i<mock.pend_names.length; i++) {
_converse.roster.create({
......@@ -908,7 +903,6 @@
test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group li').length, 700)
.then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
......@@ -935,7 +929,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
......@@ -963,7 +956,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
......@@ -991,7 +983,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
......@@ -1020,7 +1011,6 @@
}, 500)
.then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
......@@ -1151,7 +1141,6 @@
names.push($(item).text().replace(/^\s+|\s+$/g, ''));
}
};
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
for (i=0; i<mock.req_names.length; i++) {
......
......@@ -78,8 +78,9 @@
remove_contact_button.click();
return test_utils.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
}).then(function () {
expect(document.querySelector('.alert-danger .modal-title').textContent).toBe("Error");
expect(document.querySelector('.modal:not(#user-profile-modal) .modal-body p').textContent.trim())
const header = document.querySelector('.alert-danger .modal-title');
expect(header.textContent).toBe("Error");
expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
.toBe("Sorry, there was an error while trying to remove Max Frankfurter as a contact.");
document.querySelector('.alert-danger button.close').click();
const show_modal_button = view.el.querySelector('.show-user-details-modal');
......
......@@ -577,8 +577,10 @@
// Add a handler for bookmarks pushed from other connected clients
// (from the same user obviously)
_converse.connection.addHandler((message) => {
if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
_converse.bookmarks.createBookmarksFromStanza(message);
if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) {
_converse.api.waitUntil('bookmarksInitialized')
.then(() => _converse.bookmarks.createBookmarksFromStanza(message))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
}, null, 'message', 'headline', null, _converse.bare_jid);
});
......
This diff is collapsed.
......@@ -110,9 +110,11 @@
emojione.ascii = true;
function onWindowStateChanged (data) {
_converse.chatboxviews.each(function (chatboxview) {
chatboxview.onWindowStateChanged(data.state);
});
if (_converse.chatboxviews) {
_converse.chatboxviews.each(chatboxview => {
chatboxview.onWindowStateChanged(data.state);
});
}
}
_converse.api.listen.on('windowStateChanged', onWindowStateChanged);
......@@ -228,32 +230,30 @@
events: {
'click button.remove-contact': 'removeContact',
'click button.refresh-contact': 'refreshContact'
'click button.refresh-contact': 'refreshContact',
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('contactAdded', this.registerContactEventHandlers, this);
this.model.on('change', this.render, this);
this.registerContactEventHandlers();
_converse.emit('userDetailsModalInitialized', this.model);
},
toHTML () {
return tpl_user_details_modal(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'_': _,
'__': __,
'view': this,
'_converse': _converse,
'allow_contact_removal': _converse.allow_contact_removal,
'alt_profile_image': __("The User's Profile Image"),
'display_name': this.model.getDisplayName(),
'is_roster_contact': !_.isUndefined(this.model.contact),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_jid': __('Jabber ID'),
'label_nickname': __('Nickname'),
'label_remove': __('Remove as contact'),
'label_refresh': __('Refresh'),
'label_role': __('Role'),
'label_url': __('URL')
'utils': u
}));
},
......@@ -379,6 +379,7 @@
this.addSpoilerButton(options);
this.addFileUploadButton();
this.insertEmojiPicker();
_converse.emit('renderToolbar', this);
return this;
},
......@@ -737,14 +738,16 @@
if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) &&
previous_el.getAttribute('data-from') === from &&
date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes'))) {
date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', el);
}
if (!next_el) { return; }
if (!u.hasClass('chat-msg--action', 'el') &&
next_el.getAttribute('data-from') === from &&
moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', next_el);
} else {
u.removeClass('chat-msg--followup', next_el);
......
......@@ -86,6 +86,7 @@
'converse-muc',
'converse-muc-views',
'converse-notification',
'converse-omemo',
'converse-oauth',
'converse-ping',
'converse-profile',
......
......@@ -6,7 +6,6 @@
/* This is a Converse.js plugin which add support for XEP-0030: Service Discovery */
/*global Backbone, define, window */
(function (root, factory) {
define(["converse-core", "sizzle"], factory);
}(this, function (converse, sizzle) {
......
......@@ -128,13 +128,21 @@
//
// New functions which don't exist yet can also be added.
ChatBox: {
getMessageAttributesFromStanza (message, original_stanza) {
const attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
const archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
attrs.archive_id = archive_id;
function _process (attrs) {
const archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
attrs.archive_id = archive_id;
}
return attrs;
}
const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments)
if (result instanceof Promise) {
return new Promise((resolve, reject) => result.then((attrs) => resolve(_process(attrs))).catch(reject));
} else {
return _process(result);
}
return attrs;
}
},
......
......@@ -158,7 +158,8 @@
_.partial(u.renderImageURL, _converse))(url);
}
let text = this.model.get('message');
const encrypted = this.model.get('encrypted');
let text = encrypted ? this.model.get('plaintext') : this.model.get('message');
if (is_me_message) {
text = text.replace(/^\/me/, '');
}
......
......@@ -428,11 +428,7 @@
this.toggleview = new _converse.MinimizedChatsToggleView({
'model': new _converse.MinimizedChatsToggle({'id': id})
});
try {
this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id);
} catch (e) {
debugger;
}
this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id);
this.toggleview.model.fetch();
},
......
......@@ -65,6 +65,16 @@
}
});
_converse.api.listen.on('afterTearDown', () => {
if (!_converse.chatboxviews) {
return;
}
const container = _converse.chatboxviews.el.querySelector("#converse-modals");
if (container) {
container.innerHTML = '';
}
});
/************************ BEGIN API ************************/
// We extend the default converse.js API to add methods specific to MUC chat rooms.
......
......@@ -926,7 +926,9 @@
if (sender === '') {
return;
}
this.incrementUnreadMsgCounter(this.createMessage(stanza, original_stanza));
this.createMessage(stanza, original_stanza)
.then(msg => this.incrementUnreadMsgCounter(msg))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
if (sender !== this.get('nick')) {
// We only emit an event if it's not our own message
......
This diff is collapsed.
......@@ -237,7 +237,7 @@
this.trigger('showReceivedOTRMessage', msg);
});
this.otr.on('io', (msg) => {
this.sendMessage(new _converse.Message({'message':msg}));
this.sendMessage({'message':msg});
});
this.otr.on('error', (msg) => {
this.trigger('showOTRError', msg);
......
......@@ -48,32 +48,41 @@
events: {
'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted'
'submit .profile-form': 'onFormSubmitted'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
_converse.emit('profileModalInitialized', this.model);
},
toHTML () {
return tpl_profile_modal(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'label_role': __('Role'),
'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'),
'label_save': __('Save'),
'label_url': __('URL'),
'alt_avatar': __('Your avatar image')
'utils': u,
'view': this
}));
},
afterRender () {
this.tabs = _.map(this.el.querySelectorAll('.nav-item'), (tab) => new bootstrap.Tab(tab));
},
openFileSelection (ev) {
ev.preventDefault();
this.el.querySelector('input[type="file"]').click();
......
......@@ -23,7 +23,7 @@ if (typeof define !== 'undefined') {
"converse-muc-views",
"converse-muc-views", // Views related to MUC
"converse-notification", // HTML5 Notifications
"converse-oauth",
"converse-omemo",
"converse-ping", // XEP-0199 XMPP Ping
"converse-register", // XEP-0077 In-band registration
"converse-roomslist", // Show currently open chat rooms
......
<div class="message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
<div class="message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}"
data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}" data-encrypted="{{{o.is_encrypted}}}">
{[ if (o.type !== 'headline' && !o.is_me_message) { ]}
<canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>
{[ } ]}
......@@ -9,6 +10,7 @@
{[o.roles.forEach(function (role) { ]} <span class="badge badge-secondary">{{{role}}}</span> {[ }); ]}
</span>
{[ if (!o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
{[ if (o.is_encrypted) { ]}<span class="fa fa-lock"></span>{[ } ]}
</span>
{[ if (!o.is_me_message) { ]}<div class="chat-msg__body">{[ } ]}
{[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg__edit-modal"></i> {[ } ]}
......
This diff is collapsed.
<li class="toggle-omemo fa {[ if (o.omemo_active) { ]} fa-lock {[ } else { ]} fa-unlock {[ } ]}" title="{{{o.__('Messages are being sent in plaintext')}}}"></li>
<div class="modal fade" id="user-profile-modal" tabindex="-1" role="dialog" aria-labelledby="user-profile-modal-label" aria-hidden="true">
<div class="modal fade" id="user-details-modal" tabindex="-1" role="dialog" aria-labelledby="user-details-modal-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="user-profile-modal-label">{{{o.display_name}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
<h5 class="modal-title" id="user-details-modal-label">{{{o.display_name}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.__('Close')}}}"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
{[ if (o.image) { ]}
<img alt="{{{o.alt_profile_image}}}"
<img alt="{{{o.__('The User\'s Profile Image')}}}"
class="img-thumbnail avatar align-self-center mb-3"
height="100" width="100" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]}
{[ if (o.fullname) { ]}
<p><label>{{{o.label_fullname}}}:</label>&nbsp;{{{o.fullname}}}</p>
<p><label>{{{o.__('Full Name')}}}:</label>&nbsp;{{{o.fullname}}}</p>
{[ } ]}
<p><label>{{{o.label_jid}}}:</label>&nbsp;{{{o.jid}}}</p>
<p><label>{{{o.__('XMPP Address')}}}:</label>&nbsp;{{{o.jid}}}</p>
{[ if (o.nickname) { ]}
<p><label>{{{o.label_nickname}}}:</label>&nbsp;{{{o.nickname}}}</p>
<p><label>{{{o.__('Nickname')}}}:</label>&nbsp;{{{o.nickname}}}</p>
{[ } ]}
{[ if (o.url) { ]}
<p><label>{{{o.label_url}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p>
<p><label>{{{o.__('URL')}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p>
{[ } ]}
{[ if (o.email) { ]}
<p><label>{{{o.label_email}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p>
<p><label>{{{o.__('Email')}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p>
{[ } ]}
{[ if (o.role) { ]}
<p><label>{{{o.label_role}}}:</label>&nbsp;{{{o.role}}}</p>
<p><label>{{{o.__('Role')}}}:</label>&nbsp;{{{o.role}}}</p>
{[ } ]}
{[ if (o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse)) { ]}
<hr>
<ul class="list-group fingerprints">
<li class="list-group-item active">{{{o.__('OMEMO Fingerprints')}}}</li>
{[ if (!o.view.devicelist.devices) { ]}
<li class="list-group-item"><span class="spinner fa fa-spinner centered"/></li>
{[ } ]}
{[ if (o.view.devicelist.devices) { ]}
{[ o.view.devicelist.devices.each(function (device) { ]}
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="list-group-item">
<form class="fingerprint-trust">
<div class="btn-group btn-group-toggle">
<label class="btn btn--small {[ if (device.get('trusted') !== -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}">
<input type="radio" name="{{{device.get('id')}}}" value="1"
{[ if (device.get('trusted') !== -1) { ]} checked="checked" {[ } ]}>{{{o.__('Trusted')}}}
</label>
<label class="btn btn--small {[ if (device.get('trusted') === -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}">
<input type="radio" name="{{{device.get('id')}}}" value="-1"
{[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}}
</label>
</div>
<span class="fingerprint">{{{o.utils.formatFingerprint(device.get('bundle').fingerprint)}}}</span>
</form>
</li>
{[ } ]}
{[ }); ]}
{[ } ]}
</ul>
{[ } ]}
</div>
<div class="modal-footer">
{[ if (o.allow_contact_removal && o.is_roster_contact) { ]}
<button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.label_remove}}}</button>
<button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.__('Remove as contact')}}}</button>
{[ } ]}
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.label_refresh}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.label_close}}}</button>
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.__('Refresh')}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.__('Close')}}}</button>
</div>
</div>
</div>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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