Commit a19e7aef authored by JC Brand's avatar JC Brand

Add support for XEP-0066 Out of band data

parent d048cff9
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
"property" "property"
], ],
"dot-notation": [ "dot-notation": [
"error", "off",
{ {
"allowKeywords": true "allowKeywords": true
} }
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
## New Features ## New Features
- #161 XEP-0363: HTTP File Upload - #161 XEP-0363: HTTP File Upload
- mp4 and mp3 files will now be playable directly in chat - Support for rendering URLs sent according to XEP-0066 Out of Band Data.
- mp4 and mp3 files when sent as XEP-0066 Out of Band Data, will now be playable directly in chat
## 4.0.0 (Unreleased) ## 4.0.0 (Unreleased)
......
...@@ -7382,8 +7382,7 @@ body.reset { ...@@ -7382,8 +7382,7 @@ body.reset {
font-style: italic; } font-style: italic; }
#converse-embedded-chat .chatbox .chat-body .chat-message, #converse-embedded-chat .chatbox .chat-body .chat-message,
#conversejs .chatbox .chat-body .chat-message { #conversejs .chatbox .chat-body .chat-message {
overflow: auto; overflow: auto; }
margin: 0; }
#converse-embedded-chat .chatbox .chat-body .chat-message.onload, #converse-embedded-chat .chatbox .chat-body .chat-message.onload,
#conversejs .chatbox .chat-body .chat-message.onload { #conversejs .chatbox .chat-body .chat-message.onload {
animation: colorchange-chatmessage 1s; animation: colorchange-chatmessage 1s;
...@@ -7444,6 +7443,9 @@ body.reset { ...@@ -7444,6 +7443,9 @@ body.reset {
#conversejs .chatbox .chat-content .toggle-spoiler:before { #conversejs .chatbox .chat-content .toggle-spoiler:before {
padding-right: 0.25em; padding-right: 0.25em;
whitespace: nowrap; } whitespace: nowrap; }
#converse-embedded-chat .chatbox .chat-content video,
#conversejs .chatbox .chat-content video {
width: 100%; }
#converse-embedded-chat .chatbox .chat-content progress, #converse-embedded-chat .chatbox .chat-content progress,
#conversejs .chatbox .chat-content progress { #conversejs .chatbox .chat-content progress {
margin: 0.5em 0; margin: 0.5em 0;
......
...@@ -7435,8 +7435,7 @@ body { ...@@ -7435,8 +7435,7 @@ body {
font-style: italic; } font-style: italic; }
#converse-embedded-chat .chatbox .chat-body .chat-message, #converse-embedded-chat .chatbox .chat-body .chat-message,
#conversejs .chatbox .chat-body .chat-message { #conversejs .chatbox .chat-body .chat-message {
overflow: auto; overflow: auto; }
margin: 0; }
#converse-embedded-chat .chatbox .chat-body .chat-message.onload, #converse-embedded-chat .chatbox .chat-body .chat-message.onload,
#conversejs .chatbox .chat-body .chat-message.onload { #conversejs .chatbox .chat-body .chat-message.onload {
animation: colorchange-chatmessage 1s; animation: colorchange-chatmessage 1s;
...@@ -7497,6 +7496,9 @@ body { ...@@ -7497,6 +7496,9 @@ body {
#conversejs .chatbox .chat-content .toggle-spoiler:before { #conversejs .chatbox .chat-content .toggle-spoiler:before {
padding-right: 0.25em; padding-right: 0.25em;
whitespace: nowrap; } whitespace: nowrap; }
#converse-embedded-chat .chatbox .chat-content video,
#conversejs .chatbox .chat-content video {
width: 100%; }
#converse-embedded-chat .chatbox .chat-content progress, #converse-embedded-chat .chatbox .chat-content progress,
#conversejs .chatbox .chat-content progress { #conversejs .chatbox .chat-content progress {
margin: 0.5em 0; margin: 0.5em 0;
......
...@@ -167,6 +167,7 @@ ...@@ -167,6 +167,7 @@
<li>Custom status messages</li> <li>Custom status messages</li>
<li>Typing and chat state notifications (<a href="http://xmpp.org/extensions/xep-0085.html" target="_blank" rel="noopener">XEP 85</a>)</li> <li>Typing and chat state notifications (<a href="http://xmpp.org/extensions/xep-0085.html" target="_blank" rel="noopener">XEP 85</a>)</li>
<li>Desktop notifications</li> <li>Desktop notifications</li>
<li>File sharing (<a href="http://xmpp.org/extensions/xep-0363.html" target="_blank" rel="noopener">XEP 363</a>)</li>
<li>Messages appear in all connected chat clients (<a href="http://xmpp.org/extensions/xep-0280.html" target="_blank" rel="noopener">XEP 280</a>)</li> <li>Messages appear in all connected chat clients (<a href="http://xmpp.org/extensions/xep-0280.html" target="_blank" rel="noopener">XEP 280</a>)</li>
<li>Third person "/me" messages (<a href="http://xmpp.org/extensions/xep-0245.html" target="_blank" rel="noopener">XEP 245</a>)</li> <li>Third person "/me" messages (<a href="http://xmpp.org/extensions/xep-0245.html" target="_blank" rel="noopener">XEP 245</a>)</li>
<li>XMPP Ping (<a href="http://xmpp.org/extensions/xep-0199.html" target="_blank" rel="noopener">XEP 199</a>)</li> <li>XMPP Ping (<a href="http://xmpp.org/extensions/xep-0199.html" target="_blank" rel="noopener">XEP 199</a>)</li>
......
...@@ -211,7 +211,6 @@ ...@@ -211,7 +211,6 @@
} }
.chat-message { .chat-message {
overflow: auto; // Ensures that content stays inside overflow: auto; // Ensures that content stays inside
margin: 0;
&.onload { &.onload {
animation: colorchange-chatmessage 1s; animation: colorchange-chatmessage 1s;
...@@ -281,7 +280,9 @@ ...@@ -281,7 +280,9 @@
padding-right: 0.25em; padding-right: 0.25em;
whitespace: nowrap; whitespace: nowrap;
} }
video {
width: 100%
}
progress { progress {
margin: 0.5em 0; margin: 0.5em 0;
width: 100% width: 100%
......
...@@ -1578,6 +1578,171 @@ ...@@ -1578,6 +1578,171 @@
done(); done();
})); }));
it("will render audio from oob mp3 URLs",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>Have you heard this funny audio?</body>"+
" <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return view.el.querySelectorAll('.chat-content .chat-message audio').length;
}, 1000).then(function () {
var msg = view.el.querySelector('.chat-message .chat-msg-content');
expect(msg.outerHTML).toEqual('<span class="chat-msg-content">Have you heard this funny audio?</span>');
var media = view.el.querySelector('.chat-message .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
'<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
// If the <url> and <body> contents is the same, don't duplicate.
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>http://localhost/audio.mp3</body>"+
" <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
msg = view.el.querySelector('.chat-message:last-child .chat-msg-content');
expect(msg.innerHTML).toEqual('');
media = view.el.querySelector('.chat-message:last-child .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
'<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
done();
});
}));
it("will render video from oob mp4 URLs",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>Have you seen this funny video?</body>"+
" <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return view.el.querySelectorAll('.chat-content .chat-message video').length;
}, 1000).then(function () {
var msg = view.el.querySelector('.chat-message .chat-msg-content');
expect(msg.outerHTML).toEqual('<span class="chat-msg-content">Have you seen this funny video?</span>');
var media = view.el.querySelector('.chat-message .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
'<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
// If the <url> and <body> contents is the same, don't duplicate.
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>http://localhost/video.mp4</body>"+
" <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
msg = view.el.querySelector('.chat-message:last-child .chat-msg-content');
expect(msg.innerHTML).toEqual('');
media = view.el.querySelector('.chat-message:last-child .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
'<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
done();
});
}));
it("will render download links for files from oob URLs",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>Have you downloaded this funny file?</body>"+
" <x xmlns='jabber:x:oob'><url>http://localhost/funny.pdf</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return view.el.querySelectorAll('.chat-content .chat-message a').length;
}, 1000).then(function () {
var msg = view.el.querySelector('.chat-message .chat-msg-content');
expect(msg.outerHTML).toEqual('<span class="chat-msg-content">Have you downloaded this funny file?</span>');
var media = view.el.querySelector('.chat-message .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<a target="_blank" rel="noopener" href="http://localhost/funny.pdf">Download file: "funny.pdf</a>');
done();
});
}));
it("will render images from oob URLs",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
var base_url = document.URL.split(window.location.pathname)[0];
var url = base_url+"/logo/conversejs-filled.svg";
var stanza = Strophe.xmlHtmlNode(
"<message from='"+contact_jid+"'"+
" type='chat'"+
" to='dummy@localhost/resource'>"+
" <body>Have you seen this funny image?</body>"+
" <x xmlns='jabber:x:oob'><url>"+url+"</url></x>"+
"</message>").firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return view.el.querySelectorAll('.chat-content .chat-message img').length;
}, 1000).then(function () {
var msg = view.el.querySelector('.chat-message .chat-msg-content');
expect(msg.outerHTML).toEqual('<span class="chat-msg-content">Have you seen this funny image?</span>');
var media = view.el.querySelector('.chat-message .chat-msg-media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<img class="chat-image" src="http://localhost:8000/logo/conversejs-filled.svg">');
done();
});
}));
it("will render images from their URLs", it("will render images from their URLs",
mock.initConverseWithPromises( mock.initConverseWithPromises(
...@@ -1672,10 +1837,10 @@ ...@@ -1672,10 +1837,10 @@
var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
// <composing> state // <composing> state
var msg = $msg({ var msg = $msg({
from: sender_jid, 'from': sender_jid,
to: _converse.connection.jid, 'to': _converse.connection.jid,
type: 'chat', 'type': 'chat',
id: (new Date()).getTime() 'id': (new Date()).getTime()
}).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
......
...@@ -10,16 +10,15 @@ ...@@ -10,16 +10,15 @@
"emojione", "emojione",
"filesize", "filesize",
"tpl!chatboxes", "tpl!chatboxes",
"backbone.overview" "backbone.overview",
"form-utils"
], factory); ], factory);
}(this, function (converse, emojione, filesize, tpl_chatboxes) { }(this, function (converse, emojione, filesize, tpl_chatboxes) {
"use strict"; "use strict";
const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, utils, _ } = converse.env; const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
converse.plugins.add('converse-chatboxes', { converse.plugins.add('converse-chatboxes', {
...@@ -373,6 +372,7 @@ ...@@ -373,6 +372,7 @@
sender = 'them'; sender = 'them';
fullname = this.get('fullname'); fullname = this.get('fullname');
} }
const spoiler = message.querySelector(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`); const spoiler = message.querySelector(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`);
const attrs = { const attrs = {
'type': type, 'type': type,
...@@ -386,6 +386,10 @@ ...@@ -386,6 +386,10 @@
'time': time, 'time': time,
'is_spoiler': !_.isNull(spoiler) 'is_spoiler': !_.isNull(spoiler)
}; };
_.each(sizzle(`x[xmlns="${Strophe.NS.OUTOFBAND}"]`, message), (xform) => {
attrs['oob_url'] = xform.querySelector('url').textContent;
attrs['oob_desc'] = xform.querySelector('url').textContent;
});
if (spoiler) { if (spoiler) {
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : ''; attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
} }
...@@ -679,7 +683,13 @@ ...@@ -679,7 +683,13 @@
return _converse.chatboxviews.get(chatbox.get('id')); return _converse.chatboxviews.get(chatbox.get('id'));
}; };
/************************ BEGIN Event Handlers ************************/ /************************ BEGIN Event Handlers ************************/
_converse.on('addClientFeatures', () => {
_converse.connection.disco.addFeature(Strophe.NS.HTTPUPLOAD);
_converse.connection.disco.addFeature(Strophe.NS.OUTOFBAND);
});
_converse.api.listen.on('pluginsInitialized', () => { _converse.api.listen.on('pluginsInitialized', () => {
_converse.chatboxes = new _converse.ChatBoxes(); _converse.chatboxes = new _converse.ChatBoxes();
_converse.chatboxviews = new _converse.ChatBoxViews({ _converse.chatboxviews = new _converse.ChatBoxViews({
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0'); Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2'); Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick'); Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub'); Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx'); Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm'); Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
......
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
}); });
}); });
_.each(sizzle('x[type="result"][xmlns="jabber:x:data"]', stanza), (form) => { _.each(sizzle(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza), (form) => {
const data = {}; const data = {};
_.each(form.querySelectorAll('field'), (field) => { _.each(form.querySelectorAll('field'), (field) => {
data[field.getAttribute('var')] = { data[field.getAttribute('var')] = {
...@@ -172,7 +172,7 @@ ...@@ -172,7 +172,7 @@
this.dataforms.create(data); this.dataforms.create(data);
}); });
if (stanza.querySelector('feature[var="'+Strophe.NS.DISCO_ITEMS+'"]')) { if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
this.queryForItems(); this.queryForItems();
} }
_.forEach(stanza.querySelectorAll('feature'), (feature) => { _.forEach(stanza.querySelectorAll('feature'), (feature) => {
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"emojione", "emojione",
"filesize", "filesize",
"tpl!action", "tpl!action",
"tpl!file", "tpl!file_progress",
"tpl!info", "tpl!info",
"tpl!message", "tpl!message",
"tpl!spoiler_message" "tpl!spoiler_message"
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
emojione, emojione,
filesize, filesize,
tpl_action, tpl_action,
tpl_file, tpl_file_progress,
tpl_info, tpl_info,
tpl_message, tpl_message,
tpl_spoiler_message tpl_spoiler_message
...@@ -85,16 +85,27 @@ ...@@ -85,16 +85,27 @@
'label_show': __('Show hidden message') 'label_show': __('Show hidden message')
}) })
)); ));
const msg_content = msg.querySelector('.chat-msg-content');
text = xss.filterXSS(text, {'whiteList': {}});
msg_content.innerHTML = _.flow(
_.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
_.partial(u.addHyperlinks, _),
_.partial(u.addEmoji, _converse, emojione, _),
u.renderMovieURLs,
u.renderAudioURLs
)(text);
var url = this.model.get('oob_url');
if (url) {
const msg_media = msg.querySelector('.chat-msg-media');
msg_media.innerHTML = _.flow(
_.partial(u.renderFileURL, _converse),
_.partial(u.renderMovieURL, _converse),
_.partial(u.renderAudioURL, _converse),
_.partial(u.renderImageURL, _converse)
)(url);
}
const msg_content = msg.querySelector('.chat-msg-content');
if (text !== url) {
text = xss.filterXSS(text, {'whiteList': {}});
msg_content.innerHTML = _.flow(
_.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
u.addHyperlinks,
_.partial(u.addEmoji, _converse, emojione, _)
)(text);
}
u.renderImageURLs(msg_content).then(() => { u.renderImageURLs(msg_content).then(() => {
this.model.collection.trigger('rendered'); this.model.collection.trigger('rendered');
}); });
...@@ -121,7 +132,7 @@ ...@@ -121,7 +132,7 @@
}, },
renderFileUploadProgresBar () { renderFileUploadProgresBar () {
const msg = u.stringToElement(tpl_file( const msg = u.stringToElement(tpl_file_progress(
_.extend(this.model.toJSON(), _.extend(this.model.toJSON(),
{'filesize': filesize(this.model.get('file').size)} {'filesize': filesize(this.model.get('file').size)}
))); )));
......
<audio controls><source src="{{{o.url}}}" type="audio/mpeg"></audio>
<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>
<div class="message" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}"> <a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>
<span class="chat-msg-content">Uploading file: <strong>{{{o.file.name}}}</strong>, {{{o.filesize}}}</span>
<progress value="{{{o.progress}}}"/>
</div>
<div class="message" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
<span class="chat-msg-content">Uploading file: <strong>{{{o.file.name}}}</strong>, {{{o.filesize}}}</span>
<progress value="{{{o.progress}}}"/>
</div>
<img class="chat-image" src="{{{o.url}}}"/>
<div class="message chat-message {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}"> <div class="message chat-message {{{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-author chat-msg-{{{o.sender}}}">{{{o.pretty_time}}} {{{o.username}}}:&nbsp;</span>
<span class="chat-msg-content"><!-- message gets added here via renderMessage --></span> <span class="chat-msg-content"></span>
<div class="chat-msg-media"></div>
</div> </div>
<video controls><source src="{{{o.url}}}" type="video/mp4"></video>
<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>
...@@ -13,12 +13,20 @@ ...@@ -13,12 +13,20 @@
"es6-promise", "es6-promise",
"lodash.noconflict", "lodash.noconflict",
"strophe", "strophe",
"tpl!audio",
"tpl!file",
"tpl!image",
"tpl!video"
], factory); ], factory);
}(this, function ( }(this, function (
sizzle, sizzle,
Promise, Promise,
_, _,
Strophe Strophe,
tpl_audio,
tpl_file,
tpl_image,
tpl_video
) { ) {
"use strict"; "use strict";
const b64_sha1 = Strophe.SHA1.b64_sha1; const b64_sha1 = Strophe.SHA1.b64_sha1;
...@@ -213,18 +221,56 @@ ...@@ -213,18 +221,56 @@
)) ))
}; };
u.renderMovieURLs = function (text) { u.renderFileURL = function (_converse, url) {
if (text.endsWith('mp4')) { if (url.endsWith('mp3') || url.endsWith('mp4') ||
return "<video controls><source src=\"" + text + "\" type=\"video/mp4\"></video>"; url.endsWith('jpg') || url.endsWith('jpeg') ||
url.endsWith('png') || url.endsWith('gif') ||
url.endsWith('svg')) {
return url;
} }
return text; const name = url.split('/').pop(),
{ __ } = _converse;
return tpl_file({
'url': url,
'label_download': __('Download file: "%1$s', name)
})
}; };
u.renderAudioURLs = function (text) { u.renderImageURL = function (_converse, url) {
if (text.endsWith('mp3')) { const { __ } = _converse;
return "<audio controls><source src=\"" + text+ "\" type=\"audio/mpeg\"></audio>"; if (url.endsWith('jpg') || url.endsWith('jpeg') || url.endsWith('png') ||
url.endsWith('gif') || url.endsWith('svg')) {
return tpl_image({
'url': url,
'label_download': __('Download image file')
})
} }
return text; return url;
};
u.renderMovieURL = function (_converse, url) {
const { __ } = _converse;
if (url.endsWith('mp4')) {
return tpl_video({
'url': url,
'label_download': __('Download video file')
})
}
return url;
};
u.renderAudioURL = function (_converse, url) {
const { __ } = _converse;
if (url.endsWith('mp3')) {
return tpl_audio({
'url': url,
'label_download': __('Download audio file')
})
}
return url;
}; };
u.slideInAllElements = function (elements, duration=300) { u.slideInAllElements = function (elements, duration=300) {
......
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