Просмотр исходного кода

added httpFileUpload to converse.js

worlword 7 лет назад
Родитель
Сommit
b23712aec3

+ 4 - 0
locale/de/LC_MESSAGES/converse.po

@@ -145,6 +145,10 @@ msgstr "tippt nicht mehr"
 msgid "has gone away"
 msgid "has gone away"
 msgstr "ist jetzt abwesend"
 msgstr "ist jetzt abwesend"
 
 
+#: src/converse-chatview.js:860
+msgid "Upload a File"
+msgstr "Datei versenden"
+
 #: dist/converse-no-dependencies.js:14851
 #: dist/converse-no-dependencies.js:14851
 #: dist/converse-no-dependencies.js:23427
 #: dist/converse-no-dependencies.js:23427
 msgid "Remove messages"
 msgid "Remove messages"

+ 1 - 0
src/build.js

@@ -13,6 +13,7 @@
         "converse-dragresize":      "builds/converse-dragresize",
         "converse-dragresize":      "builds/converse-dragresize",
         "converse-fullscreen":      "builds/converse-fullscreen",
         "converse-fullscreen":      "builds/converse-fullscreen",
         "converse-headline":        "builds/converse-headline",
         "converse-headline":        "builds/converse-headline",
+        "converse-httpFileUpload":  "builds/converse-httpFileUpload",
         "converse-mam":             "builds/converse-mam",
         "converse-mam":             "builds/converse-mam",
         "converse-minimize":        "builds/converse-minimize",
         "converse-minimize":        "builds/converse-minimize",
         "converse-modal":           "builds/converse-modal",
         "converse-modal":           "builds/converse-modal",

+ 1 - 0
src/config.js

