Commit 1ef29bee authored by JC Brand's avatar JC Brand

Use composition instead of overrides

parent d2b1f2c9
...@@ -189,14 +189,13 @@ The code for it would look something like this: ...@@ -189,14 +189,13 @@ The code for it would look something like this:
// Commonly used utilities and variables can be found under the "env" // Commonly used utilities and variables can be found under the "env"
// namespace of the "converse" global. // namespace of the "converse" global.
var Strophe = converse.env.Strophe, const Strophe = converse.env.Strophe,
$iq = converse.env.$iq, $iq = converse.env.$iq,
$msg = converse.env.$msg, $msg = converse.env.$msg,
$pres = converse.env.$pres, $pres = converse.env.$pres,
$build = converse.env.$build, $build = converse.env.$build,
b64_sha1 = converse.env.b64_sha1, _ = converse.env._,
_ = converse.env._, dayjs = converse.env.dayjs;
dayjs = converse.env.dayjs;
These dependencies are closured so that they don't pollute the global These dependencies are closured so that they don't pollute the global
namespace, that's why you need to access them in such a way inside the module. namespace, that's why you need to access them in such a way inside the module.
...@@ -300,7 +299,7 @@ In this case, you should first listen for the ``connection`` event, and then do ...@@ -300,7 +299,7 @@ In this case, you should first listen for the ``connection`` event, and then do
converse.plugins.add('myplugin', { converse.plugins.add('myplugin', {
initialize: function () { initialize: function () {
var _converse = this._converse; const _converse = this._converse;
_converse.api.listen.on('connected', function () { _converse.api.listen.on('connected', function () {
_converse.api.archive.query({'with': 'admin2@localhost'}); _converse.api.archive.query({'with': 'admin2@localhost'});
...@@ -363,152 +362,141 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers ...@@ -363,152 +362,141 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers
.. code-block:: javascript .. code-block:: javascript
(function (root, factory) { import converse from "@converse/headless/converse-core";
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module called "myplugin" // Commonly used utilities and variables can be found under the "env"
define(["converse"], factory); // namespace of the "converse" global.
} else { const Strophe = converse.env.Strophe,
// Browser globals. If you're not using a module loader such as require.js, $iq = converse.env.$iq,
// then this line below executes. Make sure that your plugin's <script> tag $msg = converse.env.$msg,
// appears after the one from converse.js. $pres = converse.env.$pres,
factory(converse); $build = converse.env.$build,
} _ = converse.env._,
}(this, function (converse) { dayjs = converse.env.dayjs;
// The following line registers your plugin.
converse.plugins.add("myplugin", {
/* Dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* NB: These plugins need to have already been imported or loaded,
* either in your plugin or somewhere else.
*
* It's possible to make these dependencies "non-optional".
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*/
'dependencies': [],
/* Converse.js's plugin mechanism will call the initialize
* method on any plugin (if it exists) as soon as the plugin has
* been loaded.
*/
'initialize': function () {
/* Inside this method, you have access to the private
* `_converse` object.
*/
const _converse = this._converse;
_converse.log("The \"myplugin\" plugin is being initialized");
/* From the `_converse` object you can get any configuration
* options that the user might have passed in via
* `converse.initialize`.
*
* You can also specify new configuration settings for this
* plugin, or override the default values of existing
* configuration settings. This is done like so:
*/
_converse.api.settings.update({
'initialize_message': 'Initializing myplugin!'
});
/* The user can then pass in values for the configuration
* settings when `converse.initialize` gets called.
* For example:
*
* converse.initialize({
* "initialize_message": "My plugin has been initialized"
* });
*/
alert(this._converse.initialize_message);
// Commonly used utilities and variables can be found under the "env" /* Besides `_converse.api.settings.update`, there is also a
// namespace of the "converse" global. * `_converse.api.promises.add` method, which allows you to
var Strophe = converse.env.Strophe, * add new promises that your plugin is obligated to fulfill.
$iq = converse.env.$iq, *
$msg = converse.env.$msg, * This method takes a string or a list of strings which
$pres = converse.env.$pres, * represent the promise names:
$build = converse.env.$build, *
b64_sha1 = converse.env.b64_sha1, * _converse.api.promises.add('myPromise');
_ = converse.env._, *
dayjs = converse.env.dayjs; * Your plugin should then, when appropriate, resolve the
* promise by calling `_converse.api.emit`, which will also
// The following line registers your plugin. * emit an event with the same name as the promise.
converse.plugins.add("myplugin", { * For example:
/* Dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
* *
* NB: These plugins need to have already been loaded via require.js. * _converse.api.trigger('operationCompleted');
* *
* It's possible to make these dependencies "non-optional". * Other plugins can then either listen for the event
* If the setting "strict_plugin_dependencies" is set to true, * `operationCompleted` like so:
* an error will be raised if the plugin is not found. *
* _converse.api.listen.on('operationCompleted', function { ... });
*
* or they can wait for the promise to be fulfilled like so:
*
* _converse.api.waitUntil('operationCompleted', function { ... });
*/ */
'dependencies': [], },
/* Converse.js's plugin mechanism will call the initialize /* If you want to override some function or a Backbone model or
* method on any plugin (if it exists) as soon as the plugin has * view defined elsewhere in converse.js, then you do that under
* been loaded. * the "overrides" namespace.
*/
'overrides': {
/* For example, the private *_converse* object has a
* method "onConnected". You can override that method as follows:
*/ */
'initialize': function () { 'onConnected': function () {
/* Inside this method, you have access to the private // Overrides the onConnected method in converse.js
* `_converse` object.
*/ // Top-level functions in "overrides" are bound to the
var _converse = this._converse; // inner "_converse" object.
_converse.log("The \"myplugin\" plugin is being initialized"); const _converse = this;
/* From the `_converse` object you can get any configuration // Your custom code can come here ...
* options that the user might have passed in via
* `converse.initialize`. // You can access the original function being overridden
* // via the __super__ attribute.
* You can also specify new configuration settings for this // Make sure to pass on the arguments supplied to this
* plugin, or override the default values of existing // function and also to apply the proper "this" object.
* configuration settings. This is done like so: _converse.__super__.onConnected.apply(this, arguments);
*/
_converse.api.settings.update({ // Your custom code can come here ...
'initialize_message': 'Initializing myplugin!'
});
/* The user can then pass in values for the configuration
* settings when `converse.initialize` gets called.
* For example:
*
* converse.initialize({
* "initialize_message": "My plugin has been initialized"
* });
*/
alert(this._converse.initialize_message);
/* Besides `_converse.api.settings.update`, there is also a
* `_converse.api.promises.add` method, which allows you to
* add new promises that your plugin is obligated to fulfill.
*
* This method takes a string or a list of strings which
* represent the promise names:
*
* _converse.api.promises.add('myPromise');
*
* Your plugin should then, when appropriate, resolve the
* promise by calling `_converse.api.emit`, which will also
* emit an event with the same name as the promise.
* For example:
*
* _converse.api.trigger('operationCompleted');
*
* Other plugins can then either listen for the event
* `operationCompleted` like so:
*
* _converse.api.listen.on('operationCompleted', function { ... });
*
* or they can wait for the promise to be fulfilled like so:
*
* _converse.api.waitUntil('operationCompleted', function { ... });
*/
}, },
/* If you want to override some function or a Backbone model or /* Override converse.js's XMPPStatus Backbone model so that we can override the
* view defined elsewhere in converse.js, then you do that under * function that sends out the presence stanza.
* the "overrides" namespace.
*/ */
'overrides': { 'XMPPStatus': {
/* For example, the private *_converse* object has a 'sendPresence': function (type, status_message, jid) {
* method "onConnected". You can override that method as follows: // The "_converse" object is available via the __super__
*/ // attribute.
'onConnected': function () { const _converse = this.__super__._converse;
// Overrides the onConnected method in converse.js
// Custom code can come here ...
// Top-level functions in "overrides" are bound to the
// inner "_converse" object. // You can call the original overridden method, by
var _converse = this; // accessing it via the __super__ attribute.
// When calling it, you need to apply the proper
// Your custom code can come here ... // context as reference by the "this" variable.
this.__super__.sendPresence.apply(this, arguments);
// You can access the original function being overridden
// via the __super__ attribute. // Custom code can come here ...
// Make sure to pass on the arguments supplied to this
// function and also to apply the proper "this" object.
_converse.__super__.onConnected.apply(this, arguments);
// Your custom code can come here ...
},
/* Override converse.js's XMPPStatus Backbone model so that we can override the
* function that sends out the presence stanza.
*/
'XMPPStatus': {
'sendPresence': function (type, status_message, jid) {
// The "_converse" object is available via the __super__
// attribute.
var _converse = this.__super__._converse;
// Custom code can come here ...
// You can call the original overridden method, by
// accessing it via the __super__ attribute.
// When calling it, you need to apply the proper
// context as reference by the "this" variable.
this.__super__.sendPresence.apply(this, arguments);
// Custom code can come here ...
}
} }
} }
}); }
})); });
...@@ -42,20 +42,67 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -42,20 +42,67 @@ converse.plugins.add('converse-bookmark-views', {
// Overrides mentioned here will be picked up by converse.js's // Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the // plugin architecture they will replace existing methods on the
// relevant objects or classes. // relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatRoomView: { ChatRoomView: {
events: { events: {
'click .toggle-bookmark': 'toggleBookmark' 'click .toggle-bookmark': 'toggleBookmark'
}, },
async renderHeading () {
this.__super__.renderHeading.apply(this, arguments);
const { _converse } = this.__super__;
if (_converse.allow_bookmarks) {
const supported = await _converse.checkBookmarksSupport();
if (supported) {
this.renderBookmarkToggle();
}
}
}
}
},
initialize () { initialize () {
this.__super__.initialize.apply(this, arguments); /* The initialize function gets called as soon as the plugin is
this.model.on('change:bookmarked', this.onBookmarked, this); * loaded by converse.js's plugin machinery.
this.setBookmarkState(); */
const { _converse } = this,
{ __ } = _converse;
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
_converse.api.settings.update({
hide_open_bookmarks: true,
muc_respect_autojoin: true
});
Object.assign(_converse, {
removeBookmarkViaEvent (ev) {
/* Remove a bookmark as determined by the passed in
* event.
*/
ev.preventDefault();
const name = ev.target.getAttribute('data-bookmark-name');
const jid = ev.target.getAttribute('data-room-jid');
if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) {
_.invokeMap(_converse.bookmarks.where({'jid': jid}), Backbone.Model.prototype.destroy);
}
}, },
addBookmarkViaEvent (ev) {
/* Add a bookmark as determined by the passed in
* event.
*/
ev.preventDefault();
const jid = ev.target.getAttribute('data-room-jid');
const chatroom = _converse.api.rooms.open(jid, {'bring_to_foreground': true});
_converse.chatboxviews.get(jid).renderBookmarkForm();
},
});
const bookmarkableChatRoomView = {
renderBookmarkToggle () { renderBookmarkToggle () {
if (this.el.querySelector('.chat-head .toggle-bookmark')) { if (this.el.querySelector('.chat-head .toggle-bookmark')) {
return; return;
...@@ -80,21 +127,7 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -80,21 +127,7 @@ converse.plugins.add('converse-bookmark-views', {
} }
}, },
async renderHeading () {
this.__super__.renderHeading.apply(this, arguments);
const { _converse } = this.__super__;
if (_converse.allow_bookmarks) {
const supported = await _converse.checkBookmarksSupport();
if (supported) {
this.renderBookmarkToggle();
}
}
},
onBookmarked () { onBookmarked () {
const { _converse } = this.__super__,
{ __ } = _converse;
const icon = this.el.querySelector('.toggle-bookmark'); const icon = this.el.querySelector('.toggle-bookmark');
if (_.isNull(icon)) { if (_.isNull(icon)) {
return; return;
...@@ -111,7 +144,6 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -111,7 +144,6 @@ converse.plugins.add('converse-bookmark-views', {
setBookmarkState () { setBookmarkState () {
/* Set whether the groupchat is bookmarked or not. /* Set whether the groupchat is bookmarked or not.
*/ */
const { _converse } = this.__super__;
if (!_.isUndefined(_converse.bookmarks)) { if (!_.isUndefined(_converse.bookmarks)) {
const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); const models = _converse.bookmarks.where({'jid': this.model.get('jid')});
if (!models.length) { if (!models.length) {
...@@ -125,7 +157,6 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -125,7 +157,6 @@ converse.plugins.add('converse-bookmark-views', {
renderBookmarkForm () { renderBookmarkForm () {
this.hideChatRoomContents(); this.hideChatRoomContents();
if (!this.bookmark_form) { if (!this.bookmark_form) {
const { _converse } = this.__super__;
this.bookmark_form = new _converse.MUCBookmarkForm({ this.bookmark_form = new _converse.MUCBookmarkForm({
'model': this.model, 'model': this.model,
'chatroomview': this 'chatroomview': this
...@@ -141,7 +172,6 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -141,7 +172,6 @@ converse.plugins.add('converse-bookmark-views', {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
} }
const { _converse } = this.__super__;
const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); const models = _converse.bookmarks.where({'jid': this.model.get('jid')});
if (!models.length) { if (!models.length) {
this.renderBookmarkForm(); this.renderBookmarkForm();
...@@ -151,48 +181,7 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -151,48 +181,7 @@ converse.plugins.add('converse-bookmark-views', {
} }
} }
} }
}, Object.assign(_converse.ChatRoomView.prototype, bookmarkableChatRoomView);
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
_converse.api.settings.update({
hide_open_bookmarks: true,
muc_respect_autojoin: true
});
Object.assign(_converse, {
removeBookmarkViaEvent (ev) {
/* Remove a bookmark as determined by the passed in
* event.
*/
ev.preventDefault();
const name = ev.target.getAttribute('data-bookmark-name');
const jid = ev.target.getAttribute('data-room-jid');
if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) {
_.invokeMap(_converse.bookmarks.where({'jid': jid}), Backbone.Model.prototype.destroy);
}
},
addBookmarkViaEvent (ev) {
/* Add a bookmark as determined by the passed in
* event.
*/
ev.preventDefault();
const jid = ev.target.getAttribute('data-room-jid');
const chatroom = _converse.api.rooms.open(jid, {'bring_to_foreground': true});
_converse.chatboxviews.get(jid).renderBookmarkForm();
},
});
_converse.MUCBookmarkForm = Backbone.VDOMView.extend({ _converse.MUCBookmarkForm = Backbone.VDOMView.extend({
...@@ -368,6 +357,7 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -368,6 +357,7 @@ converse.plugins.add('converse-bookmark-views', {
} }
}); });
/************************ BEGIN Event Handlers ************************/
const initBookmarkViews = async function () { const initBookmarkViews = async function () {
await _converse.api.waitUntil('roomsPanelRendered'); await _converse.api.waitUntil('roomsPanelRendered');
_converse.bookmarksview = new _converse.BookmarksView({'model': _converse.bookmarks}); _converse.bookmarksview = new _converse.BookmarksView({'model': _converse.bookmarks});
...@@ -381,5 +371,11 @@ converse.plugins.add('converse-bookmark-views', { ...@@ -381,5 +371,11 @@ converse.plugins.add('converse-bookmark-views', {
} }
_converse.api.listen.on('bookmarksInitialized', initBookmarkViews); _converse.api.listen.on('bookmarksInitialized', initBookmarkViews);
_converse.api.listen.on('chatRoomOpened', view => {
view.model.on('change:bookmarked', view.onBookmarked, view);
view.setBookmarkState();
});
/************************ END Event Handlers ************************/
} }
}); });
...@@ -105,23 +105,6 @@ converse.plugins.add('converse-controlbox', { ...@@ -105,23 +105,6 @@ converse.plugins.add('converse-controlbox', {
view.close(); view.close();
}); });
return this; return this;
},
getChatBoxWidth (view) {
const { _converse } = this.__super__;
const controlbox = this.get('controlbox');
if (view.model.get('id') === 'controlbox') {
/* We return the width of the controlbox or its toggle,
* depending on which is visible.
*/
if (!controlbox || !u.isVisible(controlbox.el)) {
return u.getOuterWidth(_converse.controlboxtoggle.el, true);
} else {
return u.getOuterWidth(controlbox.el, true);
}
} else {
return this.__super__.getChatBoxWidth.apply(this, arguments);
}
} }
}, },
...@@ -232,11 +215,12 @@ converse.plugins.add('converse-controlbox', { ...@@ -232,11 +215,12 @@ converse.plugins.add('converse-controlbox', {
* Triggered when the _converse.ControlBoxView has been initialized and therefore * Triggered when the _converse.ControlBoxView has been initialized and therefore
* exists. The controlbox contains the login and register forms when the user is * exists. The controlbox contains the login and register forms when the user is
* logged out and a list of the user's contacts and group chats when logged in. * logged out and a list of the user's contacts and group chats when logged in.
* @event _converse#chatBoxInitialized * @event _converse#controlboxInitialized
* @type { _converse.ControlBoxView } * @type { _converse.ControlBoxView }
* @example _converse.api.listen.on('controlboxInitialized', view => { ... }); * @example _converse.api.listen.on('controlboxInitialized', view => { ... });
*/ */
_converse.api.trigger('controlboxInitialized', this); _converse.api.trigger('controlboxInitialized', this);
_converse.api.trigger('chatBoxInitialized', this);
}, },
render () { render () {
......
...@@ -45,42 +45,6 @@ converse.plugins.add('converse-dragresize', { ...@@ -45,42 +45,6 @@ converse.plugins.add('converse-dragresize', {
// Overrides mentioned here will be picked up by converse.js's // Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the // plugin architecture they will replace existing methods on the
// relevant objects or classes. // relevant objects or classes.
//
// New functions which don't exist yet can also be added.
registerGlobalEventHandlers () {
const that = this;
document.addEventListener('mousemove', function (ev) {
if (!that.resizing || !that.allow_dragresize) { return true; }
ev.preventDefault();
that.resizing.chatbox.resizeChatBox(ev);
});
document.addEventListener('mouseup', function (ev) {
if (!that.resizing || !that.allow_dragresize) { return true; }
ev.preventDefault();
const height = that.applyDragResistance(
that.resizing.chatbox.height,
that.resizing.chatbox.model.get('default_height')
);
const width = that.applyDragResistance(
that.resizing.chatbox.width,
that.resizing.chatbox.model.get('default_width')
);
if (that.connection.connected) {
that.resizing.chatbox.model.save({'height': height});
that.resizing.chatbox.model.save({'width': width});
} else {
that.resizing.chatbox.model.set({'height': height});
that.resizing.chatbox.model.set({'width': width});
}
that.resizing = null;
});
return this.__super__.registerGlobalEventHandlers.apply(this, arguments);
},
ChatBox: { ChatBox: {
initialize () { initialize () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
...@@ -102,9 +66,24 @@ converse.plugins.add('converse-dragresize', { ...@@ -102,9 +66,24 @@ converse.plugins.add('converse-dragresize', {
'mousedown .dragresize-topleft': 'onStartDiagonalResize' 'mousedown .dragresize-topleft': 'onStartDiagonalResize'
}, },
initialize () { render () {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); const result = this.__super__.render.apply(this, arguments);
this.__super__.initialize.apply(this, arguments); renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
},
_show () {
this.initDragResize().setDimensions();
this.__super__._show.apply(this, arguments);
}
},
HeadlinesBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
}, },
render () { render () {
...@@ -112,27 +91,70 @@ converse.plugins.add('converse-dragresize', { ...@@ -112,27 +91,70 @@ converse.plugins.add('converse-dragresize', {
renderDragResizeHandles(this.__super__._converse, this); renderDragResizeHandles(this.__super__._converse, this);
this.setWidth(); this.setWidth();
return result; return result;
}
},
ControlBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
}, },
setWidth () { render () {
// If a custom width is applied (due to drag-resizing), const result = this.__super__.render.apply(this, arguments);
// then we need to set the width of the .chatbox element as well. renderDragResizeHandles(this.__super__._converse, this);
if (this.model.get('width')) { this.setWidth();
this.el.style.width = this.model.get('width'); return result;
}
}, },
_show () { renderLoginPanel () {
const result = this.__super__.renderLoginPanel.apply(this, arguments);
this.initDragResize().setDimensions(); this.initDragResize().setDimensions();
this.__super__._show.apply(this, arguments); return result;
},
renderControlBoxPane () {
const result = this.__super__.renderControlBoxPane.apply(this, arguments);
this.initDragResize().setDimensions();
return result;
}
},
ChatRoomView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
}, },
render () {
const result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
}
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this;
_converse.api.settings.update({
'allow_dragresize': true,
});
const dragResizable = {
initDragResize () { initDragResize () {
/* Determine and store the default box size. /* Determine and store the default box size.
* We need this information for the drag-resizing feature. * We need this information for the drag-resizing feature.
*/ */
const { _converse } = this.__super__, const flyout = this.el.querySelector('.box-flyout'),
flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout); style = window.getComputedStyle(flyout);
if (_.isUndefined(this.model.get('height'))) { if (_.isUndefined(this.model.get('height'))) {
...@@ -157,6 +179,34 @@ converse.plugins.add('converse-dragresize', { ...@@ -157,6 +179,34 @@ converse.plugins.add('converse-dragresize', {
return this; return this;
}, },
resizeChatBox (ev) {
let diff;
if (_converse.resizing.direction.indexOf('top') === 0) {
diff = ev.pageY - this.prev_pageY;
if (diff) {
this.height = ((this.height-diff) > (this.model.get('min_height') || 0)) ? (this.height-diff) : this.model.get('min_height');
this.prev_pageY = ev.pageY;
this.setChatBoxHeight(this.height);
}
}
if (_.includes(_converse.resizing.direction, 'left')) {
diff = this.prev_pageX - ev.pageX;
if (diff) {
this.width = ((this.width+diff) > (this.model.get('min_width') || 0)) ? (this.width+diff) : this.model.get('min_width');
this.prev_pageX = ev.pageX;
this.setChatBoxWidth(this.width);
}
}
},
setWidth () {
// If a custom width is applied (due to drag-resizing),
// then we need to set the width of the .chatbox element as well.
if (this.model.get('width')) {
this.el.style.width = this.model.get('width');
}
},
setDimensions () { setDimensions () {
// Make sure the chat box has the right height and width. // Make sure the chat box has the right height and width.
this.adjustToViewport(); this.adjustToViewport();
...@@ -165,7 +215,6 @@ converse.plugins.add('converse-dragresize', { ...@@ -165,7 +215,6 @@ converse.plugins.add('converse-dragresize', {
}, },
setChatBoxHeight (height) { setChatBoxHeight (height) {
const { _converse } = this.__super__;
if (height) { if (height) {
height = _converse.applyDragResistance(height, this.model.get('default_height'))+'px'; height = _converse.applyDragResistance(height, this.model.get('default_height'))+'px';
} else { } else {
...@@ -178,7 +227,6 @@ converse.plugins.add('converse-dragresize', { ...@@ -178,7 +227,6 @@ converse.plugins.add('converse-dragresize', {
}, },
setChatBoxWidth (width) { setChatBoxWidth (width) {
const { _converse } = this.__super__;
if (width) { if (width) {
width = _converse.applyDragResistance(width, this.model.get('default_width'))+'px'; width = _converse.applyDragResistance(width, this.model.get('default_width'))+'px';
} else { } else {
...@@ -208,7 +256,6 @@ converse.plugins.add('converse-dragresize', { ...@@ -208,7 +256,6 @@ converse.plugins.add('converse-dragresize', {
}, },
onStartVerticalResize (ev) { onStartVerticalResize (ev) {
const { _converse } = this.__super__;
if (!_converse.allow_dragresize) { return true; } if (!_converse.allow_dragresize) { return true; }
// Record element attributes for mouseMove(). // Record element attributes for mouseMove().
const flyout = this.el.querySelector('.box-flyout'), const flyout = this.el.querySelector('.box-flyout'),
...@@ -222,7 +269,6 @@ converse.plugins.add('converse-dragresize', { ...@@ -222,7 +269,6 @@ converse.plugins.add('converse-dragresize', {
}, },
onStartHorizontalResize (ev) { onStartHorizontalResize (ev) {
const { _converse } = this.__super__;
if (!_converse.allow_dragresize) { return true; } if (!_converse.allow_dragresize) { return true; }
const flyout = this.el.querySelector('.box-flyout'), const flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout); style = window.getComputedStyle(flyout);
...@@ -235,116 +281,13 @@ converse.plugins.add('converse-dragresize', { ...@@ -235,116 +281,13 @@ converse.plugins.add('converse-dragresize', {
}, },
onStartDiagonalResize (ev) { onStartDiagonalResize (ev) {
const { _converse } = this.__super__;
this.onStartHorizontalResize(ev); this.onStartHorizontalResize(ev);
this.onStartVerticalResize(ev); this.onStartVerticalResize(ev);
_converse.resizing.direction = 'topleft'; _converse.resizing.direction = 'topleft';
}, },
};
Object.assign(_converse.ChatBoxView.prototype, dragResizable);
resizeChatBox (ev) {
let diff;
const { _converse } = this.__super__;
if (_converse.resizing.direction.indexOf('top') === 0) {
diff = ev.pageY - this.prev_pageY;
if (diff) {
this.height = ((this.height-diff) > (this.model.get('min_height') || 0)) ? (this.height-diff) : this.model.get('min_height');
this.prev_pageY = ev.pageY;
this.setChatBoxHeight(this.height);
}
}
if (_.includes(_converse.resizing.direction, 'left')) {
diff = this.prev_pageX - ev.pageX;
if (diff) {
this.width = ((this.width+diff) > (this.model.get('min_width') || 0)) ? (this.width+diff) : this.model.get('min_width');
this.prev_pageX = ev.pageX;
this.setChatBoxWidth(this.width);
}
}
}
},
HeadlinesBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize () {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
return this.__super__.initialize.apply(this, arguments);
},
render () {
const result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
}
},
ControlBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize () {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
this.__super__.initialize.apply(this, arguments);
},
render () {
const result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
},
renderLoginPanel () {
const result = this.__super__.renderLoginPanel.apply(this, arguments);
this.initDragResize().setDimensions();
return result;
},
renderControlBoxPane () {
const result = this.__super__.renderControlBoxPane.apply(this, arguments);
this.initDragResize().setDimensions();
return result;
}
},
ChatRoomView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize () {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
this.__super__.initialize.apply(this, arguments);
},
render () {
const result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
}
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this;
_converse.api.settings.update({
allow_dragresize: true,
});
_converse.applyDragResistance = function (value, default_value) { _converse.applyDragResistance = function (value, default_value) {
/* This method applies some resistance around the /* This method applies some resistance around the
...@@ -363,6 +306,45 @@ converse.plugins.add('converse-dragresize', { ...@@ -363,6 +306,45 @@ converse.plugins.add('converse-dragresize', {
} }
return value; return value;
}; };
/************************ BEGIN Event Handlers ************************/
function registerGlobalEventHandlers () {
document.addEventListener('mousemove', function (ev) {
if (!_converse.resizing || !_converse.allow_dragresize) { return true; }
ev.preventDefault();
_converse.resizing.chatbox.resizeChatBox(ev);
});
document.addEventListener('mouseup', function (ev) {
if (!_converse.resizing || !_converse.allow_dragresize) { return true; }
ev.preventDefault();
const height = _converse.applyDragResistance(
_converse.resizing.chatbox.height,
_converse.resizing.chatbox.model.get('default_height')
);
const width = _converse.applyDragResistance(
_converse.resizing.chatbox.width,
_converse.resizing.chatbox.model.get('default_width')
);
if (_converse.connection.connected) {
_converse.resizing.chatbox.model.save({'height': height});
_converse.resizing.chatbox.model.save({'width': width});
} else {
_converse.resizing.chatbox.model.set({'height': height});
_converse.resizing.chatbox.model.set({'width': width});
}
_converse.resizing = null;
});
}
_converse.api.listen.on('registeredGlobalEventHandlers', registerGlobalEventHandlers);
_converse.api.listen.on('chatBoxInitialized', view => {
window.addEventListener('resize', _.debounce(() => view.setDimensions(), 100));
});
/************************ END Event Handlers ************************/
} }
}); });
...@@ -30,7 +30,7 @@ converse.plugins.add('converse-minimize', { ...@@ -30,7 +30,7 @@ converse.plugins.add('converse-minimize', {
* *
* 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", "converse-controlbox", "converse-muc", "converse-muc-views", "converse-headline"], dependencies: ["converse-chatview", "converse-controlbox", "converse-muc-views", "converse-headline", "converse-dragresize"],
enabled (_converse) { enabled (_converse) {
return _converse.view_mode === 'overlayed'; return _converse.view_mode === 'overlayed';
...@@ -57,20 +57,6 @@ converse.plugins.add('converse-minimize', { ...@@ -57,20 +57,6 @@ converse.plugins.add('converse-minimize', {
}); });
}, },
maximize () {
u.safeSave(this, {
'minimized': false,
'time_opened': (new Date()).getTime()
});
},
minimize () {
u.safeSave(this, {
'minimized': true,
'time_minimized': (new Date()).toISOString()
});
},
maybeShow (force) { maybeShow (force) {
if (!force && this.get('minimized')) { if (!force && this.get('minimized')) {
// Must return the chatbox // Must return the chatbox
...@@ -122,65 +108,17 @@ converse.plugins.add('converse-minimize', { ...@@ -122,65 +108,17 @@ converse.plugins.add('converse-minimize', {
if (!this.model.get('minimized')) { if (!this.model.get('minimized')) {
return this.__super__.setChatBoxWidth.apply(this, arguments); return this.__super__.setChatBoxWidth.apply(this, arguments);
} }
}, }
onMinimizedChanged (item) {
if (item.get('minimized')) {
this.minimize();
} else {
this.maximize();
}
},
maximize () {
// Restores a minimized chat box
const { _converse } = this.__super__;
this.insertIntoDOM();
if (!this.model.isScrolledUp()) {
this.model.clearUnreadMsgCounter();
}
this.show();
/**
* Triggered when a previously minimized chat gets maximized
* @event _converse#chatBoxMaximized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMaximized', view => { ... });
*/
_converse.api.trigger('chatBoxMaximized', this);
return this;
},
minimize (ev) {
const { _converse } = this.__super__;
if (ev && ev.preventDefault) { ev.preventDefault(); }
// save the scroll position to restore it on maximize
if (this.model.collection && this.model.collection.browserStorage) {
this.model.save({'scroll': this.content.scrollTop});
} else {
this.model.set({'scroll': this.content.scrollTop});
}
this.setChatState(_converse.INACTIVE).model.minimize();
this.hide();
/**
* Triggered when a previously maximized chat gets Minimized
* @event _converse#chatBoxMinimized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMinimized', view => { ... });
*/
_converse.api.trigger('chatBoxMinimized', this);
},
}, },
ChatBoxHeading: { ChatBoxHeading: {
render () { render () {
const { _converse } = this.__super__, const { _converse } = this.__super__,
{ __ } = _converse; { __ } = _converse;
const result = this.__super__.render.apply(this, arguments); const result = this.__super__.render.apply(this, arguments);
const new_html = tpl_chatbox_minimize( const new_html = tpl_chatbox_minimize({
{info_minimize: __('Minimize this chat box')} 'info_minimize': __('Minimize this chat box')
); });
const el = this.el.querySelector('.toggle-chatbox-button'); const el = this.el.querySelector('.toggle-chatbox-button');
if (el) { if (el) {
el.outerHTML = new_html; el.outerHTML = new_html;
...@@ -227,11 +165,109 @@ converse.plugins.add('converse-minimize', { ...@@ -227,11 +165,109 @@ converse.plugins.add('converse-minimize', {
} }
return div.innerHTML; return div.innerHTML;
} }
}, }
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
// Add new HTML templates.
_converse.templates.chatbox_minimize = tpl_chatbox_minimize;
_converse.templates.toggle_chats = tpl_toggle_chats;
_converse.templates.trimmed_chat = tpl_trimmed_chat;
_converse.templates.chats_panel = tpl_chats_panel;
_converse.api.settings.update({
'no_trimming': false, // Set to true for tests
});
ChatBoxViews: {
const minimizableChatBox = {
maximize () {
u.safeSave(this, {
'minimized': false,
'time_opened': (new Date()).getTime()
});
},
minimize () {
u.safeSave(this, {
'minimized': true,
'time_minimized': (new Date()).toISOString()
});
}
}
Object.assign(_converse.ChatBox.prototype, minimizableChatBox);
const minimizableChatBoxView = {
maximize () {
// Restores a minimized chat box
const { _converse } = this.__super__;
this.insertIntoDOM();
if (!this.model.isScrolledUp()) {
this.model.clearUnreadMsgCounter();
}
this.show();
/**
* Triggered when a previously minimized chat gets maximized
* @event _converse#chatBoxMaximized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMaximized', view => { ... });
*/
_converse.api.trigger('chatBoxMaximized', this);
return this;
},
minimize (ev) {
const { _converse } = this.__super__;
if (ev && ev.preventDefault) { ev.preventDefault(); }
// save the scroll position to restore it on maximize
if (this.model.collection && this.model.collection.browserStorage) {
this.model.save({'scroll': this.content.scrollTop});
} else {
this.model.set({'scroll': this.content.scrollTop});
}
this.setChatState(_converse.INACTIVE).model.minimize();
this.hide();
/**
* Triggered when a previously maximized chat gets Minimized
* @event _converse#chatBoxMinimized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMinimized', view => { ... });
*/
_converse.api.trigger('chatBoxMinimized', this);
},
onMinimizedChanged (item) {
if (item.get('minimized')) {
this.minimize();
} else {
this.maximize();
}
}
}
Object.assign(_converse.ChatBoxView.prototype, minimizableChatBoxView);
const chatTrimmer = {
getChatBoxWidth (view) { getChatBoxWidth (view) {
if (!view.model.get('minimized') && u.isVisible(view.el)) { if (view.model.get('id') === 'controlbox') {
const controlbox = this.get('controlbox');
// We return the width of the controlbox or its toggle,
// depending on which is visible.
if (!controlbox || !u.isVisible(controlbox.el)) {
return u.getOuterWidth(_converse.controlboxtoggle.el, true);
} else {
return u.getOuterWidth(controlbox.el, true);
}
} else if (!view.model.get('minimized') && u.isVisible(view.el)) {
return u.getOuterWidth(view.el, true); return u.getOuterWidth(view.el, true);
} }
return 0; return 0;
...@@ -315,25 +351,8 @@ converse.plugins.add('converse-minimize', { ...@@ -315,25 +351,8 @@ converse.plugins.add('converse-minimize', {
return model; return model;
} }
} }
}, Object.assign(_converse.ChatBoxViews.prototype, chatTrimmer);
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
// Add new HTML templates.
_converse.templates.chatbox_minimize = tpl_chatbox_minimize;
_converse.templates.toggle_chats = tpl_toggle_chats;
_converse.templates.trimmed_chat = tpl_trimmed_chat;
_converse.templates.chats_panel = tpl_chats_panel;
_converse.api.settings.update({
no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
});
_converse.api.promises.add('minimizedChatsInitialized'); _converse.api.promises.add('minimizedChatsInitialized');
...@@ -525,6 +544,7 @@ converse.plugins.add('converse-minimize', { ...@@ -525,6 +544,7 @@ converse.plugins.add('converse-minimize', {
} }
}); });
/************************ BEGIN Event Handlers ************************/
Promise.all([ Promise.all([
_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('connectionInitialized'),
_converse.api.waitUntil('chatBoxViewsInitialized') _converse.api.waitUntil('chatBoxViewsInitialized')
...@@ -559,5 +579,6 @@ converse.plugins.add('converse-minimize', { ...@@ -559,5 +579,6 @@ converse.plugins.add('converse-minimize', {
_converse.chatboxviews.trimChats(chatbox); _converse.chatboxviews.trimChats(chatbox);
} }
}); });
/************************ END Event Handlers ************************/
} }
}); });
...@@ -62,41 +62,14 @@ converse.plugins.add('converse-muc-views', { ...@@ -62,41 +62,14 @@ converse.plugins.add('converse-muc-views', {
dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"], dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"],
overrides: { overrides: {
ControlBoxView: { ControlBoxView: {
renderRoomsPanel () {
const { _converse } = this.__super__;
if (this.roomspanel && u.isVisible(this.roomspanel.el)) {
return;
}
this.roomspanel = new _converse.RoomsPanel({
'model': new (_converse.RoomsPanelModel.extend({
'id': `converse.roomspanel${_converse.bare_jid}`, // Required by web storage
'browserStorage': new BrowserStorage[_converse.config.get('storage')](
`converse.roomspanel${_converse.bare_jid}`)
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement(
'beforeEnd', this.roomspanel.render().el);
/**
* Triggered once the section of the _converse.ControlBoxView
* which shows gropuchats has been rendered.
* @event _converse#roomsPanelRendered
* @example _converse.api.listen.on('roomsPanelRendered', () => { ... });
*/
_converse.api.trigger('roomsPanelRendered');
},
renderControlBoxPane () { renderControlBoxPane () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
this.__super__.renderControlBoxPane.apply(this, arguments); this.__super__.renderControlBoxPane.apply(this, arguments);
if (_converse.allow_muc) { if (_converse.allow_muc) {
this.renderRoomsPanel(); this.renderRoomsPanel();
} }
}, }
} }
}, },
...@@ -123,6 +96,35 @@ converse.plugins.add('converse-muc-views', { ...@@ -123,6 +96,35 @@ converse.plugins.add('converse-muc-views', {
} }
}); });
Object.assign(_converse.ControlBoxView.prototype, {
renderRoomsPanel () {
if (this.roomspanel && u.isVisible(this.roomspanel.el)) {
return;
}
this.roomspanel = new _converse.RoomsPanel({
'model': new (_converse.RoomsPanelModel.extend({
'id': `converse.roomspanel${_converse.bare_jid}`, // Required by web storage
'browserStorage': new BrowserStorage[_converse.config.get('storage')](
`converse.roomspanel${_converse.bare_jid}`)
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement(
'beforeEnd', this.roomspanel.render().el);
/**
* Triggered once the section of the _converse.ControlBoxView
* which shows gropuchats has been rendered.
* @event _converse#roomsPanelRendered
* @example _converse.api.listen.on('roomsPanelRendered', () => { ... });
*/
_converse.api.trigger('roomsPanelRendered');
}
});
function ___ (str) { function ___ (str) {
/* This is part of a hack to get gettext to scan strings to be /* This is part of a hack to get gettext to scan strings to be
* translated. Strings we cannot send to the function above because * translated. Strings we cannot send to the function above because
...@@ -572,6 +574,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -572,6 +574,7 @@ converse.plugins.add('converse-muc-views', {
* @example _converse.api.listen.on('chatRoomOpened', view => { ... }); * @example _converse.api.listen.on('chatRoomOpened', view => { ... });
*/ */
_converse.api.trigger('chatRoomOpened', this); _converse.api.trigger('chatRoomOpened', this);
_converse.api.trigger('chatBoxInitialized', this);
}, },
render () { render () {
......
...@@ -38,12 +38,12 @@ converse.plugins.add("converse-oauth", { ...@@ -38,12 +38,12 @@ converse.plugins.add("converse-oauth", {
/* For example, the private *_converse* object has a /* For example, the private *_converse* object has a
* method "onConnected". You can override that method as follows: * method "onConnected". You can override that method as follows:
*/ */
'LoginPanel': { LoginPanel: {
insertOAuthProviders () { insertOAuthProviders () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (_.isUndefined(this.oauth_providers_view)) { if (_.isUndefined(this.oauth_providers_view)) {
this.oauth_providers_view = this.oauth_providers_view =
new _converse.OAuthProvidersView({'model': _converse.oauth_providers}); new _converse.OAuthProvidersView({'model': _converse.oauth_providers});
this.oauth_providers_view.render(); this.oauth_providers_view.render();
......
...@@ -172,6 +172,90 @@ converse.plugins.add('converse-omemo', { ...@@ -172,6 +172,90 @@ converse.plugins.add('converse-omemo', {
}, },
ChatBox: { ChatBox: {
async getMessageAttributesFromStanza (stanza, original_stanza) {
const { _converse } = this.__super__;
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(),
attrs = await this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
if (!encrypted || !_converse.config.get('trusted')) {
return attrs;
} else {
return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs);
}
},
async sendMessage (text, spoiler_hint) {
if (this.get('omemo_active') && text) {
const { _converse } = this.__super__;
const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
attrs['is_encrypted'] = true;
attrs['plaintext'] = attrs.message;
try {
const devices = await _converse.getBundlesAndBuildSessions(this);
const stanza = await _converse.createOMEMOMessageStanza(this, this.messages.create(attrs), devices);
_converse.api.send(stanza);
} catch (e) {
this.handleMessageSendError(e);
return false;
}
return true;
} else {
return this.__super__.sendMessage.apply(this, arguments);
}
}
},
ChatBoxView: {
events: {
'click .toggle-omemo': 'toggleOMEMO'
},
initialize () {
this.__super__.initialize.apply(this, arguments);
this.model.on('change:omemo_active', this.renderOMEMOToolbarButton, this);
this.model.on('change:omemo_supported', this.onOMEMOSupportedDetermined, this);
},
showMessage (message) {
// We don't show a message if it's only keying material
if (!message.get('is_only_key')) {
return this.__super__.showMessage.apply(this, arguments);
}
}
},
ChatRoomView: {
events: {
'click .toggle-omemo': 'toggleOMEMO'
},
initialize () {
this.__super__.initialize.apply(this, arguments);
this.model.on('change:omemo_active', this.renderOMEMOToolbarButton, this);
this.model.on('change:omemo_supported', this.onOMEMOSupportedDetermined, this);
}
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add(['OMEMOInitialized']);
_converse.NUM_PREKEYS = 100; // Set here so that tests can override
/**
* Mixin object that contains OMEMO-related methods for
* {@link _converse.ChatBox} or {@link _converse.ChatRoom} objects.
*
* @typedef {Object} OMEMOEnabledChatBox
*/
const OMEMOEnabledChatBox = {
async encryptMessage (plaintext) { async encryptMessage (plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs // The client MUST use fresh, randomly generated key/IV pairs
...@@ -219,7 +303,6 @@ converse.plugins.add('converse-omemo', { ...@@ -219,7 +303,6 @@ converse.plugins.add('converse-omemo', {
}, },
reportDecryptionError (e) { reportDecryptionError (e) {
const { _converse } = this.__super__;
if (_converse.debug) { if (_converse.debug) {
const { __ } = _converse; const { __ } = _converse;
this.messages.create({ this.messages.create({
...@@ -231,8 +314,7 @@ converse.plugins.add('converse-omemo', { ...@@ -231,8 +314,7 @@ converse.plugins.add('converse-omemo', {
}, },
async handleDecryptedWhisperMessage (attrs, key_and_tag) { async handleDecryptedWhisperMessage (attrs, key_and_tag) {
const { _converse } = this.__super__, const encrypted = attrs.encrypted,
encrypted = attrs.encrypted,
devicelist = _converse.devicelists.getDeviceList(this.get('jid')); devicelist = _converse.devicelists.getDeviceList(this.get('jid'));
this.save('omemo_supported', true); this.save('omemo_supported', true);
...@@ -250,8 +332,7 @@ converse.plugins.add('converse-omemo', { ...@@ -250,8 +332,7 @@ converse.plugins.add('converse-omemo', {
}, },
decrypt (attrs) { decrypt (attrs) {
const { _converse } = this.__super__, const session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10));
session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10));
// https://xmpp.org/extensions/xep-0384.html#usecases-receiving // https://xmpp.org/extensions/xep-0384.html#usecases-receiving
if (attrs.encrypted.prekey === true) { if (attrs.encrypted.prekey === true) {
...@@ -284,8 +365,7 @@ converse.plugins.add('converse-omemo', { ...@@ -284,8 +365,7 @@ converse.plugins.add('converse-omemo', {
}, },
getEncryptionAttributesfromStanza (stanza, original_stanza, attrs) { getEncryptionAttributesfromStanza (stanza, original_stanza, attrs) {
const { _converse } = this.__super__, const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(),
encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(),
header = encrypted.querySelector('header'), header = encrypted.querySelector('header'),
key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop(); key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop();
if (key) { if (key) {
...@@ -303,22 +383,8 @@ converse.plugins.add('converse-omemo', { ...@@ -303,22 +383,8 @@ converse.plugins.add('converse-omemo', {
} }
}, },
async getMessageAttributesFromStanza (stanza, original_stanza) {
const { _converse } = this.__super__,
encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(),
attrs = await this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
if (!encrypted || !_converse.config.get('trusted')) {
return attrs;
} else {
return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs);
}
},
getSessionCipher (jid, id) { getSessionCipher (jid, id) {
const { _converse } = this.__super__, const address = new libsignal.SignalProtocolAddress(jid, id);
address = new libsignal.SignalProtocolAddress(jid, id);
this.session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); this.session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address);
return this.session_cipher; return this.session_cipher;
}, },
...@@ -330,8 +396,6 @@ converse.plugins.add('converse-omemo', { ...@@ -330,8 +396,6 @@ converse.plugins.add('converse-omemo', {
}, },
handleMessageSendError (e) { handleMessageSendError (e) {
const { _converse } = this.__super__,
{ __ } = _converse;
if (e.name === 'IQError') { if (e.name === 'IQError') {
this.save('omemo_supported', false); this.save('omemo_supported', false);
...@@ -355,46 +419,12 @@ converse.plugins.add('converse-omemo', { ...@@ -355,46 +419,12 @@ converse.plugins.add('converse-omemo', {
} else { } else {
throw e; throw e;
} }
},
async sendMessage (text, spoiler_hint) {
if (this.get('omemo_active') && text) {
const { _converse } = this.__super__;
const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
attrs['is_encrypted'] = true;
attrs['plaintext'] = attrs.message;
try {
const devices = await _converse.getBundlesAndBuildSessions(this);
const stanza = await _converse.createOMEMOMessageStanza(this, this.messages.create(attrs), devices);
_converse.api.send(stanza);
} catch (e) {
this.handleMessageSendError(e);
return false;
}
return true;
} else {
return this.__super__.sendMessage.apply(this, arguments);
}
} }
}, }
Object.assign(_converse.ChatBox.prototype, OMEMOEnabledChatBox);
ChatBoxView: {
events: {
'click .toggle-omemo': 'toggleOMEMO'
},
initialize () { const OMEMOEnabledChatView = {
this.__super__.initialize.apply(this, arguments);
this.model.on('change:omemo_active', this.renderOMEMOToolbarButton, this);
this.model.on('change:omemo_supported', this.onOMEMOSupportedDetermined, this);
},
showMessage (message) {
// We don't show a message if it's only keying material
if (!message.get('is_only_key')) {
return this.__super__.showMessage.apply(this, arguments);
}
},
onOMEMOSupportedDetermined () { onOMEMOSupportedDetermined () {
if (!this.model.get('omemo_supported') && this.model.get('omemo_active')) { if (!this.model.get('omemo_supported') && this.model.get('omemo_active')) {
...@@ -405,82 +435,47 @@ converse.plugins.add('converse-omemo', { ...@@ -405,82 +435,47 @@ converse.plugins.add('converse-omemo', {
}, },
renderOMEMOToolbarButton () { renderOMEMOToolbarButton () {
const { _converse } = this.__super__, if (this.model.get('type') !== _converse.CHATROOMS_TYPE ||
{ __ } = _converse, this.model.features.get('membersonly') &&
icon = this.el.querySelector('.toggle-omemo'), this.model.features.get('nonanonymous')) {
html = tpl_toolbar_omemo(Object.assign(this.model.toJSON(), {'__': __}));
if (icon) { const icon = this.el.querySelector('.toggle-omemo');
icon.outerHTML = html; const html = tpl_toolbar_omemo(Object.assign(this.model.toJSON(), {'__': __}));
if (icon) {
icon.outerHTML = html;
} else {
this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', html);
}
} else { } else {
this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', html); const icon = this.el.querySelector('.toggle-omemo');
if (icon) {
icon.parentElement.removeChild(icon);
}
} }
}, },
toggleOMEMO (ev) { toggleOMEMO (ev) {
const { _converse } = this.__super__, { __ } = _converse;
if (!this.model.get('omemo_supported')) { if (!this.model.get('omemo_supported')) {
return _converse.api.alert.show( let messages;
Strophe.LogLevel.ERROR, if (this.model.get('type') === _converse.CHATROOMS_TYPE) {
__('Error'), messages = [__(
[__("Cannot use end-to-end encryption because %1$s uses a client that doesn't support OMEMO.", 'Cannot use end-to-end encryption in this groupchat, '+
'either the groupchat has some anonymity or not all participants support OMEMO.'
)];
} else {
messages = [__(
"Cannot use end-to-end encryption because %1$s uses a client that doesn't support OMEMO.",
this.model.contact.getDisplayName() this.model.contact.getDisplayName()
)] )];
) }
} return _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), messages);
ev.preventDefault();
this.model.save({'omemo_active': !this.model.get('omemo_active')});
}
},
ChatRoomView: {
events: {
'click .toggle-omemo': 'toggleOMEMO'
},
initialize () {
this.__super__.initialize.apply(this, arguments);
this.model.on('change:omemo_active', this.renderOMEMOToolbarButton, this);
this.model.on('change:omemo_supported', this.onOMEMOSupportedDetermined, this);
},
toggleOMEMO (ev) {
const { _converse } = this.__super__, { __ } = _converse;
if (!this.model.get('omemo_supported')) {
return _converse.api.alert.show(
Strophe.LogLevel.ERROR,
__('Error'),
[__('Cannot use end-to-end encryption in this groupchat, '+
'either the groupchat has some anonymity or not all participants support OMEMO.')]
);
} }
ev.preventDefault(); ev.preventDefault();
this.model.save({'omemo_active': !this.model.get('omemo_active')}); this.model.save({'omemo_active': !this.model.get('omemo_active')});
},
renderOMEMOToolbarButton () {
if (this.model.features.get('membersonly') && this.model.features.get('nonanonymous')) {
this.__super__.renderOMEMOToolbarButton.apply(arguments);
} else {
const icon = this.el.querySelector('.toggle-omemo');
if (icon) {
icon.parentElement.removeChild(icon);
}
}
} }
} }
}, Object.assign(_converse.ChatBoxView.prototype, OMEMOEnabledChatView);
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add(['OMEMOInitialized']);
_converse.NUM_PREKEYS = 100; // Set here so that tests can override
async function generateFingerprint (device) { async function generateFingerprint (device) {
if (_.get(device.get('bundle'), 'fingerprint')) { if (_.get(device.get('bundle'), 'fingerprint')) {
......
...@@ -45,17 +45,6 @@ converse.plugins.add('converse-register', { ...@@ -45,17 +45,6 @@ converse.plugins.add('converse-register', {
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
LoginPanel: { LoginPanel: {
insertRegisterLink () {
const { _converse } = this.__super__;
if (_.isUndefined(this.registerlinkview)) {
this.registerlinkview = new _converse.RegisterLinkView({'model': this.model});
this.registerlinkview.render();
this.el.querySelector('.buttons').insertAdjacentElement('afterend', this.registerlinkview.el);
}
this.registerlinkview.render();
},
render (cfg) { render (cfg) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
this.__super__.render.apply(this, arguments); this.__super__.render.apply(this, arguments);
...@@ -67,11 +56,50 @@ converse.plugins.add('converse-register', { ...@@ -67,11 +56,50 @@ converse.plugins.add('converse-register', {
}, },
ControlBoxView: { ControlBoxView: {
renderLoginPanel () {
/* Also render a registration panel, when rendering the
* login panel.
*/
this.__super__.renderLoginPanel.apply(this, arguments);
this.renderRegistrationPanel();
return this;
}
}
},
initialize () { initialize () {
this.__super__.initialize.apply(this, arguments); /* The initialize function gets called as soon as the plugin is
this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this)) * loaded by converse.js's plugin machinery.
}, */
const { _converse } = this,
{ __ } = _converse;
_converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
_converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
_converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
_converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({
'allow_registration': true,
'domain_placeholder': __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form
'providers_link': 'https://compliance.conversations.im/', // Link to XMPP providers shown on registration page
'registration_domain': ''
});
Object.assign(_converse.LoginPanel.prototype, {
insertRegisterLink () {
if (_.isUndefined(this.registerlinkview)) {
this.registerlinkview = new _converse.RegisterLinkView({'model': this.model});
this.registerlinkview.render();
this.el.querySelector('.buttons').insertAdjacentElement('afterend', this.registerlinkview.el);
}
this.registerlinkview.render();
}
});
Object.assign(_converse.ControlBoxView.prototype, {
showLoginOrRegisterForm () { showLoginOrRegisterForm () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
...@@ -88,7 +116,6 @@ converse.plugins.add('converse-register', { ...@@ -88,7 +116,6 @@ converse.plugins.add('converse-register', {
}, },
renderRegistrationPanel () { renderRegistrationPanel () {
const { _converse } = this.__super__;
if (_converse.allow_registration) { if (_converse.allow_registration) {
this.registerpanel = new _converse.RegisterPanel({ this.registerpanel = new _converse.RegisterPanel({
'model': this.model 'model': this.model
...@@ -102,36 +129,7 @@ converse.plugins.add('converse-register', { ...@@ -102,36 +129,7 @@ converse.plugins.add('converse-register', {
this.showLoginOrRegisterForm(); this.showLoginOrRegisterForm();
} }
return this; return this;
},
renderLoginPanel () {
/* Also render a registration panel, when rendering the
* login panel.
*/
this.__super__.renderLoginPanel.apply(this, arguments);
this.renderRegistrationPanel();
return this;
} }
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
_converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
_converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
_converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
_converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({
'allow_registration': true,
'domain_placeholder': __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form
'providers_link': 'https://compliance.conversations.im/', // Link to XMPP providers shown on registration page
'registration_domain': ''
}); });
...@@ -679,6 +677,12 @@ converse.plugins.add('converse-register', { ...@@ -679,6 +677,12 @@ converse.plugins.add('converse-register', {
return false; return false;
} }
}); });
/************************ BEGIN Event Handlers ************************/
_converse.api.listen.on('controlboxInitialized', view => {
view.model.on('change:active-form', view.showLoginOrRegisterForm, view);
});
/************************ END Event Handlers ************************/
} }
}); });
...@@ -29,27 +29,6 @@ converse.plugins.add('converse-rosterview', { ...@@ -29,27 +29,6 @@ converse.plugins.add('converse-rosterview', {
dependencies: ["converse-roster", "converse-modal"], dependencies: ["converse-roster", "converse-modal"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
afterReconnected () {
this.__super__.afterReconnected.apply(this, arguments);
},
RosterGroups: {
comparator () {
// RosterGroupsComparator only gets set later (once i18n is
// set up), so we need to wrap it in this nameless function.
const { _converse } = this.__super__;
return _converse.RosterGroupsComparator.apply(this, arguments);
}
}
},
initialize () { initialize () {
/* 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.
...@@ -78,36 +57,6 @@ converse.plugins.add('converse-rosterview', { ...@@ -78,36 +57,6 @@ converse.plugins.add('converse-rosterview', {
'away': __('This contact is away') 'away': __('This contact is away')
}; };
const LABEL_GROUPS = __('Groups'); const LABEL_GROUPS = __('Groups');
const HEADER_CURRENT_CONTACTS = __('My contacts');
const HEADER_PENDING_CONTACTS = __('Pending contacts');
const HEADER_REQUESTING_CONTACTS = __('Contact requests');
const HEADER_UNGROUPED = __('Ungrouped');
const HEADER_WEIGHTS = {};
HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 0;
HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 1;
HEADER_WEIGHTS[HEADER_UNGROUPED] = 2;
HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3;
_converse.RosterGroupsComparator = function (a, b) {
/* Groups are sorted alphabetically, ignoring case.
* However, Ungrouped, Requesting Contacts and Pending Contacts
* appear last and in that order.
*/
a = a.get('name');
b = b.get('name');
const special_groups = Object.keys(HEADER_WEIGHTS);
const a_is_special = _.includes(special_groups, a);
const b_is_special = _.includes(special_groups, b);
if (!a_is_special && !b_is_special ) {
return a.toLowerCase() < b.toLowerCase() ? -1 : (a.toLowerCase() > b.toLowerCase() ? 1 : 0);
} else if (a_is_special && b_is_special) {
return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : (HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0);
} else if (!a_is_special && b_is_special) {
return (b === HEADER_REQUESTING_CONTACTS) ? 1 : -1;
} else if (a_is_special && !b_is_special) {
return (a === HEADER_REQUESTING_CONTACTS) ? -1 : 1;
}
};
_converse.AddContactModal = _converse.BootstrapModal.extend({ _converse.AddContactModal = _converse.BootstrapModal.extend({
...@@ -678,7 +627,7 @@ converse.plugins.add('converse-rosterview', { ...@@ -678,7 +627,7 @@ converse.plugins.add('converse-rosterview', {
let matches; let matches;
q = q.toLowerCase(); q = q.toLowerCase();
if (type === 'state') { if (type === 'state') {
if (this.model.get('name') === HEADER_REQUESTING_CONTACTS) { if (this.model.get('name') === _converse.HEADER_REQUESTING_CONTACTS) {
// When filtering by chat state, we still want to // When filtering by chat state, we still want to
// show requesting contacts, even though they don't // show requesting contacts, even though they don't
// have the state in question. // have the state in question.
...@@ -747,13 +696,13 @@ converse.plugins.add('converse-rosterview', { ...@@ -747,13 +696,13 @@ converse.plugins.add('converse-rosterview', {
}, },
onContactSubscriptionChange (contact) { onContactSubscriptionChange (contact) {
if ((this.model.get('name') === HEADER_PENDING_CONTACTS) && contact.get('subscription') !== 'from') { if ((this.model.get('name') === _converse.HEADER_PENDING_CONTACTS) && contact.get('subscription') !== 'from') {
this.removeContact(contact); this.removeContact(contact);
} }
}, },
onContactRequestChange (contact) { onContactRequestChange (contact) {
if ((this.model.get('name') === HEADER_REQUESTING_CONTACTS) && !contact.get('requesting')) { if ((this.model.get('name') === _converse.HEADER_REQUESTING_CONTACTS) && !contact.get('requesting')) {
this.removeContact(contact); this.removeContact(contact);
} }
}, },
...@@ -926,16 +875,16 @@ converse.plugins.add('converse-rosterview', { ...@@ -926,16 +875,16 @@ converse.plugins.add('converse-rosterview', {
this.update(); this.update();
if (_.has(contact.changed, 'subscription')) { if (_.has(contact.changed, 'subscription')) {
if (contact.changed.subscription === 'from') { if (contact.changed.subscription === 'from') {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS);
} else if (_.includes(['both', 'to'], contact.get('subscription'))) { } else if (_.includes(['both', 'to'], contact.get('subscription'))) {
this.addExistingContact(contact); this.addExistingContact(contact);
} }
} }
if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') { if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS);
} }
if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') { if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS); this.addContactToGroup(contact, _converse.HEADER_REQUESTING_CONTACTS);
} }
this.updateFilter(); this.updateFilter();
}, },
...@@ -961,10 +910,10 @@ converse.plugins.add('converse-rosterview', { ...@@ -961,10 +910,10 @@ converse.plugins.add('converse-rosterview', {
if (_converse.roster_groups) { if (_converse.roster_groups) {
groups = contact.get('groups'); groups = contact.get('groups');
if (groups.length === 0) { if (groups.length === 0) {
groups = [HEADER_UNGROUPED]; groups = [_converse.HEADER_UNGROUPED];
} }
} else { } else {
groups = [HEADER_CURRENT_CONTACTS]; groups = [_converse.HEADER_CURRENT_CONTACTS];
} }
_.each(groups, _.bind(this.addContactToGroup, this, contact, _, options)); _.each(groups, _.bind(this.addContactToGroup, this, contact, _, options));
}, },
...@@ -982,9 +931,9 @@ converse.plugins.add('converse-rosterview', { ...@@ -982,9 +931,9 @@ converse.plugins.add('converse-rosterview', {
return; return;
} }
if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) { if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS, options); this.addContactToGroup(contact, _converse.HEADER_PENDING_CONTACTS, options);
} else if (contact.get('requesting') === true) { } else if (contact.get('requesting') === true) {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options); this.addContactToGroup(contact, _converse.HEADER_REQUESTING_CONTACTS, options);
} }
} }
return this; return this;
......
...@@ -82,7 +82,7 @@ converse.plugins.add('converse-singleton', { ...@@ -82,7 +82,7 @@ converse.plugins.add('converse-singleton', {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (_converse.isUniView()) { if (_converse.isUniView()) {
return false; return false;
} else { } else {
return this.__super__.shouldShowOnTextMessage.apply(this, arguments); return this.__super__.shouldShowOnTextMessage.apply(this, arguments);
} }
}, },
......
...@@ -40,13 +40,11 @@ converse.plugins.add('converse-bookmarks', { ...@@ -40,13 +40,11 @@ converse.plugins.add('converse-bookmarks', {
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
ChatRoom: { ChatRoom: {
getAndPersistNickname(nick) { getAndPersistNickname(nick) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
nick = nick || _converse.getNicknameFromBookmark(this.get('jid')); nick = nick || _converse.getNicknameFromBookmark(this.get('jid'));
return this.__super__.getAndPersistNickname.call(this, nick); return this.__super__.getAndPersistNickname.call(this, nick);
}, }
} }
}, },
......
...@@ -251,7 +251,7 @@ _converse.log = function (message, level, style='') { ...@@ -251,7 +251,7 @@ _converse.log = function (message, level, style='') {
message = message.outerHTML; message = message.outerHTML;
} }
const prefix = style ? '%c' : ''; const prefix = style ? '%c' : '';
const logger = _.assign({ const logger = Object.assign({
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop, 'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop, 'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'info': _.get(console, 'log') ? console.log.bind(console) : _.noop, 'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
......
...@@ -29,9 +29,40 @@ converse.plugins.add('converse-mam', { ...@@ -29,9 +29,40 @@ converse.plugins.add('converse-mam', {
// Overrides mentioned here will be picked up by converse.js's // Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the // plugin architecture they will replace existing methods on the
// relevant objects or classes. // relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatBox: { ChatBox: {
async getDuplicateMessage (stanza) {
const message = await this.__super__.getDuplicateMessage.apply(this, arguments);
if (!message) {
return this.findDuplicateFromArchiveID(stanza);
}
return message;
},
getUpdatedMessageAttributes (message, stanza) {
const attrs = this.__super__.getUpdatedMessageAttributes.apply(this, arguments);
if (message && !message.get('is_archived')) {
return Object.assign(attrs, {
'is_archived': this.isArchived(stanza)
}, this.getStanzaIDs(stanza))
}
return attrs;
}
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this;
_converse.api.settings.update({
archived_messages_page_size: '50',
message_archiving: undefined, // Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs)
message_archiving_timeout: 20000, // Time (in milliseconds) to wait before aborting MAM request
});
const MAMEnabledChat = {
fetchNewestMessages () { fetchNewestMessages () {
/* Fetches messages that might have been archived *after* /* Fetches messages that might have been archived *after*
...@@ -40,7 +71,6 @@ converse.plugins.add('converse-mam', { ...@@ -40,7 +71,6 @@ converse.plugins.add('converse-mam', {
if (this.disable_mam) { if (this.disable_mam) {
return; return;
} }
const { _converse } = this.__super__;
const most_recent_msg = u.getMostRecentMessage(this); const most_recent_msg = u.getMostRecentMessage(this);
if (_.isNil(most_recent_msg)) { if (_.isNil(most_recent_msg)) {
...@@ -59,7 +89,6 @@ converse.plugins.add('converse-mam', { ...@@ -59,7 +89,6 @@ converse.plugins.add('converse-mam', {
if (this.disable_mam) { if (this.disable_mam) {
return; return;
} }
const { _converse } = this.__super__;
const is_groupchat = this.get('type') === CHATROOMS_TYPE; const is_groupchat = this.get('type') === CHATROOMS_TYPE;
const mam_jid = is_groupchat ? this.get('jid') : _converse.bare_jid; const mam_jid = is_groupchat ? this.get('jid') : _converse.bare_jid;
if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) { if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) {
...@@ -92,7 +121,6 @@ converse.plugins.add('converse-mam', { ...@@ -92,7 +121,6 @@ converse.plugins.add('converse-mam', {
}, },
async findDuplicateFromArchiveID (stanza) { async findDuplicateFromArchiveID (stanza) {
const { _converse } = this.__super__;
const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop();
if (!result) { if (!result) {
return null; return null;
...@@ -107,32 +135,11 @@ converse.plugins.add('converse-mam', { ...@@ -107,32 +135,11 @@ converse.plugins.add('converse-mam', {
return this.messages.findWhere(query); return this.messages.findWhere(query);
}, },
async getDuplicateMessage (stanza) { }
const message = await this.__super__.getDuplicateMessage.apply(this, arguments); Object.assign(_converse.ChatBox.prototype, MAMEnabledChat);
if (!message) {
return this.findDuplicateFromArchiveID(stanza);
}
return message;
},
getUpdatedMessageAttributes (message, stanza) {
const attrs = this.__super__.getUpdatedMessageAttributes.apply(this, arguments);
if (message && !message.get('is_archived')) {
return Object.assign(attrs, {
'is_archived': this.isArchived(stanza)
}, this.getStanzaIDs(stanza))
}
return attrs;
}
},
ChatRoom: {
initialize () {
this.__super__.initialize.apply(this, arguments);
this.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this);
this.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this);
},
Object.assign(_converse.ChatRoom.prototype, {
fetchArchivedMessagesIfNecessary () { fetchArchivedMessagesIfNecessary () {
if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED || if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED ||
!this.get('mam_enabled') || !this.get('mam_enabled') ||
...@@ -142,22 +149,9 @@ converse.plugins.add('converse-mam', { ...@@ -142,22 +149,9 @@ converse.plugins.add('converse-mam', {
this.fetchArchivedMessages(); this.fetchArchivedMessages();
this.save({'mam_initialized': true}); this.save({'mam_initialized': true});
} }
},
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
const { _converse } = this;
_converse.api.settings.update({
archived_messages_page_size: '50',
message_archiving: undefined, // Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs)
message_archiving_timeout: 20000, // Time (in milliseconds) to wait before aborting MAM request
}); });
_converse.onMAMError = function (iq) { _converse.onMAMError = function (iq) {
if (iq.querySelectorAll('feature-not-implemented').length) { if (iq.querySelectorAll('feature-not-implemented').length) {
_converse.log( _converse.log(
...@@ -220,6 +214,11 @@ converse.plugins.add('converse-mam', { ...@@ -220,6 +214,11 @@ converse.plugins.add('converse-mam', {
_converse.api.listen.on('afterMessagesFetched', chat => chat.fetchNewestMessages()); _converse.api.listen.on('afterMessagesFetched', chat => chat.fetchNewestMessages());
_converse.api.listen.on('chatReconnected', chat => chat.fetchNewestMessages()); _converse.api.listen.on('chatReconnected', chat => chat.fetchNewestMessages());
_converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.MAM)); _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.MAM));
_converse.api.listen.on('chatRoomOpened', (room) => {
room.on('change:mam_enabled', room.fetchArchivedMessagesIfNecessary, room);
room.on('change:connection_status', room.fetchArchivedMessagesIfNecessary, room);
});
/************************ END Event Handlers **************************/ /************************ END Event Handlers **************************/
......
...@@ -36,6 +36,17 @@ converse.plugins.add('converse-roster', { ...@@ -36,6 +36,17 @@ converse.plugins.add('converse-roster', {
'rosterInitialized', 'rosterInitialized',
]); ]);
_converse.HEADER_CURRENT_CONTACTS = __('My contacts');
_converse.HEADER_PENDING_CONTACTS = __('Pending contacts');
_converse.HEADER_REQUESTING_CONTACTS = __('Contact requests');
_converse.HEADER_UNGROUPED = __('Ungrouped');
const HEADER_WEIGHTS = {};
HEADER_WEIGHTS[_converse.HEADER_REQUESTING_CONTACTS] = 0;
HEADER_WEIGHTS[_converse.HEADER_CURRENT_CONTACTS] = 1;
HEADER_WEIGHTS[_converse.HEADER_UNGROUPED] = 2;
HEADER_WEIGHTS[_converse.HEADER_PENDING_CONTACTS] = 3;
_converse.registerPresenceHandler = function () { _converse.registerPresenceHandler = function () {
_converse.unregisterPresenceHandler(); _converse.unregisterPresenceHandler();
...@@ -381,6 +392,10 @@ converse.plugins.add('converse-roster', { ...@@ -381,6 +392,10 @@ converse.plugins.add('converse-roster', {
model: _converse.RosterContact, model: _converse.RosterContact,
comparator (contact1, contact2) { comparator (contact1, contact2) {
/* Groups are sorted alphabetically, ignoring case.
* However, Ungrouped, Requesting Contacts and Pending Contacts
* appear last and in that order.
*/
const status1 = contact1.presence.get('show') || 'offline'; const status1 = contact1.presence.get('show') || 'offline';
const status2 = contact2.presence.get('show') || 'offline'; const status2 = contact2.presence.get('show') || 'offline';
if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) { if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) {
...@@ -854,6 +869,23 @@ converse.plugins.add('converse-roster', { ...@@ -854,6 +869,23 @@ converse.plugins.add('converse-roster', {
_converse.RosterGroups = Backbone.Collection.extend({ _converse.RosterGroups = Backbone.Collection.extend({
model: _converse.RosterGroup, model: _converse.RosterGroup,
comparator (a, b) {
a = a.get('name');
b = b.get('name');
const special_groups = Object.keys(HEADER_WEIGHTS);
const a_is_special = _.includes(special_groups, a);
const b_is_special = _.includes(special_groups, b);
if (!a_is_special && !b_is_special ) {
return a.toLowerCase() < b.toLowerCase() ? -1 : (a.toLowerCase() > b.toLowerCase() ? 1 : 0);
} else if (a_is_special && b_is_special) {
return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : (HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0);
} else if (!a_is_special && b_is_special) {
return (b === _converse.HEADER_REQUESTING_CONTACTS) ? 1 : -1;
} else if (a_is_special && !b_is_special) {
return (a === _converse.HEADER_REQUESTING_CONTACTS) ? -1 : 1;
}
},
fetchRosterGroups () { fetchRosterGroups () {
/* Fetches all the roster groups from sessionStorage. /* Fetches all the roster groups from sessionStorage.
* *
......
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