parent
95e648e79f
commit
133df99aec
6
package-lock.json
generated
6
package-lock.json
generated
@ -1929,6 +1929,12 @@
|
||||
"integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
|
||||
"dev": true
|
||||
},
|
||||
"filesize": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
|
||||
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
|
||||
"dev": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
|
||||
|
@ -37,16 +37,17 @@
|
||||
"backbone": "1.3.3",
|
||||
"backbone.browserStorage": "0.0.3",
|
||||
"backbone.nativeview": "^0.3.3",
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap.native": "^2.0.21",
|
||||
"backbone.overview": "1.0.2",
|
||||
"backbone.vdomview": "1.0.1",
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap.native": "^2.0.21",
|
||||
"bourbon": "^4.3.2",
|
||||
"clean-css-cli": "^4.0.10",
|
||||
"emojione": "^3.0.3",
|
||||
"es6-promise": "^4.1.0",
|
||||
"eslint": "4.19.0",
|
||||
"eslint-plugin-lodash": "^2.3.3",
|
||||
"filesize": "^3.6.1",
|
||||
"font-awesome": "^4.7.0",
|
||||
"http-server": "^0.10.0",
|
||||
"install": "^0.9.5",
|
||||
|
@ -765,7 +765,7 @@
|
||||
// We send another message, for which an error will
|
||||
// not be received, to test that errors appear
|
||||
// after the relevant message.
|
||||
msg_text = 'This message will be sent, and not receive an error';
|
||||
msg_text = 'This message will be sent, and also receive an error';
|
||||
message = view.model.messages.create({
|
||||
'msgid': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104',
|
||||
'fullname': fullname,
|
||||
@ -802,12 +802,6 @@
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
expect($chat_content.find('.chat-error').text()).toEqual(error_txt);
|
||||
|
||||
/* Incoming error messages that are not tied to a
|
||||
* certain show message (via the msgid attribute),
|
||||
* are not shown at all. The reason for this is
|
||||
* that we may get error messages for chat state
|
||||
* notifications as well.
|
||||
*/
|
||||
stanza = $msg({
|
||||
'to': _converse.connection.jid,
|
||||
'type':'error',
|
||||
@ -819,7 +813,36 @@
|
||||
.c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
|
||||
.t('Server-to-server connection failed: Connecting failed: connection timeout');
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
expect($chat_content.find('.chat-error').length).toEqual(1);
|
||||
expect($chat_content.find('.chat-error').length).toEqual(2);
|
||||
|
||||
// If the last message is already an error message,
|
||||
// then we don't render it another time.
|
||||
stanza = $msg({
|
||||
'to': _converse.connection.jid,
|
||||
'type':'error',
|
||||
'id':'another-unused-id',
|
||||
'from': sender_jid
|
||||
})
|
||||
.c('error', {'type': 'cancel'})
|
||||
.c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
|
||||
.c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
|
||||
.t('Server-to-server connection failed: Connecting failed: connection timeout');
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
expect($chat_content.find('.chat-error').length).toEqual(2);
|
||||
|
||||
// A different error message will however render
|
||||
stanza = $msg({
|
||||
'to': _converse.connection.jid,
|
||||
'type':'error',
|
||||
'id':'another-id',
|
||||
'from': sender_jid
|
||||
})
|
||||
.c('error', {'type': 'cancel'})
|
||||
.c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
|
||||
.c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
|
||||
.t('Something else went wrong as well');
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
expect($chat_content.find('.chat-error').length).toEqual(3);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
@ -204,9 +204,9 @@
|
||||
done();
|
||||
}));
|
||||
|
||||
describe("when clicked", function () {
|
||||
describe("when clicked and a file chosen", function () {
|
||||
|
||||
it("a file upload slot is requested", mock.initConverseWithAsync(function (done, _converse) {
|
||||
it("is uploaded and sent out", mock.initConverseWithAsync(function (done, _converse) {
|
||||
test_utils.waitUntilDiscoConfirmed(
|
||||
_converse, _converse.domain,
|
||||
[{'category': 'server', 'type':'IM'}],
|
||||
@ -311,6 +311,135 @@
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it("shows and error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) {
|
||||
var IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
var IQ_ids = _converse.connection.IQ_ids;
|
||||
var send_backup = XMLHttpRequest.prototype.send;
|
||||
|
||||
test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []).then(function () {
|
||||
test_utils.waitUntil(function () {
|
||||
return _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector(
|
||||
'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
|
||||
}).length > 0;
|
||||
}, 300).then(function () {
|
||||
var stanza = _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector(
|
||||
'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
|
||||
})[0];
|
||||
var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
|
||||
|
||||
stanza = $iq({
|
||||
'type': 'result',
|
||||
'from': 'localhost',
|
||||
'to': 'dummy@localhost/resource',
|
||||
'id': info_IQ_id
|
||||
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {
|
||||
'category': 'server',
|
||||
'type': 'im'}).up()
|
||||
.c('feature', {
|
||||
'var': 'http://jabber.org/protocol/disco#info'}).up()
|
||||
.c('feature', {
|
||||
'var': 'http://jabber.org/protocol/disco#items'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
|
||||
_converse.api.disco.entities.get().then(function(entities) {
|
||||
expect(entities.length).toBe(2);
|
||||
expect(_.includes(entities.pluck('jid'), 'localhost')).toBe(true);
|
||||
expect(_.includes(entities.pluck('jid'), 'dummy@localhost')).toBe(true);
|
||||
|
||||
expect(entities.get(_converse.domain).features.length).toBe(2);
|
||||
expect(entities.get(_converse.domain).identities.length).toBe(1);
|
||||
|
||||
return test_utils.waitUntil(function () {
|
||||
// Converse.js sees that the entity has a disco#items feature,
|
||||
// so it will make a query for it.
|
||||
return _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]');
|
||||
}).length > 0;
|
||||
}, 300);
|
||||
});
|
||||
}).then(function () {
|
||||
var stanza = _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]');
|
||||
})[0];
|
||||
var items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
|
||||
stanza = $iq({
|
||||
'type': 'result',
|
||||
'from': 'localhost',
|
||||
'to': 'dummy@localhost/resource',
|
||||
'id': items_IQ_id
|
||||
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
|
||||
.c('item', {
|
||||
'jid': 'upload.localhost',
|
||||
'name': 'HTTP File Upload'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
|
||||
_converse.api.disco.entities.get().then(function (entities) {
|
||||
expect(entities.length).toBe(2);
|
||||
expect(entities.get('localhost').items.length).toBe(1);
|
||||
return test_utils.waitUntil(function () {
|
||||
// Converse.js sees that the entity has a disco#info feature,
|
||||
// so it will make a query for it.
|
||||
return _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
|
||||
}).length > 0;
|
||||
}, 300);
|
||||
});
|
||||
}).then(function () {
|
||||
var stanza = _.filter(IQ_stanzas, function (iq) {
|
||||
return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
|
||||
})[0];
|
||||
var IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
|
||||
expect(stanza.toLocaleString()).toBe(
|
||||
"<iq from='dummy@localhost/resource' to='upload.localhost' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
|
||||
"<query xmlns='http://jabber.org/protocol/disco#info'/>"+
|
||||
"</iq>");
|
||||
|
||||
// Upload service responds and reports a maximum file size of 5MiB
|
||||
stanza = $iq({'type': 'result', 'to': 'dummy@localhost/resource', 'id': IQ_id, 'from': 'upload.localhost'})
|
||||
.c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up()
|
||||
.c('feature', {'var':'urn:xmpp:http:upload:0'}).up()
|
||||
.c('x', {'type':'result', 'xmlns':'jabber:x:data'})
|
||||
.c('field', {'var':'FORM_TYPE', 'type':'hidden'})
|
||||
.c('value').t('urn:xmpp:http:upload:0').up().up()
|
||||
.c('field', {'var':'max-file-size'})
|
||||
.c('value').t('5242880');
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
|
||||
_converse.api.disco.entities.get().then(function (entities) {
|
||||
expect(entities.get('localhost').items.get('upload.localhost').identities.where({'category': 'store'}).length).toBe(1);
|
||||
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(
|
||||
function (result) {
|
||||
test_utils.createContacts(_converse, 'current');
|
||||
var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
test_utils.openChatBoxFor(_converse, contact_jid);
|
||||
var view = _converse.chatboxviews.get(contact_jid);
|
||||
var file = {
|
||||
'type': 'image/jpeg',
|
||||
'size': '5242881',
|
||||
'lastModifiedDate': "",
|
||||
'name': "my-juliet.jpg"
|
||||
};
|
||||
view.model.sendFiles([file]);
|
||||
return test_utils.waitUntil(function () {
|
||||
return view.el.querySelectorAll('.message').length;
|
||||
}).then(function () {
|
||||
const messages = view.el.querySelectorAll('.message.chat-error');
|
||||
expect(messages.length).toBe(1);
|
||||
expect(messages[0].textContent).toBe(
|
||||
'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.');
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
||||
})
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,7 @@ require.config({
|
||||
"emojione": "node_modules/emojione/lib/js/emojione",
|
||||
"es6-promise": "node_modules/es6-promise/dist/es6-promise.auto",
|
||||
"eventemitter": "node_modules/otr/build/dep/eventemitter",
|
||||
"filesize": "node_modules/filesize/lib/filesize",
|
||||
"form-utils": "src/utils/form",
|
||||
"i18n": "src/i18n",
|
||||
"jed": "node_modules/jed/jed",
|
||||
|
@ -8,10 +8,11 @@
|
||||
define([
|
||||
"converse-core",
|
||||
"emojione",
|
||||
"filesize",
|
||||
"tpl!chatboxes",
|
||||
"backbone.overview"
|
||||
], factory);
|
||||
}(this, function (converse, emojione, tpl_chatboxes) {
|
||||
}(this, function (converse, emojione, filesize, tpl_chatboxes) {
|
||||
"use strict";
|
||||
|
||||
const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, utils, _ } = converse.env;
|
||||
@ -289,21 +290,36 @@
|
||||
|
||||
sendFiles (files) {
|
||||
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => {
|
||||
const slot_request_url = _.get(result.pop(), 'id');
|
||||
const item = result.pop(),
|
||||
data = item.dataforms.where({'FORM_TYPE': {'value': Strophe.NS.HTTPUPLOAD, 'type': "hidden"}}).pop(),
|
||||
max_file_size = window.parseInt(_.get(data, 'attributes.max-file-size.value')),
|
||||
slot_request_url = _.get(item, 'id');
|
||||
|
||||
if (!slot_request_url) {
|
||||
const err_msg = __("Sorry, looks like file upload is not supported by your server.");
|
||||
return this.trigger('showHelpMessages', [err_msg], 'error');
|
||||
this.messages.create({
|
||||
'message': __("Sorry, looks like file upload is not supported by your server."),
|
||||
'type': 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
_.each(files, (file) => {
|
||||
this.messages.create(
|
||||
_.extend(
|
||||
this.getOutgoingMessageAttributes(), {
|
||||
'file': file,
|
||||
'progress': 0,
|
||||
'slot_request_url': slot_request_url,
|
||||
'type': this.get('message_type'),
|
||||
})
|
||||
);
|
||||
if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) {
|
||||
return this.messages.create({
|
||||
'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.',
|
||||
file.name, filesize(max_file_size)),
|
||||
'type': 'error',
|
||||
});
|
||||
} else {
|
||||
this.messages.create(
|
||||
_.extend(
|
||||
this.getOutgoingMessageAttributes(), {
|
||||
'file': file,
|
||||
'progress': 0,
|
||||
'slot_request_url': slot_request_url,
|
||||
'type': this.get('message_type'),
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
||||
},
|
||||
|
@ -529,33 +529,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
showMessage (message) {
|
||||
/* Inserts a chat message into the content area of the chat box.
|
||||
* Will also insert a new day indicator if the message is on a
|
||||
* different day.
|
||||
*
|
||||
* The message to show may either be newer than the newest
|
||||
* message, or older than the oldest message.
|
||||
*
|
||||
* Parameters:
|
||||
* (Backbone.Model) message: The message object
|
||||
*/
|
||||
const view = new _converse.MessageView({'model': message}),
|
||||
current_msg_date = moment(message.get('time')) || moment,
|
||||
previous_msg_date = this.getLastMessageDate(current_msg_date),
|
||||
message_el = view.el;
|
||||
|
||||
if (_.isNull(previous_msg_date)) {
|
||||
this.content.insertAdjacentElement('afterbegin', message_el);
|
||||
} else {
|
||||
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop();
|
||||
previous_msg_el.insertAdjacentElement('afterend', message_el);
|
||||
}
|
||||
this.insertDayIndicator(message_el);
|
||||
this.clearChatStateNotification(message.get('from'));
|
||||
this.setScrollPosition(message_el);
|
||||
},
|
||||
|
||||
setScrollPosition (message_el) {
|
||||
/* Given a newly inserted message, determine whether we
|
||||
* should keep the scrollbar in place (so as to not scroll
|
||||
@ -655,8 +628,50 @@
|
||||
return !u.isVisible(this.el);
|
||||
},
|
||||
|
||||
handleTextMessage (message) {
|
||||
this.showMessage(message);
|
||||
insertMessage (view) {
|
||||
/* Given a view representing a message, insert it inot the
|
||||
* content area of the chat box.
|
||||
*
|
||||
* Parameters:
|
||||
* (Backbone.View) message: The message Backbone.View
|
||||
*/
|
||||
if (view.model.get('type') === 'error') {
|
||||
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
|
||||
if (previous_msg_el) {
|
||||
return previous_msg_el.insertAdjacentElement('afterend', view.el);
|
||||
}
|
||||
}
|
||||
const current_msg_date = moment(view.model.get('time')) || moment,
|
||||
previous_msg_date = this.getLastMessageDate(current_msg_date);
|
||||
|
||||
if (_.isNull(previous_msg_date)) {
|
||||
this.content.insertAdjacentElement('afterbegin', view.el);
|
||||
} else {
|
||||
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop();
|
||||
if (view.model.get('type') === 'error' &&
|
||||
u.hasClass('chat-error', previous_msg_el) &&
|
||||
previous_msg_el.textContent === view.model.get('message')) {
|
||||
// We don't show a duplicate error message
|
||||
return;
|
||||
}
|
||||
previous_msg_el.insertAdjacentElement('afterend', view.el);
|
||||
}
|
||||
},
|
||||
|
||||
showMessage (message) {
|
||||
/* Inserts a chat message into the content area of the chat box.
|
||||
*
|
||||
* Will also insert a new day indicator if the message is on a
|
||||
* different day.
|
||||
*
|
||||
* Parameters:
|
||||
* (Backbone.Model) message: The message object
|
||||
*/
|
||||
const view = new _converse.MessageView({'model': message});
|
||||
this.insertMessage(view);
|
||||
this.insertDayIndicator(view.el);
|
||||
this.clearChatStateNotification(message.get('from'));
|
||||
this.setScrollPosition(view.el);
|
||||
|
||||
if (u.isNewMessage(message)) {
|
||||
if (message.get('sender') === 'me') {
|
||||
@ -676,21 +691,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
handleErrorMessage (message) {
|
||||
const message_el = this.content.querySelector(`[data-msgid="${message.get('msgid')}"]`);
|
||||
if (!_.isNull(message_el)) {
|
||||
message_el.insertAdjacentHTML(
|
||||
'afterend',
|
||||
tpl_info({
|
||||
'extra_classes': 'chat-error',
|
||||
'message': message.get('message'),
|
||||
'isodate': moment().format(),
|
||||
'data': ''
|
||||
}));
|
||||
this.scrollDown();
|
||||
}
|
||||
},
|
||||
|
||||
onMessageAdded (message) {
|
||||
/* Handler that gets called when a new message object is created.
|
||||
*
|
||||
@ -702,13 +702,13 @@
|
||||
delete this.clear_status_timeout;
|
||||
}
|
||||
if (message.get('type') === 'error') {
|
||||
this.handleErrorMessage(message);
|
||||
this.showMessage(message);
|
||||
} else {
|
||||
if (message.get('chat_state')) {
|
||||
this.showChatStateNotification(message);
|
||||
}
|
||||
if (message.get('file') || message.get('message')) {
|
||||
this.handleTextMessage(message);
|
||||
this.showMessage(message);
|
||||
}
|
||||
}
|
||||
_converse.emit('messageAdded', {
|
||||
|
@ -11,6 +11,7 @@
|
||||
"emojione",
|
||||
"tpl!action",
|
||||
"tpl!file",
|
||||
"tpl!info",
|
||||
"tpl!message",
|
||||
"tpl!spoiler_message"
|
||||
], factory);
|
||||
@ -20,6 +21,7 @@
|
||||
emojione,
|
||||
tpl_action,
|
||||
tpl_file,
|
||||
tpl_info,
|
||||
tpl_message,
|
||||
tpl_spoiler_message
|
||||
) {
|
||||
@ -53,7 +55,10 @@
|
||||
render () {
|
||||
if (this.model.get('file') && !this.model.get('message')) {
|
||||
return this.renderFileUploadProgresBar();
|
||||
} else if (this.model.get('type') === 'error') {
|
||||
return this.renderErrorMessage();
|
||||
}
|
||||
|
||||
let template, username,
|
||||
text = this.model.get('message');
|
||||
|
||||
@ -91,6 +96,10 @@
|
||||
u.renderImageURLs(msg_content).then(() => {
|
||||
this.model.collection.trigger('rendered');
|
||||
});
|
||||
return this.replaceElement(msg);
|
||||
},
|
||||
|
||||
replaceElement (msg) {
|
||||
if (!_.isNil(this.el.parentElement)) {
|
||||
this.el.parentElement.replaceChild(msg, this.el);
|
||||
}
|
||||
@ -98,13 +107,20 @@
|
||||
return this.el;
|
||||
},
|
||||
|
||||
renderErrorMessage () {
|
||||
const moment_time = moment(this.model.get('time')),
|
||||
msg = u.stringToElement(
|
||||
tpl_info(_.extend(this.model.toJSON(), {
|
||||
'extra_classes': 'chat-error',
|
||||
'isodate': moment_time.format(),
|
||||
'data': ''
|
||||
})));
|
||||
return this.replaceElement(msg);
|
||||
},
|
||||
|
||||
renderFileUploadProgresBar () {
|
||||
const msg = u.stringToElement(tpl_file(this.model.toJSON()));
|
||||
if (!_.isNil(this.el.parentElement)) {
|
||||
this.el.parentElement.replaceChild(msg, this.el);
|
||||
}
|
||||
this.setElement(msg);
|
||||
return this.el;
|
||||
return this.replaceElement(msg);
|
||||
},
|
||||
|
||||
isMeCommand () {
|
||||
|
Loading…
Reference in New Issue
Block a user