@@ -74,6 +74,7 @@ require.config({
         "converse-dragresize":      "src/converse-dragresize",
         "converse-dragresize":      "src/converse-dragresize",
         "converse-fullscreen":      "src/converse-fullscreen",
         "converse-fullscreen":      "src/converse-fullscreen",
         "converse-headline":        "src/converse-headline",
         "converse-headline":        "src/converse-headline",
+        "converse-httpFileUpload":  "src/converse-httpFileUpload",
         "converse-mam":             "src/converse-mam",
         "converse-mam":             "src/converse-mam",
         "converse-minimize":        "src/converse-minimize",
         "converse-minimize":        "src/converse-minimize",
         "converse-modal":           "src/converse-modal",
         "converse-modal":           "src/converse-modal",

+ 60 - 10
src/converse-chatview.js

@@ -25,6 +25,7 @@
             "tpl!spoiler_button",
             "tpl!spoiler_button",
             "tpl!spoiler_message",
             "tpl!spoiler_message",
             "tpl!toolbar",
             "tpl!toolbar",
+            "converse-httpFileUpload",
             "converse-chatboxes"
             "converse-chatboxes"
     ], factory);
     ], factory);
 }(this, function (
 }(this, function (
@@ -44,7 +45,8 @@
             tpl_spinner,
             tpl_spinner,
             tpl_spoiler_button,
             tpl_spoiler_button,
             tpl_spoiler_message,
             tpl_spoiler_message,
-            tpl_toolbar
+            tpl_toolbar,
+            filetransfer
     ) {
     ) {
     "use strict";
     "use strict";
     const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
     const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
@@ -54,6 +56,8 @@
         FORWARD_SLASH: 47
         FORWARD_SLASH: 47
     };
     };
 
 
+    Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
+
     converse.plugins.add('converse-chatview', {
     converse.plugins.add('converse-chatview', {
         /* Plugin dependencies are other plugins which might be
         /* Plugin dependencies are other plugins which might be
          * overridden or relied upon, and therefore need to be loaded before
          * overridden or relied upon, and therefore need to be loaded before
@@ -108,7 +112,8 @@
                     'call': false,
                     'call': false,
                     'clear': true,
                     'clear': true,
                     'emoji': true,
                     'emoji': true,
-                    'spoiler': true
+                    'spoiler': true,
+                    'fileUpload': true
                 },
                 },
             });
             });
             emojione.imagePathPNG = _converse.emojione_image_path;
             emojione.imagePathPNG = _converse.emojione_image_path;
@@ -247,7 +252,22 @@
                     'click .toggle-smiley': 'toggleEmojiMenu',
                     'click .toggle-smiley': 'toggleEmojiMenu',
                     'click .toggle-spoiler': 'toggleSpoilerMessage',
                     'click .toggle-spoiler': 'toggleSpoilerMessage',
                     'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
                     'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
-                    'keypress .chat-textarea': 'keyPressed'
+                    'keypress .chat-textarea': 'keyPressed',
+                    'click .toggle-fileUpload': 'toggleFileUpload',
+                    'change .fileUpload_input': 'handleFileSelect'
+                },
+
+                toggleFileUpload(ev) {
+                    _converse.FileUpload.prototype.initFiletransfer(_converse.connection);
+                    var uploadDialog = this.el.querySelector('.fileUpload_input');
+                    uploadDialog.click();
+                },
+
+                handleFileSelect(evt) {
+                    var files = evt.target.files;
+                    var file = files[0];
+                    var jid = this.jid;
+                    _converse.FileUpload.prototype.setFile(file,this);
                 },
                 },
 
 
                 initialize () {
                 initialize () {
@@ -368,9 +388,11 @@
                         'label_clear': __('Clear all messages'),
                         'label_clear': __('Clear all messages'),
                         'label_insert_smiley': __('Insert a smiley'),
                         'label_insert_smiley': __('Insert a smiley'),
                         'label_start_call': __('Start a call'),
                         'label_start_call': __('Start a call'),
+                        'label_upload_file': __('Upload a File'),
                         'label_toggle_spoiler': label_toggle_spoiler,
                         'label_toggle_spoiler': label_toggle_spoiler,
                         'show_call_button': _converse.visible_toolbar_buttons.call,
                         'show_call_button': _converse.visible_toolbar_buttons.call,
                         'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
                         'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
+                        'show_fileUpload_button': _converse.visible_toolbar_buttons.fileUpload,
                         'use_emoji': _converse.visible_toolbar_buttons.emoji,
                         'use_emoji': _converse.visible_toolbar_buttons.emoji,
                     });
                     });
                 },
                 },
@@ -639,7 +661,14 @@
                     if (attrs.is_spoiler) {
                     if (attrs.is_spoiler) {
                         this.renderSpoilerMessage(msg, attrs)
                         this.renderSpoilerMessage(msg, attrs)
                     }
                     }
-                    u.renderImageURLs(msg_content).then(this.scrollDown.bind(this));
+                    
+                    if(msg_content.textContent.endsWith('mp4')){
+                        msg_content.innerHTML = u.renderMovieURLs(msg_content);
+                    } else if(msg_content.textContent.endsWith('mp3')){
+                        msg_content.innerHTML = u.renderAudioURLs(msg_content); 
+                    } else {
+                        u.renderImageURLs(msg_content).then(this.scrollDown.bind(this));
+                    }
                     return msg;
                     return msg;
                 },
                 },
 
 
@@ -771,6 +800,19 @@
                     return stanza;
                     return stanza;
                 },
                 },
 
 
+                createFileMessageStanza(message){
+                    const stanza = $msg({
+                        'from': _converse.connection.jid,
+                        'to': this.model.get('jid'),
+                        'type': 'chat',
+                        'id': message.get('msgid')
+                    }).c('body').t(message.get('message')).up()
+                      .c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up()
+                      .c('x', {'xmlns': Strophe.NS.OUTOFBAND}).c('url').t(message.get('message')).up();
+
+                    return stanza;
+                },
+
                 sendMessage (message) {
                 sendMessage (message) {
                     /* Responsible for sending off a text message.
                     /* Responsible for sending off a text message.
                      *
                      *
@@ -779,8 +821,7 @@
                      */
                      */
                     // TODO: We might want to send to specfic resources.
                     // TODO: We might want to send to specfic resources.
                     // Especially in the OTR case.
                     // Especially in the OTR case.
-                    const messageStanza = this.createMessageStanza(message);
-                    _converse.connection.send(messageStanza);
+                    _converse.connection.send(message);
                     if (_converse.forward_messages) {
                     if (_converse.forward_messages) {
                         // Forward the message, so that other connected resources are also aware of it.
                         // Forward the message, so that other connected resources are also aware of it.
                         _converse.connection.send(
                         _converse.connection.send(
@@ -790,7 +831,7 @@
                                 'xmns': Strophe.NS.DELAY,
                                 'xmns': Strophe.NS.DELAY,
                                 'stamp': moment().format()
                                 'stamp': moment().format()
                             }).up()
                             }).up()
-                            .cnode(messageStanza.tree())
+                            .cnode(message.tree())
                         );
                         );
                     }
                     }
                 },
                 },
