Commit de6ecbf0 authored by JC Brand's avatar JC Brand

Render messages via a new MessageView view

updates #161
parent 264e6830
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
## New Features ## New Features
- files can now be sent via http-file-upload (XEP-0363) - #161 XEP-0363: HTTP File Upload
- mp4 and mp3 files will now be playable directly in chat. - mp4 and mp3 files will now be playable directly in chat
## 4.0.0 (Unreleased) ## 4.0.0 (Unreleased)
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
var u = converse.env.utils; var u = converse.env.utils;
return describe("Chatboxes", function () { return describe("Chatboxes", function () {
describe("A Chatbox", function () { describe("A Chatbox", function () {
it("has a /help command to show the available commands", it("has a /help command to show the available commands",
...@@ -239,7 +240,6 @@ ...@@ -239,7 +240,6 @@
}); });
})); }));
it("can be saved to, and retrieved from, browserStorage", it("can be saved to, and retrieved from, browserStorage",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
...@@ -654,6 +654,8 @@ ...@@ -654,6 +654,8 @@
return chatbox.get('fullname') === mock.cur_names[0]; return chatbox.get('fullname') === mock.cur_names[0];
}, 100); }, 100);
}).then(function () { }).then(function () {
var author_el = chatboxview.el.querySelector('.chat-msg-author');
expect( _.includes(author_el.textContent, 'Max Frankfurter')).toBeTruthy();
done(); done();
}); });
})); }));
......
...@@ -77,6 +77,7 @@ require.config({ ...@@ -77,6 +77,7 @@ require.config({
"converse-headline": "src/converse-headline", "converse-headline": "src/converse-headline",
"converse-http-file-upload":"src/converse-http-file-upload", "converse-http-file-upload":"src/converse-http-file-upload",
"converse-mam": "src/converse-mam", "converse-mam": "src/converse-mam",
"converse-message-view": "src/converse-message-view",
"converse-minimize": "src/converse-minimize", "converse-minimize": "src/converse-minimize",
"converse-modal": "src/converse-modal", "converse-modal": "src/converse-modal",
"converse-muc": "src/converse-muc", "converse-muc": "src/converse-muc",
......
// Converse.js (A browser based XMPP chat client) // Converse.js
// http://conversejs.org // http://conversejs.org
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) { (function (root, factory) {
define([ define([
...@@ -106,6 +104,7 @@ ...@@ -106,6 +104,7 @@
this.messages = new _converse.Messages(); this.messages = new _converse.Messages();
this.messages.browserStorage = new Backbone.BrowserStorage[_converse.message_storage]( this.messages.browserStorage = new Backbone.BrowserStorage[_converse.message_storage](
b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`)); b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`));
this.messages.chatbox = this;
this.save({ this.save({
// The chat_state will be set to ACTIVE once the chat box is opened // The chat_state will be set to ACTIVE once the chat box is opened
......
// Converse.js (A browser based XMPP chat client) // Converse.js
// http://conversejs.org // http://conversejs.org
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) { (function (root, factory) {
define([ define([
...@@ -21,15 +19,14 @@ ...@@ -21,15 +19,14 @@
"tpl!error_message", "tpl!error_message",
"tpl!help_message", "tpl!help_message",
"tpl!info", "tpl!info",
"tpl!message",
"tpl!new_day", "tpl!new_day",
"tpl!spinner", "tpl!spinner",
"tpl!spoiler_button", "tpl!spoiler_button",
"tpl!spoiler_message",
"tpl!status_message", "tpl!status_message",
"tpl!toolbar", "tpl!toolbar",
"converse-http-file-upload", "converse-http-file-upload",
"converse-chatboxes" "converse-chatboxes",
"converse-message-view"
], factory); ], factory);
}(this, function ( }(this, function (
converse, converse,
...@@ -45,14 +42,11 @@ ...@@ -45,14 +42,11 @@
tpl_error_message, tpl_error_message,
tpl_help_message, tpl_help_message,
tpl_info, tpl_info,
tpl_message,
tpl_new_day, tpl_new_day,
tpl_spinner, tpl_spinner,
tpl_spoiler_button, tpl_spoiler_button,
tpl_spoiler_message,
tpl_status_message, tpl_status_message,
tpl_toolbar, tpl_toolbar
filetransfer
) { ) {
"use strict"; "use strict";
const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env; const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
...@@ -73,7 +67,7 @@ ...@@ -73,7 +67,7 @@
* *
* NB: These plugins need to have already been loaded via require.js. * NB: These plugins need to have already been loaded via require.js.
*/ */
dependencies: ["converse-chatboxes", "converse-disco"], dependencies: ["converse-chatboxes", "converse-disco", "converse-message-view"],
overrides: { overrides: {
// Overrides mentioned here will be picked up by converse.js's // Overrides mentioned here will be picked up by converse.js's
...@@ -239,6 +233,7 @@ ...@@ -239,6 +233,7 @@
} }
}); });
_converse.ChatBoxView = Backbone.NativeView.extend({ _converse.ChatBoxView = Backbone.NativeView.extend({
length: 200, length: 200,
className: 'chatbox hidden', className: 'chatbox hidden',
...@@ -262,6 +257,8 @@ ...@@ -262,6 +257,8 @@
this.markScrolled = _.debounce(this._markScrolled, 100); this.markScrolled = _.debounce(this._markScrolled, 100);
this.createEmojiPicker(); this.createEmojiPicker();
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.on('show', this.show, this); this.model.on('show', this.show, this);
this.model.on('destroy', this.remove, this); this.model.on('destroy', this.remove, this);
// TODO check for changed fullname as well // TODO check for changed fullname as well
...@@ -513,7 +510,7 @@ ...@@ -513,7 +510,7 @@
} }
}, },
showMessage (attrs) { showMessage (message) {
/* Inserts a chat message into the content area of the chat box. /* 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 * Will also insert a new day indicator if the message is on a
* different day. * different day.
...@@ -522,12 +519,12 @@ ...@@ -522,12 +519,12 @@
* message, or older than the oldest message. * message, or older than the oldest message.
* *
* Parameters: * Parameters:
* (Object) attrs: An object containing the message * (Backbone.Model) message: The message object
* attributes.
*/ */
const current_msg_date = moment(attrs.time) || moment, const view = new _converse.MessageView({'model': message}),
current_msg_date = moment(message.get('time')) || moment,
previous_msg_date = this.getLastMessageDate(current_msg_date), previous_msg_date = this.getLastMessageDate(current_msg_date),
message_el = this.renderMessage(attrs); message_el = view.el;
if (_.isNull(previous_msg_date)) { if (_.isNull(previous_msg_date)) {
this.content.insertAdjacentElement('afterbegin', message_el); this.content.insertAdjacentElement('afterbegin', message_el);
...@@ -536,7 +533,7 @@ ...@@ -536,7 +533,7 @@
previous_msg_el.insertAdjacentElement('afterend', message_el); previous_msg_el.insertAdjacentElement('afterend', message_el);
} }
this.insertDayIndicator(message_el); this.insertDayIndicator(message_el);
this.clearChatStateNotification(attrs.from); this.clearChatStateNotification(message.get('from'));
this.setScrollPosition(message_el); this.setScrollPosition(message_el);
}, },
...@@ -563,101 +560,6 @@ ...@@ -563,101 +560,6 @@
} }
}, },
getExtraMessageTemplateAttributes (attrs) {
/* Provides a hook for sending more attributes to the
* message template.
*
* Parameters:
* (Object) attrs: An object containing message attributes.
*/
if (attrs.is_spoiler) {
return {'label_show': __('Show hidden message')};
} else {
return {}
}
},
getExtraMessageClasses (attrs) {
if (_converse.show_message_load_animation) {
return 'onload ' + (attrs.delayed && 'delayed' || '');
} else {
return attrs.delayed && 'delayed' || '';
}
},
renderSpoilerMessage (msg, attrs) {
/* Render a "spoiler" message, as defined in XEP-0382
*
* Parameters:
* (HTMLElement) msg: The chat message DOM element
* (Object) attrs: An object containing the message attributes.
*/
const hint = msg.querySelector('.spoiler-hint');
hint.appendChild(document.createTextNode(attrs.spoiler_hint || ''));
},
renderMessage (attrs) {
/* Renders a chat message based on the passed in attributes.
*
* Parameters:
* (Object) attrs: An object containing the message attributes.
*
* Returns:
* The DOM element representing the message.
*/
let text = attrs.message,
fullname = this.model.get('fullname') || attrs.fullname,
template, username;
const match = text.match(/^\/(.*?)(?: (.*))?$/);
if ((match) && (match[1] === 'me')) {
text = text.replace(/^\/me/, '');
template = tpl_action;
if (attrs.sender === 'me') {
fullname = _converse.xmppstatus.get('fullname') || attrs.fullname;
username = _.isNil(fullname)? _converse.bare_jid: fullname;
} else {
username = attrs.fullname;
}
} else {
username = attrs.sender === 'me' && __('me') || fullname;
template = attrs.is_spoiler ? tpl_spoiler_message : tpl_message;
}
text = u.geoUriToHttp(text, _converse);
const msg_time = moment(attrs.time) || moment;
const msg = u.stringToElement(template(
_.extend(this.getExtraMessageTemplateAttributes(attrs), {
'msgid': attrs.msgid,
'sender': attrs.sender,
'time': msg_time.format(_converse.time_format),
'isodate': msg_time.format(),
'username': username,
'extra_classes': this.getExtraMessageClasses(attrs)
})
));
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': {}}))
);
if (attrs.is_spoiler) {
this.renderSpoilerMessage(msg, attrs)
}
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.scrollDown.bind(this));
}
return msg;
},
showHelpMessages (msgs, type, spinner) { showHelpMessages (msgs, type, spinner) {
_.each(msgs, (msg) => { _.each(msgs, (msg) => {
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
...@@ -734,7 +636,8 @@ ...@@ -734,7 +636,8 @@
}, },
handleTextMessage (message) { handleTextMessage (message) {
this.showMessage(_.clone(message.attributes)); this.showMessage(message);
if (u.isNewMessage(message)) { if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') { if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area // We remove the "scrolled" flag so that the chat area
...@@ -813,7 +716,7 @@ ...@@ -813,7 +716,7 @@
} }
}, },
onMessageSubmitted (text, spoiler_hint, file=null) { onMessageSubmitted (text, spoiler_hint) {
/* This method gets called once the user has typed a message /* This method gets called once the user has typed a message
* and then pressed enter in a chat box. * and then pressed enter in a chat box.
* *
...@@ -833,7 +736,7 @@ ...@@ -833,7 +736,7 @@
return; return;
} }
const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint); const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
this.model.sendMessage(attrs, file); this.model.sendMessage(attrs);
}, },
getOutgoingMessageAttributes (text, spoiler_hint) { getOutgoingMessageAttributes (text, spoiler_hint) {
......
// Converse.js (A browser based XMPP chat client) // Converse.js
// http://conversejs.org // https://conversejs.org
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
//
/*global Backbone, define, window, JSON */
(function (root, factory) { (function (root, factory) {
define(["sizzle", define(["sizzle",
"es6-promise", "es6-promise",
...@@ -80,6 +79,7 @@ ...@@ -80,6 +79,7 @@
'converse-headline', 'converse-headline',
'converse-http-file-upload', 'converse-http-file-upload',
'converse-mam', 'converse-mam',
'converse-message-view',
'converse-minimize', 'converse-minimize',
'converse-modal', 'converse-modal',
'converse-muc', 'converse-muc',
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
'change input.fileupload': 'onFileSelection' 'change input.fileupload': 'onFileSelection'
}, },
toggleFileUpload (ev) { toggleFileUpload (ev) {
this.el.querySelector('input.fileupload').click(); this.el.querySelector('input.fileupload').click();
}, },
......
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) { (function (root, factory) {
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
define([ define([
......
...@@ -515,6 +515,8 @@ ...@@ -515,6 +515,8 @@
this.markScrolled = _.debounce(this._markScrolled, 100); this.markScrolled = _.debounce(this._markScrolled, 100);
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.on('change:affiliation', this.renderHeading, this); this.model.on('change:affiliation', this.renderHeading, this);
this.model.on('change:chat_state', this.sendChatState, this); this.model.on('change:chat_state', this.sendChatState, this);
this.model.on('change:connection_status', this.afterConnected, this); this.model.on('change:connection_status', this.afterConnected, this);
...@@ -654,19 +656,6 @@ ...@@ -654,19 +656,6 @@
} }
}, },
getExtraMessageClasses (attrs) {
let extra_classes = _converse.ChatBoxView.prototype
.getExtraMessageClasses.apply(this, arguments);
if (this.is_chatroom && attrs.sender === 'them' &&
this.model.isUserMentioned(attrs.message)) {
// Add special class to mark groupchat messages
// in which we are mentioned.
extra_classes += ' mentioned';
}
return extra_classes;
},
getToolbarOptions () { getToolbarOptions () {
return _.extend( return _.extend(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
......
<div class="message chat-message {{{o.extra_classes}}}" data-isodate="{{{o.isodate}}}" data-msgid="{{{o.msgid}}}"> <div class="message chat-message {{{o.extra_classes}}}" data-isodate="{{{o.isodate}}}" data-msgid="{{{o.msgid}}}">
<span class="chat-msg-author chat-msg-{{{o.sender}}}">{{{o.time}}} {{{o.username}}}:&nbsp;</span> <span class="chat-msg-author chat-msg-{{{o.sender}}}">{{{o.time}}} {{{o.username}}}:&nbsp;</span>
<div class="spoiler-hint"><!-- message gets added here via renderMessage --></div> <div class="spoiler-hint">{{{o.spoiler_hint}}}</div>
<a class="icon-eye toggle-spoiler" data-toggle-state="closed" href="#">{{{o.label_show}}}</a> <a class="icon-eye toggle-spoiler" data-toggle-state="closed" href="#">{{{o.label_show}}}</a>
<div class="chat-msg-content spoiler collapsed"><!-- message gets added here via renderMessage --></div> <div class="chat-msg-content spoiler collapsed"><!-- message gets added here via renderMessage --></div>
</div> </div>
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