Browse Source

Some UI improvements

- Render images as thumbnails
- Use the image.html template when rendering images from pasted URLs
- Update message and spoiler markup to render avatars
- Use the default avatar as fallback when user doesn't have one
- Instead of 'me' render own name or JID
JC Brand 7 years ago
parent
commit
3d42425083

+ 0 - 1
docs/source/developer_guidelines.rst

@@ -101,7 +101,6 @@ Brief description of converse.js's dependencies
 
 Converse.js relies on the following dependencies:
 
-* `JQuery <http://jquery.com/>`_ for DOM manipulation and `promises <http://api.jquery.com/promise/>`_.
 * `moment.js <http://momentjs.com/>`_ provides a better API for handling dates and times.
 * `Strophe.js <http://strophe.im/>`_ maintains the XMPP session, is used to
   build XMPP stanzas, to send them, and to register handlers for received stanzas.

File diff suppressed because it is too large
+ 0 - 4
mockup/chatbox.html


+ 22 - 3
mockup/chatroom.html

@@ -97,7 +97,6 @@
                                     </div>
 								</div>
 
-
 								<div class="message chat-msg">
                                     <canvas height="36" width="36" class="avatar"></canvas>
                                     <div class="chat-msg-content">
@@ -111,6 +110,24 @@
                                             Or, if thou wilt not, be but sworn my love,
                                             And I'll no longer be a Capulet.
                                         </p>
+                                        <div class="chat-msg-media"></div>
+                                    </div>
+								</div>
+
+								<div class="message chat-msg">
+                                    <canvas height="36" width="36" class="avatar"></canvas>
+                                    <div class="chat-msg-content">
+                                        <span class="chat-msg-heading">
+                                            <span class="chat-msg-author">Juliet Capulet</span>
+                                            <span class="chat-msg-time text-muted">19:45</span>
+                                        </span>
+                                        <p class="chat-msg-text"></p>
+                                        <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"> 
+                                            </a>
+                                        </div>
                                     </div>
 								</div>
 
@@ -121,13 +138,15 @@
                                             <span class="chat-msg-author">Romeo Montague</span>
                                             <span class="chat-msg-time text-muted">19:36</span>
                                         </span>
-                                        <div class="spoiler-hint">By a name
-                                            <a class="badge badge-info toggle-spoiler" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>Show more</a>
+                                        <div>
+                                            <span class="spoiler-hint">By a name</span>
+                                            <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>Show more</a>
                                         </div>
                                         <div class="chat-msg-text spoiler collapsed">
                                             I know not how to tell thee who I am: My name, dear saint, is hateful to
                                             myself, Because it is an enemy to thee. Had I it written, I would tear the word. 
                                         </div>
+                                        <div class="chat-msg-media"></div>
                                     </div>
                                 </div>
 

+ 1 - 1
mockup/utils.js

