From c01e9f82655bc06719a9677312384b5781e7bc3c Mon Sep 17 00:00:00 2001 From: JC Brand Date: Tue, 17 Apr 2018 15:17:39 +0200 Subject: [PATCH] Move methods from chatbox view to message view Specifically the methods related to requesting an upload slot and uploading a file. Also show a progress indicator while a file is being uploaded. Updates #161 --- docs/source/configuration.rst | 11 -- spec/chatbox.js | 3 +- spec/chatroom.js | 9 +- spec/controlbox.js | 2 + spec/http-file-upload.js | 41 +++-- src/converse-chatboxes.js | 242 ++++++++++++++++++----------- src/converse-chatview.js | 35 +---- src/converse-core.js | 3 + src/converse-mam.js | 4 +- src/converse-message-view.js | 126 +++++++++------ src/converse-muc.js | 1 + src/templates/action.html | 6 +- src/templates/file.html | 3 + src/templates/message.html | 4 +- src/templates/spoiler_message.html | 4 +- src/utils/core.js | 18 ++- 16 files changed, 305 insertions(+), 207 deletions(-) create mode 100644 src/templates/file.html diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index c96ed7db8..73ed0c583 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -1214,17 +1214,6 @@ the operating system or browser (which might not support emoji). See also `emojione_image_path`_. - -show_message_load_animation ---------------------------- -* Default: ``false`` - -Determines whether a CSS3 background-color fade-out animation is shown when messages -appear in chats. - -Set to ``false`` by default since this option causes performance issues on Firefox. - - show_only_online_users ---------------------- diff --git a/spec/chatbox.js b/spec/chatbox.js index 1195e5eca..a442cb796 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -705,7 +705,7 @@ expect(chatbox.messages.length).toEqual(1); var msg_obj = chatbox.messages.models[0]; expect(msg_obj.get('message')).toEqual(message); - expect(msg_obj.get('fullname')).toEqual(sender_jid); + expect(msg_obj.get('fullname')).toEqual(undefined); expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('delayed')).toEqual(false); // Now check that the message appears inside the chatbox in the DOM @@ -714,6 +714,7 @@ expect(msg_txt).toEqual(message); var sender_txt = $chat_content.find('span.chat-msg-them').text(); expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy(); + expect(sender_txt.indexOf('max.frankfurter@localhost')).not.toBe(-1); done(); })); }); diff --git a/spec/chatroom.js b/spec/chatroom.js index 34a9f0411..6a2ad1a25 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -862,10 +862,10 @@ var message = '/me is tired'; var nick = mock.chatroom_names[0], msg = $msg({ - from: 'lounge@localhost/'+nick, - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' + 'from': 'lounge@localhost/'+nick, + 'id': (new Date()).getTime(), + 'to': 'dummy@localhost', + 'type': 'groupchat' }).c('body').t(message).tree(); view.model.onMessage(msg); expect(_.includes($(view.el).find('.chat-msg-author').text(), '**Dyon van de Wege')).toBeTruthy(); @@ -3306,6 +3306,7 @@ to: 'dummy@localhost', type: 'groupchat' }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + view.model.onMessage(msg); // Check that the notification appears inside the chatbox in the DOM diff --git a/spec/controlbox.js b/spec/controlbox.js index 2667b9b61..ade8f68c9 100644 --- a/spec/controlbox.js +++ b/spec/controlbox.js @@ -241,6 +241,7 @@ xhr.onload(); } }; + const XMLHttpRequestBackup = window.XMLHttpRequest; window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest'); XMLHttpRequest.and.callFake(function () { return xhr; @@ -288,6 +289,7 @@ ""+ ""+ ""); + window.XMLHttpRequest = XMLHttpRequestBackup; done(); }); })); diff --git a/spec/http-file-upload.js b/spec/http-file-upload.js index 8c5d789fd..a717695aa 100644 --- a/spec/http-file-upload.js +++ b/spec/http-file-upload.js @@ -204,12 +204,14 @@ })); describe("when clicked", function () { + it("a file upload slot is requested", mock.initConverseWithAsync(function (done, _converse) { test_utils.waitUntilDiscoConfirmed( _converse, _converse.domain, [{'category': 'server', 'type':'IM'}], ['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { + var send_backup = XMLHttpRequest.prototype.send; var IQ_stanzas = _converse.connection.IQ_stanzas; test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () { @@ -224,11 +226,11 @@ 'lastModifiedDate': "", 'name': "my-juliet.jpg" }; - view.model.sendFile(file); + view.model.sendFiles([file]); return test_utils.waitUntil(function () { return _.filter(IQ_stanzas, function (iq) { return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); - }); + }).length > 0; }).then(function () { var iq = IQ_stanzas.pop(); expect(iq.toLocaleString()).toBe( @@ -243,6 +245,9 @@ "content-type='image/jpeg'/>"+ ""); + var base_url = document.URL.split(window.location.pathname)[0]; + var message = base_url+"/logo/conversejs-filled.svg"; + var stanza = Strophe.xmlHtmlNode( "Basic Base64String=="+ "
foo=bar; user=romeo
"+ " "+ - " "+ + " "+ ""+ "
").firstElementChild; - spyOn(view.model, 'uploadFile').and.callFake(function () { - return new window.Promise((resolve, reject) => { resolve(); }); + + spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { + const message = view.model.messages.at(0); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); + message.set('progress', 0.5); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); + message.set('progress', 1); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); + message.save({ + 'upload': _converse.SUCCESS, + 'message': message.get('get') + }); }); var sent_stanza; spyOn(_converse.connection, 'send').and.callFake(function (stanza) { @@ -267,19 +282,27 @@ return test_utils.waitUntil(function () { return sent_stanza; - }).then(function () { - expect(view.model.uploadFile).toHaveBeenCalled(); + }, 1000).then(function () { expect(sent_stanza.toLocaleString()).toBe( ""+ - "https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg"+ + ""+message+""+ ""+ ""+ - "https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg"+ + ""+message+""+ ""+ ""); + return test_utils.waitUntil(function () { + return view.el.querySelector('.chat-image'); + }, 1000); + }).then(function () { + // Check that the image renders + expect(view.el.querySelector('.chat-message .chat-msg-content').innerHTML).toEqual( + ''+ + '') + XMLHttpRequest.prototype.send = send_backup; done(); }); }); diff --git a/src/converse-chatboxes.js b/src/converse-chatboxes.js index b7974ed37..0d56f38e8 100644 --- a/src/converse-chatboxes.js +++ b/src/converse-chatboxes.js @@ -7,15 +7,19 @@ (function (root, factory) { define([ "converse-core", + "emojione", "tpl!chatboxes", "backbone.overview" ], factory); -}(this, function (converse, tpl_chatboxes) { +}(this, function (converse, emojione, tpl_chatboxes) { "use strict"; const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, utils, _ } = converse.env; + const u = converse.env.utils; + Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob'); + converse.plugins.add('converse-chatboxes', { overrides: { @@ -74,10 +78,100 @@ _converse.Message = Backbone.Model.extend({ - defaults(){ + + defaults () { return { - msgid: _converse.connection.getUniqueId() + 'msgid': _converse.connection.getUniqueId(), + 'time': moment().format() }; + }, + + initialize () { + if (this.get('file')) { + this.on('change:put', this.uploadFile, this); + + if (!_.includes([_converse.SUCCESS, _converse.FAILURE], this.get('upload'))) { + this.getRequestSlotURL(); + } + } + }, + + sendSlotRequestStanza () { + /* Send out an IQ stanza to request a file upload slot. + * + * https://xmpp.org/extensions/xep-0363.html#request + */ + const file = this.get('file'); + return new Promise((resolve, reject) => { + const iq = converse.env.$iq({ + 'from': _converse.jid, + 'to': this.get('slot_request_url'), + 'type': 'get' + }).c('request', { + 'xmlns': Strophe.NS.HTTPUPLOAD, + 'filename': file.name, + 'size': file.size, + 'content-type': file.type + }) + _converse.connection.sendIQ(iq, resolve, reject); + }); + }, + + getRequestSlotURL () { + this.sendSlotRequestStanza().then((stanza) => { + const slot = stanza.querySelector('slot'); + if (slot) { + this.save({ + 'get': slot.querySelector('get').getAttribute('url'), + 'put': slot.querySelector('put').getAttribute('url'), + }); + } else { + return this.save({ + 'type': 'error', + 'message': __("Sorry, could not determine upload URL.") + }); + } + }).catch((e) => { + _converse.log(e, Strophe.LogLevel.ERROR); + return this.save({ + 'type': 'error', + 'message': __("Sorry, could not determine upload URL.") + }); + }); + }, + + uploadFile () { + const xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + _converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO); + if (xhr.status === 200 || xhr.status === 201) { + this.save({ + 'upload': _converse.SUCCESS, + 'message': this.get('get') + }); + } else { + this.save({ + 'upload': _converse.FAILURE, + 'message': __('Sorry, could not succesfully upload your file') + }); + } + } + }; + xhr.upload.addEventListener("progress", (evt) => { + if (evt.lengthComputable) { + this.set('progress', evt.loaded / evt.total); + } + }, false); + xhr.onerror = () => { + this.save({ + 'upload': _converse.FAILURE, + 'message': __('Sorry, could not succesfully upload your file') + }); + }; + xhr.open('PUT', this.get('put'), true); + xhr.setRequestHeader("Content-type", 'application/octet-stream'); + xhr.send(this.get('file')); } }); @@ -97,6 +191,7 @@ 'num_unread': 0, 'show_avatar': true, 'type': 'chatbox', + 'message_type': 'chat', 'url': '' }, @@ -106,6 +201,12 @@ b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`)); this.messages.chatbox = this; + this.messages.on('change:upload', (message) => { + if (message.get('upload') === _converse.SUCCESS) { + this.sendMessageStanza(message); + } + }); + this.save({ // The chat_state will be set to ACTIVE once the chat box is opened // and we listen for change:chat_state, so shouldn't set it to ACTIVE here. @@ -125,7 +226,7 @@ const stanza = $msg({ 'from': _converse.connection.jid, 'to': this.get('jid'), - 'type': 'chat', + 'type': this.get('message_type'), 'id': message.get('msgid') }).c('body').t(message.get('message')).up() .c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up(); @@ -149,17 +250,34 @@ if (_converse.forward_messages) { // Forward the message, so that other connected resources are also aware of it. _converse.connection.send( - $msg({ to: _converse.bare_jid, type: 'chat', id: message.get('msgid') }) - .c('forwarded', {'xmlns': Strophe.NS.FORWARD}) - .c('delay', { - 'xmns': Strophe.NS.DELAY, - 'stamp': moment().format() - }).up() - .cnode(messageStanza.tree()) + $msg({ + 'to': _converse.bare_jid, + 'type': this.get('message_type'), + 'id': message.get('msgid') + }).c('forwarded', {'xmlns': Strophe.NS.FORWARD}) + .c('delay', { + 'xmns': Strophe.NS.DELAY, + 'stamp': moment().format() + }).up() + .cnode(messageStanza.tree()) ); } }, + getOutgoingMessageAttributes (text, spoiler_hint) { + const fullname = _converse.xmppstatus.get('fullname'), + is_spoiler = this.get('composing_spoiler'); + + return { + 'fullname': _.isEmpty(fullname) ? _converse.bare_jid : fullname, + 'sender': 'me', + 'time': moment().format(), + 'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined, + 'is_spoiler': is_spoiler, + 'spoiler_hint': is_spoiler ? spoiler_hint : undefined + }; + }, + sendMessage (attrs) { /* Responsible for sending off a text message. * @@ -169,83 +287,25 @@ this.sendMessageStanza(this.messages.create(attrs)); }, - notifyUploadFailure (err_msg, error) { - err_msg = err_msg || __("Sorry, failed to upload the file"); - this.trigger('showHelpMessages', [err_msg], 'error'); - if (error instanceof Error) { - _converse.log(error, Strophe.LogLevel.ERROR); - } - }, - - sendFile (file) { - _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => { - if (!result.length) { - this.notifyUploadFailure(__("Sorry, file upload is not supported by your server.")); - } - const request_slot_url = result[0].id; - if (!request_slot_url) { - return this.notifyUploadFailure(__("Could not determine request slot URL for file upload")); - } - this.trigger('showHelpMessages', [__('The file upload starts now')], 'info'); - this.requestSlot(file, request_slot_url).then((stanza) => { - const slot = stanza.querySelector('slot'); - if (slot) { - const put = slot.querySelector('put').getAttribute('url'); - const get = slot.querySelector('get').getAttribute('url'); - this.uploadFile(put, file) - .then(_.bind(this.sendMessage, this, {'message': get, 'file': true})) - .catch(this.notifyUploadFailure.bind(this, null)); - } else { - this.notifyUploadFailure(); - } - }).catch(this.notifyUploadFailure.bind(this, null)); - }); - }, - sendFiles (files) { - _.each(files, this.sendFile.bind(this)); - }, - - requestSlot (file, request_slot_url) { - /* Send out an IQ stanza to request a file upload slot. - * - * https://xmpp.org/extensions/xep-0363.html#request - */ - return new Promise((resolve, reject) => { - const iq = converse.env.$iq({ - 'from': _converse.jid, - 'to': request_slot_url, - 'type': 'get' - }).c('request', { - 'xmlns': Strophe.NS.HTTPUPLOAD, - 'filename': file.name, - 'size': file.size, - 'content-type': file.type - }) - _converse.connection.sendIQ(iq, resolve, reject); - }); - }, - - uploadFile (url, file) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { - _converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO); - if (xhr.status === 200 || xhr.status === 201) { - resolve(url, file); - } else { - xhr.onerror(); - } - } - }; - xhr.onerror = function () { - reject(xhr.responseText); - }; - xhr.open('PUT', url, true); - xhr.setRequestHeader("Content-type", 'application/octet-stream'); - xhr.send(file); - }); + _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => { + const slot_request_url = _.get(result.pop(), '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'); + } + _.each(files, (file) => { + 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)); }, getMessageBody (message) { @@ -255,7 +315,7 @@ _.propertyOf(message.querySelector('body'))('textContent'); }, - getMessageAttributes (message, delay, original_stanza) { + getMessageAttributesFromStanza (message, delay, original_stanza) { /* Parses a passed in message stanza and returns an object * of attributes. * @@ -292,10 +352,10 @@ let sender, fullname; if ((is_groupchat && from === this.get('nick')) || (!is_groupchat && from === _converse.bare_jid)) { sender = 'me'; - fullname = _converse.xmppstatus.get('fullname') || from; + fullname = _converse.xmppstatus.get('fullname'); } else { sender = 'them'; - fullname = this.get('fullname') || from; + fullname = this.get('fullname'); } const spoiler = message.querySelector(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`); const attrs = { @@ -320,7 +380,7 @@ /* Create a Backbone.Message object inside this chat box * based on the identified message stanza. */ - return this.messages.create(this.getMessageAttributes.apply(this, arguments)); + return this.messages.create(this.getMessageAttributesFromStanza.apply(this, arguments)); }, newMessageWillBeHidden () { diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 2dd248775..123eefdc1 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -105,7 +105,6 @@ 'chatview_avatar_height': 32, 'chatview_avatar_width': 32, 'show_toolbar': true, - 'show_message_load_animation': false, 'time_format': 'HH:mm', 'visible_toolbar_buttons': { 'call': false, @@ -613,24 +612,25 @@ showChatStateNotification (message) { /* Support for XEP-0085, Chat State Notifications */ let text; - const from = message.get('from'); - const data = `data-csn=${from}`; + const from = message.get('from'), + username = message.get('fullname') || from, + data = `data-csn=${from}`; this.clearChatStateNotification(from); if (message.get('chat_state') === _converse.COMPOSING) { if (message.get('sender') === 'me') { text = __('Typing from another device'); } else { - text = message.get('fullname')+' '+__('is typing'); + text = username +' '+__('is typing'); } } else if (message.get('chat_state') === _converse.PAUSED) { if (message.get('sender') === 'me') { text = __('Stopped typing on the other device'); } else { - text = message.get('fullname')+' '+__('has stopped typing'); + text = username +' '+__('has stopped typing'); } } else if (message.get('chat_state') === _converse.GONE) { - text = message.get('fullname')+' '+__('has gone away'); + text = username +' '+__('has gone away'); } else { return; } @@ -707,7 +707,7 @@ if (message.get('chat_state')) { this.showChatStateNotification(message); } - if (message.get('message')) { + if (message.get('file') || message.get('message')) { this.handleTextMessage(message); } } @@ -755,29 +755,10 @@ if (this.parseMessageForCommands(text)) { return; } - const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint); + const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); this.model.sendMessage(attrs); }, - getOutgoingMessageAttributes (text, spoiler_hint) { - /* Overridable method which returns the attributes to be - * passed to Backbone.Message's constructor. - */ - const fullname = _converse.xmppstatus.get('fullname'), - is_spoiler = this.model.get('composing_spoiler'), - attrs = { - 'fullname': _.isEmpty(fullname) ? _converse.bare_jid : fullname, - 'sender': 'me', - 'time': moment().format(), - 'message': u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse), - 'is_spoiler': is_spoiler - }; - if (is_spoiler) { - attrs.spoiler_hint = spoiler_hint; - } - return attrs; - }, - sendChatState () { /* Sends a message with the status of the user in this chat session * as taken from the 'chat_state' attribute of the chat box. diff --git a/src/converse-core.js b/src/converse-core.js index d6e838c77..cb84fb662 100644 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -142,6 +142,9 @@ 10: 'RECONNECTING', }; + _converse.SUCCESS = 'success'; + _converse.FAILURE = 'failure'; + _converse.DEFAULT_IMAGE_TYPE = 'image/png'; _converse.DEFAULT_IMAGE = "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"; diff --git a/src/converse-mam.js b/src/converse-mam.js index 0a7f2f9a1..e55cfc991 100644 --- a/src/converse-mam.js +++ b/src/converse-mam.js @@ -128,8 +128,8 @@ // // New functions which don't exist yet can also be added. ChatBox: { - getMessageAttributes (message, delay, original_stanza) { - const attrs = this.__super__.getMessageAttributes.apply(this, arguments); + getMessageAttributesFromStanza (message, delay, original_stanza) { + const attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments); const archive_id = getMessageArchiveID(original_stanza); if (archive_id) { attrs.archive_id = archive_id; diff --git a/src/converse-message-view.js b/src/converse-message-view.js index 8eb058584..a257e80f7 100644 --- a/src/converse-message-view.js +++ b/src/converse-message-view.js @@ -10,16 +10,18 @@ "xss", "emojione", "tpl!action", + "tpl!file", "tpl!message", "tpl!spoiler_message" ], factory); }(this, function ( - converse, - xss, - emojione, - tpl_action, - tpl_message, - tpl_spoiler_message + converse, + xss, + emojione, + tpl_action, + tpl_file, + tpl_message, + tpl_spoiler_message ) { "use strict"; const { Backbone, _, moment } = converse.env; @@ -38,60 +40,57 @@ _converse.MessageView = Backbone.NativeView.extend({ initialize () { - this.model.collection.chatbox.on('change:fullname', this.render, this); + const chatbox = this.model.collection.chatbox; + chatbox.on('change:fullname', (chatbox) => this.model.save('fullname', chatbox.get('fullname'))); + + this.model.on('change:fullname', this.render, this); + this.model.on('change:progress', this.renderFileUploadProgresBar, this); + this.model.on('change:type', this.render, this); + this.model.on('change:upload', this.render, this); this.render(); }, render () { - const chatbox = this.model.collection.chatbox; + if (this.model.get('file') && !this.model.get('message')) { + return this.renderFileUploadProgresBar(); + } + let template, username, + text = this.model.get('message'); - let text = this.model.get('message'), - fullname = chatbox.get('fullname') || chatbox.get('jid'), - template, username; - - const match = text.match(/^\/(.*?)(?: (.*))?$/); - if ((match) && (match[1] === 'me')) { - text = text.replace(/^\/me/, ''); - template = tpl_action; - if (this.model.get('sender') === 'me') { - fullname = _converse.xmppstatus.get('fullname') || this.model.get('fullname'); - username = _.isNil(fullname)? _converse.bare_jid: fullname; - } else { - username = this.model.get('fullname'); - } + // TODO: store proper username on the message itself + if (this.isMeCommand()) { + const arr = this.getValuesForMeCommand(); + template = arr[0]; + username = arr[1]; + text = arr[2]; } else { - username = this.model.get('sender') === 'me' && __('me') || fullname; + const fullname = _converse.xmppstatus.get('fullname') || this.model.get('fullname'); + username = this.model.get('sender') === 'me' && __('me') || fullname || this.model.get('from'); template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message; } - text = u.geoUriToHttp(text, _converse); - - const msg_time = moment(this.model.get('time')) || moment; + const moment_time = moment(this.model.get('time')); const msg = u.stringToElement(template( _.extend(this.model.toJSON(), { - 'time': msg_time.format(_converse.time_format), - 'isodate': msg_time.format(), + 'pretty_time': moment_time.format(_converse.time_format), + 'time': moment_time.format(), 'username': username, 'extra_classes': this.getExtraMessageClasses(), 'label_show': __('Show hidden message') }) )); - if (_converse.show_message_load_animation) { - window.setTimeout(_.partial(u.removeClass, 'onload', msg), 2000); - } const msg_content = msg.querySelector('.chat-msg-content'); - msg_content.innerHTML = u.addEmoji( - _converse, emojione, u.addHyperlinks(xss.filterXSS(text, {'whiteList': {}})) - ); + text = xss.filterXSS(text, {'whiteList': {}}); + msg_content.innerHTML = _.flow( + _.partial(u.geoUriToHttp, _, _converse.geouri_replacement), + _.partial(u.addHyperlinks, _), + _.partial(u.addEmoji, _converse, emojione, _), + u.renderMovieURLs, + u.renderAudioURLs + )(text); - if (msg_content.textContent.endsWith('mp4')) { - msg_content.innerHTML = u.renderMovieURLs(msg_content); - } else if (msg_content.textContent.endsWith('mp3')) { - msg_content.innerHTML = u.renderAudioURLs(msg_content); - } else { - u.renderImageURLs(msg_content).then(() => { - this.model.collection.trigger('rendered'); - }); - } + u.renderImageURLs(msg_content).then(() => { + this.model.collection.trigger('rendered'); + }); if (!_.isNil(this.el.parentElement)) { this.el.parentElement.replaceChild(msg, this.el); } @@ -99,13 +98,42 @@ return this.el; }, - getExtraMessageClasses () { - let extra_classes; - if (_converse.show_message_load_animation) { - extra_classes = 'onload ' + (this.model.get('delayed') && 'delayed' || ''); - } else { - extra_classes = this.model.get('delayed') && 'delayed' || ''; + 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; + }, + + isMeCommand () { + const match = this.model.get('message').match(/^\/(.*?)(?: (.*))?$/); + return match && match[1] === 'me'; + }, + + getValuesForMeCommand() { + let username, text; + const match = this.model.get('message').match(/^\/(.*?)(?: (.*))?$/); + if (match && match[1] === 'me') { + text = this.model.get('message').replace(/^\/me/, ''); + } + if (this.model.get('sender') === 'me') { + const fullname = _converse.xmppstatus.get('fullname') || this.model.get('fullname'); + username = _.isNil(fullname) ? _converse.bare_jid : fullname; + } else { + username = this.model.get('fullname') || this.model.get('from'); + } + return [tpl_action, username, text] + }, + + processMessageText () { + var text = this.get('message'); + text = u.geoUriToHttp(text, _converse.geouri_replacement); + }, + + getExtraMessageClasses () { + let extra_classes = this.model.get('delayed') && 'delayed' || ''; if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') { if (this.model.collection.chatbox.isUserMentioned(this.model.get('message'))) { // Add special class to mark groupchat messages diff --git a/src/converse-muc.js b/src/converse-muc.js index c27b71451..02b54895e 100644 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -182,6 +182,7 @@ 'features_fetched': false, 'roomconfig': {}, 'type': converse.CHATROOMS_TYPE, + 'message_type': 'groupchat' } ); }, diff --git a/src/templates/action.html b/src/templates/action.html index 8d5608ae5..a3d36583a 100644 --- a/src/templates/action.html +++ b/src/templates/action.html @@ -1,4 +1,4 @@ -
- {{{o.time}}} **{{{o.username}}}  - +
+ {{{o.pretty_time}}} **{{{o.username}}} +
diff --git a/src/templates/file.html b/src/templates/file.html new file mode 100644 index 000000000..f143556fd --- /dev/null +++ b/src/templates/file.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/templates/message.html b/src/templates/message.html index 15cfe1474..060c894fd 100644 --- a/src/templates/message.html +++ b/src/templates/message.html @@ -1,4 +1,4 @@ -
- {{{o.time}}} {{{o.username}}}:  +
+ {{{o.pretty_time}}} {{{o.username}}}: 
diff --git a/src/templates/spoiler_message.html b/src/templates/spoiler_message.html index a511b8824..e151d1777 100644 --- a/src/templates/spoiler_message.html +++ b/src/templates/spoiler_message.html @@ -1,5 +1,5 @@ -
- {{{o.time}}} {{{o.username}}}:  +
+ {{{o.pretty_time}}} {{{o.username}}}: 
{{{o.spoiler_hint}}}
{{{o.label_show}}} diff --git a/src/utils/core.js b/src/utils/core.js index 173666b90..5e5315756 100644 --- a/src/utils/core.js +++ b/src/utils/core.js @@ -213,12 +213,18 @@ )) }; - u.renderMovieURLs = function (obj) { - return ""; + u.renderMovieURLs = function (text) { + if (text.endsWith('mp4')) { + return ""; + } + return text; }; - u.renderAudioURLs = function (obj) { - return ""; + u.renderAudioURLs = function (text) { + if (text.endsWith('mp3')) { + return ""; + } + return text; }; u.slideInAllElements = function (elements, duration=300) { @@ -714,9 +720,9 @@ el.dispatchEvent(evt); }; - u.geoUriToHttp = function(text, _converse) { + u.geoUriToHttp = function(text, geouri_replacement) { const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g; - return text.replace(regex, _converse.geouri_replacement); + return text.replace(regex, geouri_replacement); }; u.httpToGeoUri = function(text, _converse) {