Jelajahi Sumber

Render profile avatar as canvas.

We now have uniform avatar rendering for the profile, messages and
chatboxes.

By rendering as canvas, we can avoid stretching the image.

In the process I also moved the ChatBoxViews collection into its own
plugin `converse-chatboxviews` and placed the AvatarAware views there.

fixes #1157
JC Brand 6 tahun lalu
induk
melakukan
023249f62e

+ 2 - 1
css/converse.css

@@ -8960,7 +8960,8 @@ body.reset {
     width: 100%; }
   #conversejs .avatar {
     border-radius: 10%;
-    border: 1px solid lightgrey; }
+    border: 1px solid lightgrey;
+    background: white; }
   #conversejs .activated {
     display: block !important; }
   #conversejs .button-primary {

+ 219 - 145
dist/converse.js

@@ -59547,11 +59547,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__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html"), __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js"), __webpack_require__(/*! utils/form */ "./src/utils/form.js"), __webpack_require__(/*! utils/emoji */ "./src/utils/emoji.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
+  !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! utils/form */ "./src/utils/form.js"), __webpack_require__(/*! utils/emoji */ "./src/utils/emoji.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, filesize, tpl_chatboxes) {
+})(void 0, function (converse, filesize) {
   "use strict";
 
   const _converse$env = converse.env,
@@ -59569,20 +59569,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
   Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0');
   converse.plugins.add('converse-chatboxes', {
     dependencies: ["converse-roster", "converse-vcard"],
-    overrides: {
-      // Overrides mentioned here will be picked up by converse.js's
-      // plugin architecture they will replace existing methods on the
-      // relevant objects or classes.
-      initStatus: function initStatus(reconnecting) {
-        const _converse = this.__super__._converse;
-
-        if (!reconnecting) {
-          _converse.chatboxviews.closeAllChatBoxes();
-        }
-
-        return this.__super__.initStatus.apply(this, arguments);
-      }
-    },
 
     initialize() {
       /* The initialize function gets called as soon as the plugin is
@@ -60396,79 +60382,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           return chatbox;
         }
 
-      });
-      _converse.ChatBoxViews = Backbone.Overview.extend({
-        _ensureElement() {
-          /* Override method from backbone.js
-           * If the #conversejs element doesn't exist, create it.
-           */
-          if (!this.el) {
-            let el = _converse.root.querySelector('#conversejs');
-
-            if (_.isNull(el)) {
-              el = document.createElement('div');
-              el.setAttribute('id', 'conversejs');
-
-              const body = _converse.root.querySelector('body');
-
-              if (body) {
-                body.appendChild(el);
-              } else {
-                // Perhaps inside a web component?
-                _converse.root.appendChild(el);
-              }
-            }
-
-            el.innerHTML = '';
-            this.setElement(el, false);
-          } else {
-            this.setElement(_.result(this, 'el'), false);
-          }
-        },
-
-        initialize() {
-          this.model.on("destroy", this.removeChat, this);
-          this.el.classList.add(`converse-${_converse.view_mode}`);
-          this.render();
-        },
-
-        render() {
-          try {
-            this.el.innerHTML = tpl_chatboxes();
-          } catch (e) {
-            this._ensureElement();
-
-            this.el.innerHTML = tpl_chatboxes();
-          }
-
-          this.row_el = this.el.querySelector('.row');
-        },
-
-        insertRowColumn(el) {
-          /* Add a new DOM element (likely a chat box) into the
-           * the row managed by this overview.
-           */
-          this.row_el.insertAdjacentElement('afterBegin', el);
-        },
-
-        removeChat(item) {
-          this.remove(item.get('id'));
-        },
-
-        closeAllChatBoxes() {
-          /* This method gets overridden in src/converse-controlbox.js if
-           * the controlbox plugin is active.
-           */
-          this.each(function (view) {
-            view.close();
-          });
-          return this;
-        },
-
-        chatBoxMayBeShown(chatbox) {
-          return this.model.chatBoxMayBeShown(chatbox);
-        }
-
       });
 
       function autoJoinChats() {
@@ -60521,15 +60434,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
       _converse.api.listen.on('pluginsInitialized', () => {
         _converse.chatboxes = new _converse.ChatBoxes();
-        _converse.chatboxviews = new _converse.ChatBoxViews({
-          'model': _converse.chatboxes
-        });
 
         _converse.emit('chatBoxesInitialized');
       });
 
-      _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
-
       _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected());
       /************************ END Event Handlers ************************/
 
@@ -60680,6 +60588,200 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
 /***/ }),
 