@@ -52,7 +52,7 @@ function toggleSpoilerMessage (ev) {
 }
 
 window.initSpoilers = function () {
-    const spoilers = document.querySelectorAll('.toggle-spoiler');
+    const spoilers = document.querySelectorAll('.spoiler-toggle');
     _.each(spoilers, (spoiler_el) => {
         spoiler_el.addEventListener('click', toggleSpoilerMessage);
     });

+ 13 - 13
spec/spoilers.js

@@ -44,9 +44,9 @@
             _converse.chatboxes.onMessage(msg);
 
             var view = _converse.chatboxviews.get(sender_jid);
-            expect(_.includes(view.el.querySelector('.chat-msg-author').textContent, 'Max Frankfurter')).toBeTruthy();
+            expect(view.el.querySelector('.chat-msg-author').textContent).toBe('Max Frankfurter');
 
-            var message_content = view.el.querySelector('.chat-msg-content');
+            var message_content = view.el.querySelector('.chat-msg-text');
             expect(message_content.textContent).toBe(spoiler);
 
             var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
@@ -82,7 +82,7 @@
             var view = _converse.chatboxviews.get(sender_jid);
             expect(_.includes(view.el.querySelector('.chat-msg-author').textContent, 'Max Frankfurter')).toBeTruthy();
 
-            var message_content = view.el.querySelector('.chat-msg-content');
+            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,17 +148,17 @@
                 expect(body_el.textContent).toBe('This is the spoiler');
 
                 /* Test the HTML spoiler message */
-                expect(view.el.querySelector('.chat-msg-author').textContent.split(':')[1].trim().split(' ')[1]).toBe('me');
+                expect(view.el.querySelector('.chat-msg-author').textContent).toBe('dummy@localhost');
 
-                var spoiler_msg_el = view.el.querySelector('.chat-msg-content.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();
 
-                spoiler_toggle = view.el.querySelector('.toggle-spoiler');
-                expect(spoiler_toggle.textContent).toBe('Show hidden message');
+                spoiler_toggle = view.el.querySelector('.spoiler-toggle');
+                expect(spoiler_toggle.textContent).toBe('Show more');
                 spoiler_toggle.click();
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
-                expect(spoiler_toggle.textContent).toBe('Hide hidden message');
+                expect(spoiler_toggle.textContent).toBe('Show less');
                 spoiler_toggle.click();
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
                 done();
@@ -227,17 +227,17 @@
                 expect(body_el.textContent).toBe('This is the spoiler');
 
                 /* Test the HTML spoiler message */
-                expect(view.el.querySelector('.chat-msg-author').textContent.split(':')[1].trim().split(' ')[1]).toBe('me');
+                expect(view.el.querySelector('.chat-msg-author').textContent).toBe('dummy@localhost');
 
-                var spoiler_msg_el = view.el.querySelector('.chat-msg-content.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();
 
-                spoiler_toggle = view.el.querySelector('.toggle-spoiler');
-                expect(spoiler_toggle.textContent).toBe('Show hidden message');
+                spoiler_toggle = view.el.querySelector('.spoiler-toggle');
+                expect(spoiler_toggle.textContent).toBe('Show more');
                 spoiler_toggle.click();
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
-                expect(spoiler_toggle.textContent).toBe('Hide hidden message');
+                expect(spoiler_toggle.textContent).toBe('Show less');
                 spoiler_toggle.click();
                 expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
                 done();

+ 0 - 1
src/converse-chatboxes.js

@@ -193,7 +193,6 @@
                     'image': _converse.DEFAULT_IMAGE,
                     'image_type': _converse.DEFAULT_IMAGE_TYPE,
                     'num_unread': 0,
-                    'show_avatar': true,
                     'type': 'chatbox',
                     'message_type': 'chat',
                     'url': ''

+ 6 - 4
src/converse-chatview.js

@@ -249,7 +249,7 @@
                     'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
                     'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
                     'click .toggle-smiley': 'toggleEmojiMenu',
-                    'click .toggle-spoiler': 'toggleSpoilerMessage',
+                    'click .spoiler-toggle': 'toggleSpoilerMessage',
                     'click .upload-file': 'toggleFileUpload',
                     'keypress .chat-textarea': 'keyPressed',
                     'input .chat-textarea': 'inputChanged'
@@ -373,7 +373,7 @@
                                 this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
                             }
                         }
-                    });
+                    }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                 },
 
                 insertHeading () {
@@ -907,14 +907,16 @@
                         toggle_el.parentElement.parentElement.querySelector('.spoiler')
                     );
                     if (toggle_el.getAttribute("data-toggle-state") == "closed") {
-                        toggle_el.textContent = __('Show less');
+                        toggle_el.textContent = 'Show less';
                         icon_el.classList.remove("fa-eye");
                         icon_el.classList.add("fa-eye-slash");
+                        toggle_el.insertAdjacentElement('afterBegin', icon_el);
                         toggle_el.setAttribute("data-toggle-state", "open");
                     } else {
-                        toggle_el.textContent = __('Show more');
+                        toggle_el.textContent = 'Show more';
                         icon_el.classList.remove("fa-eye-slash");
                         icon_el.classList.add("fa-eye");
+                        toggle_el.insertAdjacentElement('afterBegin', icon_el);
                         toggle_el.setAttribute("data-toggle-state", "closed");
                     }
                 },

