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 @@
## New Features
- files can now be sent via http-file-upload (XEP-0363)
- mp4 and mp3 files will now be playable directly in chat.
- #161 XEP-0363: HTTP File Upload
- mp4 and mp3 files will now be playable directly in chat
## 4.0.0 (Unreleased)
......
......@@ -18,6 +18,7 @@
var u = converse.env.utils;
return describe("Chatboxes", function () {
describe("A Chatbox", function () {
it("has a /help command to show the available commands",
......@@ -239,7 +240,6 @@
});
}));
it("can be saved to, and retrieved from, browserStorage",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
......@@ -654,6 +654,8 @@
return chatbox.get('fullname') === mock.cur_names[0];
}, 100);
}).then(function () {
var author_el = chatboxview.el.querySelector('.chat-msg-author');
expect( _.includes(author_el.textContent, 'Max Frankfurter')).toBeTruthy();
done();
});
}));
......
......@@ -77,6 +77,7 @@ require.config({
"converse-headline": "src/converse-headline",
"converse-http-file-upload":"src/converse-http-file-upload",
"converse-mam": "src/converse-mam",
"converse-message-view": "src/converse-message-view",
"converse-minimize": "src/converse-minimize",
"converse-modal": "src/converse-modal",
"converse-muc": "src/converse-muc",
......
// Converse.js (A browser based XMPP chat client)
// Converse.js
// 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)
//
/*global define */
(function (root, factory) {
define([
......@@ -106,6 +104,7 @@
this.messages = new _converse.Messages();
this.messages.browserStorage = new Backbone.BrowserStorage[_converse.message_storage](
b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`));
this.messages.chatbox = this;
this.save({
// 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
//
// 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)
//
/*global define */
(function (root, factory) {
define([
......@@ -21,15 +19,14 @@
"tpl!error_message",
"tpl!help_message",
"tpl!info",
"tpl!message",
"tpl!new_day",
"tpl!spinner",
"tpl!spoiler_button",
"tpl!spoiler_message",
"tpl!status_message",
"tpl!toolbar",
"converse-http-file-upload",
"converse-chatboxes"
"converse-chatboxes",
"converse-message-view"
], factory);
}(this, function (
converse,
......@@ -45,14 +42,11 @@
tpl_error_message,
tpl_help_message,
tpl_info,
tpl_message,
tpl_new_day,
tpl_spinner,
tpl_spoiler_button,
tpl_spoiler_message,
tpl_status_message,
tpl_toolbar,
filetransfer
tpl_toolbar
) {
"use strict";
const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
......@@ -73,7 +67,7 @@
*
* 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 mentioned here will be picked up by converse.js's
......@@ -239,6 +233,7 @@
}
});
_converse.ChatBoxView = Backbone.NativeView.extend({
length: 200,
className: 'chatbox hidden',
......@@ -262,6 +257,8 @@
this.markScrolled = _.debounce(this._markScrolled, 100);
this.createEmojiPicker();
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('destroy', this.remove, this);
// TODO check for changed fullname as well
......@@ -513,7 +510,7 @@
}
},
showMessage (attrs) {
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.
......@@ -522,12 +519,12 @@
* message, or older than the oldest message.
*
* Parameters:
* (Object) attrs: An object containing the message
* attributes.
* (Backbone.Model) message: The message object
*/
const current_msg_date = moment(attrs.time) || moment,
previous_msg_date = this.getLastMessageDate(current_msg_date),
message_el = this.renderMessage(attrs);
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);
......@@ -536,7 +533,7 @@
previous_msg_el.insertAdjacentElement('afterend', message_el);
}
this.insertDayIndicator(message_el);
this.clearChatStateNotification(attrs.from);
this.clearChatStateNotification(message.get('from'));
this.setScrollPosition(message_el);
},
......@@ -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) {
_.each(msgs, (msg) => {
this.content.insertAdjacentHTML(
......@@ -734,7 +636,8 @@
},
handleTextMessage (message) {
this.showMessage(_.clone(message.attributes));
this.showMessage(message);
if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area
......@@ -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
* and then pressed enter in a chat box.
*
......@@ -833,7 +736,7 @@
return;
}
const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
this.model.sendMessage(attrs, file);
this.model.sendMessage(attrs);
},
getOutgoingMessageAttributes (text, spoiler_hint) {
......
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
// Converse.js
// 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)
//
/*global Backbone, define, window, JSON */
(function (root, factory) {
define(["sizzle",
"es6-promise",
......@@ -80,6 +79,7 @@
'converse-headline',
'converse-http-file-upload',
'converse-mam',
'converse-message-view',
'converse-minimize',
'converse-modal',
'converse-muc',
......
......@@ -34,7 +34,6 @@
'click .upload-file': 'toggleFileUpload',
'change input.fileupload': 'onFileSelection'
},
toggleFileUpload (ev) {
this.el.querySelector('input.fileupload').click();
......
......@@ -90,11 +90,11 @@
msg_content.innerHTML = u.addEmoji(
_converse, emojione, u.addHyperlinks(xss.filterXSS(text, {'whiteList': {}}))
);
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);
msg_content.innerHTML = u.renderAudioURLs(msg_content);
} else {
u.renderImageURLs(msg_content).then(() => {
this.model.collection.trigger('rendered');
......
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([
......
......@@ -515,6 +515,8 @@
this.markScrolled = _.debounce(this._markScrolled, 100);
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:chat_state', this.sendChatState, this);
this.model.on('change:connection_status', this.afterConnected, this);
......@@ -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 () {
return _.extend(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
......
<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>
<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>
<div class="chat-msg-content spoiler collapsed"><!-- message gets added here via renderMessage --></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