Browse Source

Update with new message markup

JC Brand 7 years ago
parent
commit
acd9a18696

+ 17 - 29
css/converse.css

@@ -7381,7 +7381,7 @@ body.reset {
     background-color: #3AA569;
     box-shadow: 1px 3px 5px 3px rgba(0, 0, 0, 0.4);
     z-index: 1;
-    overflow-y: scroll;
+    overflow-y: hidden;
     width: 100%; }
     @media screen and (max-height: 450px) {
       #conversejs .chatbox .box-flyout {
@@ -7648,18 +7648,6 @@ body.reset {
   #conversejs.converse-overlayed .chatbox .box-flyout {
     min-width: 250px !important;
     width: 250px; }
-  #conversejs.converse-embedded .chatbox .chat-body .chat-message,
-  #conversejs.converse-overlayed .chatbox .chat-body .chat-message {
-    line-height: 20px; }
-    #conversejs.converse-embedded .chatbox .chat-body .chat-message .chat-msg-author,
-    #conversejs.converse-overlayed .chatbox .chat-body .chat-message .chat-msg-author {
-      line-height: 20px; }
-    #conversejs.converse-embedded .chatbox .chat-body .chat-message .chat-msg-content,
-    #conversejs.converse-overlayed .chatbox .chat-body .chat-message .chat-msg-content {
-      line-height: 20px; }
-      #conversejs.converse-embedded .chatbox .chat-body .chat-message .chat-msg-content .emojione,
-      #conversejs.converse-overlayed .chatbox .chat-body .chat-message .chat-msg-content .emojione {
-        margin-bottom: -5px; }
 #conversejs.converse-embedded .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu,
 #conversejs.converse-overlayed .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu {
   min-width: 235px; }
@@ -7746,16 +7734,6 @@ body.reset {
     background-color: #3AA569;
     border-top-left-radius: 4px;
     border-top-right-radius: 4px; }
-    #conversejs.converse-fullscreen .chatbox .chat-body .chat-message {
-      line-height: 16px;
-      font-size: 12px; }
-      #conversejs.converse-fullscreen .chatbox .chat-body .chat-message .chat-msg-author {
-        line-height: 16px; }
-      #conversejs.converse-fullscreen .chatbox .chat-body .chat-message .chat-msg-content {
-        line-height: 16px; }
-        #conversejs.converse-fullscreen .chatbox .chat-body .chat-message .chat-msg-content .emojione {
-          height: 16px;
-          margin-bottom: -4px; }
   #conversejs.converse-fullscreen .chatbox .chat-content {
     border-top-left-radius: 4px;
     border-top-right-radius: 4px; }
@@ -8884,6 +8862,8 @@ body.reset {
     align-items: stretch;
     margin-left: 0.5rem;
     width: 100%; }
+  #conversejs .message.chat-msg .chat-msg__content--action {
+    margin-left: 0; }
   #conversejs .message.chat-msg .chat-msg__body {
     display: flex;
     flex-direction: row;
@@ -8893,8 +8873,9 @@ body.reset {
     display: flex;
     flex-direction: column;
     width: 100%; }
-  #conversejs .message.chat-msg .chat-msg-edited {
-    cursor: pointer; }
+  #conversejs .message.chat-msg .chat-msg__edit-modal {
+    cursor: pointer;
+    padding-right: 0.5em; }
   #conversejs .message.chat-msg.headline .chat-msg__body {
     margin-left: 0; }
   #conversejs .message.chat-msg .chat-msg__text {
@@ -8906,11 +8887,12 @@ body.reset {
       word-break: break-all; }
     #conversejs .message.chat-msg .chat-msg__text .emojione {
       margin-bottom: -6px; }
-  #conversejs .message.chat-msg .chat-msg-media {
-    margin-top: 0.25rem; }
-    #conversejs .message.chat-msg .chat-msg-media a {
+  #conversejs .message.chat-msg .chat-msg__media {
+    margin-top: 0.25rem;
+    word-break: break-all; }
+    #conversejs .message.chat-msg .chat-msg__media a {
       word-wrap: break-word; }
-    #conversejs .message.chat-msg .chat-msg-media audio {
+    #conversejs .message.chat-msg .chat-msg__media audio {
       width: 100%; }
   #conversejs .message.chat-msg .chat-msg__actions .chat-msg__action {
     height: 14px;
@@ -8934,6 +8916,7 @@ body.reset {
     padding-bottom: 0.25rem;
     display: block; }
     #conversejs .message.chat-msg .chat-msg__heading .chat-msg__author {
+      white-space: nowrap;
       font-family: "Century Gothic", futura, "URW Gothic L", Verdana, sans-serif;
       font-size: 115%; }
       #conversejs .message.chat-msg .chat-msg__heading .chat-msg__author .badge {
@@ -8943,6 +8926,7 @@ body.reset {
       padding-left: 0.25em;
       color: #8c8c8c; }
   #conversejs .message.chat-msg.chat-msg--action .chat-msg__content {
+    flex-wrap: wrap;
     flex-direction: row;
     justify-content: flex-start; }
   #conversejs .message.chat-msg.chat-msg--action .chat-msg__text {
@@ -8951,6 +8935,10 @@ body.reset {
     margin-top: 0;
     padding-bottom: 0;
     width: auto; }
+  #conversejs .message.chat-msg.chat-msg--action .chat-msg__author {
+    font-size: 14px; }
+  #conversejs .message.chat-msg.chat-msg--action .chat-msg__time {
+    margin-left: 0; }
   #conversejs .message.chat-msg.chat-msg--followup .chat-msg__heading,
   #conversejs .message.chat-msg.chat-msg--followup .chat-msg__avatar {
     display: none; }

+ 91 - 91
dist/converse.js