+ 4 - 2
src/converse-core.js

@@ -1509,10 +1509,12 @@
 
             defaults () {
                 return {
-                    "status":  _converse.default_state,
                     "jid": _converse.bare_jid,
                     "nickname": _converse.nickname,
-                    "vcard_updated": null
+                    "status":  _converse.default_state,
+                    "vcard_updated": null,
+                    'image': _converse.DEFAULT_IMAGE,
+                    'image_type': _converse.DEFAULT_IMAGE_TYPE
                 }
             },
 

+ 0 - 1
src/converse-headline.js

@@ -74,7 +74,6 @@
             _converse.HeadlinesBox = _converse.ChatBox.extend({
                 defaults: {
                     'type': 'headline',
-                    'show_avatar': false,
                     'bookmarked': false,
                     'chat_state': undefined,
                     'num_unread': 0,

+ 17 - 8
src/converse-message-view.js

@@ -44,8 +44,8 @@
             _converse.MessageView = Backbone.NativeView.extend({
 
                 initialize () {
-                    const chatbox = this.model.collection.chatbox;
-                    chatbox.on('change:fullname', (chatbox) => this.model.save('fullname', chatbox.get('fullname')));
+                    this.chatbox = this.model.collection.chatbox;
+                    this.chatbox.on('change:fullname', (chatbox) => this.model.save('fullname', chatbox.get('fullname')));
 
                     this.model.on('change:fullname', this.render, this);
                     this.model.on('change:progress', this.renderFileUploadProgresBar, this);
@@ -61,7 +61,7 @@
                         return this.renderErrorMessage();
                     }
 
-                    let template, username,
+                    let template, username, image, image_type,
                         text = this.model.get('message');
 
                     // TODO: store proper username on the message itself
@@ -71,9 +71,16 @@
                         username = arr[1];
                         text = arr[2];
                     } else {
-                        const fullname = _converse.xmppstatus.get('fullname') || this.model.get('fullname');
-                        username = this.model.get('sender') === 'me' && __('me') || fullname || this.model.get('from');
+                        username = this.model.get('fullname') || this.model.get('from');
                         template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
+
+                        if (this.model.get('sender') === 'me') {
+                            image_type = _converse.xmppstatus.get('image_type');
+                            image = _converse.xmppstatus.get('image');
+                        } else {
+                            image_type = this.chatbox.get('image_type');
+                            image = this.chatbox.get('image');
+                        }
                     }
                     const moment_time = moment(this.model.get('time'));
                     const msg = u.stringToElement(template(
@@ -82,7 +89,9 @@
                             'time': moment_time.format(),
                             'username': username,
                             'extra_classes': this.getExtraMessageClasses(),
-                            'label_show': __('Show more')
+                            'label_show': __('Show more'),
+                            'image_type': image_type,
+                            'image': image
                         })
                     ));
 
@@ -97,7 +106,7 @@
                         )(url);
                     }
 
-                    const msg_content = msg.querySelector('.chat-msg-content');
+                    const msg_content = msg.querySelector('.chat-msg-text');
                     if (text !== url) {
                         text = xss.filterXSS(text, {'whiteList': {}});
                         msg_content.innerHTML = _.flow(
@@ -106,7 +115,7 @@
                             _.partial(u.addEmoji, _converse, emojione, _)
                         )(text);
                     }