@@ -814,7 +855,7 @@
                     }
                     }
                 },
                 },
 
 
-                onMessageSubmitted (text, spoiler_hint) {
+                onMessageSubmitted (text, spoiler_hint, file = null) {
                     /* This method gets called once the user has typed a message
                     /* This method gets called once the user has typed a message
                      * and then pressed enter in a chat box.
                      * and then pressed enter in a chat box.
                      *
                      *
@@ -833,9 +874,18 @@
                     if (this.parseMessageForCommands(text)) {
                     if (this.parseMessageForCommands(text)) {
                         return;
                         return;
                     }
                     }
-                    const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint)
+                    const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
                     const message = this.model.messages.create(attrs);
                     const message = this.model.messages.create(attrs);
-                    this.sendMessage(message);
+                    
+                    /* check, if a file was send. If true it will send the file with XEP-0066. */
+                    var messageStanza;
+                    if(file !== null){
+                        messageStanza = this.createFileMessageStanza(message);
+                    }
+                    else {
+                        messageStanza = this.createMessageStanza(message);
+                    }
+                    this.sendMessage(messageStanza);
                 },
                 },
 
 
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                 getOutgoingMessageAttributes (text, spoiler_hint) {

+ 1 - 0
src/converse-core.js

@@ -78,6 +78,7 @@
         'converse-dropdown',
         'converse-dropdown',
         'converse-fullscreen',
         'converse-fullscreen',
         'converse-headline',
         'converse-headline',
+        'converse-httpFileUpload',
         'converse-mam',
         'converse-mam',
         'converse-minimize',
         'converse-minimize',
         'converse-modal',
         'converse-modal',

+ 269 - 0
src/converse-httpFileUpload.js

@@ -0,0 +1,269 @@
+/*
+    The MIT License (MIT)
+
+    Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org>
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+    THE SOFTWARE.
+*/
+
+/**
+ * Implements Http File Upload (XEP-0363)
+ *
+ * @see {@link http://xmpp.org/extensions/xep-0363.html}
+ */
+(function (root, factory) {
+    define([
+        "converse-core",
+    ], factory);
+}(this, function (
+        converse
+    ) {
+    "use strict";
+    const { $msg, Backbone, Strophe, _, b64_sha1, moment, utils } = converse.env;
+
+    Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload');
+
+    var requestSlotUrl; 
+    var ready;
+    var httpUploadOption = {
+        enable: true
+    }
+
+    converse.plugins.add('converse-httpFileUpload', {
+
+        dependencies: ["converse-chatboxes", "converse-disco"],
+
+        initialize() {
+            const { _converse } = this,
+                { __ } = _converse;
+            var connection = _converse.connection;
+            var domain;
+            var file;
+            var chatBox;
+            
+            _converse.FileUpload = Backbone.NativeView.extend({
+                /**
+                * Set up http file upload.
+                * 
+                * @param {*} connection the current strophe-connection
+                */
+                initFiletransfer () {
+                    connection = _converse.connection;
+                    domain = _converse.connection.domain; 
+
+                    if (httpUploadOption && requestSlotUrl != undefined) {
+                        ready = true;
+                        return;
+                    }
+                    this.discoverUploadService();
+                },
+                
+                /**
+                * Discover upload service for http upload.
+                *
+                */
+                discoverUploadService () {
+                    var self = this;
+                    console.log('discover http upload service');
+                    connection.disco.items(domain, null, function(items) {
+                        var childs = items.getElementsByTagName('item');
+                        for(var i = 0; i < childs.length; i++){
+                            var jid = childs[i].attributes.jid.value; 
+                            if (ready) {
+                                // abort, because we already found a service
+                                return false;
+                            }
+                            self.queryItemForUploadService(jid);
+                        }
+                    });
+                },
+                
+                /**
+                 * Query item for upload service.
+                 *
+                 * @param {String} jid of the logged-in user
+                 * @param {Function} cb Callback on success
+                */
+                queryItemForUploadService (jid) {
+                    var self = this;
+                    console.log('query ' + jid + ' for upload service');
+                
+                    connection.disco.info(jid, null, function(info) {
+                        var httpUploadFeature;
+                        var temp = info.getElementsByTagName('feature');
+                        for(var i = 0; i < temp.length; i++){
+                            var feature = temp[i].attributes.var;
+                            if(feature != undefined && feature.value === Strophe.NS.HTTPUPLOAD){
+                                requestSlotUrl = jid;
+                                ready = true;
+                                self.sendFile();
+                            }
+                        }
+                    });
+                },
+
+                /**
+                 * Saves the file the user has picked.
+                 * 
+                 * @param {*} file the name of the file the user has picked.
+                 * @param {*} chatBox the chatbox from which the user initiated the file-upload
+                 */
+                setFile (file1, chatBox1){
+                    file = file1;
+                    chatBox = chatBox1;
+                    this.sendFile();
+                },
+                
+                /**
+                * Upload file.
+                * Waits till the Upload-Service is discovered and till the user has picked a file.
+                *
+                */
+                sendFile () {
+                    var self = this;
+                    if(file === undefined){
+                        console.log("waiting to choose a file");
+                        return;
+                    }
+                    else if(requestSlotUrl === undefined){
+                        console.log("waiting for service discovery");
+                        return;
+                    }
+
+                    console.log('Send file via http upload');
+                    chatBox.showHelpMessages([__('The file upload starts now')],'info');
+                    this.requestSlot(file, function(data) {
+                        if (!data) {
+                            // general error
+                            console.log('Unknown error while requesting upload slot.');
+                            alert(__('File upload failed. Please check the log.'));
+                        } else if (data.error) {
+                            // specific error
+                            console.log('The XMPP-Server return an error of the type: ' + data.error.type);
+                            alert(__('File upload failed. Please check the log.'));
+                        } else if (data.get && data.put) {
+                            console.log('slot received, start upload to ' + data.put);
+                            self.uploadFile(data.put, file, function() {
+                                console.log(data.put);
+                                
+                                chatBox.onMessageSubmitted(data.put, null, file);
+                                file = undefined;
+                            });
+                        }
+                    });
+                },
+
+                /**
+                * Request upload slot from xmpp-server
+                *
+                * @param  {File} file the file the user picked
+                * @param  {Function} cb Callback after finished request
+                */
+                requestSlot (file, cb) {
+                    var self = this;
+                    console.log("try sending file to: " + requestSlotUrl);
+                    var iq = converse.env.$iq({
+                        to: requestSlotUrl,
+                        type: 'get'
+                    }).c('request', {
+                        xmlns: Strophe.NS.HTTPUPLOAD
+                    }).c('filename').t(file.name)
+                    .up()
+                    .c('size').t(file.size);
+                
+                    connection.sendIQ(iq, function(stanza) {
+                        self.successfulRequestSlotCB(stanza, cb);
+                    }, function(stanza) {
+                        self.failedRequestSlotCB(stanza, cb);
+                    });
+                },
+                
+                /**
+                 * Upload the given file to the given url.
+                *
+                * @param  {String} url upload url
+                * @param  {File} file the file the user picked
+                * @param  {Function} success_cb callback on successful transition
+                */
+                uploadFile (url, file, success_cb) {
+                    console.log("uploadFile start");
+                    var xmlhttp = new XMLHttpRequest();
+                    var type = 'PUT';
+                    var contentType = 'application/octet-stream';
+                    var data = file;
+                    var processData = false;
+                    xmlhttp.onreadystatechange = function() {
+                        if (xmlhttp.readyState == XMLHttpRequest.DONE) {   
+                            console.log("Status: " + xmlhttp.status);
+                            if (xmlhttp.status == 200 || xmlhttp.status == 201) {
+                                console.log('file successful uploaded');
+                                if (success_cb) {
+                                    success_cb();
+                                }    
+                            }
+                            else {
+                                console.log('error while uploading file to ' + url);
+                                alert(__('Could not upload File please try again.'));
+                            }
+                        }
+                    };
+                
+                    xmlhttp.open(type, url, true);
+                    xmlhttp.setRequestHeader("Content-type", contentType);
+                    xmlhttp.send(data);
+
+                    console.log("uploadFile end");
+                },
+                
+                /**
+                * Process successful response to slot request.
+                *
+                * @param {String} stanza
+                * @param {Function} cb
+                */
+                successfulRequestSlotCB (stanza, cb) {
+                    var slot = stanza.getElementsByTagName('slot')[0];
+                
+                    if (slot != undefined) {
+                        var put = slot.getElementsByTagName('put')[0].textContent;
+                        var get = slot.getElementsByTagName('get')[0].textContent;
+                        cb({
+                            put: put,
+                            get: get
+                        });
+                    } else {
+                        this.failedRequestSlotCB(stanza, cb);
+                    }
+                },
+                
+                /**
+                * Process failed response to slot request.
+                *
+                * @param  {String} stanza
+                * @param  {Function} cb
+                */
+                failedRequestSlotCB (stanza, cb) {
+                    chatBox.showHelpMessages([__('Fileupload failed')],'info');
+                }
+            })
+        }
+    });
+    
+    return converse;
+}));

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

@@ -82,6 +82,8 @@
         'unmoderated': 'moderated'
         'unmoderated': 'moderated'
     };
     };
 
 
