Bladeren bron

Add support for XEP-0066 Out of band data

JC Brand 7 jaren geleden
bovenliggende
commit
a19e7aeffe

+ 1 - 1
.eslintrc.json

@@ -57,7 +57,7 @@
             "property"
         ],
         "dot-notation": [
-            "error",
+            "off",
             {
                 "allowKeywords": true
             }

+ 2 - 1
CHANGES.md

@@ -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)
 

+ 4 - 2
css/converse.css

@@ -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;

+ 4 - 2
css/inverse.css

@@ -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;

+ 1 - 0
index.html

@@ -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>

+ 3 - 2
sass/_chatbox.scss

@@ -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%

+ 169 - 4
spec/chatbox.js

@@ -1578,6 +1578,171 @@
                     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",
                     mock.initConverseWithPromises(
@@ -1672,10 +1837,10 @@
                     var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                     // <composing> state
                     var msg = $msg({
-                            from: sender_jid,
-                            to: _converse.connection.jid,
-                            type: 'chat',
-                            id: (new Date()).getTime()
+                            'from': sender_jid,
+                            'to': _converse.connection.jid,
+                            'type': 'chat',
+                            'id': (new Date()).getTime()
                         }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                     _converse.chatboxes.onMessage(msg);
                     expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));

+ 14 - 4
src/converse-chatboxes.js

@@ -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({

+ 1 - 0
src/converse-core.js

@@ -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');

+ 2 - 2
src/converse-disco.js

@@ -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) => {

+ 23 - 12
src/converse-message-view.js

@@ -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)}
                         )));

+ 2 - 0
src/templates/audio.html

@@ -0,0 +1,2 @@
+<audio controls><source src="{{{o.url}}}" type="audio/mpeg"></audio>
+<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>

+ 1 - 4
src/templates/file.html

@@ -1,4 +1 @@
-<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>

+ 4 - 0
src/templates/file_progress.html

@@ -0,0 +1,4 @@
+<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>

+ 1 - 0
src/templates/image.html

@@ -0,0 +1 @@
+<img class="chat-image" src="{{{o.url}}}"/>

+ 2 - 1
src/templates/message.html

@@ -1,4 +1,5 @@
 <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>

+ 2 - 0
src/templates/video.html

@@ -0,0 +1,2 @@
+<video controls><source src="{{{o.url}}}" type="video/mp4"></video>
+<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.label_download}}}</a>

+ 55 - 9
src/utils/core.js

@@ -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) {