+/***/ "./src/converse-chatboxviews.js":
+/*!**************************************!*\
+  !*** ./src/converse-chatboxviews.js ***!
+  \**************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
+
+// Converse.js
+// http://conversejs.org
+//
+// 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__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html"), __webpack_require__(/*! converse-chatboxes */ "./src/converse-chatboxes.js"), __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.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, tpl_chatboxes) {
+  "use strict";
+
+  const _converse$env = converse.env,
+        Backbone = _converse$env.Backbone,
+        _ = _converse$env._;
+  const AvatarMixin = {
+    renderAvatar() {
+      const canvas_el = this.el.querySelector('canvas');
+
+      if (_.isNull(canvas_el)) {
+        return;
+      }
+
+      const image_type = this.model.vcard.get('image_type'),
+            image = this.model.vcard.get('image'),
+            img_src = "data:" + image_type + ";base64," + image,
+            img = new Image();
+
+      img.onload = () => {
+        const ctx = canvas_el.getContext('2d'),
+              ratio = img.width / img.height;
+        ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
+
+        if (ratio < 1) {
+          const scaled_img_with = canvas_el.width * ratio,
+                x = Math.floor((canvas_el.width - scaled_img_with) / 2);
+          ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height);
+        } else {
+          ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio);
+        }
+      };
+
+      img.src = img_src;
+    }
+
+  };
+  converse.plugins.add('converse-chatboxviews', {
+    dependencies: ["converse-chatboxes"],
+    overrides: {
+      // Overrides mentioned here will be picked up by converse.js's
+      // plugin architecture they will replace existing methods on the
+      // relevant objects or classes.
+      initStatus: function initStatus(reconnecting) {
+        const _converse = this.__super__._converse;
+
+        if (!reconnecting) {
+          _converse.chatboxviews.closeAllChatBoxes();
+        }
+
+        return this.__super__.initStatus.apply(this, arguments);
+      }
+    },
+
+    initialize() {
+      /* The initialize function gets called as soon as the plugin is
+       * loaded by converse.js's plugin machinery.
+       */
+      const _converse = this._converse,
+            __ = _converse.__;
+
+      _converse.api.promises.add(['chatBoxViewsInitialized']);
+
+      _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin);
+      _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin);
+      _converse.ChatBoxViews = Backbone.Overview.extend({
+        _ensureElement() {
+          /* Override method from backbone.js
+           * If the #conversejs element doesn't exist, create it.
+           */
+          if (!this.el) {
+            let el = _converse.root.querySelector('#conversejs');
+
+            if (_.isNull(el)) {
+              el = document.createElement('div');
+              el.setAttribute('id', 'conversejs');
+
+              const body = _converse.root.querySelector('body');
+
+              if (body) {
+                body.appendChild(el);
+              } else {
+                // Perhaps inside a web component?
+                _converse.root.appendChild(el);
+              }
+            }
+
+            el.innerHTML = '';
+            this.setElement(el, false);
+          } else {
+            this.setElement(_.result(this, 'el'), false);
+          }
+        },
+
+        initialize() {
+          this.model.on("destroy", this.removeChat, this);
+          this.el.classList.add(`converse-${_converse.view_mode}`);
+          this.render();
+        },
+
+        render() {
+          try {
+            this.el.innerHTML = tpl_chatboxes();
+          } catch (e) {
+            this._ensureElement();
+
+            this.el.innerHTML = tpl_chatboxes();
+          }
+
+          this.row_el = this.el.querySelector('.row');
+        },
+
+        insertRowColumn(el) {
+          /* Add a new DOM element (likely a chat box) into the
+           * the row managed by this overview.
+           */
+          this.row_el.insertAdjacentElement('afterBegin', el);
+        },
+
+        removeChat(item) {
+          this.remove(item.get('id'));
+        },
+
+        closeAllChatBoxes() {
+          /* This method gets overridden in src/converse-controlbox.js if
+           * the controlbox plugin is active.
+           */
+          this.each(function (view) {
+            view.close();
+          });
+          return this;
+        },
+
+        chatBoxMayBeShown(chatbox) {
+          return this.model.chatBoxMayBeShown(chatbox);
+        }
+
+      });
+      /************************ BEGIN Event Handlers ************************/
+
+      _converse.api.waitUntil('rosterContactsFetched').then(() => {
+        _converse.roster.on('add', contact => {
+          /* When a new contact is added, check if we already have a
+           * chatbox open for it, and if so attach it to the chatbox.
+           */
+          const chatbox = _converse.chatboxes.findWhere({
+            'jid': contact.get('jid')
+          });
+
+          if (chatbox) {
+            chatbox.addRelatedContact(contact);
+          }
+        });
+      });
+
+      _converse.api.listen.on('chatBoxesInitialized', () => {
+        _converse.chatboxviews = new _converse.ChatBoxViews({
+          'model': _converse.chatboxes
+        });
+
+        _converse.emit('chatBoxViewsInitialized');
+      });
+
+      _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
+      /************************ END Event Handlers ************************/
+
+    }
+
+  });
+  return converse;
+});
+
+/***/ }),
+
 /***/ "./src/converse-chatview.js":
 /*!**********************************!*\
   !*** ./src/converse-chatview.js ***!
@@ -60696,7 +60798,7 @@ 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__(/*! utils/emoji */ "./src/utils/emoji.js"), __webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.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_ARRAY__ = [__webpack_require__(/*! utils/emoji */ "./src/utils/emoji.js"), __webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.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-chatboxviews */ "./src/converse-chatboxviews.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__));
@@ -60724,7 +60826,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
      *
      * NB: These plugins need to have already been loaded via require.js.
      */
-    dependencies: ["converse-chatboxes", "converse-disco", "converse-message-view", "converse-modal"],
+    dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"],
 
     initialize() {
       /* The initialize function gets called as soon as the plugin is
@@ -61972,7 +62074,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
       });
 
-      _converse.on('chatBoxesInitialized', () => {
+      _converse.on('chatBoxViewsInitialized', () => {
         const that = _converse.chatboxviews;
 
         _converse.chatboxes.on('add', item => {
@@ -62643,7 +62745,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
       });
 
-      _converse.on('chatBoxesInitialized', () => {
+      _converse.on('chatBoxViewsInitialized', () => {
         const that = _converse.chatboxviews;
 
         _converse.chatboxes.on('add', item => {
@@ -62678,7 +62780,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         }
       });
 
-      Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxesInitialized')]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
+      Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
 
       _converse.on('chatBoxesFetched', () => {
         const controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox();
@@ -62788,7 +62890,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
   _.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically
 
 
-  _converse.core_plugins = ['converse-autocomplete', 'converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the
+  _converse.core_plugins = ['converse-autocomplete', 'converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatboxviews', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the
   // webserver, which is often also set to 60 and might therefore sometimes
   // return a 504 error page instead of passing through to the BOSH proxy.
 
@@ -65959,7 +66061,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
       _converse.on('reconnected', registerHeadlineHandler);
 
-      _converse.on('chatBoxesInitialized', () => {
+      _converse.on('chatBoxViewsInitialized', () => {
         const that = _converse.chatboxviews;
 
         _converse.chatboxes.on('add', item => {
@@ -66682,35 +66784,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
        */
       const _converse = this._converse,
             __ = _converse.__;
-      _converse.ViewWithAvatar = Backbone.NativeView.extend({
-        renderAvatar() {
-          const canvas_el = this.el.querySelector('canvas');
-
-          if (_.isNull(canvas_el)) {
-            return;
-          }
-
-          const image_type = this.model.vcard.get('image_type'),
-                image = this.model.vcard.get('image'),
-                img_src = "data:" + image_type + ";base64," + image,
-                img = new Image();
-
-          img.onload = () => {
-            const ctx = canvas_el.getContext('2d'),
-                  ratio = img.width / img.height;
-            ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
-
-            if (ratio < 1) {
-              ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * (1 / ratio));
-            } else {
-              ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio);
-            }
-          };
-
-          img.src = img_src;
-        }
-
-      });
       _converse.MessageVersionsModal = _converse.BootstrapModal.extend({
         toHTML() {
           return tpl_message_versions_modal(_.extend(this.model.toJSON(), {
@@ -67513,7 +67586,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         }
 
       });
-      Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxesInitialized')]).then(() => {
+      Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(() => {
         _converse.minimized_chats = new _converse.MinimizedChats({
           model: _converse.chatboxes
         });
@@ -69679,7 +69752,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
       /************************ BEGIN Event Handlers ************************/
 
 
-      _converse.on('chatBoxesInitialized', () => {
+      _converse.on('chatBoxViewsInitialized', () => {
         const that = _converse.chatboxviews;
 
         _converse.chatboxes.on('add', item => {
@@ -73040,7 +73113,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 // Converse.js (A browser based XMPP chat client)
 // http://conversejs.org
 //
-// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
+// Copyright (c) 2013-2017, Jan-Carel Brand <jc@opkode.com>
 // Licensed under the Mozilla Public License (MPLv2)
 //
 
@@ -73062,7 +73135,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         moment = _converse$env.moment;
   const u = converse.env.utils;
   converse.plugins.add('converse-profile', {
-    dependencies: ["converse-modal", "converse-vcard"],
+    dependencies: ["converse-modal", "converse-vcard", "converse-chatboxviews"],
 
     initialize() {
       /* The initialize function gets called as soon as the plugin is
@@ -73219,7 +73292,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         }
 
       });
-      _converse.XMPPStatusView = Backbone.VDOMView.extend({
+      _converse.XMPPStatusView = _converse.VDOMViewWithAvatar.extend({
         tagName: "div",
         events: {
           "click a.show-profile": "showProfileModal",
@@ -73235,6 +73308,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         toHTML() {
           const chat_status = this.model.get('status') || 'offline';
           return tpl_profile_view(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
+            '__': __,
             'fullname': this.model.vcard.get('fullname') || _converse.bare_jid,
             'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)),
             'chat_status': chat_status,
@@ -73246,6 +73320,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }));
         },
 
+        afterRender() {
+          this.renderAvatar();
+        },
+
         showProfileModal(ev) {
           if (_.isUndefined(this.profile_modal)) {
             this.profile_modal = new _converse.ProfileModal({
@@ -77153,7 +77231,7 @@ if (true) {
   __webpack_require__(/*! converse-mam */ "./src/converse-mam.js"), // XEP-0313 Message Archive Management
   __webpack_require__(/*! converse-minimize */ "./src/converse-minimize.js"), // Allows chat boxes to be minimized
   __webpack_require__(/*! converse-muc */ "./src/converse-muc.js"), // XEP-0045 Multi-user chat
-  __webpack_require__(/*! converse-muc-views */ "./src/converse-muc-views.js"), __webpack_require__(/*! converse-muc-views */ "./src/converse-muc-views.js"), // Views related to MUC
+  __webpack_require__(/*! converse-muc-views */ "./src/converse-muc-views.js"), // Views related to MUC
   __webpack_require__(/*! converse-notification */ "./src/converse-notification.js"), // HTML5 Notifications
   __webpack_require__(/*! converse-omemo */ "./src/converse-omemo.js"), __webpack_require__(/*! converse-ping */ "./src/converse-ping.js"), // XEP-0199 XMPP Ping
   __webpack_require__(/*! converse-register */ "./src/converse-register.js"), // XEP-0077 In-band registration
@@ -77794,9 +77872,9 @@ __p += '\n                    </a>\n                ';
  } ;
 __p += '\n                <p class="user-custom-message">' +
 __e( o.status ) +
-'</p>\n            </div>\n        </div>\n    </div>\n    <div class="chatbox-buttons row no-gutters">\n        <a class="chatbox-btn close-chatbox-button fa fa-close" title=' +
+'</p>\n            </div>\n        </div>\n    </div>\n    <div class="chatbox-buttons row no-gutters">\n        <a class="chatbox-btn close-chatbox-button fa fa-times" title=' +
 __e(o.info_close) +
-'></a>\n        <a class="chatbox-btn show-user-details-modal fa fa-vcard" title="' +
+'></a>\n        <a class="chatbox-btn show-user-details-modal fa fa-id-card" title="' +
 __e(o.info_details) +
 '"></a>\n    </div>\n</div>\n';
 return __p
@@ -78442,7 +78520,7 @@ var __t, __p = '', __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 __p += '<!-- src/templates/controlbox.html -->\n<div class="flyout box-flyout">\n    <div class="chat-head controlbox-head">\n        ';
  if (!o.sticky_controlbox) { ;
-__p += '\n            <a class="chatbox-btn close-chatbox-button fa fa-close"></a>\n        ';
+__p += '\n            <a class="chatbox-btn close-chatbox-button fa fa-times"></a>\n        ';
  } ;
 __p += '\n    </div>\n    <div class="controlbox-panes"></div>\n</div>\n';
 return __p
@@ -79550,13 +79628,9 @@ var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./no
 module.exports = function(o) {
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
-__p += '<!-- src/templates/profile_view.html -->\n<div class="userinfo controlbox-padded">\n<div class="profile d-flex">\n    <a class="show-profile" href="#">\n        <img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:' +
-__e(o.image_type) +
-';base64,' +
-__e(o.image) +
-'"/>\n    </a>\n    <span class="username w-100 align-self-center">' +
+__p += '<!-- src/templates/profile_view.html -->\n<div class="userinfo controlbox-padded">\n<div class="profile d-flex">\n    <a class="show-profile" href="#">\n        <canvas alt="o.__(\'Your avatar\')" class="avatar align-self-center" height="40" width="40"></canvas>\n    </a>\n    <span class="username w-100 align-self-center">' +
 __e(o.fullname) +
-'</span>\n    <!-- <a class="chatbox-btn fa fa-vcard align-self-center" title="' +
+'</span>\n    <!-- <a class="chatbox-btn fa fa-id-card align-self-center" title="' +
 __e(o.title_your_profile) +
 '" data-toggle="modal" data-target="#userProfileModal"></a> -->\n    <!-- <a class="chatbox-btn fa fa-cog align-self-center" title="' +
 __e(o.title_change_status) +

+ 1 - 0
sass/_core.scss

@@ -410,6 +410,7 @@ body.reset {
     .avatar {
         border-radius: 10%;
         border: 1px solid lightgrey;
+        background: white;
     }
 
     .activated {

+ 1 - 87
src/converse-chatboxes.js

@@ -8,12 +8,10 @@
     define([
         "converse-core",
         "filesize",
-        "templates/chatboxes.html",
-        "backbone.overview",
         "utils/form",
         "utils/emoji"
     ], factory);
-}(this, function (converse, filesize, tpl_chatboxes) {
+}(this, function (converse, filesize) {
     "use strict";
 
     const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } = converse.env;
@@ -27,20 +25,6 @@
 
         dependencies: ["converse-roster", "converse-vcard"],
 
-        overrides: {
-            // Overrides mentioned here will be picked up by converse.js's
-            // plugin architecture they will replace existing methods on the
-            // relevant objects or classes.
-
-            initStatus: function (reconnecting) {
-                const { _converse } = this.__super__;
-                if (!reconnecting) {
-                    _converse.chatboxviews.closeAllChatBoxes();
-                }
-                return this.__super__.initStatus.apply(this, arguments);
-            }
-        },
-
         initialize () {
             /* The initialize function gets called as soon as the plugin is
              * loaded by converse.js's plugin machinery.
@@ -780,72 +764,6 @@
                 }
             });
 
-            _converse.ChatBoxViews = Backbone.Overview.extend({
-
-                _ensureElement () {
-                    /* Override method from backbone.js
-                     * If the #conversejs element doesn't exist, create it.
-                     */
-                    if (!this.el) {
-                        let el = _converse.root.querySelector('#conversejs');
-                        if (_.isNull(el)) {
-                            el = document.createElement('div');
-                            el.setAttribute('id', 'conversejs');
-                            const body = _converse.root.querySelector('body');
-                            if (body) {
-                                body.appendChild(el);
-                            } else {
-                                // Perhaps inside a web component?
-                                _converse.root.appendChild(el);
-                            }
-                        }
-                        el.innerHTML = '';
-                        this.setElement(el, false);
-                    } else {
-                        this.setElement(_.result(this, 'el'), false);
-                    }
-                },
-
-                initialize () {
-                    this.model.on("destroy", this.removeChat, this);
-                    this.el.classList.add(`converse-${_converse.view_mode}`);
-                    this.render();
-                },
-
-                render () {
-                    try {
-                        this.el.innerHTML = tpl_chatboxes();
-                    } catch (e) {
-                        this._ensureElement();
-                        this.el.innerHTML = tpl_chatboxes();
-                    }
-                    this.row_el = this.el.querySelector('.row');
-                },
-
-                insertRowColumn (el) {
-                    /* Add a new DOM element (likely a chat box) into the
-                     * the row managed by this overview.
-                     */
-                    this.row_el.insertAdjacentElement('afterBegin', el);
-                },
-
-                removeChat (item) {
-                    this.remove(item.get('id'));
-                },
-
-                closeAllChatBoxes () {
-                    /* This method gets overridden in src/converse-controlbox.js if
-                     * the controlbox plugin is active.
-                     */
-                    this.each(function (view) { view.close(); });
-                    return this;
-                },
-
-                chatBoxMayBeShown (chatbox) {
-                    return this.model.chatBoxMayBeShown(chatbox);
-                }
-            });
-
 
             function autoJoinChats () {
                 /* Automatically join private chats, based on the
@@ -892,13 +810,9 @@
 
             _converse.api.listen.on('pluginsInitialized', () => {
                 _converse.chatboxes = new _converse.ChatBoxes();
-                _converse.chatboxviews = new _converse.ChatBoxViews({
-                    'model': _converse.chatboxes
-                });
                 _converse.emit('chatBoxesInitialized');
             });
 
-            _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
             _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected());
             /************************ END Event Handlers ************************/
 

+ 173 - 0
src/converse-chatboxviews.js

@@ -0,0 +1,173 @@
+// Converse.js
+// http://conversejs.org
+//
+// Copyright (c) 2012-2018, the Converse.js developers
+// Licensed under the Mozilla Public License (MPLv2)
+
+(function (root, factory) {
+    define([
+        "converse-core",
+        "templates/chatboxes.html",
+        "converse-chatboxes",
+        "backbone.overview"
+    ], factory);
+}(this, function (converse, tpl_chatboxes) {
+    "use strict";
+
+    const { Backbone, _ } = converse.env;
+
+    const AvatarMixin = {
+        renderAvatar () {
+            const canvas_el = this.el.querySelector('canvas');
+            if (_.isNull(canvas_el)) {
+                return;
+            }
+            const image_type = this.model.vcard.get('image_type'),
+                    image = this.model.vcard.get('image'),
+                    img_src = "data:" + image_type + ";base64," + image,
+                    img = new Image();
+
+            img.onload = () => {
+                const ctx = canvas_el.getContext('2d'),
+                        ratio = img.width / img.height;
+                ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
+                if (ratio < 1) {
+                    const scaled_img_with = canvas_el.width*ratio,
+                            x = Math.floor((canvas_el.width-scaled_img_with)/2);
+                    ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height);
+                } else {
+                    ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio);
+                }
+            };
+            img.src = img_src;
+        },
+    };
+
+
+    converse.plugins.add('converse-chatboxviews', {
+
+        dependencies: ["converse-chatboxes"],
+
+        overrides: {
+            // Overrides mentioned here will be picked up by converse.js's
+            // plugin architecture they will replace existing methods on the
+            // relevant objects or classes.
+
+            initStatus: function (reconnecting) {
+                const { _converse } = this.__super__;
+                if (!reconnecting) {
+                    _converse.chatboxviews.closeAllChatBoxes();
+                }
+                return this.__super__.initStatus.apply(this, arguments);
+            }
+        },
+
+        initialize () {
+            /* The initialize function gets called as soon as the plugin is
+             * loaded by converse.js's plugin machinery.
+             */
+            const { _converse } = this,
+                  { __ } = _converse;
+
+            _converse.api.promises.add([
+                'chatBoxViewsInitialized'
+            ]);
+
+            _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin);
+            _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin);
+
+
+            _converse.ChatBoxViews = Backbone.Overview.extend({
+
+                _ensureElement () {
+                    /* Override method from backbone.js
+                     * If the #conversejs element doesn't exist, create it.
+                     */
+                    if (!this.el) {
+                        let el = _converse.root.querySelector('#conversejs');
+                        if (_.isNull(el)) {
+                            el = document.createElement('div');
+                            el.setAttribute('id', 'conversejs');
+                            const body = _converse.root.querySelector('body');
+                            if (body) {
+                                body.appendChild(el);
+                            } else {
+                                // Perhaps inside a web component?
+                                _converse.root.appendChild(el);
+                            }
+                        }
+                        el.innerHTML = '';
+                        this.setElement(el, false);
+                    } else {
+                        this.setElement(_.result(this, 'el'), false);
+                    }
+                },
+
+                initialize () {
+                    this.model.on("destroy", this.removeChat, this);
+                    this.el.classList.add(`converse-${_converse.view_mode}`);
+                    this.render();
+                },
+
+                render () {
+                    try {
+                        this.el.innerHTML = tpl_chatboxes();
+                    } catch (e) {
+                        this._ensureElement();
+                        this.el.innerHTML = tpl_chatboxes();
+                    }
+                    this.row_el = this.el.querySelector('.row');
+                },
+
+                insertRowColumn (el) {
+                    /* Add a new DOM element (likely a chat box) into the
+                     * the row managed by this overview.
+                     */
+                    this.row_el.insertAdjacentElement('afterBegin', el);
+                },
+
+                removeChat (item) {
+                    this.remove(item.get('id'));
+                },
+
+                closeAllChatBoxes () {
+                    /* This method gets overridden in src/converse-controlbox.js if
+                     * the controlbox plugin is active.
+                     */
+                    this.each(function (view) { view.close(); });
+                    return this;
+                },
+
+                chatBoxMayBeShown (chatbox) {
+                    return this.model.chatBoxMayBeShown(chatbox);
+                }
+            });
+
+
+            /************************ BEGIN Event Handlers ************************/
+            _converse.api.waitUntil('rosterContactsFetched').then(() => {
+                _converse.roster.on('add', (contact) => {
+                    /* When a new contact is added, check if we already have a
+                     * chatbox open for it, and if so attach it to the chatbox.
+                     */
+                    const chatbox = _converse.chatboxes.findWhere({'jid': contact.get('jid')});
+                    if (chatbox) {
+                        chatbox.addRelatedContact(contact);
+                    }
+                });
+            });
+
+
+            _converse.api.listen.on('chatBoxesInitialized', () => {
+                _converse.chatboxviews = new _converse.ChatBoxViews({
+                    'model': _converse.chatboxes
+                });
+                _converse.emit('chatBoxViewsInitialized');
+            });
+
+            _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
+            /************************ END Event Handlers ************************/
+        }
+    });
+    return converse;
+}));

+ 3 - 3
src/converse-chatview.js

@@ -26,7 +26,7 @@
             "templates/status_message.html",
             "templates/toolbar.html",
             "converse-modal",
-            "converse-chatboxes",
+            "converse-chatboxviews",
             "converse-message-view"
     ], factory);
 }(this, function (
@@ -64,7 +64,7 @@
          *
          * NB: These plugins need to have already been loaded via require.js.
          */
-        dependencies: ["converse-chatboxes", "converse-disco", "converse-message-view", "converse-modal"],
+        dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"],
 
 
         initialize () {
@@ -1263,7 +1263,7 @@
                 }
             });
 
-            _converse.on('chatBoxesInitialized', () => {
+            _converse.on('chatBoxViewsInitialized', () => {
                 const that = _converse.chatboxviews;
                 _converse.chatboxes.on('add', item => {
                     if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) {

+ 2 - 2
src/converse-controlbox.js

@@ -567,7 +567,7 @@
                 }
             });
 
-            _converse.on('chatBoxesInitialized', () => {
+            _converse.on('chatBoxViewsInitialized', () => {
                 const that = _converse.chatboxviews;
                 _converse.chatboxes.on('add', item => {
                     if (item.get('type') === _converse.CONTROLBOX_TYPE) {
@@ -598,7 +598,7 @@
 
             Promise.all([
                 _converse.api.waitUntil('connectionInitialized'),
-                _converse.api.waitUntil('chatBoxesInitialized')
+                _converse.api.waitUntil('chatBoxViewsInitialized')
             ]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
 
             _converse.on('chatBoxesFetched', () => {

+ 1 - 0
src/converse-core.js

@@ -77,6 +77,7 @@
         'converse-bookmarks',
         'converse-caps',
         'converse-chatboxes',
+        'converse-chatboxviews',
         'converse-chatview',
         'converse-controlbox',
         'converse-core',

+ 1 - 1
src/converse-headline.js

@@ -136,7 +136,7 @@
             _converse.on('reconnected', registerHeadlineHandler);
 
 
-            _converse.on('chatBoxesInitialized', () => {
+            _converse.on('chatBoxViewsInitialized', () => {
                 const that = _converse.chatboxviews;
                 _converse.chatboxes.on('add', item => {
                     if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) {

+ 0 - 26
src/converse-message-view.js

@@ -40,32 +40,6 @@
             const { _converse } = this,
                 { __ } = _converse;
 
-            _converse.ViewWithAvatar = Backbone.NativeView.extend({
-
-                renderAvatar () {
-                    const canvas_el = this.el.querySelector('canvas');
-                    if (_.isNull(canvas_el)) {
-                        return;
-                    }
-                    const image_type = this.model.vcard.get('image_type'),
-                          image = this.model.vcard.get('image'),
-                          img_src = "data:" + image_type + ";base64," + image,
-                          img = new Image();
-
-                    img.onload = () => {
-                        const ctx = canvas_el.getContext('2d'),
-                              ratio = img.width / img.height;
-                        ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
-                        if (ratio < 1) {
-                            ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * (1 / ratio));
-                        } else {
-                            ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio);
-                        }
-                    };
-                    img.src = img_src;
-                },
-            });
-
 
             _converse.MessageVersionsModal = _converse.BootstrapModal.extend({
 

+ 1 - 1
src/converse-minimize.js

@@ -523,7 +523,7 @@
 
             Promise.all([
                 _converse.api.waitUntil('connectionInitialized'),
-                _converse.api.waitUntil('chatBoxesInitialized')
+                _converse.api.waitUntil('chatBoxViewsInitialized')
             ]).then(() => {
                 _converse.minimized_chats = new _converse.MinimizedChats({
                     model: _converse.chatboxes

+ 1 - 1
src/converse-muc-views.js

@@ -1951,7 +1951,7 @@
             }
 
             /************************ BEGIN Event Handlers ************************/
-            _converse.on('chatBoxesInitialized', () => {
+            _converse.on('chatBoxViewsInitialized', () => {
                 const that = _converse.chatboxviews;
                 _converse.chatboxes.on('add', item => {
                     if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) {

+ 8 - 3
src/converse-profile.js

@@ -1,7 +1,7 @@
 // Converse.js (A browser based XMPP chat client)
 // http://conversejs.org
 //
-// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
+// Copyright (c) 2013-2017, Jan-Carel Brand <jc@opkode.com>
 // Licensed under the Mozilla Public License (MPLv2)
 //
 /*global define */
@@ -34,7 +34,7 @@
 
     converse.plugins.add('converse-profile', {
 
-        dependencies: ["converse-modal", "converse-vcard"],
+        dependencies: ["converse-modal", "converse-vcard", "converse-chatboxviews"],
 
         initialize () {
             /* The initialize function gets called as soon as the plugin is
@@ -196,7 +196,7 @@
                 }
             });
 
-            _converse.XMPPStatusView = Backbone.VDOMView.extend({
+            _converse.XMPPStatusView = _converse.VDOMViewWithAvatar.extend({
                 tagName: "div",
                 events: {
                     "click a.show-profile": "showProfileModal",
@@ -214,6 +214,7 @@
                     return tpl_profile_view(_.extend(
                         this.model.toJSON(),
                         this.model.vcard.toJSON(), {
+                        '__': __,
                         'fullname': this.model.vcard.get('fullname') || _converse.bare_jid,
                         'status_message': this.model.get('status_message') ||
                                             __("I am %1$s", this.getPrettyStatus(chat_status)),
@@ -226,6 +227,10 @@
                     }));
                 },
 
+                afterRender () {
+                    this.renderAvatar();
+                },
+
                 showProfileModal (ev) {
                     if (_.isUndefined(this.profile_modal)) {
                         this.profile_modal = new _converse.ProfileModal({model: this.model});

+ 0 - 1
src/converse.js

@@ -20,7 +20,6 @@ if (typeof define !== 'undefined') {
         "converse-mam",             // XEP-0313 Message Archive Management
         "converse-minimize",        // Allows chat boxes to be minimized
         "converse-muc",             // XEP-0045 Multi-user chat
-        "converse-muc-views",
         "converse-muc-views",       // Views related to MUC
         "converse-notification",    // HTML5 Notifications
         "converse-omemo",

+ 2 - 2
src/templates/chatbox_head.html

@@ -16,7 +16,7 @@
         </div>
     </div>
     <div class="chatbox-buttons row no-gutters">
-        <a class="chatbox-btn close-chatbox-button fa fa-close" title={{{o.info_close}}}></a>
-        <a class="chatbox-btn show-user-details-modal fa fa-vcard" title="{{{o.info_details}}}"></a>
+        <a class="chatbox-btn close-chatbox-button fa fa-times" title={{{o.info_close}}}></a>
+        <a class="chatbox-btn show-user-details-modal fa fa-id-card" title="{{{o.info_details}}}"></a>
     </div>
 </div>

+ 1 - 1
src/templates/controlbox.html

@@ -1,7 +1,7 @@
 <div class="flyout box-flyout">
     <div class="chat-head controlbox-head">
         {[ if (!o.sticky_controlbox) { ]}
-            <a class="chatbox-btn close-chatbox-button fa fa-close"></a>
+            <a class="chatbox-btn close-chatbox-button fa fa-times"></a>
         {[ } ]}
     </div>
     <div class="controlbox-panes"></div>

+ 2 - 2
src/templates/profile_view.html

@@ -1,10 +1,10 @@
 <div class="userinfo controlbox-padded">
 <div class="profile d-flex">
     <a class="show-profile" href="#">
-        <img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
+        <canvas alt="o.__('Your avatar')" class="avatar align-self-center" height="40" width="40"></canvas>
     </a>
     <span class="username w-100 align-self-center">{{{o.fullname}}}</span>
-    <!-- <a class="chatbox-btn fa fa-vcard align-self-center" title="{{{o.title_your_profile}}}" data-toggle="modal" data-target="#userProfileModal"></a> -->
+    <!-- <a class="chatbox-btn fa fa-id-card align-self-center" title="{{{o.title_your_profile}}}" data-toggle="modal" data-target="#userProfileModal"></a> -->
     <!-- <a class="chatbox-btn fa fa-cog align-self-center" title="{{{o.title_change_status}}}" data-toggle="modal" data-target="#settingsModal"></a> -->
     {[ if (o._converse.allow_logout) { ]}
         <a class="chatbox-btn logout fa fa-sign-out-alt align-self-center" title="{{{o.title_log_out}}}"></a>