+    Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
+
     converse.plugins.add('converse-muc-views', {
     converse.plugins.add('converse-muc-views', {
         /* Dependencies are other plugins which might be
         /* Dependencies are other plugins which might be
          * overridden or relied upon, and therefore need to be loaded before
          * overridden or relied upon, and therefore need to be loaded before
@@ -693,6 +695,17 @@
                         msgid
                         msgid
                     });
                     });
                 },
                 },
+                sendChatRoomFile (text) {
+                    const msgid = _converse.connection.getUniqueId();
+                    const stanza = $msg({
+                        'from': _converse.connection.jid,
+                        'to': this.model.get('jid'),
+                        'type': 'groupchat',
+                        'id': msgid
+                    }).c("body").t(text).up()
+                      .c("x", {'xmlns': Strophe.NS.OUTOFBAND}).c('url').t(text).up();
+                     _converse.connection.send(stanza);
+                },
 
 
                 modifyRole(room, nick, role, reason, onSuccess, onError) {
                 modifyRole(room, nick, role, reason, onSuccess, onError) {
                     const item = $build("item", {nick, role});
                     const item = $build("item", {nick, role});
@@ -732,13 +745,16 @@
                     this.showStatusNotification(__("Error: could not execute the command"), true);
                     this.showStatusNotification(__("Error: could not execute the command"), true);
                 },
                 },
 
 
-                onMessageSubmitted (text) {
+                onMessageSubmitted (text, notNeeded, file = null) {
                     /* Gets called when the user presses enter to send off a
                     /* Gets called when the user presses enter to send off a
                      * message in a chat room.
                      * message in a chat room.
                      *
                      *
                      * Parameters:
                      * Parameters:
                      *    (String) text - The message text.
                      *    (String) text - The message text.
                      */
                      */
+                    if(file !== null){
+                        return this.sendChatRoomFile(text);
+                    }
                     if (_converse.muc_disable_moderator_commands) {
                     if (_converse.muc_disable_moderator_commands) {
                         return this.sendChatRoomMessage(text);
                         return this.sendChatRoomMessage(text);
                     }
                     }

+ 1 - 0
src/converse.js

@@ -24,6 +24,7 @@ if (typeof define !== 'undefined') {
         "converse-minimize",    // Allows chat boxes to be minimized
         "converse-minimize",    // Allows chat boxes to be minimized
         "converse-dragresize",  // Allows chat boxes to be resized by dragging them
         "converse-dragresize",  // Allows chat boxes to be resized by dragging them
         "converse-headline",    // Support for headline messages
         "converse-headline",    // Support for headline messages
+        "converse-httpFileUpload", // Support for XEP-0363
         "converse-fullscreen"
         "converse-fullscreen"
         /* END: Removable components */
         /* END: Removable components */
     ], function (converse) {
     ], function (converse) {

+ 1 - 0
src/inverse.js

@@ -25,6 +25,7 @@ if (typeof define !== 'undefined') {
         "converse-register",    // XEP-0077 In-band registration
         "converse-register",    // XEP-0077 In-band registration
         "converse-roomslist",   // Show currently open chat rooms
         "converse-roomslist",   // Show currently open chat rooms
         "converse-vcard",       // XEP-0054 VCard-temp
         "converse-vcard",       // XEP-0054 VCard-temp
+        "converse-httpFileUpload", // Support for XEP-0363
         /* END: Removable components */
         /* END: Removable components */
 
 
         "converse-inverse",     // Inverse plugin for converse.js
         "converse-inverse",     // Inverse plugin for converse.js

+ 6 - 0
src/templates/chatroom_toolbar.html

@@ -4,6 +4,12 @@
     <div class="emoji-picker dropdown-menu toolbar-menu"></div>
     <div class="emoji-picker dropdown-menu toolbar-menu"></div>
 </li>
 </li>
 {[ } ]}
 {[ } ]}
+{[ if (o.show_fileUpload_button)  { ]}
+<input type="file" class="fileUpload_input" style="display:none"/>
+<li class="toggle-fileUpload">
+    <a class="fa fa-paperclip" title="{{{o.label_upload_file}}}"></a>
+</li>
+{[ } ]}
 {[ if (o.show_call_button)  { ]}
 {[ if (o.show_call_button)  { ]}
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 {[ } ]}
 {[ } ]}

+ 6 - 0
src/templates/toolbar.html

@@ -3,6 +3,12 @@
     <div class="emoji-picker dropdown-menu toolbar-menu"></div>
     <div class="emoji-picker dropdown-menu toolbar-menu"></div>
 </li>
 </li>
 {[ } ]}
 {[ } ]}
+{[ if (o.show_fileUpload_button)  { ]}
+<input type="file" class="fileUpload_input" style="display:none"/>
+<li class="toggle-fileUpload">
+    <a class="fa fa-paperclip" title="{{{o.label_upload_file}}}"></a>
+</li>
+{[ } ]}
 {[ if (o.show_call_button)  { ]}
 {[ if (o.show_call_button)  { ]}
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 {[ } ]}
 {[ } ]}

+ 8 - 0
src/utils/core.js

@@ -213,6 +213,14 @@
             ))
             ))
     };
     };
 
 