-                    u.renderImageURLs(msg_content).then(() => {
+                    u.renderImageURLs(_converse, msg_content).then(() => {
                         this.model.collection.trigger('rendered');
                     });
                     return this.replaceElement(msg);

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

@@ -1485,7 +1485,7 @@
                  * Chat rooms can be listed, joined and new rooms can be created.
                  */
                 tagName: 'div',
-                className: 'controlbox-pane',
+                className: 'controlbox-section',
                 id: 'chatrooms',
                 events: {
                     'click a.chatbox-btn.fa-users': 'showAddRoomModal',

+ 0 - 4
src/converse-muc.js

@@ -304,15 +304,11 @@
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                     const is_spoiler = this.get('composing_spoiler');
                     return {
-                        'from': _converse.connection.jid,
                         'fullname': this.get('nick'),
                         'is_spoiler': is_spoiler,
                         'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
-                        'msgid': _converse.connection.getUniqueId(),
                         'sender': 'me',
                         'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
-                        'time': moment().format(),
-                        'to': this.get('jid'),
                         'type': 'groupchat',
                     };
                 },

+ 1 - 0
src/converse-rosterview.js

@@ -719,6 +719,7 @@
             _converse.RosterView = Backbone.OrderedListView.extend({
                 tagName: 'div',
                 id: 'converse-roster',
+                className: 'controlbox-section',
 
                 ItemView: _converse.RosterGroupView,
                 listItems: 'model',

+ 1 - 1
src/templates/bookmark.html

@@ -1,4 +1,4 @@
-<div class="room-item">
+<div class="list-item room-item">
 <div class="available-chatroom d-flex flex-row {[ if (o.hidden) { ]} hidden {[ } ]}" data-room-jid="{{{o.jid}}}">
     <a class="open-room w-100" data-room-jid="{{{o.jid}}}" title="{{{o.open_title}}}" href="#">{{{o.name}}}</a>
     <a class="remove-bookmark fa fa-bookmark align-self-center {[ if (o.bookmarked) { ]} button-on {[ } ]}"

+ 0 - 8
src/templates/chatbox_head.html

@@ -1,14 +1,6 @@
 <div class="chat-head chat-head-chatbox row no-gutters">
     <div class="col">
         <div class="row no-gutters">
-            {[ if (o.show_avatar) { ]}
-            <div class="col-auto">
-                <img alt="User Avatar"
-                    class="avatar"
-                    height="{{{o.avatar_height}}}px" width="{{{o.avatar_width}}}px"
-                    src="data:{{{o.image_type || o._converse.DEFAULT_IMAGE_TYPE}}};base64,{{{o.image || o._converse.DEFAULT_IMAGE}}}"/>
-            </div>
-            {[ } ]}
             <div class="col chat-title" title="{{{o.jid}}}">
                 {[ if (o.url) { ]}
                     <a href="{{{o.url}}}" target="_blank" rel="noopener" class="user">

+ 4 - 1
src/templates/image.html

@@ -1 +1,4 @@
-<img class="chat-image" src="{{{o.url}}}"/>
+<a href="{{{o.url}}}"
+    target="_blank" rel="noopener">
+    <img class="chat-image img-thumbnail" src="{{{o.url}}}"> 
+</a>

+ 9 - 3
src/templates/message.html

@@ -1,5 +1,11 @@
 <div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
-    <span class="chat-msg-author chat-msg-{{{o.sender}}}">{{{o.pretty_time}}} {{{o.username}}}:&nbsp;</span>
-    <span class="chat-msg-content"></span>
-    <div class="chat-msg-media"></div>
+    <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
+    <div class="chat-msg-content">
+        <span class="chat-msg-heading">
+            <span class="chat-msg-author">{{{o.username}}}</span>
+            <span class="chat-msg-time tex{{{o.username}}}t-muted">{{{o.pretty_time}}}</span>
+        </span>
+        <p class="chat-msg-text">
+        <div class="chat-msg-media"></div>
+    </div>
 </div>

+ 1 - 1
src/templates/room_panel.html

@@ -1,6 +1,6 @@
 <!-- <div id="chatrooms"> -->
 <div class="d-flex">
-    <span class="w-100">{{{o.heading_chatrooms}}}</span>
+    <span class="w-100 controlbox-heading">{{{o.heading_chatrooms}}}</span>
     <a class="chatbox-btn trigger-list-chatrooms-modal fa fa-list-ul" title="{{{o.title_list_rooms}}}" data-toggle="modal" data-target="#list-chatrooms-modal"></a>
     <a class="chatbox-btn trigger-add-chatrooms-modal fa fa-users" title="{{{o.title_new_room}}}" data-toggle="modal" data-target="#add-chatrooms-modal"></a>
 </div>

+ 1 - 1
src/templates/rooms_list_item.html

@@ -1,4 +1,4 @@
-<div class="room-item">
+<div class="list-item room-item">
 <div class="available-chatroom d-flex flex-row {[ if (o.num_unread_general) { ]} unread-msgs {[ } ]}" data-room-jid="{{{o.jid}}}">
 {[ if (o.num_unread) { ]}
     <span class="msgs-indicator badge badge-info">{{{ o.num_unread }}}</span>

+ 1 - 1
src/templates/roster.html

@@ -1,5 +1,5 @@
 <div class="d-flex">
-    <span class="w-100">{{{o.heading_contacts}}}</span>
+    <span class="w-100 controlbox-heading">{{{o.heading_contacts}}}</span>
     <a class="chatbox-btn add-contact fa fa-user-plus" title="{{{o.title_add_contact}}}"
        data-toggle="modal" data-target="#add-contact-modal"></a>
 </div>

+ 12 - 4
src/templates/spoiler_message.html

@@ -1,6 +1,14 @@
 <div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
-    <span class="chat-msg-author chat-msg-{{{o.sender}}}">{{{o.pretty_time}}} {{{o.username}}}:&nbsp;</span>
-    <div class="spoiler-hint">{{{o.spoiler_hint}}}</div>
-    <a class="toggle-spoiler" data-toggle-state="closed" href="#"><i class="fa fa-eye"></li>{{{o.label_show}}}</a>
-    <div class="chat-msg-content spoiler collapsed"><!-- message gets added here via renderMessage --></div>
+    <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
+    <div class="chat-msg-content">
+        <span class="chat-msg-heading">
+            <span class="chat-msg-author">{{{o.username}}}</span>
+            <span class="chat-msg-time text-muted">{{{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>

+ 10 - 8
src/utils/core.js

@@ -218,30 +218,32 @@
         return text;
     };
 
-    u.renderImageURLs = function (obj) {
+    u.renderImageURLs = function (_converse, obj) {
         /* Returns a Promise which resolves once all images have been loaded.
          */
+        const { __ } = _converse;
         const list = obj.textContent.match(URL_REGEX) || [];
         return Promise.all(
             _.map(list, (url) =>
                 new Promise((resolve, reject) =>
                     isImage(url).then(function (img) {
-                        // XXX: need to create a new image, otherwise the event
-                        // listener doesn't fire
                         const i = new Image();
-                        i.className = 'chat-image';
                         i.src = img.src;
                         i.addEventListener('load', resolve);
                         // We also resolve for non-images, otherwise the
                         // Promise.all resolves prematurely.
                         i.addEventListener('error', resolve);
-                        var anchors = sizzle(`a[href="${url}"]`, obj);
-                        _.each(anchors, (a) => {
-                            a.replaceChild(i, a.firstChild);
+
+                        _.each(sizzle(`a[href="${url}"]`, obj), (a) => {
+                            a.innerHTML = tpl_image({
+                                'url': url,
+                                'label_download': __('Download image file')
+                            })
                         });
                     }).catch(resolve)
                 )
-            ))
+            )
+        )
     };
 
     u.renderFileURL = function (_converse, url) {

Some files were not shown because too many files changed in this diff