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 @@
"property"
],
"dot-notation": [
"error",
"off",
{
"allowKeywords": true
}
......
......@@ -5,7 +5,8 @@
## New Features
- #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)
......
......@@ -7382,8 +7382,7 @@ body.reset {
font-style: italic; }
#converse-embedded-chat .chatbox .chat-body .chat-message,
#conversejs .chatbox .chat-body .chat-message {
overflow: auto;
margin: 0; }
overflow: auto; }
#converse-embedded-chat .chatbox .chat-body .chat-message.onload,
#conversejs .chatbox .chat-body .chat-message.onload {
animation: colorchange-chatmessage 1s;
......@@ -7444,6 +7443,9 @@ body.reset {
#conversejs .chatbox .chat-content .toggle-spoiler:before {
padding-right: 0.25em;
whitespace: nowrap; }
#converse-embedded-chat .chatbox .chat-content video,
#conversejs .chatbox .chat-content video {
width: 100%; }
#converse-embedded-chat .chatbox .chat-content progress,
#conversejs .chatbox .chat-content progress {
margin: 0.5em 0;
......
......@@ -7435,8 +7435,7 @@ body {
font-style: italic; }
#converse-embedded-chat .chatbox .chat-body .chat-message,
#conversejs .chatbox .chat-body .chat-message {
overflow: auto;
margin: 0; }
overflow: auto; }
#converse-embedded-chat .chatbox .chat-body .chat-message.onload,
#conversejs .chatbox .chat-body .chat-message.onload {
animation: colorchange-chatmessage 1s;
......@@ -7497,6 +7496,9 @@ body {
#conversejs .chatbox .chat-content .toggle-spoiler:before {
padding-right: 0.25em;
whitespace: nowrap; }
#converse-embedded-chat .chatbox .chat-content video,
#conversejs .chatbox .chat-content video {
width: 100%; }
#converse-embedded-chat .chatbox .chat-content progress,
#conversejs .chatbox .chat-content progress {
margin: 0.5em 0;
......
......@@ -167,6 +167,7 @@
<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>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>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>
......
......@@ -211,7 +211,6 @@
}
.chat-message {
overflow: auto; // Ensures that content stays inside
margin: 0;
&.onload {
animation: colorchange-chatmessage 1s;
......@@ -281,7 +280,9 @@
padding-right: 0.25em;
whitespace: nowrap;
}
video {
width: 100%
}
progress {
margin: 0.5em 0;
width: 100%
......
This diff is collapsed.
......@@ -10,16 +10,15 @@
"emojione",
"filesize",
"tpl!chatboxes",
"backbone.overview"
"backbone.overview",
"form-utils"
], factory);
}(this, function (converse, emojione, filesize, tpl_chatboxes) {
"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;
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
converse.plugins.add('converse-chatboxes', {
......@@ -373,6 +372,7 @@
sender = 'them';
fullname = this.get('fullname');
}
const spoiler = message.querySelector(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`);
const attrs = {
'type': type,
......@@ -386,6 +386,10 @@
'time': time,
'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) {
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
}
......@@ -679,7 +683,13 @@
return _converse.chatboxviews.get(chatbox.get('id'));
};
/************************ 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.chatboxes = new _converse.ChatBoxes();
_converse.chatboxviews = new _converse.ChatBoxViews({
......
......@@ -41,6 +41,7 @@
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
......
......@@ -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 = {};
_.each(form.querySelectorAll('field'), (field) => {
data[field.getAttribute('var')] = {
......@@ -172,7 +172,7 @@
this.dataforms.create(data);
});
if (stanza.querySelector('feature[var="'+Strophe.NS.DISCO_ITEMS+'"]')) {
if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
this.queryForItems();
}
_.forEach(stanza.querySelectorAll('feature'), (feature) => {
......
......@@ -11,7 +11,7 @@
"emojione",
"filesize",
"tpl!action",
"tpl!file",
"tpl!file_progress",
"tpl!info",
"tpl!message",
"tpl!spoiler_message"
......@@ -22,7 +22,7 @@
emojione,
filesize,
tpl_action,
tpl_file,
tpl_file_progress,
tpl_info,
tpl_message,
tpl_spoiler_message
......@@ -85,16 +85,27 @@
'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(() => {
this.model.collection.trigger('rendered');
});
......@@ -121,7 +132,7 @@
},
renderFileUploadProgresBar () {
const msg = u.stringToElement(tpl_file(
const msg = u.stringToElement(tpl_file_progress(
_.extend(this.model.toJSON(),
{'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}}}">
<span class="chat-msg-content">Uploading file: <strong>{{{o.file.name}}}</strong>, {{{o.filesize}}}</span>
<progress value="{{{o.progress}}}"/>
</div>
<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>
<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}}}">
<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>
<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 @@
"es6-promise",
"lodash.noconflict",
"strophe",
"tpl!audio",
"tpl!file",
"tpl!image",
"tpl!video"
], factory);
}(this, function (
sizzle,
Promise,
_,
Strophe
Strophe,
tpl_audio,
tpl_file,
tpl_image,
tpl_video
) {
"use strict";
const b64_sha1 = Strophe.SHA1.b64_sha1;
......@@ -213,18 +221,56 @@
))
};
u.renderMovieURLs = function (text) {
if (text.endsWith('mp4')) {
return "<video controls><source src=\"" + text + "\" type=\"video/mp4\"></video>";
u.renderFileURL = function (_converse, url) {
if (url.endsWith('mp3') || url.endsWith('mp4') ||
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) {
if (text.endsWith('mp3')) {
return "<audio controls><source src=\"" + text+ "\" type=\"audio/mpeg\"></audio>";
u.renderImageURL = function (_converse, url) {
const { __ } = _converse;
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) {
......
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