@@ -69333,11 +69333,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 // Copyright (c) 2012-2018, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
 (function (root, factory) {
-  !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! emojione */ "./node_modules/emojione/lib/js/emojione.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! templates/action.html */ "./src/templates/action.html"), __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"), __webpack_require__(/*! templates/chatbox_head.html */ "./src/templates/chatbox_head.html"), __webpack_require__(/*! templates/chatbox_message_form.html */ "./src/templates/chatbox_message_form.html"), __webpack_require__(/*! templates/emojis.html */ "./src/templates/emojis.html"), __webpack_require__(/*! templates/error_message.html */ "./src/templates/error_message.html"), __webpack_require__(/*! templates/help_message.html */ "./src/templates/help_message.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/new_day.html */ "./src/templates/new_day.html"), __webpack_require__(/*! templates/user_details_modal.html */ "./src/templates/user_details_modal.html"), __webpack_require__(/*! templates/toolbar_fileupload.html */ "./src/templates/toolbar_fileupload.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! templates/spoiler_button.html */ "./src/templates/spoiler_button.html"), __webpack_require__(/*! templates/status_message.html */ "./src/templates/status_message.html"), __webpack_require__(/*! templates/toolbar.html */ "./src/templates/toolbar.html"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"), __webpack_require__(/*! converse-chatboxes */ "./src/converse-chatboxes.js"), __webpack_require__(/*! converse-message-view */ "./src/converse-message-view.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
+  !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! emojione */ "./node_modules/emojione/lib/js/emojione.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"), __webpack_require__(/*! templates/chatbox_head.html */ "./src/templates/chatbox_head.html"), __webpack_require__(/*! templates/chatbox_message_form.html */ "./src/templates/chatbox_message_form.html"), __webpack_require__(/*! templates/emojis.html */ "./src/templates/emojis.html"), __webpack_require__(/*! templates/error_message.html */ "./src/templates/error_message.html"), __webpack_require__(/*! templates/help_message.html */ "./src/templates/help_message.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/new_day.html */ "./src/templates/new_day.html"), __webpack_require__(/*! templates/user_details_modal.html */ "./src/templates/user_details_modal.html"), __webpack_require__(/*! templates/toolbar_fileupload.html */ "./src/templates/toolbar_fileupload.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! templates/spoiler_button.html */ "./src/templates/spoiler_button.html"), __webpack_require__(/*! templates/status_message.html */ "./src/templates/status_message.html"), __webpack_require__(/*! templates/toolbar.html */ "./src/templates/toolbar.html"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"), __webpack_require__(/*! converse-chatboxes */ "./src/converse-chatboxes.js"), __webpack_require__(/*! converse-message-view */ "./src/converse-message-view.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
 				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
 				(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-})(void 0, function (converse, bootstrap, emojione, xss, tpl_action, tpl_chatbox, tpl_chatbox_head, tpl_chatbox_message_form, tpl_emojis, tpl_error_message, tpl_help_message, tpl_info, tpl_new_day, tpl_user_details_modal, tpl_toolbar_fileupload, tpl_spinner, tpl_spoiler_button, tpl_status_message, tpl_toolbar) {
+})(void 0, function (converse, bootstrap, emojione, xss, tpl_chatbox, tpl_chatbox_head, tpl_chatbox_message_form, tpl_emojis, tpl_error_message, tpl_help_message, tpl_info, tpl_new_day, tpl_user_details_modal, tpl_toolbar_fileupload, tpl_spinner, tpl_spoiler_button, tpl_status_message, tpl_toolbar) {
   "use strict";
 
   const _converse$env = converse.env,
@@ -70055,18 +70055,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
                 date = moment(el.getAttribute('data-isodate')),
                 next_el = el.nextElementSibling;
 
-          if (!u.hasClass('chat-action', el) && !u.hasClass('chat-action', previous_el) && previous_el.getAttribute('data-from') === from && date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes'))) {
-            u.addClass('chat-msg-followup', el);
+          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'))) {
+            u.addClass('chat-msg--followup', el);
           }
 
           if (!next_el) {
             return;
           }
 
-          if (!u.hasClass('chat-action', 'el') && next_el.getAttribute('data-from') === from && moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
-            u.addClass('chat-msg-followup', next_el);
+          if (!u.hasClass('chat-msg--action', 'el') && next_el.getAttribute('data-from') === from && moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
+            u.addClass('chat-msg--followup', next_el);
           } else {
-            u.removeClass('chat-msg-followup', next_el);
+            u.removeClass('chat-msg--followup', next_el);
           }
         },
 
@@ -74581,11 +74581,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 // Copyright (c) 2013-2018, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
 (function (root, factory) {
-  !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! emojione */ "./node_modules/emojione/lib/js/emojione.js"), __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! templates/action.html */ "./src/templates/action.html"), __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html"), __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/message.html */ "./src/templates/message.html"), __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html"), __webpack_require__(/*! templates/spoiler_message.html */ "./src/templates/spoiler_message.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
+  !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! emojione */ "./node_modules/emojione/lib/js/emojione.js"), __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html"), __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/message.html */ "./src/templates/message.html"), __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
 				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
 				(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-})(void 0, function (converse, xss, emojione, filesize, tpl_action, tpl_csn, tpl_file_progress, tpl_info, tpl_message, tpl_message_versions_modal, tpl_spoiler_message) {
+})(void 0, function (converse, xss, emojione, filesize, tpl_csn, tpl_file_progress, tpl_info, tpl_message, tpl_message_versions_modal) {
   "use strict";
 
   const _converse$env = converse.env,
@@ -74654,7 +74654,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         },
 
         render() {
-          const is_followup = u.hasClass('chat-msg-followup', this.el);
+          const is_followup = u.hasClass('chat-msg--followup', this.el);
           let msg;
 
           if (this.model.isOnlyChatStateNotification()) {
@@ -74668,7 +74668,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
 
           if (is_followup) {
-            u.addClass('chat-msg-followup', this.el);
+            u.addClass('chat-msg--followup', this.el);
           }
 
           return this.el;
@@ -74693,21 +74693,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         },
 
         renderChatMessage() {
-          let template,
-              text = this.model.get('message');
-
-          if (this.isMeCommand()) {
-            template = tpl_action;
-            text = this.model.get('message').replace(/^\/me/, '');
-          } else {
-            template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
-          }
-
-          const moment_time = moment(this.model.get('time')),
+          const is_me_message = this.isMeCommand(),
+                moment_time = moment(this.model.get('time')),
                 role = this.model.vcard.get('role'),
                 roles = role ? role.split(',') : [];
-          const msg = u.stringToElement(template(_.extend(this.model.toJSON(), {
+          const msg = u.stringToElement(tpl_message(_.extend(this.model.toJSON(), {
             '__': __,
+            'is_me_message': is_me_message,
             'roles': roles,
             'pretty_time': moment_time.format(_converse.time_format),
             'time': moment_time.format(),
@@ -74715,13 +74707,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             'label_show': __('Show more'),
             'username': this.model.getDisplayName()
           })));
-          var url = this.model.get('oob_url');
+          const url = this.model.get('oob_url');
 
           if (url) {
-            msg.querySelector('.chat-msg-media').innerHTML = _.flow(_.partial(u.renderFileURL, _converse), _.partial(u.renderMovieURL, _converse), _.partial(u.renderAudioURL, _converse), _.partial(u.renderImageURL, _converse))(url);
+            msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(u.renderFileURL, _converse), _.partial(u.renderMovieURL, _converse), _.partial(u.renderAudioURL, _converse), _.partial(u.renderImageURL, _converse))(url);
           }
 
-          const msg_content = msg.querySelector('.chat-msg-text');
+          let text = this.model.get('message');
+
+          if (is_me_message) {
+            text = text.replace(/^\/me/, '');
+          }
+
+          const msg_content = msg.querySelector('.chat-msg__text');
 
           if (text !== url) {
             text = xss.filterXSS(text, {
@@ -83939,30 +83937,6 @@ if (!String.prototype.trim) {
 
 /***/ }),
 
-/***/ "./src/templates/action.html":
-/*!***********************************!*\
-  !*** ./src/templates/action.html ***!
-  \***********************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./node_modules/lodash/escape.js")};
-module.exports = function(o) {
-var __t, __p = '', __e = _.escape;
-__p += '<!-- src/templates/action.html -->\n<div class="message chat-msg chat-action ' +
-__e(o.extra_classes) +
-'" data-isodate="' +
-__e(o.time) +
-'" data-from="' +
-__e(o.from) +
-'">\n    <span class="chat-msg-heading">\n        <span class="chat-msg-author">**' +
-__e(o.username) +
-'</span>\n    </span>\n    <p class="chat-msg-text"><!-- message gets added here via renderMessage --></p>\n</div>\n';
-return __p
-};
-
-/***/ }),
-
 /***/ "./src/templates/add_chatroom_modal.html":
 /*!***********************************************!*\
   !*** ./src/templates/add_chatroom_modal.html ***!
@@ -85230,7 +85204,7 @@ __p += '<!-- src/templates/file_progress.html -->\n<div class="message chat-msg"
 __e(o.time) +
 '" data-msgid="' +
 __e(o.msgid) +
-'">\n    <canvas class="avatar" height="36" width="36"></canvas>\n    <div class="chat-msg-content">\n        <span class="chat-msg-text">Uploading file: <strong>' +
+'">\n    <canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>\n    <div class="chat-msg__content">\n        <span class="chat-msg__text">Uploading file: <strong>' +
 __e(o.file.name) +
 '</strong>, ' +
 __e(o.filesize) +
@@ -85699,7 +85673,11 @@ var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 __p += '<!-- src/templates/message.html -->\n<div class="message chat-msg ' +
 __e(o.type) +
-' ' +
+' ';
+ if (o.is_me_message) { ;
+__p += ' chat-msg--action ';
+ } ;
+__p += ' ' +
 __e(o.extra_classes) +
 '" data-isodate="' +
 __e(o.time) +
@@ -85708,10 +85686,26 @@ __e(o.msgid) +
 '" data-from="' +
 __e(o.from) +
 '">\n    ';
- if (o.type !== 'headline') { ;
-__p += '\n    <canvas class="avatar" height="36" width="36"></canvas>\n    ';
+ if (o.type !== 'headline' && !o.is_me_message) { ;
+__p += '\n    <canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>\n    ';
+ } ;
+__p += '\n    <div class="chat-msg__content ';
+ if (o.is_me_message) { ;
+__p += 'chat-msg__content--action';
  } ;
-__p += '\n    <div class="chat-msg-content">\n        <span class="chat-msg-heading">\n            <span class="chat-msg-author">' +
+__p += '">\n        <span class="chat-msg__heading">\n            ';
+ if (o.is_me_message) { ;
+__p += '<time timestamp="' +
+__e(o.isodate) +
+'" class="chat-msg__time">' +
+__e(o.pretty_time) +
+'</time>';
+ } ;
+__p += '\n            <span class="chat-msg__author">';
+ if (o.is_me_message) { ;
+__p += '**';
+ }; ;
+__p +=
 __e(o.username) +
 '\n                ';
 o.roles.forEach(function (role) { ;
@@ -85719,17 +85713,49 @@ __p += ' <span class="badge badge-secondary">' +
 __e(role) +
 '</span> ';
  }); ;
-__p += '\n            </span>\n            <time timestamp="' +
+__p += '\n            </span>\n            ';
+ if (!o.is_me_message) { ;
+__p += '<time timestamp="' +
 __e(o.isodate) +
-'" class="chat-msg-time">' +
+'" class="chat-msg__time">' +
 __e(o.pretty_time) +
-'</time>\n        </span>\n        ';
+'</time>';
+ } ;
+__p += '\n        </span>\n        ';
+ if (!o.is_me_message) { ;
+__p += '<div class="chat-msg__body">';
+ } ;
+__p += ' \n            ';
  if (o.edited) { ;
 __p += ' <i title="' +
 __e(o.__('This message has been edited')) +
 '" class="fa fa-edit chat-msg-edited"></i> ';
  } ;
-__p += '\n        <span class="chat-msg-text"></span>\n        <div class="chat-msg-media"></div>\n    </div>\n</div>\n';
+__p += '\n            ';
+ if (!o.is_me_message) { ;
+__p += '<div class="chat-msg__message">';
+ } ;
+__p += ' \n                ';
+ if (o.is_spoiler) { ;
+__p += '\n                    <div class="chat-msg__spoiler-hint">\n                        <span class="spoiler-hint">' +
+__e(o.spoiler_hint) +
+'</span>\n                        <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>' +
+__e(o.label_show) +
+'</a>\n                    </div>\n                ';
+ } ;
+__p += '\n                <div class="chat-msg__text';
+ if (o.is_spoiler) { ;
+__p += ' spoiler collapsed';
+ } ;
+__p += '"><!-- message gets added here via renderMessage --></div>\n                <div class="chat-msg__media"></div>\n            ';
+ if (!o.is_me_message) { ;
+__p += '</div>';
+ } ;
+__p += '\n        ';
+ if (!o.is_me_message) { ;
+__p += '</div>';
+ } ;
+__p += ' \n    </div>\n</div>\n';
 return __p
 };
 
@@ -86751,36 +86777,6 @@ return __p
 
 /***/ }),
 
-/***/ "./src/templates/spoiler_message.html":
-/*!********************************************!*\
-  !*** ./src/templates/spoiler_message.html ***!
-  \********************************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./node_modules/lodash/escape.js")};
-module.exports = function(o) {
-var __t, __p = '', __e = _.escape;
-__p += '<!-- src/templates/spoiler_message.html -->\n<div class="message chat-msg ' +
-__e(o.extra_classes) +
-'" data-isodate="' +
-__e(o.time) +
-'" data-msgid="' +
-__e(o.msgid) +
-'">\n    <canvas class="avatar" height="36" width="36"></canvas>\n    <div class="chat-msg-content">\n        <span class="chat-msg-heading">\n            <span class="chat-msg-author">' +
-__e(o.username) +
-'</span>\n            <span class="chat-msg-time">' +
-__e(o.pretty_time) +
-'</span>\n        </span>\n        <div>\n            <span class="spoiler-hint">' +
-__e(o.spoiler_hint) +
-'</span>\n            <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>' +
-__e(o.label_show) +
-'</a>\n        </div>\n        <div class="chat-msg-text spoiler collapsed"><!-- message gets added here via renderMessage --></div>\n    </div>\n</div>\n';
-return __p
-};
-
-/***/ }),
-
 /***/ "./src/templates/status_message.html":
 /*!*******************************************!*\
   !*** ./src/templates/status_message.html ***!
@@ -87275,6 +87271,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
     return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
   };
 
+  u.escapeURL = function (url) {
+    return encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
+  };
+
   u.addHyperlinks = function (text) {
     return URI.withinString(text, function (url) {
       var uri = new URI(url);
@@ -87284,8 +87284,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         url = 'http://' + url;
       }
 
-      url = encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
-      return `<a target="_blank" rel="noopener" href="${u.escapeHTML(url)}">${u.escapeHTML(uri.readable())}</a>`;
+      url = u.escapeHTML(u.escapeURL(url));
+      return `<a target="_blank" rel="noopener" href="${url}">${u.escapeHTML(uri.readable())}</a>`;
     });
   };
 

+ 7 - 3
mockup/chatroom.html

@@ -45,8 +45,11 @@
                                     Romeo Montague has entered the room</div>
 
                                 <div class="message chat-msg chat-msg--action" data-isodate="2018-04-36T18:07:38+02:00">
-                                    <div class="chat-msg__content">
-                                        <span class="chat-msg__heading"><span class="chat-msg__author">**Romeo Montague</span></span>
+                                    <div class="chat-msg__content chat-msg__content--action">
+                                        <span class="chat-msg__heading">
+                                            <time timestamp="2018-12-29" class="chat-msg__time">15:29</time>
+                                            <span class="chat-msg__author">**Romeo Montague</span>
+                                        </span>
                                         <span class="chat-msg__text">looks around</span>
                                     </div>
                                 </div>
@@ -177,7 +180,7 @@
                                         </div>
                                         <div class="chat-msg__body">
                                             <div class="chat-msg__message">
-                                                <div class="chat-msg-media">
+                                                <div class="chat-msg__media">
                                                     <a href="https://images.unsplash.com/photo-1496660067708-010ebdd7ce72?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ea3514e6e00d8ce25c24d992b97929d9&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb"
                                                     target="_blank" rel="noopener">
                                                         <img class="chat-image img-thumbnail" src="https://images.unsplash.com/photo-1496660067708-010ebdd7ce72?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ea3514e6e00d8ce25c24d992b97929d9&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb"> 
@@ -227,6 +230,7 @@
                                             <span class="chat-msg__time">19:49</span>
                                         </div>
                                         <div class="chat-msg__body">
+                                            <i title="This message has been edited" class="fa fa-edit chat-msg__edit-modal"></i>
                                             <div class="chat-msg__message">
                                                 <span class="chat-msg__text">I mean, sir, in delay We waste our lights in vain, like lamps by day.</span>
                                             </div>

+ 1 - 31
sass/_chatbox.scss

@@ -124,7 +124,7 @@
             background-color: $chat-head-color;
             box-shadow: 1px 3px 5px 3px rgba(0, 0, 0, 0.4);
             z-index: 1;
-            overflow-y: scroll;
+            overflow-y: hidden;
             width: 100%;
 
             @media screen and (max-height: $mobile-landscape-height) {
@@ -464,21 +464,6 @@
             min-width: $overlayed-chat-width!important;
             width: $overlayed-chat-width;
         }
-
-        .chat-body {
-            .chat-message {
-                line-height: $line-height-large;
-                .chat-msg-author {
-                    line-height: $line-height-large;
-                }
-                .chat-msg-content {
-                    line-height: $line-height-large;
-                    .emojione {
-                        margin-bottom: -5px;
-                    }
-                }
-            }
-        }
     }
     .chatbox {
         form.sendXMPPMessage {
@@ -597,21 +582,6 @@
             background-color: $chat-head-color;
             border-top-left-radius: $chatbox-border-radius;
             border-top-right-radius: $chatbox-border-radius;
-
-            .chat-message {
-                line-height: $line-height;
-                font-size: $font-size-small;
-                .chat-msg-author {
-                    line-height: $line-height;
-                }
-                .chat-msg-content {
-                    line-height: $line-height;
-                    .emojione {
-                        height: $line-height;
-                        margin-bottom: -$line-height/4;
-                    }
-                }
-            }
         }
         .chat-content {
             border-top-left-radius: $chatbox-border-radius;

+ 16 - 2
sass/_messages.scss

@@ -104,6 +104,7 @@
                     whitespace: nowrap;
                 }
             }
+
             .chat-msg__content {
                 display: flex;
                 flex-direction: column;
@@ -112,6 +113,9 @@
                 margin-left: 0.5rem;
                 width: 100%;
             }
+            .chat-msg__content--action {
+                margin-left: 0;
+            }
 
             .chat-msg__body {
                 display: flex;
@@ -126,8 +130,9 @@
                 width: 100%;
             }
 
-            .chat-msg-edited {
+            .chat-msg__edit-modal {
                 cursor: pointer;
+                padding-right: 0.5em;
             }
             &.headline {
                 .chat-msg__body {
@@ -148,8 +153,9 @@
                 }
             }
             
-            .chat-msg-media {
+            .chat-msg__media {
                 margin-top: 0.25rem;
+                word-break: break-all;
                 a {
                     word-wrap: break-word;
                 }
@@ -188,6 +194,7 @@
                 display: block;
 
                 .chat-msg__author {
+                    white-space: nowrap;
                     font-family: $heading-font; 
                     font-size: 115%;
                     .badge {
@@ -202,6 +209,7 @@
             }
             &.chat-msg--action {
                 .chat-msg__content {
+                    flex-wrap: wrap;
                     flex-direction: row;
                     justify-content: flex-start;
                 }
@@ -213,6 +221,12 @@
                     padding-bottom: 0;
                     width: auto;
                 }
+                .chat-msg__author {
+                    font-size: $message-font-size;
+                }
+                .chat-msg__time {
+                    margin-left: 0;
+                }
             }
 
             &.chat-msg--followup {

+ 14 - 14
spec/chatbox.js

@@ -76,33 +76,33 @@
                     test_utils.waitUntil(function () {
                         return u.isVisible(view.el);
                     }).then(function () {
-                        expect(view.el.querySelectorAll('.chat-action').length).toBe(1);
-                        expect(_.includes(view.el.querySelector('.chat-msg-author').textContent, '**Max Frankfurter')).toBeTruthy();
-                        expect($(view.el).find('.chat-msg-text').text()).toBe(' is tired');
+                        expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1);
+                        expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Max Frankfurter')).toBeTruthy();
+                        expect($(view.el).find('.chat-msg__text').text()).toBe(' is tired');
 
                         message = '/me is as well';
                         test_utils.sendMessage(view, message);
-                        expect(view.el.querySelectorAll('.chat-action').length).toBe(2);
+                        expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
 
-                        return test_utils.waitUntil(() => $(view.el).find('.chat-msg-author:last').text() === '**Max Mustermann');
+                        return test_utils.waitUntil(() => $(view.el).find('.chat-msg__author:last').text().trim() === '**Max Mustermann');
                     }).then(function () {
-                        expect($(view.el).find('.chat-msg-text:last').text()).toBe(' is as well');
-                        expect($(view.el).find('.chat-msg:last').hasClass('chat-msg-followup')).toBe(false);
+                        expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well');
+                        expect($(view.el).find('.chat-msg:last').hasClass('chat-msg--followup')).toBe(false);
 
                         // Check that /me messages after a normal message don't
-                        // get the 'chat-msg-followup' class.
+                        // get the 'chat-msg--followup' class.
                         message = 'This a normal message';
                         test_utils.sendMessage(view, message);
                         let message_el = view.el.querySelector('.message:last-child');
-                        expect(u.hasClass('chat-msg-followup', message_el)).toBeFalsy();
+                        expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
 
                         message = '/me wrote a 3rd person message';
                         test_utils.sendMessage(view, message);
                         message_el = view.el.querySelector('.message:last-child');
-                        expect(view.el.querySelectorAll('.chat-action').length).toBe(3);
-                        expect($(view.el).find('.chat-msg-text:last').text()).toBe(' wrote a 3rd person message');
-                        expect($(view.el).find('.chat-msg-author:last').is(':visible')).toBeTruthy();
-                        expect(u.hasClass('chat-msg-followup', message_el)).toBeFalsy();
+                        expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
+                        expect($(view.el).find('.chat-msg__text:last').text()).toBe(' wrote a 3rd person message');
+                        expect($(view.el).find('.chat-msg__author:last').is(':visible')).toBeTruthy();
+                        expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
                         done();
                     });
                 });
@@ -1627,7 +1627,7 @@
                         return $(view.el).find('.chat-content').find('.chat-msg').length;
                     }, 1000).then(function () {
                         expect(view.model.sendMessage).toHaveBeenCalled();
-                        var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+                        var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
                         expect(msg.html()).toEqual(
                             '<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&amp;'+
                             'mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.7869'+

+ 8 - 8
spec/chatroom.js

@@ -867,8 +867,8 @@
                             '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();
-                    expect($(view.el).find('.chat-msg-text').text()).toBe(' is tired');
+                    expect(_.includes($(view.el).find('.chat-msg__author').text(), '**Dyon van de Wege')).toBeTruthy();
+                    expect($(view.el).find('.chat-msg__text').text()).toBe(' is tired');
 
                     message = '/me is as well';
                     msg = $msg({
@@ -878,8 +878,8 @@
                         type: 'groupchat'
                     }).c('body').t(message).tree();
                     view.model.onMessage(msg);
-                    expect(_.includes($(view.el).find('.chat-msg-author:last').text(), '**Max Mustermann')).toBeTruthy();
-                    expect($(view.el).find('.chat-msg-text:last').text()).toBe(' is as well');
+                    expect(_.includes($(view.el).find('.chat-msg__author:last').text(), '**Max Mustermann')).toBeTruthy();
+                    expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well');
                     done();
                 });
             }));
@@ -1545,7 +1545,7 @@
                     view.model.onMessage(message.nodeTree);
                     var $chat_content = $(view.el).find('.chat-content');
                     expect($chat_content.find('.chat-msg').length).toBe(1);
-                    expect($chat_content.find('.chat-msg-text').text()).toBe(text);
+                    expect($chat_content.find('.chat-msg__text').text()).toBe(text);
                     expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
                     done();
                 });
@@ -1583,7 +1583,7 @@
                     }).c('body').t(text);
                     view.model.onMessage(message.nodeTree);
                     expect($chat_content.find('.chat-msg').length).toBe(1);
-                    expect($chat_content.find('.chat-msg-text').last().text()).toBe(text);
+                    expect($chat_content.find('.chat-msg__text').last().text()).toBe(text);
                     // We don't emit an event if it's our own message
                     expect(_converse.emit.calls.count(), 1);
                     done();
@@ -1624,7 +1624,7 @@
 
                         // Now check that the message appears inside the chatbox in the DOM
                         var $chat_content = $(view.el).find('.chat-content');
-                        var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
+                        var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text();
                         expect(msg_txt).toEqual(message);
                         expect(view.content.scrollTop).toBe(0);
                         done();
@@ -3475,7 +3475,7 @@
                             var messages = view.el.querySelectorAll('.message');
                             expect(messages.length).toBe(8);
                             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                            expect(view.el.querySelector('.chat-msg .chat-msg-text').textContent).toBe('hello world');
+                            expect(view.el.querySelector('.chat-msg .chat-msg__text').textContent).toBe('hello world');
 
                             // Test that the composing notifications get removed
                             // via timeout.

+ 3 - 3
spec/http-file-upload.js

@@ -364,7 +364,7 @@
                                             }, 1000);
                                         }).then(function () {
                                             // Check that the image renders
-                                            expect(view.el.querySelector('.chat-msg .chat-msg-media').innerHTML.trim()).toEqual(
+                                            expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                                                 '<!-- src/templates/image.html -->\n'+
                                                 '<a href="http://localhost:8000/logo/conversejs-filled.svg" target="_blank" rel="noopener">'+
                                                     '<img class="chat-image img-thumbnail" src="http://localhost:8000/logo/conversejs-filled.svg">'+
@@ -472,7 +472,7 @@
                                                 }, 1000);
                                             }).then(function () {
                                                 // Check that the image renders
-                                                expect(view.el.querySelector('.chat-msg .chat-msg-media').innerHTML.trim()).toEqual(
+                                                expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                                                     '<!-- src/templates/image.html -->\n'+
                                                     '<a href="http://localhost:8000/logo/conversejs-filled.svg" target="_blank" rel="noopener">'+
                                                         '<img class="chat-image img-thumbnail" src="http://localhost:8000/logo/conversejs-filled.svg"></a>')
@@ -683,7 +683,7 @@
                                         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');
-                                        expect(view.el.querySelector('.chat-content .chat-msg-text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
+                                        expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
                                         done();
                                     });
                                     var sent_stanza;

+ 170 - 177
spec/messages.js

@@ -45,7 +45,7 @@
                 keyCode: 13 // Enter
             });
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            expect(view.el.querySelector('.chat-msg-text').textContent)
+            expect(view.el.querySelector('.chat-msg__text').textContent)
                 .toBe('But soft, what light through yonder airlock breaks?');
 
             const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
@@ -152,11 +152,11 @@
             expect(textarea.value).toBe('');
             const messages = view.el.querySelectorAll('.chat-msg');
             expect(messages.length).toBe(3);
-            expect(messages[0].querySelector('.chat-msg-text').textContent)
+            expect(messages[0].querySelector('.chat-msg__text').textContent)
                 .toBe('But soft, what light through yonder window breaks?');
-            expect(messages[1].querySelector('.chat-msg-text').textContent)
+            expect(messages[1].querySelector('.chat-msg__text').textContent)
                 .toBe('It is the east, and Juliet is the sun.');
-            expect(messages[2].querySelector('.chat-msg-text').textContent)
+            expect(messages[2].querySelector('.chat-msg__text').textContent)
                 .toBe('Arise, fair sun, and kill the envious moon');
 
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
@@ -209,11 +209,11 @@
                     expect(msg_obj.get('is_delayed')).toEqual(false);
                     // Now check that the message appears inside the chatbox in the DOM
                     const chat_content = chatboxview.el.querySelector('.chat-content');
-                    expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
-                    expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
+                    expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
+                    expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
                     return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
                     .then(function () {
-                        expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Max Frankfurter');
+                        expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Max Frankfurter');
                         done();
                     });
                 });
@@ -240,7 +240,7 @@
 
                 var chatboxview = _converse.chatboxviews.get(sender_jid);
                 expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(chatboxview.el.querySelector('.chat-msg-text').textContent)
+                expect(chatboxview.el.querySelector('.chat-msg__text').textContent)
                     .toBe('But soft, what light through yonder airlock breaks?');
 
                 _converse.chatboxes.onMessage($msg({
@@ -251,11 +251,11 @@
                     }).c('body').t('But soft, what light through yonder chimney breaks?').up()
                       .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
-                test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg-text').textContent ===
+                test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent ===
                     'But soft, what light through yonder chimney breaks?').then(() => {
 
                     expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1);
-                    expect(chatboxview.el.querySelectorAll('.chat-msg-content .fa-edit').length).toBe(1);
+                    expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
 
                     _converse.chatboxes.onMessage($msg({
                             'from': sender_jid,
@@ -265,12 +265,12 @@
                         }).c('body').t('But soft, what light through yonder window breaks?').up()
                         .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
-                    return test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg-text').textContent ===
+                    return test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent ===
                         'But soft, what light through yonder window breaks?');
                 }).then(() => {
                     expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1);
-                    expect(chatboxview.el.querySelectorAll('.chat-msg-content .fa-edit').length).toBe(1);
-                    chatboxview.el.querySelector('.chat-msg-content .fa-edit').click();
+                    expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
+                    chatboxview.el.querySelector('.chat-msg__content .fa-edit').click();
                     const modal = chatboxview.model.messages.at(0).message_versions_modal;
                     return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
                 }).then(() => {
@@ -324,7 +324,7 @@
                     expect(chatbox).toBeDefined();
                     expect(chatboxview).toBeDefined();
 
-                    var author_el = chatboxview.el.querySelector('.chat-msg-author');
+                    var author_el = chatboxview.el.querySelector('.chat-msg__author');
                     expect(chatbox.get('fullname') === sender_jid);
                     expect( _.includes(author_el.textContent.trim(), 'max.frankfurter@localhost')).toBeTruthy();
 
@@ -333,7 +333,7 @@
                         expect(_converse.api.vcard.get).toHaveBeenCalled();
                         return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
                     }).then(function () {
-                        var author_el = chatboxview.el.querySelector('.chat-msg-author');
+                        var author_el = chatboxview.el.querySelector('.chat-msg__author');
                         expect( _.includes(author_el.textContent.trim(), 'Max Frankfurter')).toBeTruthy();
                         done();
                     });
@@ -389,9 +389,9 @@
                     expect(msg_obj.get('is_delayed')).toEqual(false);
                     // Now check that the message appears inside the chatbox in the DOM
                     var chat_content = chatboxview.el.querySelector('.chat-content');
-                    expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
-                    expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
-                    expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('max.frankfurter@localhost');
+                    expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
+                    expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
+                    expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('max.frankfurter@localhost');
                     done();
                 }));
             });
@@ -436,7 +436,7 @@
                     });
                     view.model.sendMessage(message);
                     var $chat_content = $(view.el).find('.chat-content');
-                    var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
+                    var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text();
                     expect(msg_txt).toEqual(msg_text);
 
                     // We send another message, for which an error will
@@ -451,7 +451,7 @@
                         'message': msg_text
                     });
                     view.model.sendMessage(message);
-                    msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
+                    msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text();
                     expect(msg_txt).toEqual(msg_text);
 
                     /* <message xmlns="jabber:client"
@@ -571,7 +571,7 @@
 
                     // Now check that the message appears inside the chatbox in the DOM
                     var $chat_content = $(chatboxview.el).find('.chat-content');
-                    var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
+                    var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text();
                     expect(msg_txt).toEqual(message);
                     return test_utils.waitUntil(function () {
                         return u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator'));
@@ -634,7 +634,7 @@
                     expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
                     var chatboxview = _converse.chatboxviews.get(sender_jid);
                     var $chat_content = $(chatboxview.el).find('.chat-content:last');
-                    var msg_txt = $chat_content.find('.chat-msg').find('.chat-msg-text').text();
+                    var msg_txt = $chat_content.find('.chat-msg').find('.chat-msg__text').text();
                     expect(msg_txt).toEqual(message);
                     done();
                 });
@@ -781,10 +781,10 @@
                 expect($time.text()).toEqual('Sunday Dec 31st 2017')
 
                 $day = $chat_content.find('.date-separator:first');
-                expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Older message');
+                expect($day[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message');
 
-                var $el = $chat_content.find('.chat-msg:first').find('.chat-msg-text')
-                expect($el.hasClass('chat-msg-followup')).toBe(false);
+                var $el = $chat_content.find('.chat-msg:first').find('.chat-msg__text')
+                expect($el.hasClass('chat-msg--followup')).toBe(false);
                 expect($el.text()).toEqual('Older message');
 
                 $time = $chat_content.find('time.separator-text:eq(1)');
@@ -792,35 +792,35 @@
 
                 $day = $chat_content.find('.date-separator:eq(1)');
                 expect($day.data('isodate')).toEqual(moment('2018-01-01T00:00:00').format());
-                expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Inbetween message');
+                expect($day[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message');
 
                 $el = $chat_content.find('.chat-msg:eq(1)');
-                expect($el.find('.chat-msg-text').text()).toEqual('Inbetween message');
-                expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('another inbetween message');
+                expect($el.find('.chat-msg__text').text()).toEqual('Inbetween message');
+                expect($el[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message');
                 $el = $chat_content.find('.chat-msg:eq(2)');
-                expect($el.find('.chat-msg-text').text()).toEqual('another inbetween message');
-                expect($el.hasClass('chat-msg-followup')).toBe(true);
+                expect($el.find('.chat-msg__text').text()).toEqual('another inbetween message');
+                expect($el.hasClass('chat-msg--followup')).toBe(true);
 
                 $time = $chat_content.find('time.separator-text:nth(2)');
                 expect($time.text()).toEqual("Tuesday Jan 2nd 2018");
 
                 $day = $chat_content.find('.date-separator:nth(2)');
                 expect($day.data('isodate')).toEqual(moment('2018-01-02T00:00:00').format());
-                expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('An earlier message on the next day');
+                expect($day[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day');
 
                 $el = $chat_content.find('.chat-msg:eq(3)');
-                expect($el.find('.chat-msg-text').text()).toEqual('An earlier message on the next day');
-                expect($el.hasClass('chat-msg-followup')).toBe(false);
+                expect($el.find('.chat-msg__text').text()).toEqual('An earlier message on the next day');
+                expect($el.hasClass('chat-msg--followup')).toBe(false);
 
                 $el = $chat_content.find('.chat-msg:eq(4)');
-                expect($el.find('.chat-msg-text').text()).toEqual('message');
-                expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('newer message from the next day');
-                expect($el.hasClass('chat-msg-followup')).toBe(false);
+                expect($el.find('.chat-msg__text').text()).toEqual('message');
+                expect($el[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day');
+                expect($el.hasClass('chat-msg--followup')).toBe(false);
 
                 $day = $chat_content.find('.date-separator:last');
                 expect($day.data('isodate')).toEqual(moment().startOf('day').format());
-                expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('latest message');
-                expect($el.hasClass('chat-msg-followup')).toBe(false);
+                expect($day[0].nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message');
+                expect($el.hasClass('chat-msg--followup')).toBe(false);
                 done();
             });
         }));
@@ -839,7 +839,7 @@
             sinon.spy(_converse, 'log');
             sinon.spy(_converse.chatboxes, 'getChatBox');
             sinon.spy(u, 'isHeadlineMessage');
-            var msg = $msg({
+            const msg = $msg({
                     from: 'localhost',
                     to: _converse.bare_jid,
                     type: 'chat',
@@ -902,12 +902,12 @@
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('is_delayed')).toEqual(false);
             // Now check that the message appears inside the chatbox in the DOM
-            var chat_content = chatboxview.el.querySelector('.chat-content');
-            expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(msgtext);
-            expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
+            const chat_content = chatboxview.el.querySelector('.chat-content');
+            expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
+            expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
             return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Candice van der Knijff')
             .then(function () {
-                expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
+                expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Candice van der Knijff');
                 done();
             });
         }));
@@ -917,16 +917,15 @@
                 null, ['rosterGroupsFetched'], {},
                 function (done, _converse) {
 
-            var contact, sent_stanza, IQ_id, stanza;
             test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']).then(function () {
                 test_utils.createContacts(_converse, 'current');
                 test_utils.openControlBox();
 
                 // Send a message from a different resource
                 spyOn(_converse, 'log');
-                var msgtext = 'This is a sent carbon message';
-                var recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var msg = $msg({
+                const msgtext = 'This is a sent carbon message';
+                const recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
+                const msg = $msg({
                         'from': _converse.bare_jid,
                         'id': (new Date()).getTime(),
                         'to': _converse.connection.jid,
@@ -943,20 +942,19 @@
                 _converse.chatboxes.onMessage(msg);
 
                 // Check that the chatbox and its view now exist
-                var chatbox = _converse.chatboxes.get(recipient_jid);
-                var chatboxview = _converse.chatboxviews.get(recipient_jid);
+                const chatbox = _converse.chatboxes.get(recipient_jid);
+                const chatboxview = _converse.chatboxviews.get(recipient_jid);
                 expect(chatbox).toBeDefined();
                 expect(chatboxview).toBeDefined();
                 // Check that the message was received and check the message parameters
                 expect(chatbox.messages.length).toEqual(1);
-                var msg_obj = chatbox.messages.models[0];
+                const  msg_obj = chatbox.messages.models[0];
                 expect(msg_obj.get('message')).toEqual(msgtext);
                 expect(msg_obj.get('fullname')).toEqual(_converse.xmppstatus.get('fullname'));
                 expect(msg_obj.get('sender')).toEqual('me');
                 expect(msg_obj.get('is_delayed')).toEqual(false);
                 // Now check that the message appears inside the chatbox in the DOM
-                var $chat_content = $(chatboxview.el).find('.chat-content');
-                var msg_txt = $chat_content.find('.chat-msg').find('.chat-msg-text').text();
+                const msg_txt = chatboxview.el.querySelector('.chat-content .chat-msg .chat-msg__text').textContent;
                 expect(msg_txt).toEqual(msgtext);
                 done();
             });
@@ -1014,6 +1012,9 @@
                 null, ['rosterGroupsFetched'], {},
                 function (done, _converse) {
 
+            if (_converse.view_mode === 'fullscreen') {
+                return done();
+            }
             test_utils.createContacts(_converse, 'current');
             test_utils.openControlBox();
             test_utils.waitUntil(function () {
@@ -1089,7 +1090,6 @@
                 var message = 'This is a day old message';
                 var chatbox = _converse.chatboxes.get(contact_jid);
                 var chatboxview = _converse.chatboxviews.get(contact_jid);
-                var $chat_content = $(chatboxview.el).find('.chat-content');
                 var msg_obj;
                 var msg_txt;
                 var sender_txt;
@@ -1113,16 +1113,17 @@
 
                 return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Candice van der Knijff')
                 .then(function () {
-                    var chat_content = chatboxview.el.querySelector('.chat-content');
-                    expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
-                    expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
-                    expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
+                    const chat_content = chatboxview.el.querySelector('.chat-content');
+                    expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
+                    expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
+                    expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Candice van der Knijff');
 
-                    var $day = $chat_content.find('.date-separator');
-                    expect($day.length).toEqual(1);
-                    expect($day.attr('class')).toEqual('message date-separator');
-                    expect($day.data('isodate')).toEqual(moment(one_day_ago.startOf('day')).format());
+                    expect(chat_content.querySelectorAll('.date-separator').length).toEqual(1);
+                    const day = chat_content.querySelector('.date-separator');
+                    expect(day.getAttribute('class')).toEqual('message date-separator');
+                    expect(day.getAttribute('data-isodate')).toEqual(moment(one_day_ago.startOf('day')).format());
 
+                    const $chat_content = $(chat_content);
                     var $time = $chat_content.find('time.separator-text');
                     expect($time.text()).toEqual(moment(one_day_ago.startOf('day')).format("dddd MMM Do YYYY"));
 
@@ -1141,7 +1142,7 @@
                     expect($chat_content[0].querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements
 
                     var message_date = new Date();
-                    $day = $chat_content.find('.date-separator:last');
+                    const $day = $chat_content.find('.date-separator:last');
                     expect($day.length).toEqual(1);
                     expect($day.attr('class')).toEqual('message date-separator');
                     expect($day.data('isodate')).toEqual(moment(message_date).startOf('day').format());
@@ -1156,12 +1157,12 @@
                     expect(msg_obj.get('fullname')).toEqual(contact_name);
                     expect(msg_obj.get('sender')).toEqual('them');
                     expect(msg_obj.get('is_delayed')).toEqual(false);
-                    msg_txt = $chat_content.find('.chat-msg').last().find('.chat-msg-text').text();
+                    msg_txt = $chat_content.find('.chat-msg').last().find('.chat-msg__text').text();
                     expect(msg_txt).toEqual(message);
 
-                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-text').textContent).toEqual(message);
-                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
-                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
+                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__text').textContent).toEqual(message);
+                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
+                    expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__author').textContent.trim()).toBe('Candice van der Knijff');
                     done();
                 });
             });
@@ -1186,7 +1187,7 @@
             expect(view.model.sendMessage).toHaveBeenCalled();
             expect(view.model.messages.length, 2);
             expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]);
-            expect($(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text').text()).toEqual(message);
+            expect($(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text').text()).toEqual(message);
             done();
         }));
 
@@ -1205,7 +1206,7 @@
             spyOn(view.model, 'sendMessage').and.callThrough();
             test_utils.sendMessage(view, message);
             expect(view.model.sendMessage).toHaveBeenCalled();
-            var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('&lt;p&gt;This message contains &lt;em&gt;some&lt;/em&gt; &lt;b&gt;markup&lt;/b&gt;&lt;/p&gt;');
             done();
@@ -1226,7 +1227,7 @@
             spyOn(view.model, 'sendMessage').and.callThrough();
             test_utils.sendMessage(view, message);
             expect(view.model.sendMessage).toHaveBeenCalled();
-            var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('This message contains a hyperlink: <a target="_blank" rel="noopener" href="http://www.opkode.com">www.opkode.com</a>');
             done();
@@ -1248,14 +1249,14 @@
             message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
             test_utils.sendMessage(view, message);
             expect(view.model.sendMessage).toHaveBeenCalled();
-            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
             message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
             test_utils.sendMessage(view, message);
 
             expect(view.model.sendMessage).toHaveBeenCalled();
-            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
 
@@ -1263,7 +1264,7 @@
             test_utils.sendMessage(view, message);
 
             expect(view.model.sendMessage).toHaveBeenCalled();
-            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
 
@@ -1271,7 +1272,7 @@
             test_utils.sendMessage(view, message);
 
             expect(view.model.sendMessage).toHaveBeenCalled();
-            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+            msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
             expect(msg.text()).toEqual(message);
             expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
             done();
@@ -1292,7 +1293,7 @@
 
             const view = _converse.chatboxviews.get(contact_jid);
             const chat_content = view.el.querySelector('.chat-content');
-            expect(chat_content.querySelector('.chat-msg-text').innerHTML).toBe('Hey<br>Have you heard the news?');
+            expect(chat_content.querySelector('.chat-msg__text').innerHTML).toBe('Hey<br>Have you heard the news?');
 
             stanza = Strophe.xmlHtmlNode(
                 "<message from='"+contact_jid+"'"+
@@ -1301,7 +1302,7 @@
                 "    <body>Hey\n\n\nHave you heard the news?</body>"+
                 "</message>").firstChild;
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            expect(chat_content.querySelector('.message:last-child .chat-msg-text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
+            expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
 
             stanza = Strophe.xmlHtmlNode(
                 "<message from='"+contact_jid+"'"+
@@ -1310,7 +1311,7 @@
                 "    <body>Hey\nHave you heard\nthe news?</body>"+
                 "</message>").firstChild;
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            expect(chat_content.querySelector('.message:last-child .chat-msg-text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
+            expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
             done();
         }));
 
@@ -1319,18 +1320,18 @@
                 null, ['rosterGroupsFetched'], {},
                 function (done, _converse) {
 
-            test_utils.createContacts(_converse, 'current');
-            var base_url = document.URL.split(window.location.pathname)[0];
-            var message = base_url+"/logo/conversejs-filled.svg";
-            var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            test_utils.createContacts(_converse, 'current', 1);
+            const base_url = document.URL.split(window.location.pathname)[0];
+            let message = base_url+"/logo/conversejs-filled.svg";
+            const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
             test_utils.openChatBoxFor(_converse, contact_jid);
-            var view = _converse.chatboxviews.get(contact_jid);
+            const view = _converse.chatboxviews.get(contact_jid);
             spyOn(view.model, 'sendMessage').and.callThrough();
             test_utils.sendMessage(view, message);
 
             test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000).then(() => {
                 expect(view.model.sendMessage).toHaveBeenCalled();
-                var msg = $(view.el).find('.chat-content .chat-msg').last().find('.chat-msg-text');
+                const msg = $(view.el).find('.chat-content .chat-msg').last().find('.chat-msg__text');
                 expect(msg.html().trim()).toEqual(
                     '<!-- src/templates/image.html -->\n'+
                     '<a href="'+base_url+'/logo/conversejs-filled.svg" target="_blank" rel="noopener"><img class="chat-image img-thumbnail"'+
@@ -1340,7 +1341,7 @@
                 return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
             }).then(() => {
                 expect(view.model.sendMessage).toHaveBeenCalled();
-                var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+                const msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
                 expect(msg.html().trim()).toEqual(
                     '<!-- src/templates/image.html -->\n'+
                     '<a href="'+base_url+'/logo/conversejs-filled.svg?param1=val1&amp;param2=val2" target="_blank" rel="noopener"><img'+
@@ -1352,7 +1353,7 @@
                 return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
             }).then(function () {
                 expect(view.model.sendMessage).toHaveBeenCalled();
-                var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
+                const msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text');
                 expect(msg[0].textContent.trim()).toEqual('hello world');
                 expect(msg[0].querySelectorAll('img').length).toEqual(2);
                 done();
@@ -1377,10 +1378,10 @@
             expect(chatbox.messages.models.length, 1);
             var msg_object = chatbox.messages.models[0];
 
-            var msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg-author');
+            var msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author');
             expect(msg_author.textContent.trim()).toBe('Max Mustermann');
 
-            var msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg-time');
+            var msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time');
             var time = moment(msg_object.get('time')).format(_converse.time_format);
             expect(msg_time.textContent).toBe(time);
             done();
@@ -1394,7 +1395,7 @@
             test_utils.createContacts(_converse, 'current');
             test_utils.openControlBox();
 
-            test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group').length, 300)
+            test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300)
             .then(function () {
                 const base_time = new Date();
                 const ONE_MINUTE_LATER = 60000;
@@ -1468,19 +1469,19 @@
                 var chat_content = view.el.querySelector('.chat-content');
                 expect(chat_content.querySelectorAll('.message').length).toBe(6);
                 expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5);
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
                     "Another message 3 minutes later");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
                     "Another message 14 minutes since we started");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
                     "Another message 1 minute and 1 second since the previous one");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
                     "Another message within 10 minutes, but from a different person");
 
                 // Let's add a delayed, inbetween message
@@ -1499,21 +1500,21 @@
 
                 expect(chat_content.querySelectorAll('.message').length).toBe(7);
                 expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6);
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
                     "Another message 3 minutes later");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
                     "A delayed message, sent 5 minutes since we started");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
                     "Another message 14 minutes since we started");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
                     "Another message 1 minute and 1 second since the previous one");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
 
                 _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
                     .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
@@ -1527,25 +1528,25 @@
                         .tree());
                 expect(chat_content.querySelectorAll('.message').length).toBe(8);
                 expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7);
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
                     "Another message 3 minutes later");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
                     "A carbon message 4 minutes later");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
                     "A delayed message, sent 5 minutes since we started");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
                     "Another message 14 minutes since we started");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
-                expect(chat_content.querySelector('.message:nth-child(7) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe(
                     "Another message 1 minute and 1 second since the previous one");
-                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
-                expect(chat_content.querySelector('.message:nth-child(8) .chat-msg-text').textContent).toBe(
+                expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(8) .chat-msg__text').textContent).toBe(
                     "Another message within 10 minutes, but from a different person");
 
                 jasmine.clock().uninstall();
@@ -1561,13 +1562,13 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-                test_utils.createContacts(_converse, 'current');
-                var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                test_utils.createContacts(_converse, 'current', 1);
+                const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 test_utils.openChatBoxFor(_converse, contact_jid);
-                var view = _converse.chatboxviews.get(contact_jid);
+                const view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view.model, 'sendMessage').and.callThrough();
 
-                var stanza = Strophe.xmlHtmlNode(
+                const stanza = Strophe.xmlHtmlNode(
                     "<message from='"+contact_jid+"'"+
                     "         type='chat'"+
                     "         to='dummy@localhost/resource'>"+
@@ -1576,19 +1577,17 @@
                     "</message>").firstChild;
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                test_utils.waitUntil(function () {
-                    return view.el.querySelectorAll('.chat-content .chat-msg audio').length;
-                }, 1000).then(function () {
-                    var msg = view.el.querySelector('.chat-msg .chat-msg-text');
-                    expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you heard this funny audio?</span>');
-                    var media = view.el.querySelector('.chat-msg .chat-msg-media');
+                test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000).then(function () {
+                    let msg = view.el.querySelector('.chat-msg .chat-msg__text');
+                    expect(msg.outerHTML).toEqual('<div class="chat-msg__text">Have you heard this funny audio?</div>');
+                    let media = view.el.querySelector('.chat-msg .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/audio.html -->'+
                         '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
                         '<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
 
                     // If the <url> and <body> contents is the same, don't duplicate.
-                    var stanza = Strophe.xmlHtmlNode(
+                    const stanza = Strophe.xmlHtmlNode(
                         "<message from='"+contact_jid+"'"+
                         "         type='chat'"+
                         "         to='dummy@localhost/resource'>"+
@@ -1597,9 +1596,9 @@
                         "</message>").firstChild;
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                    msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
-                    expect(msg.innerHTML).toEqual('');
-                    media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
+                    msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
+                    expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
+                    media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/audio.html -->'+
                         '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
@@ -1614,12 +1613,12 @@
                     function (done, _converse) {
 
                 test_utils.createContacts(_converse, 'current');
-                var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 test_utils.openChatBoxFor(_converse, contact_jid);
-                var view = _converse.chatboxviews.get(contact_jid);
+                const view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view.model, 'sendMessage').and.callThrough();
 
-                var stanza = Strophe.xmlHtmlNode(
+                const stanza = Strophe.xmlHtmlNode(
                     "<message from='"+contact_jid+"'"+
                     "         type='chat'"+
                     "         to='dummy@localhost/resource'>"+
@@ -1628,19 +1627,17 @@
                     "</message>").firstChild;
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                test_utils.waitUntil(function () {
-                    return view.el.querySelectorAll('.chat-content .chat-msg video').length;
-                }, 2000).then(function () {
-                    var msg = view.el.querySelector('.chat-msg .chat-msg-text');
-                    expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny video?</span>');
-                    var media = view.el.querySelector('.chat-msg .chat-msg-media');
+                test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000).then(function () {
+                    let msg = view.el.querySelector('.chat-msg .chat-msg__text');
+                    expect(msg.outerHTML).toEqual('<div class="chat-msg__text">Have you seen this funny video?</div>');
+                    let media = view.el.querySelector('.chat-msg .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/video.html -->'+
                         '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
                         '<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
 
                     // If the <url> and <body> contents is the same, don't duplicate.
-                    var stanza = Strophe.xmlHtmlNode(
+                    const stanza = Strophe.xmlHtmlNode(
                         "<message from='"+contact_jid+"'"+
                         "         type='chat'"+
                         "         to='dummy@localhost/resource'>"+
@@ -1649,9 +1646,9 @@
                         "</message>").firstChild;
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                    msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
-                    expect(msg.innerHTML).toEqual('');
-                    media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
+                    msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
+                    expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
+                    media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/video.html -->'+
                         '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
@@ -1665,13 +1662,13 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-                test_utils.createContacts(_converse, 'current');
-                var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                test_utils.createContacts(_converse, 'current', 1);
+                const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 test_utils.openChatBoxFor(_converse, contact_jid);
-                var view = _converse.chatboxviews.get(contact_jid);
+                const view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view.model, 'sendMessage').and.callThrough();
 
-                var stanza = Strophe.xmlHtmlNode(
+                const stanza = Strophe.xmlHtmlNode(
                     "<message from='"+contact_jid+"'"+
                     "         type='chat'"+
                     "         to='dummy@localhost/resource'>"+
@@ -1680,12 +1677,10 @@
                     "</message>").firstChild;
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                test_utils.waitUntil(function () {
-                    return view.el.querySelectorAll('.chat-content .chat-msg a').length;
-                }, 1000).then(function () {
-                    var msg = view.el.querySelector('.chat-msg .chat-msg-text');
-                    expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you downloaded this funny file?</span>');
-                    var media = view.el.querySelector('.chat-msg .chat-msg-media');
+                test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000).then(function () {
+                    const msg = view.el.querySelector('.chat-msg .chat-msg__text');
+                    expect(msg.outerHTML).toEqual('<div class="chat-msg__text">Have you downloaded this funny file?</div>');
+                    const media = view.el.querySelector('.chat-msg .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/file.html -->'+
                         '<a target="_blank" rel="noopener" href="http://localhost/funny.pdf">Download "funny.pdf"</a>');
@@ -1699,14 +1694,14 @@
                     function (done, _converse) {
 
                 test_utils.createContacts(_converse, 'current');
-                var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 test_utils.openChatBoxFor(_converse, contact_jid);
-                var view = _converse.chatboxviews.get(contact_jid);
+                const view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view.model, 'sendMessage').and.callThrough();
-                var base_url = document.URL.split(window.location.pathname)[0];
-                var url = base_url+"/logo/conversejs-filled.svg";
+                const base_url = document.URL.split(window.location.pathname)[0];
+                const url = base_url+"/logo/conversejs-filled.svg";
 
-                var stanza = Strophe.xmlHtmlNode(
+                const stanza = Strophe.xmlHtmlNode(
                     "<message from='"+contact_jid+"'"+
                     "         type='chat'"+
                     "         to='dummy@localhost/resource'>"+
@@ -1715,12 +1710,10 @@
                     "</message>").firstChild;
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                test_utils.waitUntil(function () {
-                    return view.el.querySelectorAll('.chat-content .chat-msg img').length;
-                }, 2000).then(function () {
-                    var msg = view.el.querySelector('.chat-msg .chat-msg-text');
-                    expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny image?</span>');
-                    var media = view.el.querySelector('.chat-msg .chat-msg-media');
+                test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg img').length, 2000).then(function () {
+                    const msg = view.el.querySelector('.chat-msg .chat-msg__text');
+                    expect(msg.outerHTML).toEqual('<div class="chat-msg__text">Have you seen this funny image?</div>');
+                    const media = view.el.querySelector('.chat-msg .chat-msg__media');
                     expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
                         '<!-- src/templates/image.html -->'+
                         '<a href="http://localhost:8000/logo/conversejs-filled.svg" target="_blank" rel="noopener">'+
@@ -1768,7 +1761,7 @@
                     }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
 
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(view.el.querySelector('.chat-msg-text').textContent)
+                expect(view.el.querySelector('.chat-msg__text').textContent)
                     .toBe('But soft, what light through yonder airlock breaks?');
 
                 view.model.onMessage($msg({
@@ -1779,11 +1772,11 @@
                     }).c('body').t('But soft, what light through yonder chimney breaks?').up()
                         .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
-                return test_utils.waitUntil(() => view.el.querySelector('.chat-msg-text').textContent ===
+                return test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                     'But soft, what light through yonder chimney breaks?');
             }).then(() => {
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(view.el.querySelectorAll('.chat-msg-content .fa-edit').length).toBe(1);
+                expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
 
                 view.model.onMessage($msg({
                         'from': 'lounge@localhost/newguy',
@@ -1793,12 +1786,12 @@
                     }).c('body').t('But soft, what light through yonder window breaks?').up()
                         .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
-            return test_utils.waitUntil(() => view.el.querySelector('.chat-msg-text').textContent ===
+            return test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                 'But soft, what light through yonder window breaks?');
             }).then(() => {
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(view.el.querySelectorAll('.chat-msg-content .fa-edit').length).toBe(1);
-                view.el.querySelector('.chat-msg-content .fa-edit').click();
+                expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
+                view.el.querySelector('.chat-msg__content .fa-edit').click();
                 const modal = view.model.messages.at(0).message_versions_modal;
                 return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
             }).then(() => {
@@ -1838,7 +1831,7 @@
                     keyCode: 13 // Enter
                 });
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(view.el.querySelector('.chat-msg-text').textContent)
+                expect(view.el.querySelector('.chat-msg__text').textContent)
                     .toBe('But soft, what light through yonder airlock breaks?');
 
                 const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});

+ 8 - 8
spec/spoilers.js

@@ -40,9 +40,9 @@
 
             return test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
             .then(function () {
-                expect(view.el.querySelector('.chat-msg-author').textContent).toBe('Max Frankfurter');
+                expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter');
 
-                var message_content = view.el.querySelector('.chat-msg-text');
+                var message_content = view.el.querySelector('.chat-msg__text');
                 expect(message_content.textContent).toBe(spoiler);
 
                 var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
@@ -79,9 +79,9 @@
             var view = _converse.chatboxviews.get(sender_jid);
             return test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
             .then(function () {
-                expect(_.includes(view.el.querySelector('.chat-msg-author').textContent, 'Max Frankfurter')).toBeTruthy();
+                expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy();
 
-                var message_content = view.el.querySelector('.chat-msg-text');
+                var message_content = view.el.querySelector('.chat-msg__text');
                 expect(message_content.textContent).toBe(spoiler);
 
                 var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
@@ -148,9 +148,9 @@
                 expect(body_el.textContent).toBe('This is the spoiler');
 
                 /* Test the HTML spoiler message */
-                expect(view.el.querySelector('.chat-msg-author').textContent).toBe('Max Mustermann');
+                expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann');
 
-                var spoiler_msg_el = view.el.querySelector('.chat-msg-text.spoiler');
+                var spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
                 expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
 
@@ -227,9 +227,9 @@
                 expect(body_el.textContent).toBe('This is the spoiler');
 
                 /* Test the HTML spoiler message */
-                expect(view.el.querySelector('.chat-msg-author').textContent).toBe('Max Mustermann');
+                expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann');
 
-                var spoiler_msg_el = view.el.querySelector('.chat-msg-text.spoiler');
+                var spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
                 expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
 

+ 5 - 7
src/converse-chatview.js

@@ -9,7 +9,6 @@
             "bootstrap",
             "emojione",
             "xss",
-            "templates/action.html",
             "templates/chatbox.html",
             "templates/chatbox_head.html",
             "templates/chatbox_message_form.html",
@@ -33,7 +32,6 @@
             bootstrap,
             emojione,
             xss,
-            tpl_action,
             tpl_chatbox,
             tpl_chatbox_head,
             tpl_chatbox_message_form,
@@ -747,19 +745,19 @@
                           date = moment(el.getAttribute('data-isodate')),
                           next_el = el.nextElementSibling;
 
-                    if (!u.hasClass('chat-action', el) && !u.hasClass('chat-action', previous_el) &&
+                    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'))) {
-                        u.addClass('chat-msg-followup', el);
+                        u.addClass('chat-msg--followup', el);
                     }
                     if (!next_el) { return; }
 
-                    if (!u.hasClass('chat-action', 'el') &&
+                    if (!u.hasClass('chat-msg--action', 'el') &&
                             next_el.getAttribute('data-from') === from &&
                             moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
-                        u.addClass('chat-msg-followup', next_el);
+                        u.addClass('chat-msg--followup', next_el);
                     } else {
-                        u.removeClass('chat-msg-followup', next_el);
+                        u.removeClass('chat-msg--followup', next_el);
                     }
                 },
 

+ 14 - 19
src/converse-message-view.js

@@ -10,26 +10,22 @@
         "xss",
         "emojione",
         "filesize",
-        "templates/action.html",
         "templates/csn.html",
         "templates/file_progress.html",
         "templates/info.html",
         "templates/message.html",
         "templates/message_versions_modal.html",
-        "templates/spoiler_message.html"
     ], factory);
 }(this, function (
         converse,
         xss,
         emojione,
         filesize,
-        tpl_action,
         tpl_csn,
         tpl_file_progress,
         tpl_info,
         tpl_message,
-        tpl_message_versions_modal,
-        tpl_spoiler_message
+        tpl_message_versions_modal
     ) {
     "use strict";
     const { Backbone, _, moment } = converse.env;
@@ -101,7 +97,7 @@
                 },
 
                 render () {
-                    const is_followup = u.hasClass('chat-msg-followup', this.el);
+                    const is_followup = u.hasClass('chat-msg--followup', this.el);
                     let msg;
                     if (this.model.isOnlyChatStateNotification()) {
                         this.renderChatStateNotification()
@@ -113,7 +109,7 @@
                         this.renderChatMessage();
                     }
                     if (is_followup) {
-                        u.addClass('chat-msg-followup', this.el);
+                        u.addClass('chat-msg--followup', this.el);
                     }
                     return this.el;
                 },
@@ -135,21 +131,16 @@
                 },
 
                 renderChatMessage () {
-                    let template, text = this.model.get('message');
-                    if (this.isMeCommand()) {
-                        template = tpl_action;
-                        text = this.model.get('message').replace(/^\/me/, '');
-                    } else {
-                        template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
-                    }
-                    const moment_time = moment(this.model.get('time')),
+                    const is_me_message = this.isMeCommand(),
+                          moment_time = moment(this.model.get('time')),
                           role = this.model.vcard.get('role'),
                           roles = role ? role.split(',') : [];
 
-                    const msg = u.stringToElement(template(
+                    const msg = u.stringToElement(tpl_message(
                         _.extend(
                             this.model.toJSON(), {
                             '__': __,
+                            'is_me_message': is_me_message,
                             'roles': roles,
                             'pretty_time': moment_time.format(_converse.time_format),
                             'time': moment_time.format(),
@@ -159,16 +150,20 @@
                         })
                     ));
 
-                    var url = this.model.get('oob_url');
+                    const url = this.model.get('oob_url');
                     if (url) {
-                        msg.querySelector('.chat-msg-media').innerHTML = _.flow(
+                        msg.querySelector('.chat-msg__media').innerHTML = _.flow(
                             _.partial(u.renderFileURL, _converse),
                             _.partial(u.renderMovieURL, _converse),
                             _.partial(u.renderAudioURL, _converse),
                             _.partial(u.renderImageURL, _converse))(url);
                     }
 
-                    const msg_content = msg.querySelector('.chat-msg-text');
+                    let text = this.model.get('message');
+                    if (is_me_message) {
+                        text = text.replace(/^\/me/, '');
+                    }
+                    const msg_content = msg.querySelector('.chat-msg__text');
                     if (text !== url) {
                         text = xss.filterXSS(text, {'whiteList': {}});
                         msg_content.innerHTML = _.flow(

+ 0 - 6
src/templates/action.html

@@ -1,6 +0,0 @@
-<div class="message chat-msg chat-action {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-from="{{{o.from}}}">
-    <span class="chat-msg-heading">
-        <span class="chat-msg-author">**{{{o.username}}}</span>
-    </span>
-    <p class="chat-msg-text"><!-- message gets added here via renderMessage --></p>
-</div>

+ 3 - 3
src/templates/file_progress.html

@@ -1,7 +1,7 @@
 <div class="message chat-msg" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
-    <canvas class="avatar" height="36" width="36"></canvas>
-    <div class="chat-msg-content">
-        <span class="chat-msg-text">Uploading file: <strong>{{{o.file.name}}}</strong>, {{{o.filesize}}}</span>
+    <canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>
+    <div class="chat-msg__content">
+        <span class="chat-msg__text">Uploading file: <strong>{{{o.file.name}}}</strong>, {{{o.filesize}}}</span>
         <progress value="{{{o.progress}}}"/>
     </div>
 </div>

+ 21 - 10
src/templates/message.html

@@ -1,16 +1,27 @@
-<div class="message chat-msg {{{o.type}}} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
-    {[ if (o.type !== 'headline') { ]}
-    <canvas class="avatar" height="36" width="36"></canvas>
+<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}}}">
+    {[ if (o.type !== 'headline' && !o.is_me_message) { ]}
+    <canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>
     {[ } ]}
-    <div class="chat-msg-content">
-        <span class="chat-msg-heading">
-            <span class="chat-msg-author">{{{o.username}}}
+    <div class="chat-msg__content {[ if (o.is_me_message) { ]}chat-msg__content--action{[ } ]}">
+        <span class="chat-msg__heading">
+            {[ if (o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
+            <span class="chat-msg__author">{[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}}
                 {[o.roles.forEach(function (role) { ]} <span class="badge badge-secondary">{{{role}}}</span> {[ }); ]}
             </span>
-            <time timestamp="{{{o.isodate}}}" class="chat-msg-time">{{{o.pretty_time}}}</time>
+            {[ if (!o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
         </span>
-        {[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg-edited"></i> {[ } ]}
-        <span class="chat-msg-text"></span>
-        <div class="chat-msg-media"></div>
+        {[ 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-edited"></i> {[ } ]}
+            {[ if (!o.is_me_message) { ]}<div class="chat-msg__message">{[ } ]} 
+                {[ if (o.is_spoiler) { ]}
+                    <div class="chat-msg__spoiler-hint">
+                        <span class="spoiler-hint">{{{o.spoiler_hint}}}</span>
+                        <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>{{{o.label_show}}}</a>
+                    </div>
+                {[ } ]}
+                <div class="chat-msg__text{[ if (o.is_spoiler) { ]} spoiler collapsed{[ } ]}"><!-- message gets added here via renderMessage --></div>
+                <div class="chat-msg__media"></div>
+            {[ if (!o.is_me_message) { ]}</div>{[ } ]}
+        {[ if (!o.is_me_message) { ]}</div>{[ } ]} 
     </div>
 </div>

+ 0 - 14
src/templates/spoiler_message.html

@@ -1,14 +0,0 @@
-<div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
-    <canvas class="avatar" height="36" width="36"></canvas>
-    <div class="chat-msg-content">
-        <span class="chat-msg-heading">
-            <span class="chat-msg-author">{{{o.username}}}</span>
-            <span class="chat-msg-time">{{{o.pretty_time}}}</span>
-        </span>
-        <div>
-            <span class="spoiler-hint">{{{o.spoiler_hint}}}</span>
-            <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>{{{o.label_show}}}</a>
-        </div>
-        <div class="chat-msg-text spoiler collapsed"><!-- message gets added here via renderMessage --></div>
-    </div>
-</div>

+ 8 - 3
src/utils/core.js

@@ -210,6 +210,10 @@
             .replace(/"/g, "&quot;");
     };
 
+    u.escapeURL = function (url) {
+        return encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
+    };
+
     u.addHyperlinks = function (text) {
         return URI.withinString(text, function (url) {
             var uri = new URI(url);
@@ -217,8 +221,8 @@
             if (!url.startsWith('http://') && !url.startsWith('https://')) {
                 url = 'http://' + url;
             }
-            url = encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
-            return `<a target="_blank" rel="noopener" href="${u.escapeHTML(url)}">${u.escapeHTML(uri.readable())}</a>`;
+            url = u.escapeHTML(u.escapeURL(url));
+            return `<a target="_blank" rel="noopener" href="${url}">${u.escapeHTML(uri.readable())}</a>`;
         });
     };
 
@@ -255,7 +259,8 @@
     };
 
     u.renderFileURL = function (_converse, url) {
-        const uri = new URI(url), { __ } = _converse,
+        const uri = new URI(url),
+              { __ } = _converse,
               filename = uri.filename();
         if (!_.includes(["https", "http"], uri.protocol()) ||
             filename.endsWith('mp3') || filename.endsWith('mp4') ||