+    u.renderMovieURLs = function (obj) {
+        return "<video controls><source src=\"" + obj.textContent + "\" type=\"video/mp4\"></video>";
+    };
+
+    u.renderAudioURLs = function (obj) {
+        return "<audio controls><source src=\"" + obj.textContent + "\" type=\"audio/mpeg\"></audio>";
+    };
+
     u.slideInAllElements = function (elements, duration=300) {
     u.slideInAllElements = function (elements, duration=300) {
         return Promise.all(
         return Promise.all(
             _.map(
             _.map(

+ 1 - 0
tests/runner-transpiled.js

@@ -24,6 +24,7 @@ config.paths["converse-core"] =         "builds/converse-core";
 config.paths["converse-disco"] =        "builds/converse-disco";
 config.paths["converse-disco"] =        "builds/converse-disco";
 config.paths["converse-dragresize"] =   "builds/converse-dragresize";
 config.paths["converse-dragresize"] =   "builds/converse-dragresize";
 config.paths["converse-headline"] =     "builds/converse-headline";
 config.paths["converse-headline"] =     "builds/converse-headline";
+config.paths["converse-httpFileUpload"]="builds/converse-httpFileUpload";
 config.paths["converse-fullscreen"] =   "builds/converse-fullscreen";
 config.paths["converse-fullscreen"] =   "builds/converse-fullscreen";
 config.paths["converse-mam"] =          "builds/converse-mam";
 config.paths["converse-mam"] =          "builds/converse-mam";
 config.paths["converse-minimize"] =     "builds/converse-minimize";
 config.paths["converse-minimize"] =     "builds/converse-minimize";