Commit 9c2a5bd3 authored by JC Brand's avatar JC Brand

Merge branch 'worlword-httpFileUpload' into http-file-upload

parents 042c1947 d278a027
# Changelog # Changelog
## Http-File-Upload
## New Features
- files can now be sent via http-file-upload (XEP-0363)
- mp4 and mp3 files will now be playable directly in chat.
## 4.0.0 (Unreleased) ## 4.0.0 (Unreleased)
## UI changes ## UI changes
......
...@@ -145,6 +145,10 @@ msgstr "tippt nicht mehr" ...@@ -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 "Choose a file to send"
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"
......
...@@ -416,7 +416,7 @@ ...@@ -416,7 +416,7 @@
expect(view).toBeDefined(); expect(view).toBeDefined();
var $toolbar = $(view.el).find('ul.chat-toolbar'); var $toolbar = $(view.el).find('ul.chat-toolbar');
expect($toolbar.length).toBe(1); expect($toolbar.length).toBe(1);
expect($toolbar.children('li').length).toBe(2); expect($toolbar.children('li').length).toBe(3);
done(); done();
})); }));
......
...@@ -13,6 +13,7 @@ ...@@ -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-http-file-upload":"builds/converse-http-file-upload",
"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",
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
}(this, function (converse, tpl_chatboxes) { }(this, function (converse, tpl_chatboxes) {
"use strict"; "use strict";
const { Backbone, Promise, Strophe, b64_sha1, moment, utils, _ } = converse.env; const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, utils, _ } = converse.env;
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
converse.plugins.add('converse-chatboxes', { converse.plugins.add('converse-chatboxes', {
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
/* The initialize function gets called as soon as the plugin is /* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery. * loaded by converse.js's plugin machinery.
*/ */
const { _converse } = this; const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add([ _converse.api.promises.add([
'chatBoxesFetched', 'chatBoxesFetched',
...@@ -115,6 +116,101 @@ ...@@ -115,6 +116,101 @@
}); });
}, },
createFileMessageStanza (message, to) {
const stanza = $msg({
'from': _converse.connection.jid,
'to': to,
'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;
},
sendFile (file, chatbox) {
const self = this;
const request_slot_url = 'upload.' + _converse.domain;
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, request_slot_url)
.then((result) => {
chatbox.showHelpMessages([__('The file upload starts now')],'info');
self.requestSlot(file, request_slot_url, function(data) {
if (!data) {
alert(__('File upload failed. Please check the log.'));
} else if (data.error) {
alert(__('File upload failed. Please check the log.'));
} else if (data.get && data.put) {
self.uploadFile(data.put, file, function() {
console.log(data.put);
chatbox.onMessageSubmitted(data.put, null, file);
});
}
});
});
},
requestSlot (file, request_slot_url, cb) {
const self = this;
console.log("try sending file to: " + request_slot_url);
const iq = converse.env.$iq({
to: request_slot_url,
type: 'get'
}).c('request', {
xmlns: Strophe.NS.HTTPUPLOAD
}).c('filename').t(file.name)
.up()
.c('size').t(file.size);
_converse.connection.sendIQ(iq, function(stanza) {
self.successfulRequestSlotCB(stanza, cb);
}, function(stanza) {
self.failedRequestSlotCB(stanza, cb);
});
},
uploadFile (url, file, callback) {
console.log("uploadFile start");
const xmlhttp = new XMLHttpRequest();
const contentType = 'application/octet-stream';
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === XMLHttpRequest.DONE) {
console.log("Status: " + xmlhttp.status);
if (xmlhttp.status === 200 || xmlhttp.status === 201) {
if (callback) {
callback();
}
}
else {
alert(__('Could not upload File please try again.'));
}
}
};
xmlhttp.open('PUT', url, true);
xmlhttp.setRequestHeader("Content-type", contentType);
xmlhttp.send(file);
},
successfulRequestSlotCB (stanza, cb) {
const 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);
}
},
failedRequestSlotCB (stanza, cb) {
alert(__('Could not upload File please try again.'));
},
getMessageBody (message) { getMessageBody (message) {
const type = message.getAttribute('type'); const type = message.getAttribute('type');
return (type === 'error') ? return (type === 'error') ?
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"tpl!spoiler_message", "tpl!spoiler_message",
"tpl!status_message", "tpl!status_message",
"tpl!toolbar", "tpl!toolbar",
"converse-http-file-upload",
"converse-chatboxes" "converse-chatboxes"
], factory); ], factory);
}(this, function ( }(this, function (
...@@ -50,7 +51,8 @@ ...@@ -50,7 +51,8 @@
tpl_spoiler_button, tpl_spoiler_button,
tpl_spoiler_message, tpl_spoiler_message,
tpl_status_message, tpl_status_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;
...@@ -237,7 +239,6 @@ ...@@ -237,7 +239,6 @@
} }
}); });
_converse.ChatBoxView = Backbone.NativeView.extend({ _converse.ChatBoxView = Backbone.NativeView.extend({
length: 200, length: 200,
className: 'chatbox hidden', className: 'chatbox hidden',
...@@ -648,7 +649,14 @@ ...@@ -648,7 +649,14 @@
if (attrs.is_spoiler) { if (attrs.is_spoiler) {
this.renderSpoilerMessage(msg, attrs) this.renderSpoilerMessage(msg, attrs)
} }
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)); u.renderImageURLs(msg_content).then(this.scrollDown.bind(this));
}
return msg; return msg;
}, },
...@@ -807,7 +815,7 @@ ...@@ -807,7 +815,7 @@
return stanza; return stanza;
}, },
sendMessage (message) { sendMessage (message, file = null) {
/* Responsible for sending off a text message. /* Responsible for sending off a text message.
* *
* Parameters: * Parameters:
...@@ -815,7 +823,13 @@ ...@@ -815,7 +823,13 @@
*/ */
// 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); var messageStanza;
if (file !== null) {
messageStanza = this.model.createFileMessageStanza(message, this.model.get('jid'));
}
else {
messageStanza = this.createMessageStanza(message);
}
_converse.connection.send(messageStanza); _converse.connection.send(messageStanza);
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.
...@@ -850,7 +864,7 @@ ...@@ -850,7 +864,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.
* *
...@@ -869,9 +883,9 @@ ...@@ -869,9 +883,9 @@
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); this.sendMessage(message, file);
}, },
getOutgoingMessageAttributes (text, spoiler_hint) { getOutgoingMessageAttributes (text, spoiler_hint) {
...@@ -1228,13 +1242,11 @@ ...@@ -1228,13 +1242,11 @@
} }
}); });
_converse.on('connected', () => { _converse.on('connected', () => {
// Advertise that we support XEP-0382 Message Spoilers // Advertise that we support XEP-0382 Message Spoilers
_converse.connection.disco.addFeature(Strophe.NS.SPOILER); _converse.connection.disco.addFeature(Strophe.NS.SPOILER);
}); });
/************************ BEGIN API ************************/ /************************ BEGIN API ************************/
_.extend(_converse.api, { _.extend(_converse.api, {
'chatviews': { 'chatviews': {
......
/**
* Adds Support for Http File Upload (XEP-0363)
*
*/
(function (root, factory) { (function (root, factory) {
define([ define([
"converse-core", "converse-core",
...@@ -22,11 +26,15 @@ ...@@ -22,11 +26,15 @@
* *
* NB: These plugins need to have already been loaded via require.js. * NB: These plugins need to have already been loaded via require.js.
*/ */
dependencies: ["converse-chatview"], dependencies: ["converse-chatboxes", "converse-chatview", "converse-muc-views"],
overrides: { overrides: {
ChatBoxView: { ChatBoxView: {
events: {
'click .upload-file': 'toggleFileUpload',
'change input.fileupload': 'handleFileSelect'
},
addFileUploadButton (options) { addFileUploadButton (options) {
const { __ } = this.__super__._converse; const { __ } = this.__super__._converse;
this.el.querySelector('.chat-toolbar').insertAdjacentHTML( this.el.querySelector('.chat-toolbar').insertAdjacentHTML(
...@@ -44,15 +52,26 @@ ...@@ -44,15 +52,26 @@
} }
}); });
return result; return result;
} },
toggleFileUpload (ev) {
this.el.querySelector('.input.fileupload').click();
},
handleFileSelect (evt) {
var files = evt.target.files;
var file = files[0];
this.model.sendFile(file, this);
} }
}, },
initialize () { ChatRoomView: {
/* The initialize function gets called as soon as the plugin is events: {
* loaded by converse.js's plugin machinery. 'click .upload-file': 'toggleFileUpload',
*/ 'change .input.fileupload': 'handleFileSelect'
const { _converse } = this; }
}
} }
}); });
return converse;
})); }));
...@@ -826,13 +826,18 @@ ...@@ -826,13 +826,18 @@
this.showErrorMessage(__("Error: could not execute the command"), true); this.showErrorMessage(__("Error: could not execute the command"), true);
}, },
onMessageSubmitted (text) { // the notNeeded-Parameter is there so this method has the same amount of parameters as converse-chatview.js->onMessageSubmitted
// this allows to call the same method from diffrent plugins
onMessageSubmitted (text, notNeeded = null, 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.model.sendChatRoomFile(text,this.model.get('jid'));
}
if (_converse.muc_disable_moderator_commands) { if (_converse.muc_disable_moderator_commands) {
return this.sendChatRoomMessage(text); return this.sendChatRoomMessage(text);
} }
......
...@@ -336,6 +336,18 @@ ...@@ -336,6 +336,18 @@
return jid + (nick !== null ? `/${nick}` : ""); return jid + (nick !== null ? `/${nick}` : "");
}, },
sendChatRoomFile (text, to) {
const msgid = _converse.connection.getUniqueId();
const stanza = $msg({
'from': _converse.connection.jid,
'to': to,
'type': 'groupchat',
'id': msgid
}).c("body").t(text).up()
.c("x", {'xmlns': Strophe.NS.OUTOFBAND}).c('url').t(text).up();
_converse.connection.send(stanza);
},
directInvite (recipient, reason) { directInvite (recipient, reason) {
/* Send a direct invitation as per XEP-0249 /* Send a direct invitation as per XEP-0249
* *
......
...@@ -13,7 +13,7 @@ if (typeof define !== 'undefined') { ...@@ -13,7 +13,7 @@ if (typeof define !== 'undefined') {
"converse-dragresize", // Allows chat boxes to be resized by dragging them "converse-dragresize", // Allows chat boxes to be resized by dragging them
"converse-fullscreen", "converse-fullscreen",
"converse-headline", // Support for headline messages "converse-headline", // Support for headline messages
"converse-http-file-upload", "converse-http-file-upload",// Support for XEP-0363
"converse-mam", // XEP-0313 Message Archive Management "converse-mam", // XEP-0313 Message Archive Management
"converse-minimize", // Allows chat boxes to be minimized "converse-minimize", // Allows chat boxes to be minimized
"converse-muc", // XEP-0045 Multi-user chat "converse-muc", // XEP-0045 Multi-user chat
......
...@@ -25,6 +25,7 @@ if (typeof define !== 'undefined') { ...@@ -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-http-file-upload", // Support for XEP-0363
/* END: Removable components */ /* END: Removable components */
"converse-inverse", // Inverse plugin for converse.js "converse-inverse", // Inverse plugin for converse.js
......
<li class="upload-file fa fa-paperclip" title="{{{o.tooltip_upload_file}}}"></li> <input type="file" class="fileupload" style="display:none"/>
<li class="upload-file">
<a class="fa fa-paperclip" title="{{{o.tooltip_upload_file}}}"></a>
</li>
...@@ -213,6 +213,14 @@ ...@@ -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(
......
...@@ -24,6 +24,7 @@ config.paths["converse-core"] = "builds/converse-core"; ...@@ -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-http-file-upload"]="builds/converse-http-file-upload";
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";
......
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