Commit 3d424250 authored by JC Brand's avatar JC Brand

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
parent f913ee86
...@@ -101,7 +101,6 @@ Brief description of converse.js's dependencies ...@@ -101,7 +101,6 @@ Brief description of converse.js's dependencies
Converse.js relies on the following 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. * `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 * `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. build XMPP stanzas, to send them, and to register handlers for received stanzas.
......
...@@ -25,12 +25,6 @@ ...@@ -25,12 +25,6 @@
<div class="chat-head chat-head-chatbox row no-gutters"> <div class="chat-head chat-head-chatbox row no-gutters">
<div class="col"> <div class="col">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-auto">
<img alt="User Avatar"
class="avatar"
height="50px" width="50px"
src="data:image/png;base64,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"/>
</div>
<div class="col chat-title" title="j@chat.example.org">Juliet Capulet <div class="col chat-title" title="j@chat.example.org">Juliet Capulet
<p class="user-custom-message" title="On the balcony">On the balcony</p> <p class="user-custom-message" title="On the balcony">On the balcony</p>
</div> </div>
......
...@@ -97,7 +97,6 @@ ...@@ -97,7 +97,6 @@
</div> </div>
</div> </div>
<div class="message chat-msg"> <div class="message chat-msg">
<canvas height="36" width="36" class="avatar"></canvas> <canvas height="36" width="36" class="avatar"></canvas>
<div class="chat-msg-content"> <div class="chat-msg-content">
...@@ -111,6 +110,24 @@ ...@@ -111,6 +110,24 @@
Or, if thou wilt not, be but sworn my love, Or, if thou wilt not, be but sworn my love,
And I'll no longer be a Capulet. And I'll no longer be a Capulet.
</p> </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>
</div> </div>
...@@ -121,13 +138,15 @@ ...@@ -121,13 +138,15 @@
<span class="chat-msg-author">Romeo Montague</span> <span class="chat-msg-author">Romeo Montague</span>
<span class="chat-msg-time text-muted">19:36</span> <span class="chat-msg-time text-muted">19:36</span>
</span> </span>
<div class="spoiler-hint">By a name <div>
<a class="badge badge-info toggle-spoiler" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>Show more</a> <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>
<div class="chat-msg-text spoiler collapsed"> <div class="chat-msg-text spoiler collapsed">
I know not how to tell thee who I am: My name, dear saint, is hateful to 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. myself, Because it is an enemy to thee. Had I it written, I would tear the word.
</div> </div>
<div class="chat-msg-media"></div>
</div> </div>
</div> </div>
......
...@@ -52,7 +52,7 @@ function toggleSpoilerMessage (ev) { ...@@ -52,7 +52,7 @@ function toggleSpoilerMessage (ev) {
} }
window.initSpoilers = function () { window.initSpoilers = function () {
const spoilers = document.querySelectorAll('.toggle-spoiler'); const spoilers = document.querySelectorAll('.spoiler-toggle');
_.each(spoilers, (spoiler_el) => { _.each(spoilers, (spoiler_el) => {
spoiler_el.addEventListener('click', toggleSpoilerMessage); spoiler_el.addEventListener('click', toggleSpoilerMessage);
}); });
......
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
var view = _converse.chatboxviews.get(sender_jid); 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); expect(message_content.textContent).toBe(spoiler);
var spoiler_hint_el = view.el.querySelector('.spoiler-hint'); var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
var view = _converse.chatboxviews.get(sender_jid); var view = _converse.chatboxviews.get(sender_jid);
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-content'); var message_content = view.el.querySelector('.chat-msg-text');
expect(message_content.textContent).toBe(spoiler); expect(message_content.textContent).toBe(spoiler);
var spoiler_hint_el = view.el.querySelector('.spoiler-hint'); var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
...@@ -148,17 +148,17 @@ ...@@ -148,17 +148,17 @@
expect(body_el.textContent).toBe('This is the spoiler'); expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */ /* 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(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler'); spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show hidden message'); expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy(); 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(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done(); done();
...@@ -227,17 +227,17 @@ ...@@ -227,17 +227,17 @@
expect(body_el.textContent).toBe('This is the spoiler'); expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */ /* 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(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler'); spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show hidden message'); expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy(); 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(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done(); done();
......
...@@ -193,7 +193,6 @@ ...@@ -193,7 +193,6 @@
'image': _converse.DEFAULT_IMAGE, 'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE, 'image_type': _converse.DEFAULT_IMAGE_TYPE,
'num_unread': 0, 'num_unread': 0,
'show_avatar': true,
'type': 'chatbox', 'type': 'chatbox',
'message_type': 'chat', 'message_type': 'chat',
'url': '' 'url': ''
......
...@@ -249,7 +249,7 @@ ...@@ -249,7 +249,7 @@
'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .toggle-spoiler': 'toggleSpoilerMessage', 'click .spoiler-toggle': 'toggleSpoilerMessage',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed', 'keypress .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
...@@ -373,7 +373,7 @@ ...@@ -373,7 +373,7 @@
this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html); this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
} }
} }
}); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}, },
insertHeading () { insertHeading () {
...@@ -907,14 +907,16 @@ ...@@ -907,14 +907,16 @@
toggle_el.parentElement.parentElement.querySelector('.spoiler') toggle_el.parentElement.parentElement.querySelector('.spoiler')
); );
if (toggle_el.getAttribute("data-toggle-state") == "closed") { 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.remove("fa-eye");
icon_el.classList.add("fa-eye-slash"); icon_el.classList.add("fa-eye-slash");
toggle_el.insertAdjacentElement('afterBegin', icon_el);
toggle_el.setAttribute("data-toggle-state", "open"); toggle_el.setAttribute("data-toggle-state", "open");
} else { } else {
toggle_el.textContent = __('Show more'); toggle_el.textContent = 'Show more';
icon_el.classList.remove("fa-eye-slash"); icon_el.classList.remove("fa-eye-slash");
icon_el.classList.add("fa-eye"); icon_el.classList.add("fa-eye");
toggle_el.insertAdjacentElement('afterBegin', icon_el);
toggle_el.setAttribute("data-toggle-state", "closed"); toggle_el.setAttribute("data-toggle-state", "closed");
} }
}, },
......
...@@ -1509,10 +1509,12 @@ ...@@ -1509,10 +1509,12 @@
defaults () { defaults () {
return { return {
"status": _converse.default_state,
"jid": _converse.bare_jid, "jid": _converse.bare_jid,
"nickname": _converse.nickname, "nickname": _converse.nickname,
"vcard_updated": null "status": _converse.default_state,
"vcard_updated": null,
'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE
} }
}, },
......
...@@ -74,7 +74,6 @@ ...@@ -74,7 +74,6 @@
_converse.HeadlinesBox = _converse.ChatBox.extend({ _converse.HeadlinesBox = _converse.ChatBox.extend({
defaults: { defaults: {
'type': 'headline', 'type': 'headline',
'show_avatar': false,
'bookmarked': false, 'bookmarked': false,
'chat_state': undefined, 'chat_state': undefined,
'num_unread': 0, 'num_unread': 0,
......
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
_converse.MessageView = Backbone.NativeView.extend({ _converse.MessageView = Backbone.NativeView.extend({
initialize () { initialize () {
const chatbox = this.model.collection.chatbox; this.chatbox = this.model.collection.chatbox;
chatbox.on('change:fullname', (chatbox) => this.model.save('fullname', chatbox.get('fullname'))); 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:fullname', this.render, this);
this.model.on('change:progress', this.renderFileUploadProgresBar, this); this.model.on('change:progress', this.renderFileUploadProgresBar, this);
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
return this.renderErrorMessage(); return this.renderErrorMessage();
} }
let template, username, let template, username, image, image_type,
text = this.model.get('message'); text = this.model.get('message');
// TODO: store proper username on the message itself // TODO: store proper username on the message itself
...@@ -71,9 +71,16 @@ ...@@ -71,9 +71,16 @@
username = arr[1]; username = arr[1];
text = arr[2]; text = arr[2];
} else { } else {
const fullname = _converse.xmppstatus.get('fullname') || this.model.get('fullname'); username = this.model.get('fullname') || this.model.get('from');
username = this.model.get('sender') === 'me' && __('me') || fullname || this.model.get('from');
template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message; 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 moment_time = moment(this.model.get('time'));
const msg = u.stringToElement(template( const msg = u.stringToElement(template(
...@@ -82,7 +89,9 @@ ...@@ -82,7 +89,9 @@
'time': moment_time.format(), 'time': moment_time.format(),
'username': username, 'username': username,
'extra_classes': this.getExtraMessageClasses(), 'extra_classes': this.getExtraMessageClasses(),
'label_show': __('Show more') 'label_show': __('Show more'),
'image_type': image_type,
'image': image
}) })
)); ));
...@@ -97,7 +106,7 @@ ...@@ -97,7 +106,7 @@
)(url); )(url);
} }
const msg_content = msg.querySelector('.chat-msg-content'); const msg_content = msg.querySelector('.chat-msg-text');
if (text !== url) { if (text !== url) {
text = xss.filterXSS(text, {'whiteList': {}}); text = xss.filterXSS(text, {'whiteList': {}});
msg_content.innerHTML = _.flow( msg_content.innerHTML = _.flow(
...@@ -106,7 +115,7 @@ ...@@ -106,7 +115,7 @@
_.partial(u.addEmoji, _converse, emojione, _) _.partial(u.addEmoji, _converse, emojione, _)
)(text); )(text);
} }
u.renderImageURLs(msg_content).then(() => { u.renderImageURLs(_converse, msg_content).then(() => {
this.model.collection.trigger('rendered'); this.model.collection.trigger('rendered');
}); });
return this.replaceElement(msg); return this.replaceElement(msg);
......
...@@ -1485,7 +1485,7 @@ ...@@ -1485,7 +1485,7 @@
* Chat rooms can be listed, joined and new rooms can be created. * Chat rooms can be listed, joined and new rooms can be created.
*/ */
tagName: 'div', tagName: 'div',
className: 'controlbox-pane', className: 'controlbox-section',
id: 'chatrooms', id: 'chatrooms',
events: { events: {
'click a.chatbox-btn.fa-users': 'showAddRoomModal', 'click a.chatbox-btn.fa-users': 'showAddRoomModal',
......
...@@ -304,15 +304,11 @@ ...@@ -304,15 +304,11 @@
getOutgoingMessageAttributes (text, spoiler_hint) { getOutgoingMessageAttributes (text, spoiler_hint) {
const is_spoiler = this.get('composing_spoiler'); const is_spoiler = this.get('composing_spoiler');
return { return {
'from': _converse.connection.jid,
'fullname': this.get('nick'), 'fullname': this.get('nick'),
'is_spoiler': is_spoiler, 'is_spoiler': is_spoiler,
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined, 'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
'msgid': _converse.connection.getUniqueId(),
'sender': 'me', 'sender': 'me',
'spoiler_hint': is_spoiler ? spoiler_hint : undefined, 'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
'time': moment().format(),
'to': this.get('jid'),
'type': 'groupchat', 'type': 'groupchat',
}; };
}, },
......
...@@ -719,6 +719,7 @@ ...@@ -719,6 +719,7 @@
_converse.RosterView = Backbone.OrderedListView.extend({ _converse.RosterView = Backbone.OrderedListView.extend({
tagName: 'div', tagName: 'div',
id: 'converse-roster', id: 'converse-roster',
className: 'controlbox-section',
ItemView: _converse.RosterGroupView, ItemView: _converse.RosterGroupView,
listItems: 'model', listItems: 'model',
......
<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}}}"> <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="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 {[ } ]}" <a class="remove-bookmark fa fa-bookmark align-self-center {[ if (o.bookmarked) { ]} button-on {[ } ]}"
......
<div class="chat-head chat-head-chatbox row no-gutters"> <div class="chat-head chat-head-chatbox row no-gutters">
<div class="col"> <div class="col">
<div class="row no-gutters"> <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}}}"> <div class="col chat-title" title="{{{o.jid}}}">
{[ if (o.url) { ]} {[ if (o.url) { ]}
<a href="{{{o.url}}}" target="_blank" rel="noopener" class="user"> <a href="{{{o.url}}}" target="_blank" rel="noopener" class="user">
......
<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>
<div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}"> <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> <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
<span class="chat-msg-content"></span> <div class="chat-msg-content">
<div class="chat-msg-media"></div> <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> </div>
<!-- <div id="chatrooms"> --> <!-- <div id="chatrooms"> -->
<div class="d-flex"> <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-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> <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> </div>
......
<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}}}"> <div class="available-chatroom d-flex flex-row {[ if (o.num_unread_general) { ]} unread-msgs {[ } ]}" data-room-jid="{{{o.jid}}}">
{[ if (o.num_unread) { ]} {[ if (o.num_unread) { ]}
<span class="msgs-indicator badge badge-info">{{{ o.num_unread }}}</span> <span class="msgs-indicator badge badge-info">{{{ o.num_unread }}}</span>
......
<div class="d-flex"> <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}}}" <a class="chatbox-btn add-contact fa fa-user-plus" title="{{{o.title_add_contact}}}"
data-toggle="modal" data-target="#add-contact-modal"></a> data-toggle="modal" data-target="#add-contact-modal"></a>
</div> </div>
......
<div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}"> <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> <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
<div class="spoiler-hint">{{{o.spoiler_hint}}}</div> <div class="chat-msg-content">
<a class="toggle-spoiler" data-toggle-state="closed" href="#"><i class="fa fa-eye"></li>{{{o.label_show}}}</a> <span class="chat-msg-heading">
<div class="chat-msg-content spoiler collapsed"><!-- message gets added here via renderMessage --></div> <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> </div>
...@@ -218,30 +218,32 @@ ...@@ -218,30 +218,32 @@
return text; return text;
}; };
u.renderImageURLs = function (obj) { u.renderImageURLs = function (_converse, obj) {
/* Returns a Promise which resolves once all images have been loaded. /* Returns a Promise which resolves once all images have been loaded.
*/ */
const { __ } = _converse;
const list = obj.textContent.match(URL_REGEX) || []; const list = obj.textContent.match(URL_REGEX) || [];
return Promise.all( return Promise.all(
_.map(list, (url) => _.map(list, (url) =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
isImage(url).then(function (img) { isImage(url).then(function (img) {
// XXX: need to create a new image, otherwise the event
// listener doesn't fire
const i = new Image(); const i = new Image();
i.className = 'chat-image';
i.src = img.src; i.src = img.src;
i.addEventListener('load', resolve); i.addEventListener('load', resolve);
// We also resolve for non-images, otherwise the // We also resolve for non-images, otherwise the
// Promise.all resolves prematurely. // Promise.all resolves prematurely.
i.addEventListener('error', resolve); i.addEventListener('error', resolve);
var anchors = sizzle(`a[href="${url}"]`, obj);
_.each(anchors, (a) => { _.each(sizzle(`a[href="${url}"]`, obj), (a) => {
a.replaceChild(i, a.firstChild); a.innerHTML = tpl_image({
'url': url,
'label_download': __('Download image file')
})
}); });
}).catch(resolve) }).catch(resolve)
) )
)) )
)
}; };
u.renderFileURL = function (_converse, url) { u.renderFileURL = function (_converse, url) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment