Browse Source

Add eslint with lodash checking and apply its suggestions

JC Brand 8 years ago
parent
commit
081f075aa9

+ 264 - 0
.eslintrc.json

@@ -0,0 +1,264 @@
+{
+    "env": {
+        "browser": true,
+        "jasmine": true
+    },
+    "plugins": ["lodash"],
+    "extends": ["eslint:recommended", "plugin:lodash/canonical"],
+    "globals": {
+        "window": true,
+        "sinon": true,
+        "define": true
+    },
+    "rules": {
+        "lodash/prefer-lodash-method": [2, {
+            "ignoreMethods": ["find", "endsWith", "startsWith", "filter", "map"]
+        }],
+        "lodash/prefer-startswith": "off",
+        "lodash/prefer-constant": "off",
+        "lodash/prefer-noop": "off",
+        "lodash/prefer-lodash-typecheck": "off",
+        "lodash/preferred-alias": "off",
+        "accessor-pairs": "error",
+        "array-bracket-spacing": "off",
+        "array-callback-return": "error",
+        "arrow-body-style": "error",
+        "arrow-parens": "error",
+        "arrow-spacing": "error",
+        "block-scoped-var": "off",
+        "block-spacing": "off",
+        "brace-style": "off",
+        "callback-return": "off",
+        "camelcase": "off",
+        "capitalized-comments": "off",
+        "class-methods-use-this": "error",
+        "comma-dangle": "off",
+        "comma-spacing": "off",
+        "comma-style": "off",
+        "complexity": "off",
+        "computed-property-spacing": [
+            "error",
+            "never"
+        ],
+        "consistent-return": "off",
+        "consistent-this": "off",
+        "curly": "off",
+        "default-case": "off",
+        "dot-location": [
+            "error",
+            "property"
+        ],
+        "dot-notation": [
+            "error",
+            {
+                "allowKeywords": false
+            }
+        ],
+        "eol-last": "error",
+        "eqeqeq": "off",
+        "func-call-spacing": "off",
+        "no-spaced-func": "off",
+        "func-name-matching": "error",
+        "func-names": "off",
+        "func-style": "off",
+        "generator-star-spacing": "error",
+        "global-require": "off",
+        "guard-for-in": "error",
+        "handle-callback-err": "error",
+        "id-blacklist": "error",
+        "id-length": "off",
+        "id-match": "error",
+        "indent": "off",
+        "init-declarations": "off",
+        "jsx-quotes": "error",
+        "key-spacing": "off",
+        "keyword-spacing": "off",
+        "line-comment-position": "off",
+        "linebreak-style": [
+            "error",
+            "unix"
+        ],
+        "lines-around-comment": "off",
+        "lines-around-directive": "off",
+        "max-depth": "error",
+        "max-len": "off",
+        "max-lines": "off",
+        "max-nested-callbacks": "error",
+        "max-params": "off",
+        "max-statements": "off",
+        "max-statements-per-line": "off",
+        "multiline-ternary": "off",
+        "new-parens": "error",
+        "newline-after-var": "off",
+        "newline-before-return": "off",
+        "newline-per-chained-call": "off",
+        "no-alert": "off",
+        "no-array-constructor": "error",
+        "no-await-in-loop": "error",
+        "no-bitwise": "off",
+        "no-caller": "error",
+        "no-console": "off",
+        "no-catch-shadow": "error",
+        "no-cond-assign": [
+            "error",
+            "except-parens"
+        ],
+        "no-confusing-arrow": "error",
+        "no-continue": "off",
+        "no-div-regex": "error",
+        "no-duplicate-imports": "error",
+        "no-else-return": "off",
+        "no-empty-function": "off",
+        "no-eq-null": "error",
+        "no-eval": "error",
+        "no-extend-native": "off",
+        "no-extra-bind": "off",
+        "no-extra-label": "error",
+        "no-extra-parens": "off",
+        "no-floating-decimal": "error",
+        "no-implicit-globals": "off",
+        "no-implied-eval": "error",
+        "no-inline-comments": "off",
+        "no-inner-declarations": [
+            "error",
+            "functions"
+        ],
+        "no-invalid-this": "off",
+        "no-iterator": "error",
+        "no-label-var": "error",
+        "no-labels": "error",
+        "no-lone-blocks": "error",
+        "no-lonely-if": "off",
+        "no-loop-func": "error",
+        "no-magic-numbers": "off",
+        "no-mixed-operators": "off",
+        "no-mixed-requires": "error",
+        "no-multi-assign": "off",
+        "no-multi-spaces": "off",
+        "no-multi-str": "error",
+        "no-multiple-empty-lines": "error",
+        "no-native-reassign": "error",
+        "no-negated-condition": "off",
+        "no-negated-in-lhs": "error",
+        "no-nested-ternary": "off",
+        "no-new": "error",
+        "no-new-func": "error",
+        "no-new-object": "error",
+        "no-new-require": "error",
+        "no-new-wrappers": "error",
+        "no-octal-escape": "error",
+        "no-param-reassign": "off",
+        "no-path-concat": "error",
+        "no-plusplus": "off",
+        "no-process-env": "error",
+        "no-process-exit": "error",
+        "no-proto": "error",
+        "no-prototype-builtins": "error",
+        "no-restricted-globals": "error",
+        "no-restricted-imports": "error",
+        "no-restricted-modules": "error",
+        "no-restricted-properties": "error",
+        "no-restricted-syntax": "error",
+        "no-return-assign": "error",
+        "no-return-await": "error",
+        "no-script-url": "error",
+        "no-self-compare": "error",
+        "no-sequences": "error",
+        "no-shadow": "off",
+        "no-shadow-restricted-names": "error",
+        "no-sync": "error",
+        "no-tabs": "error",
+        "no-template-curly-in-string": "error",
+        "no-ternary": "off",
+        "no-throw-literal": "error",
+        "no-trailing-spaces": "off",
+        "no-undef-init": "error",
+        "no-undefined": "off",
+        "no-underscore-dangle": "off",
+        "no-unmodified-loop-condition": "error",
+        "no-unneeded-ternary": "off",
+        "no-unused-vars": "off",
+        "no-unused-expressions": "off",
+        "no-use-before-define": "off",
+        "no-useless-call": "error",
+        "no-useless-computed-key": "error",
+        "no-useless-concat": "error",
+        "no-useless-constructor": "error",
+        "no-useless-escape": "off",
+        "no-useless-rename": "error",
+        "no-useless-return": "off",
+        "no-var": "off",
+        "no-void": "error",
+        "no-warning-comments": "off",
+        "no-whitespace-before-property": "error",
+        "no-with": "error",
+        "object-curly-newline": "off",
+        "object-curly-spacing": "off",
+        "object-property-newline": [
+            "error",
+            {
+                "allowMultiplePropertiesPerLine": true
+            }
+        ],
+        "object-shorthand": "off",
+        "one-var": "off",
+        "one-var-declaration-per-line": "off",
+        "operator-assignment": "off",
+        "operator-linebreak": [
+            "error",
+            "after"
+        ],
+        "padded-blocks": "off",
+        "prefer-arrow-callback": "off",
+        "prefer-const": "error",
+        "prefer-destructuring": [
+            "error",
+            {
+                "array": false,
+                "object": false
+            }
+        ],
+        "prefer-numeric-literals": "error",
+        "prefer-promise-reject-errors": "error",
+        "prefer-reflect": "off",
+        "prefer-rest-params": "off",
+        "prefer-spread": "off",
+        "prefer-template": "off",
+        "quote-props": "off",
+        "quotes": "off",
+        "radix": [
+            "error",
+            "always"
+        ],
+        "require-await": "error",
+        "require-jsdoc": "off",
+        "rest-spread-spacing": "error",
+        "semi": "off",
+        "semi-spacing": "off",
+        "sort-imports": "error",
+        "sort-keys": "off",
+        "sort-vars": "off",
+        "space-before-blocks": "off",
+        "space-before-function-paren": "off",
+        "space-in-parens": "off",
+        "space-infix-ops": "off",
+        "space-unary-ops": "off",
+        "spaced-comment": "off",
+        "strict": "off",
+        "symbol-description": "error",
+        "template-curly-spacing": "error",
+        "unicode-bom": [
+            "error",
+            "never"
+        ],
+        "valid-jsdoc": "error",
+        "vars-on-top": "off",
+        "wrap-iife": [
+            "error",
+            "any"
+        ],
+        "wrap-regex": "error",
+        "yield-star-spacing": "error",
+        "yoda": "off"
+    }
+}

+ 16 - 13
Makefile

@@ -3,11 +3,12 @@ BOWER           ?= node_modules/.bin/bower
 BUILDDIR        = ./docs
 BUNDLE          ?= ./.bundle/bin/bundle
 GRUNT           ?= ./node_modules/.bin/grunt
-HTTPSERVE		?= ./node_modules/.bin/http-server
-JSHINT 			?= ./node_modules/.bin/jshint
+HTTPSERVE       ?= ./node_modules/.bin/http-server
+JSHINT          ?= ./node_modules/.bin/jshint
+ESLINT          ?= ./node_modules/.bin/eslint
 PAPER           =
 PHANTOMJS       ?= ./node_modules/.bin/phantomjs
-RJS				?= ./node_modules/.bin/r.js
+RJS             ?= ./node_modules/.bin/r.js
 PO2JSON         ?= ./node_modules/.bin/po2json
 SASS            ?= ./.bundle/bin/sass
 CLEANCSS        ?= ./node_modules/.bin/cleancss
@@ -16,16 +17,13 @@ SPHINXOPTS      =
 
 # Internal variables.
 ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ./docs/source
-SOURCES	= $(wildcard *.js) $(wildcard spec/*.js) $(wildcard src/*.js)
+SOURCES    = $(wildcard *.js) $(wildcard spec/*.js) $(wildcard src/*.js)
 JSHINTEXCEPTIONS = $(GENERATED) \
-		   src/otr.js \
-		   src/crypto.js \
-		   src/build-mobile.js \
-		   src/build-no-jquery.js \
-		   src/build-no-dependencies.js \
-		   src/build.js \
-		   src/bigint.js
-CHECKSOURCES	= $(filter-out $(JSHINTEXCEPTIONS),$(SOURCES))
+           src/build-mobile.js \
+           src/build-no-jquery.js \
+           src/build-no-dependencies.js \
+           src/build.js \
+CHECKSOURCES    = $(filter-out $(JSHINTEXCEPTIONS),$(SOURCES))
 
 .PHONY: all
 all: dev dist
@@ -189,8 +187,13 @@ build:: stamp-bundler stamp-bower css
 jshint: stamp-bower
 	$(JSHINT) --config jshintrc $(CHECKSOURCES)
 
+.PHONY: eslint
+eslint: stamp-npm
+	$(ESLINT) src/
+	$(ESLINT) spec/
+
 .PHONY: check
-check: stamp-bower jshint
+check: stamp-bower jshint eslint
 	$(PHANTOMJS) node_modules/phantom-jasmine/lib/run_jasmine_test.coffee tests.html
 
 ########################################################################

+ 4 - 1
package.json

@@ -39,19 +39,22 @@
     "bower": "latest",
     "clean-css": "^3.4.19",
     "crypto-js": "3.1.2-5",
+    "eslint": "^3.14.1",
+    "eslint-plugin-lodash": "^2.3.3",
     "greenkeeper": "^4.1.0",
     "grunt": "^1.0.1",
     "grunt-cli": "^1.1.0",
     "grunt-json": "^0.2.0",
     "http-server": "^0.9.0",
+    "install": "^0.8.4",
     "jed": "0.5.4",
     "jquery": "2.2.3",
     "jquery-easing": "0.0.1",
     "jquery.browser": ">=0.1.0",
     "jshint": "^2.9.4",
     "lodash": "^4.17.4",
-    "lodash-cli": "^4.17.4",
     "moment": "~2.13.0",
+    "npm": "^4.1.1",
     "otr": "0.2.16",
     "phantom-jasmine": "0.1.8",
     "phantomjs": "~1.9.7-1",

+ 5 - 5
spec/chatbox.js

@@ -154,8 +154,8 @@
                     var attrs = ['id', 'box_id', 'visible'];
                     var new_attrs, old_attrs;
                     for (var i=0; i<attrs.length; i++) {
-                        new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
-                        old_attrs = _.pluck(_.pluck(converse.chatboxes.models, 'attributes'), attrs[i]);
+                        new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
+                        old_attrs = _.map(_.map(converse.chatboxes.models, 'attributes'), attrs[i]);
                         expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
                     }
                     converse.rosterview.render();
@@ -739,7 +739,7 @@
                             sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                             msg = $msg({
                                     from: sender_jid,
-                                    to: converse.bare_jid+'/'+"some-other-resource",
+                                    to: converse.bare_jid+"/some-other-resource",
                                     type: 'chat',
                                     id: (new Date()).getTime()
                                 }).c('body').t("This message will not be shown").up()
@@ -758,7 +758,7 @@
                             message = "This message sent to a different resource will be shown";
                             msg = $msg({
                                     from: sender_jid,
-                                    to: converse.bare_jid+'/'+"some-other-resource",
+                                    to: converse.bare_jid+"/some-other-resource",
                                     type: 'chat',
                                     id: '134234623462346'
                                 }).c('body').t(message).up()
@@ -1571,7 +1571,7 @@
                         test_utils.openChatBoxFor(converse, sender_jid);
                         var view = converse.chatboxviews.get(sender_jid);
                         expect(view.$el.find('.chat-event').length).toBe(0);
-                        view.showStatusNotification(sender_jid+' '+'is typing');
+                        view.showStatusNotification(sender_jid+' is typing');
                         expect(view.$el.find('.chat-event').length).toBe(1);
                         var msg = $msg({
                                 from: sender_jid,

+ 4 - 4
spec/chatroom.js

@@ -331,8 +331,8 @@
                             .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'})
                                 .c('title').t('Configuration for "coven" Room').up()
                                 .c('instructions').t('Complete this form to modify the configuration of your room.').up()
-                                .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'}).
-                                    c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
+                                .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'})
+                                    .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
                                 .c('field', {
                                     'label': 'Natural-Language Room Name',
                                     'type': 'text-single',
@@ -1078,8 +1078,8 @@
                 var attrs = ['id', 'box_id', 'visible'];
                 var new_attrs, old_attrs;
                 for (var i=0; i<attrs.length; i++) {
-                    new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
-                    old_attrs = _.pluck(_.pluck(converse.chatboxes.models, 'attributes'), attrs[i]);
+                    new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
+                    old_attrs = _.map(_.map(converse.chatboxes.models, 'attributes'), attrs[i]);
                     // FIXME: should have have to sort here? Order must
                     // probably be the same...
                     // This should be fixed once the controlbox always opens

+ 4 - 4
spec/controlbox.js

@@ -1031,7 +1031,7 @@
                 var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
                 converse.connection._dataRecv(test_utils.createRequest(stanza));
                 expect(converse.roster.pluck('jid').length).toBe(1);
-                expect(_.contains(converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
+                expect(_.includes(converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
 
                 // Taken from the spec
                 // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
@@ -1057,7 +1057,7 @@
                     subscription:'both'
                 }).c('group').t('Friends');
                 converse.roster.onReceivedFromServer(stanza.tree());
-                expect(_.contains(converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
+                expect(_.includes(converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
             }));
         });
 
@@ -1083,8 +1083,8 @@
                 // have the same attributes values as the original ones.
                 attrs = ['jid', 'fullname', 'subscription', 'ask'];
                 for (var i=0; i<attrs.length; i++) {
-                    new_attrs = _.pluck(_.pluck(new_roster.models, 'attributes'), attrs[i]);
-                    old_attrs = _.pluck(_.pluck(converse.roster.models, 'attributes'), attrs[i]);
+                    new_attrs = _.map(_.map(new_roster.models, 'attributes'), attrs[i]);
+                    old_attrs = _.map(_.map(converse.roster.models, 'attributes'), attrs[i]);
                     // Roster items in storage are not necessarily sorted,
                     // so we have to sort them here to do a proper
                     // comparison

+ 5 - 5
spec/converse.js

@@ -219,7 +219,7 @@
                 // You can retrieve multiple contacts by passing in an array
                 var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                 var list = converse_api.contacts.get([jid, jid2]);
-                expect(Array.isArray(list)).toBeTruthy();
+                expect(_.isArray(list)).toBeTruthy();
                 expect(list[0].fullname).toBe(mock.cur_names[0]);
                 expect(list[1].fullname).toBe(mock.cur_names[1]);
                 // Check that all JIDs are returned if you call without any parameters
@@ -262,7 +262,7 @@
                 var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                 test_utils.openChatBoxFor(converse, jid2);
                 var list = converse_api.chats.get([jid, jid2]);
-                expect(Array.isArray(list)).toBeTruthy();
+                expect(_.isArray(list)).toBeTruthy();
                 expect(list[0].get('box_id')).toBe(b64_sha1(jid));
                 expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
             }));
@@ -279,7 +279,7 @@
                     expect(box instanceof Object).toBeTruthy();
                     expect(box.get('box_id')).toBe(b64_sha1(jid));
                     expect(
-                        Object.keys(box),
+                        _.keys(box),
                         ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
                     );
                     chatboxview = converse.chatboxviews.get(jid);
@@ -287,7 +287,7 @@
                     // Test for multiple JIDs
                     var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                     var list = converse_api.chats.open([jid, jid2]);
-                    expect(Array.isArray(list)).toBeTruthy();
+                    expect(_.isArray(list)).toBeTruthy();
                     expect(list[0].get('box_id')).toBe(b64_sha1(jid));
                     expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
                 });
@@ -296,7 +296,7 @@
 
         describe("The \"settings\" API", function() {
             it("has methods 'get' and 'set' to set configuration settings", mock.initConverse(function (converse) {
-                expect(Object.keys(converse_api.settings)).toEqual(["get", "set"]);
+                expect(_.keys(converse_api.settings)).toEqual(["get", "set"]);
                 expect(converse_api.settings.get("play_sounds")).toBe(false);
                 converse_api.settings.set("play_sounds", true);
                 expect(converse_api.settings.get("play_sounds")).toBe(true);

+ 1 - 1
spec/headline.js

@@ -86,7 +86,7 @@
             waits(250);
             runs(function () {
                 expect(
-                    _.contains(
+                    _.includes(
                         converse.chatboxviews.keys(),
                         'notify.example.com')
                     ).toBeTruthy();

+ 1 - 1
spec/mam.js

@@ -447,7 +447,7 @@
                     .c('never').up();
                 converse.connection._dataRecv(test_utils.createRequest(stanza));
                 expect(feature.save).toHaveBeenCalled();
-                expect(feature.get('preferences').default).toBe('never');
+                expect(feature.get('preferences')['default']).toBe('never');
 
                 // Restore
                 converse.message_archiving = 'never';

+ 1 - 1
spec/minchats.js

@@ -39,7 +39,7 @@
             expect(chatview.model.get('minimized')).toBeTruthy();
             expect(converse.minimized_chats.$el.is(':visible')).toBeTruthy();
             expect(converse.minimized_chats.keys().length).toBe(2);
-            expect(_.contains(converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
+            expect(_.includes(converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
         }));
 
         it("can be toggled to hide or show minimized chats", mock.initConverse(function (converse) {

+ 4 - 3
spec/transcripts.js

@@ -1,15 +1,16 @@
 /*global converse */
 (function (root, factory) {
     define([
-        "jquery",
+        "converse-api",
         "mock",
         "test_utils",
         "utils",
         "transcripts"
         ], factory
     );
-} (this, function ($, mock, test_utils, utils, transcripts) {
+} (this, function (converse_api, mock, test_utils, utils, transcripts) {
     var _ = converse_api.env._;
+    var $ = converse_api.env.jQuery;
     var Strophe = converse_api.env.Strophe;
     var IGNORED_TAGS = [
         'stream:features',
@@ -63,7 +64,7 @@
                         if (el.nodeType === 3) {
                             return;  // Ignore text
                         }
-                        if (_.contains(IGNORED_TAGS, el.nodeName.toLowerCase())) {
+                        if (_.includes(IGNORED_TAGS, el.nodeName.toLowerCase())) {
                             return;
                         }
                         var _stanza = traverseElement(el);

+ 2 - 2
spec/utils.js

@@ -43,7 +43,7 @@
             expect(context.show_toolbar).toBeFalsy();
             expect(context.chatview_avatar_width).toBe(32);
             expect(context.chatview_avatar_height).toBe(48);
-            expect(Object.keys(context.visible_toolbar_buttons)).toEqual(Object.keys(settings.visible_toolbar_buttons));
+            expect(_.keys(context.visible_toolbar_buttons)).toEqual(_.keys(settings.visible_toolbar_buttons));
             expect(context.visible_toolbar_buttons.emoticons).toBeFalsy();
             expect(context.visible_toolbar_buttons.call).toBeFalsy();
             expect(context.visible_toolbar_buttons.toggle_occupants).toBeFalsy();
@@ -57,7 +57,7 @@
                 }
             };
             utils.applyUserSettings(context, settings, user_settings);
-            expect(Object.keys(context.visible_toolbar_buttons)).toEqual(Object.keys(settings.visible_toolbar_buttons));
+            expect(_.keys(context.visible_toolbar_buttons)).toEqual(_.keys(settings.visible_toolbar_buttons));
             expect(context.visible_toolbar_buttons.toggle_occupants).toBeTruthy();
         });
     });

+ 19 - 18
src/converse-api.js

@@ -47,10 +47,10 @@
                 },
                 'set': function (value, message) {
                     var data = {'status': value};
-                    if (!_.contains(_.keys(converse.STATUS_WEIGHTS), value)) {
+                    if (!_.includes(_.keys(converse.STATUS_WEIGHTS), value)) {
                         throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1');
                     }
-                    if (typeof message === "string") {
+                    if (_.isString(message)) {
                         data.status_message = message;
                     }
                     converse.xmppstatus.sendPresence(value);
@@ -68,17 +68,17 @@
         },
         'settings': {
             'get': function (key) {
-                if (_.contains(Object.keys(converse.default_settings), key)) {
+                if (_.includes(_.keys(converse.default_settings), key)) {
                     return converse[key];
                 }
             },
             'set': function (key, val) {
                 var o = {};
-                if (typeof key === "object") {
-                    _.assignIn(converse, _.pick(key, Object.keys(converse.default_settings)));
-                } else if (typeof key === "string") {
+                if (_.isObject(key)) {
+                    _.assignIn(converse, _.pick(key, _.keys(converse.default_settings)));
+                } else if (_.isString("string")) {
                     o[key] = val;
-                    _.assignIn(converse, _.pick(o, Object.keys(converse.default_settings)));
+                    _.assignIn(converse, _.pick(o, _.keys(converse.default_settings)));
                 }
             }
         },
@@ -91,15 +91,15 @@
                     }
                     return null;
                 };
-                if (typeof jids === "undefined") {
+                if (_.isUndefined(jids)) {
                     jids = converse.roster.pluck('jid');
-                } else if (typeof jids === "string") {
+                } else if (_.isString(jids)) {
                     return _transform(jids);
                 }
                 return _.map(jids, _transform);
             },
             'add': function (jid, name) {
-                if (typeof jid !== "string" || jid.indexOf('@') < 0) {
+                if (!_.isString(jid) || !_.includes(jid, '@')) {
                     throw new TypeError('contacts.add: invalid jid');
                 }
                 converse.roster.addAndSubscribe(jid, _.isEmpty(name)? jid: name);
@@ -108,10 +108,10 @@
         'chats': {
             'open': function (jids) {
                 var chatbox;
-                if (typeof jids === "undefined") {
+                if (_.isUndefined(jids)) {
                     converse.log("chats.open: You need to provide at least one JID", "error");
                     return null;
-                } else if (typeof jids === "string") {
+                } else if (_.isString(jids)) {
                     chatbox = converse.wrappedChatBox(
                         converse.chatboxes.getChatBox(jids, true).trigger('show')
                     );
@@ -125,7 +125,7 @@
                 });
             },
             'get': function (jids) {
-                if (typeof jids === "undefined") {
+                if (_.isUndefined(jids)) {
                     var result = [];
                     converse.chatboxes.each(function (chatbox) {
                         // FIXME: Leaky abstraction from MUC. We need to add a
@@ -135,13 +135,14 @@
                         }
                     });
                     return result;
-                } else if (typeof jids === "string") {
+                } else if (_.isString(jids)) {
                     return converse.wrappedChatBox(converse.chatboxes.getChatBox(jids));
                 }
                 return _.map(jids,
                     _.partial(
-                        _.compose(
-                            converse.wrappedChatBox.bind(converse), converse.chatboxes.getChatBox.bind(converse.chatboxes)
+                        _.flow(
+                            converse.chatboxes.getChatBox.bind(converse.chatboxes),
+                            converse.wrappedChatBox.bind(converse)
                         ), _, true
                     )
                 );
@@ -149,7 +150,7 @@
         },
         'tokens': {
             'get': function (id) {
-                if (!converse.expose_rid_and_sid || typeof converse.connection === "undefined") {
+                if (!converse.expose_rid_and_sid || _.isUndefined(converse.connection)) {
                     return null;
                 }
                 if (id.toLowerCase() === 'rid') {
@@ -170,7 +171,7 @@
                 converse.off(evt, handler);
             },
             'stanza': function (name, options, handler) {
-                if (typeof options === 'function') {
+                if (_.isFunction(options)) {
                     handler = options;
                     options = {};
                 } else {

+ 3 - 3
src/converse-bookmarks.js

@@ -207,7 +207,7 @@
                 model: converse.Bookmark,
 
                 initialize: function () {
-                    this.on('add', _.compose(this.markRoomAsBookmarked, this.openBookmarkedRoom));
+                    this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked));
                     this.on('remove', this.markRoomAsUnbookmarked, this);
                     this.on('remove', this.sendBookmarkStanza, this);
 
@@ -369,7 +369,7 @@
                     this.render();
                 },
 
-                render: function (cfg) {
+                render: function () {
                     this.$el.html(converse.templates.bookmarks_list({
                         'toggle_state': this.list_model.get('toggle-state'),
                         'desc_bookmarks': __('Click to toggle the bookmarks list'),
@@ -378,7 +378,7 @@
                     if (this.list_model.get('toggle-state') !== converse.OPENED) {
                         this.$('.bookmarks').hide();
                     }
-                    this.model.each(this.renderBookmarkListElement, this);
+                    this.model.each(this.renderBookmarkListElement.bind(this));
                     var controlboxview = converse.chatboxviews.get('controlbox');
                     if (!_.isUndefined(controlboxview)) {
                         this.$el.prependTo(controlboxview.$('#chatrooms'));

+ 25 - 21
src/converse-chatview.js

@@ -221,12 +221,12 @@
                      */
                     var that = this;
                     var insert = prepend ? this.$content.prepend : this.$content.append;
-                    _.compose(
-                        this.scrollDownMessageHeight.bind(this),
+                    _.flow(
                         function ($el) {
                             insert.call(that.$content, $el);
                             return $el;
-                        }
+                        },
+                        this.scrollDownMessageHeight.bind(this)
                     )(this.renderMessage(attrs));
                 },
 
@@ -282,18 +282,22 @@
                     msg_dates.push(current_msg_date);
                     msg_dates.sort();
                     idx = msg_dates.indexOf(current_msg_date)-1;
-                    _.compose(
-                            this.scrollDownMessageHeight.bind(this),
-                            function ($el) {
-                                $el.insertAfter(this.$content.find('.chat-message[data-isodate="'+msg_dates[idx]+'"]'));
-                                return $el;
-                            }.bind(this)
-                        )(this.renderMessage(attrs));
-                },
-
-                getExtraMessageTemplateAttributes: function (attrs) {
-                    // Provides a hook for sending more attributes to the
-                    // message template.
+                    _.flow(
+                        function ($el) {
+                            $el.insertAfter(this.$content.find('.chat-message[data-isodate="'+msg_dates[idx]+'"]'));
+                            return $el;
+                        }.bind(this),
+                        this.scrollDownMessageHeight.bind(this)
+                    )(this.renderMessage(attrs));
+                },
+
+                getExtraMessageTemplateAttributes: function () {
+                    /* Provides a hook for sending more attributes to the
+                     * message template.
+                     *
+                     * Parameters:
+                     *  (Object) attrs: An object containing message attributes.
+                     */
                     return {};
                 },
 
@@ -373,7 +377,7 @@
                         this.clear_status_timeout = window.setTimeout(this.clearStatusNotification.bind(this), 30000);
                     } else if (message.get('chat_state') === converse.PAUSED) {
                         this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing'));
-                    } else if (_.contains([converse.INACTIVE, converse.ACTIVE], message.get('chat_state'))) {
+                    } else if (_.includes([converse.INACTIVE, converse.ACTIVE], message.get('chat_state'))) {
                         this.$content.find('div.chat-event').remove();
                     } else if (message.get('chat_state') === converse.GONE) {
                         this.showStatusNotification(message.get('fullname')+' '+__('has gone away'));
@@ -435,7 +439,7 @@
                      * Parameters:
                      *    (Object) message - The message Backbone object that was added.
                      */
-                    if (typeof this.clear_status_timeout !== 'undefined') {
+                    if (!_.isUndefined(this.clear_status_timeout)) {
                         window.clearTimeout(this.clear_status_timeout);
                         delete this.clear_status_timeout;
                     }
@@ -544,7 +548,7 @@
                      *    (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
                      *    (Boolean) no_save - Just do the cleanup or setup but don't actually save the state.
                      */
-                    if (typeof this.chat_state_timeout !== 'undefined') {
+                    if (!_.isUndefined(this.chat_state_timeout)) {
                         window.clearTimeout(this.chat_state_timeout);
                         delete this.chat_state_timeout;
                     }
@@ -649,7 +653,7 @@
 
                 showStatusMessage: function (msg) {
                     msg = msg || this.model.get('status');
-                    if (typeof msg === "string") {
+                    if (_.isString(msg)) {
                         this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
                     }
                     return this;
@@ -757,12 +761,12 @@
                 },
 
                 show: function (focus) {
-                    if (typeof this.debouncedShow === 'undefined') {
+                    if (_.isUndefined(this.debouncedShow)) {
                         /* We wrap the method in a debouncer and set it on the
                          * instance, so that we have it debounced per instance.
                          * Debouncing it on the class-level is too broad.
                          */
-                        this.debouncedShow = _.debounce(this._show, 250, true);
+                        this.debouncedShow = _.debounce(this._show, 250, {'leading': true});
                     }
                     this.debouncedShow.apply(this, arguments);
                     return this;

+ 9 - 5
src/converse-controlbox.js

@@ -120,7 +120,7 @@
 
                 onChatBoxesFetched: function (collection, resp) {
                     this.__super__.onChatBoxesFetched.apply(this, arguments);
-                    if (!_.include(_.pluck(resp, 'id'), 'controlbox')) {
+                    if (!_.includes(_.map(resp, 'id'), 'controlbox')) {
                         this.add({
                             id: 'controlbox',
                             box_id: 'controlbox'
@@ -363,8 +363,12 @@
                     return this;
                 },
 
-                showHelpMessages: function (msgs) {
-                    // Override showHelpMessages in ChatBoxView, for now do nothing.
+                showHelpMessages: function () {
+                    /* Override showHelpMessages in ChatBoxView, for now do nothing.
+                     *
+                     * Parameters:
+                     *  (Array) msgs: Array of messages
+                     */
                     return;
                 }
             });
@@ -431,7 +435,7 @@
                     if (errors) { return; }
                     if (converse.locked_domain) {
                         jid = Strophe.escapeNode(jid) + '@' + converse.locked_domain;
-                    } else if (converse.default_domain && jid.indexOf('@') === -1) {
+                    } else if (converse.default_domain && !_.includes(jid, '@')) {
                         jid = jid + '@' + converse.default_domain;
                     }
                     this.connect($form, jid, password);
@@ -723,7 +727,7 @@
                 },
 
                 updateOnlineCount: _.debounce(function () {
-                    if (typeof converse.roster === 'undefined') {
+                    if (_.isUndefined(converse.roster)) {
                         return;
                     }
                     var $count = this.$('#online-count');

+ 31 - 31
src/converse-core.js

@@ -117,8 +117,8 @@
 
     converse.log = function (txt, level) {
         var logger;
-        if (typeof console === "undefined" || typeof console.log === "undefined") {
-            logger = { log: function () {}, error: function () {} };
+        if (_.isUndefined(console) || _.isUndefined(console.log)) {
+            logger = { log: _.noop, error: _.noop };
         } else {
             logger = console;
         }
@@ -134,11 +134,11 @@
 
     converse.initialize = function (settings, callback) {
         "use strict";
-        settings = typeof settings !== "undefined" ? settings : {};
+        settings = !_.isUndefined(settings) ? settings : {};
         var init_deferred = new $.Deferred();
         var converse = this;
 
-        if (typeof converse.chatboxes !== 'undefined') {
+        if (!_.isUndefined(converse.chatboxes)) {
             // Looks like converse.initialized was called again without logging
             // out or disconnecting in the previous session.
             // This happens in tests.
@@ -190,8 +190,8 @@
 
         // Detect support for the user's locale
         // ------------------------------------
-        var locales = typeof locales === "undefined" ? {} : locales;
-        this.isConverseLocale = function (locale) { return typeof locales[locale] !== "undefined"; };
+        var locales = _.isUndefined(locales) ? {} : locales;
+        this.isConverseLocale = function (locale) { return !_.isUndefined(locales[locale]); };
         this.isMomentLocale = function (locale) { return moment.locale() !== moment.locale(locale); };
         if (!moment.locale) { //moment.lang is deprecated after 2.8.1, use moment.locale instead
             moment.locale = moment.lang;
@@ -248,7 +248,7 @@
         };
         _.assignIn(this, this.default_settings);
         // Allow only whitelisted configuration attributes to be overwritten
-        _.assignIn(this, _.pick(settings, Object.keys(this.default_settings)));
+        _.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
 
         // BBB
         if (this.prebind === true) { this.authentication = converse.PREBIND; }
@@ -265,7 +265,7 @@
 
         // Module-level variables
         // ----------------------
-        this.callback = callback || function () {};
+        this.callback = callback || _.noop;
         /* When reloading the page:
          * For new sessions, we need to send out a presence stanza to notify
          * the server/network that we're online.
@@ -300,11 +300,14 @@
         };
 
         this.sendCSI = function (stat) {
-            /* Send out a Chat Status Notification (XEP-0352) */
-            if (converse.features[Strophe.NS.CSI] || true) {
-                converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
-                converse.inactive = (stat === converse.INACTIVE) ? true : false;
-            }
+            /* Send out a Chat Status Notification (XEP-0352)
+             *
+             * Parameters:
+             *  (String) stat: The user's chat status
+             */
+            // XXX if (converse.features[Strophe.NS.CSI] || true) {
+            converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
+            converse.inactive = (stat === converse.INACTIVE) ? true : false;
         };
 
         this.onUserActivity = function () {
@@ -400,7 +403,7 @@
         };
 
 
-        this.reconnect = _.debounce(function (condition) {
+        this.reconnect = _.debounce(function () {
             converse.log('The connection has dropped, attempting to reconnect.');
             converse.giveFeedback(
                 __("Reconnecting"),
@@ -547,7 +550,7 @@
 
         this.logOut = function () {
             converse.setDisconnectionCause(converse.LOGOUT, undefined, true);
-            if (typeof converse.connection !== 'undefined') {
+            if (!_.isUndefined(converse.connection)) {
                 converse.connection.disconnect();
             }
             converse.chatboxviews.closeAllChatBoxes();
@@ -659,7 +662,7 @@
         };
 
         this.unregisterPresenceHandler = function () {
-            if (typeof converse.presence_ref !== 'undefined') {
+            if (!_.isUndefined(converse.presence_ref)) {
                 converse.connection.deleteHandler(converse.presence_ref);
                 delete converse.presence_ref;
             }
@@ -748,7 +751,7 @@
 
         this.RosterContact = Backbone.Model.extend({
 
-            initialize: function (attributes, options) {
+            initialize: function (attributes) {
                 var jid = attributes.jid;
                 var bare_jid = Strophe.getBareJidFromJid(jid);
                 var resource = Strophe.getResourceFromJid(jid);
@@ -804,7 +807,7 @@
                 }));
             },
 
-            ackUnsubscribe: function (jid) {
+            ackUnsubscribe: function () {
                 /* Upon receiving the presence stanza of type "unsubscribed",
                  * the user SHOULD acknowledge receipt of that subscription state
                  * notification by sending a presence stanza of type "unsubscribe"
@@ -919,7 +922,7 @@
             },
 
             subscribeToSuggestedItems: function (msg) {
-                $(msg).find('item').each(function (i, items) {
+                $(msg).find('item').each(function () {
                     if (this.getAttribute('action') === 'add') {
                         converse.roster.addAndSubscribe(
                                 this.getAttribute('jid'), null, converse.xmppstatus.get('fullname'));
@@ -964,7 +967,7 @@
                 var iq = $iq({type: 'set'})
                     .c('query', {xmlns: Strophe.NS.ROSTER})
                     .c('item', { jid: jid, name: name });
-                _.map(groups, function (group) { iq.c('group').t(group).up(); });
+                _.each(groups, function (group) { iq.c('group').t(group).up(); });
                 converse.connection.sendIQ(iq, callback, errback);
             },
 
@@ -984,7 +987,7 @@
                 groups = groups || [];
                 name = _.isEmpty(name)? jid: name;
                 this.sendContactAddIQ(jid, name, groups,
-                    function (iq) {
+                    function () {
                         var contact = this.create(_.assignIn({
                             ask: undefined,
                             fullname: name,
@@ -1010,7 +1013,7 @@
                 if (item) {
                     resources = item.get('resources');
                     if (resources) {
-                        if (_.indexOf(resources, resource) === -1) {
+                        if (!_.includes(resources, resource)) {
                             resources.push(resource);
                             item.set({'resources': resources});
                         }
@@ -1044,7 +1047,7 @@
                     ignored = _.union(ignored, ['dnd', 'xa', 'away']);
                 }
                 for (i=0; i<models_length; i++) {
-                    if (_.indexOf(ignored, models[i].get('chat_status')) === -1) {
+                    if (!_.includes(ignored, models[i].get('chat_status'))) {
                         count++;
                     }
                 }
@@ -1107,13 +1110,10 @@
                  */
                 var jid = item.getAttribute('jid');
                 if (this.isSelf(jid)) { return; }
-                var groups = [],
+                var groups = _.map(item.getElementsByTagName('group'), Strophe.getText),
                     contact = this.get(jid),
                     ask = item.getAttribute("ask"),
                     subscription = item.getAttribute("subscription");
-                $.map(item.getElementsByTagName('group'), function (group) {
-                    groups.push(Strophe.getText(group));
-                });
                 if (!contact) {
                     if ((subscription === "none" && ask === null) || (subscription === "remove")) {
                         return; // We're lazy when adding contacts.
@@ -1245,7 +1245,7 @@
 
 
         this.RosterGroup = Backbone.Model.extend({
-            initialize: function (attributes, options) {
+            initialize: function (attributes) {
                 this.set(_.assignIn({
                     description: DESC_GROUP_TOGGLE,
                     state: converse.OPENED
@@ -1360,7 +1360,7 @@
                 };
             },
 
-            createMessage: function ($message, $delay, original_stanza) {
+            createMessage: function () {
                 return this.messages.create(this.getMessageAttributes.apply(this, arguments));
             }
         });
@@ -1617,8 +1617,8 @@
 
             constructPresence: function (type, status_message) {
                 var presence;
-                type = typeof type === 'string' ? type : (this.get('status') || converse.default_state);
-                status_message = typeof status_message === 'string' ? status_message : undefined;
+                type = _.isString(type) ? type : (this.get('status') || converse.default_state);
+                status_message = _.isString(status_message) ? status_message : undefined;
                 // Most of these presence types are actually not explicitly sent,
                 // but I add all of them here for reference and future proofing.
                 if ((type === 'unavailable') ||

+ 4 - 4
src/converse-dragresize.js

@@ -110,7 +110,7 @@
                      * We need this information for the drag-resizing feature.
                      */
                     var $flyout = this.$el.find('.box-flyout');
-                    if (typeof this.model.get('height') === 'undefined') {
+                    if (_.isUndefined(this.model.get('height'))) {
                         var height = $flyout.height();
                         var width = $flyout.width();
                         this.model.set('height', height);
@@ -212,7 +212,7 @@
                             this.setChatBoxHeight(this.height);
                         }
                     }
-                    if (converse.resizing.direction.indexOf('left') !== -1) {
+                    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');
@@ -292,9 +292,9 @@
                 * default_value. If value is close enough to
                 * default_value, then default_value is returned instead.
                 */
-                if (typeof value === 'undefined') {
+                if (_.isUndefined(value)) {
                     return undefined;
-                } else if (typeof default_value === 'undefined') {
+                } else if (_.isUndefined(default_value)) {
                     return value;
                 }
                 var resistance = 10;

+ 2 - 2
src/converse-headline.js

@@ -76,7 +76,7 @@
                 },
 
                 initialize: function () {
-                    if (typeof this.setDimensions !== "undefined") {
+                    if (!_.isUndefined(this.setDimensions)) {
                         // setDimensions is defined for dragresize
                         $(window).on('resize', _.debounce(this.setDimensions.bind(this), 100));
                     }
@@ -103,7 +103,7 @@
                                 )
                             )
                         );
-                    if (typeof this.setWidth !== "undefined") {
+                    if (!_.isUndefined(this.setWidth)) {
                         // setWidth is defined for dragresize
                         $(window).on('resize', _.debounce(this.setWidth.bind(this), 100));
                     }

+ 5 - 5
src/converse-mam.js

@@ -99,7 +99,7 @@
                     converse.queryForArchivedMessages(options, function (messages) {
                             this.clearSpinner();
                             if (messages.length) {
-                                _.map(messages, converse.chatboxes.onMessage.bind(converse.chatboxes));
+                                _.each(messages, converse.chatboxes.onMessage.bind(converse.chatboxes));
                             }
                         }.bind(this),
                         function () {
@@ -163,7 +163,7 @@
                  * get the next or previous page in the result set.
                  */
                 var date, messages = [];
-                if (typeof options === "function") {
+                if (_.isFunction(options)) {
                     callback = options;
                     errback = callback;
                 }
@@ -176,14 +176,14 @@
                 */
                 var queryid = converse.connection.getUniqueId();
                 var attrs = {'type':'set'};
-                if (typeof options !== "undefined" && options.groupchat) {
+                if (!_.isUndefined(options) && options.groupchat) {
                     if (!options['with']) {
                         throw new Error('You need to specify a "with" value containing the chat room JID, when querying groupchat messages.');
                     }
                     attrs.to = options['with'];
                 }
                 var stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid});
-                if (typeof options !== "undefined") {
+                if (!_.isUndefined(options)) {
                     stanza.c('x', {'xmlns':Strophe.NS.XFORM, 'type': 'submit'})
                             .c('field', {'var':'FORM_TYPE', 'type': 'hidden'})
                             .c('value').t(Strophe.NS.MAM).up().up();
@@ -209,7 +209,7 @@
                     }
                 }
 
-                if (typeof callback === "function") {
+                if (_.isFunction(callback)) {
                     converse.connection.addHandler(function (message) {
                         var $msg = $(message), rsm,
                             $fin = $msg.find('fin[xmlns="'+Strophe.NS.MAM+'"]');

+ 3 - 3
src/converse-minimize.js

@@ -255,7 +255,7 @@
                     }
                     var oldest_chat, boxes_width, view,
                         $minimized = converse.minimized_chats.$el,
-                        minimized_width = _.contains(this.model.pluck('minimized'), true) ? $minimized.outerWidth(true) : 0,
+                        minimized_width = _.includes(this.model.pluck('minimized'), true) ? $minimized.outerWidth(true) : 0,
                         new_id = newchat ? newchat.model.get('id') : null;
 
                     boxes_width = _.reduce(this.xget(new_id), function (memo, view) {
@@ -283,7 +283,7 @@
                     exclude_ids.push('controlbox');
                     var i = 0;
                     var model = this.model.sort().at(i);
-                    while (_.contains(exclude_ids, model.get('id')) ||
+                    while (_.includes(exclude_ids, model.get('id')) ||
                         model.get('minimized') === true) {
                         i++;
                         model = this.model.at(i);
@@ -370,7 +370,7 @@
                     this.model.messages.off('add',null,this);
                     this.remove();
                     this.model.maximize();
-                }, 200, true)
+                }, 200, {'leading': true})
             });
 
             converse.MinimizedChats = Backbone.Overview.extend({

+ 74 - 58
src/converse-muc.js

@@ -496,13 +496,15 @@
                     this.insertIntoTextArea(ev.target.textContent);
                 },
 
-                requestMemberList: function (affiliation) {
+                requestMemberList: function (chatroom_jid, affiliation) {
                     /* Send an IQ stanza to the server, asking it for the
                      * member-list of this room.
                      *
                      * See: http://xmpp.org/extensions/xep-0045.html#modifymember
                      *
                      * Parameters:
+                     *  (String) chatroom_jid: The JID of the chatroom for
+                     *      which the member-list is being requested
                      *  (String) affiliation: The specific member list to
                      *      fetch. 'admin', 'owner' or 'member'.
                      *
@@ -512,7 +514,7 @@
                      */
                     var deferred = new $.Deferred();
                     affiliation = affiliation || 'member';
-                    var iq = $iq({to: this.model.get('jid'), type: "get"})
+                    var iq = $iq({to: chatroom_jid, type: "get"})
                         .c("query", {xmlns: Strophe.NS.MUC_ADMIN})
                             .c("item", {'affiliation': affiliation});
                     converse.connection.sendIQ(iq, deferred.resolve, deferred.reject);
@@ -561,8 +563,8 @@
                      *  (Array) new_list: Array containing the new affiliations
                      *  (Array) old_list: Array containing the old affiliations
                      */
-                    var new_jids = _.pluck(new_list, 'jid');
-                    var old_jids = _.pluck(old_list, 'jid');
+                    var new_jids = _.map(new_list, 'jid');
+                    var old_jids = _.map(old_list, 'jid');
 
                     // Get the new affiliations
                     var delta = _.map(_.difference(new_jids, old_jids), function (jid) {
@@ -587,6 +589,30 @@
                     return delta;
                 },
 
+                sendAffiliationIQ: function (chatroom_jid, affiliation, member) {
+                    /* Send an IQ stanza specifying an affiliation change.
+                     *
+                     * Paremeters:
+                     *  (String) chatroom_jid: JID of the relevant room
+                     *  (String) affiliation: affiliation (could also be stored
+                     *      on the member object).
+                     *  (Object) member: Map containing the member's jid and
+                     *      optionally a reason and affiliation.
+                     */
+                    var deferred = new $.Deferred();
+                    var iq = $iq({to: chatroom_jid, type: "set"})
+                        .c("query", {xmlns: Strophe.NS.MUC_ADMIN})
+                        .c("item", {
+                            'affiliation': member.affiliation || affiliation,
+                            'jid': member.jid
+                        });
+                    if (!_.isUndefined(member.reason)) {
+                        iq.c("reason", member.reason);
+                    }
+                    converse.connection.sendIQ(iq, deferred.resolve, deferred.reject);
+                    return deferred;
+                },
+
                 setAffiliation: function (affiliation, members) {
                     /* Send IQ stanzas to the server to set an affiliation for
                      * the provided JIDs.
@@ -599,6 +625,7 @@
                      * Related ticket: https://prosody.im/issues/issue/795
                      *
                      * Parameters:
+                     *  (String) affiliation: The affiliation
                      *  (Object) members: A map of jids, affiliations and
                      *      optionally reasons. Only those entries with the
                      *      same affiliation as being currently set will be
@@ -615,21 +642,11 @@
                         return _.isUndefined(member.affiliation) ||
                                 member.affiliation === affiliation;
                     });
-                    var promises = _.map(members, function (member) {
-                        var deferred = new $.Deferred();
-                        var iq = $iq({to: this.model.get('jid'), type: "set"})
-                            .c("query", {xmlns: Strophe.NS.MUC_ADMIN})
-                            .c("item", {
-                                'affiliation': member.affiliation || affiliation,
-                                'jid': member.jid
-                            });
-                        if (!_.isUndefined(member.reason)) {
-                            iq.c("reason", member.reason);
-                        }
-                        converse.connection.sendIQ(iq, deferred.resolve, deferred.reject);
-                        return deferred;
-                    }, this);
-                    return $.when.apply($, promises);
+                    var promises = _.map(
+                        members,
+                        _.partial(this.sendAffiliationIQ, this.model.get('jid'), affiliation)
+                    );
+                    return $.when.apply($, promises); 
                 },
 
                 setAffiliations: function (members, onSuccess, onError) {
@@ -648,8 +665,8 @@
                         onSuccess(null);
                         return;
                     }
-                    var affiliations = _.uniq(_.pluck(members, 'affiliation'));
-                    var promises = _.map(affiliations, _.partial(this.setAffiliation, _, members), this);
+                    var affiliations = _.uniq(_.map(members, 'affiliation'));
+                    var promises = _.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members));
                     $.when.apply($, promises).done(onSuccess).fail(onError);
                 },
 
@@ -661,24 +678,20 @@
                      *  Any amount of XMLElement objects, representing the IQ
                      *  stanzas.
                      */
-                    return _.flatten(_.map(arguments, this.parseMemberListIQ));
+                    return _.flatMap(arguments, this.parseMemberListIQ);
                 },
 
                 getJidsWithAffiliations: function (affiliations) {
                     /* Returns a map of JIDs that have the affiliations
                      * as provided.
                      */
-                    if (typeof affiliations === "string") {
+                    if (_.isString(affiliations)) {
                         affiliations = [affiliations];
                     }
-                    var that = this;
                     var deferred = new $.Deferred();
-                    var promises = [];
-                    _.each(affiliations, function (affiliation) {
-                        promises.push(that.requestMemberList(affiliation));
-                    });
+                    var promises = _.map(affiliations, _.partial(this.requestMemberList, this.model.get('jid')));
                     $.when.apply($, promises).always(
-                        _.compose(deferred.resolve, this.marshallAffiliationIQs.bind(this))
+                        _.flow(this.marshallAffiliationIQs.bind(this), deferred.resolve)
                     );
                     return deferred.promise();
                 },
@@ -838,7 +851,7 @@
                 clearChatRoomMessages: function (ev) {
                     /* Remove all messages from the chat room UI.
                      */
-                    if (typeof ev !== "undefined") { ev.stopPropagation(); }
+                    if (!_.isUndefined(ev)) { ev.stopPropagation(); }
                     var result = confirm(__("Are you sure you want to clear the messages from this room?"));
                     if (result === true) {
                         this.$content.empty();
@@ -995,7 +1008,7 @@
                             room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
                         this.getRoomFeatures();
                     }
-                    _.compose(this.onChatRoomMessage.bind(this), this.showStatusMessages.bind(this))(stanza);
+                    _.flow(this.showStatusMessages.bind(this), this.onChatRoomMessage.bind(this))(stanza);
                     return true;
                 },
 
@@ -1354,7 +1367,7 @@
                     if (_.isUndefined(ev) && this.model.get('auto_configure')) {
                         this.fetchRoomConfiguration().then(that.autoConfigureChatRoom.bind(that));
                     } else {
-                        if (typeof ev !== 'undefined' && ev.preventDefault) {
+                        if (!_.isUndefined(ev) && ev.preventDefault) {
                             ev.preventDefault();
                         }
                         this.showSpinner();
@@ -1472,7 +1485,7 @@
                      */
                     this.$('.chatroom-body').children().addClass('hidden');
                     this.$('span.centered.spinner').remove();
-                    if (typeof message !== "string") {
+                    if (!_.isString(message)) {
                         message = '';
                     }
                     this.$('.chatroom-body').append(
@@ -1583,7 +1596,7 @@
                         'messages': _.reject(_.map(statuses, mapper), _.isUndefined),
                     };
                     // 2. Get disconnection messages based on the <status> elements
-                    var codes = _.map(statuses, function (stat) { return stat.getAttribute('code'); });
+                    var codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code');
                     var disconnection_codes = _.intersection(codes, _.keys(converse.muc.disconnect_messages));
                     var disconnected = is_self && disconnection_codes.length > 0;
                     if (disconnected) {
@@ -1649,10 +1662,12 @@
 
                     // Unfortunately this doesn't work (returns empty list)
                     // var elements = stanza.querySelectorAll('x[xmlns="'+Strophe.NS.MUC_USER+'"]');
-                    var elements = _.chain(stanza.querySelectorAll('x')).filter(function (x) {
-                        return x.getAttribute('xmlns') === Strophe.NS.MUC_USER;
-                    }).value();
-
+                    var elements = _.filter(
+                        stanza.querySelectorAll('x'),
+                        function (x) {
+                            return x.getAttribute('xmlns') === Strophe.NS.MUC_USER;
+                        }
+                    );
                     var notifications = _.map(
                         elements,
                         _.partial(this.parseXUserElement.bind(this), _, stanza, is_self)
@@ -1845,7 +1860,7 @@
                         function (messages) {
                             that.clearSpinner();
                             if (messages.length) {
-                                _.map(messages, that.onChatRoomMessage.bind(that));
+                                _.each(messages, that.onChatRoomMessage.bind(that));
                             }
                         },
                         function () {
@@ -2027,11 +2042,12 @@
                     }, {
                         name: 'contacts-dataset',
                         source: function (q, cb) {
-                            var results = [];
-                            _.each(converse.roster.filter(utils.contains(['fullname', 'jid'], q)), function (n) {
-                                results.push({value: n.get('fullname'), jid: n.get('jid')});
-                            });
-                            cb(results);
+                            cb(_.map(
+                                converse.roster.filter(utils.contains(['fullname', 'jid'], q)),
+                                function (n) {
+                                    return {value: n.get('fullname'), jid: n.get('jid')};
+                                }
+                            ));
                         },
                         templates: {
                             suggestion: _.template('<p data-jid="{{jid}}">{{value}}</p>')
@@ -2331,7 +2347,7 @@
                         'box_id': b64_sha1(room_jid),
                         'password': $x.attr('password')
                     });
-                    if (!_.contains(
+                    if (!_.includes(
                                 [Strophe.Status.CONNECTING, Strophe.Status.CONNECTED],
                                 chatroom.get('connection_status'))
                             ) {
@@ -2359,9 +2375,9 @@
                  * settings).
                  */
                 _.each(converse.auto_join_rooms, function (room) {
-                    if (typeof room === 'string') {
+                    if (_.isString(room)) {
                         converse_api.rooms.open(room);
-                    } else if (typeof room === 'object') {
+                    } else if (_.isObject(room)) {
                         converse_api.rooms.open(room.jid, room.nick);
                     } else {
                         converse.log('Invalid room criteria specified for "auto_join_rooms"', 'error');
@@ -2388,26 +2404,26 @@
             _.extend(converse_api, {
                 'rooms': {
                     'close': function (jids) {
-                        if (typeof jids === "undefined") {
+                        if (_.isUndefined(jids)) {
                             converse.chatboxviews.each(function (view) {
                                 if (view.is_chatroom && view.model) {
                                     view.close();
                                 }
                             });
-                        } else if (typeof jids === "string") {
+                        } else if (_.isString(jids)) {
                             var view = converse.chatboxviews.get(jids);
                             if (view) { view.close(); }
                         } else {
-                            _.map(jids, function (jid) {
+                            _.each(jids, function (jid) {
                                 var view = converse.chatboxviews.get(jid);
                                 if (view) { view.close(); }
                             });
                         }
                     },
                     'open': function (jids, attrs) {
-                        if (typeof attrs === "string") {
+                        if (_.isString(attrs)) {
                             attrs = {'nick': attrs};
-                        } else if (typeof attrs === "undefined") {
+                        } else if (_.isUndefined(attrs)) {
                             attrs = {};
                         }
                         if (_.isUndefined(attrs.maximize)) {
@@ -2416,20 +2432,20 @@
                         if (!attrs.nick && converse.muc_nickname_from_jid) {
                             attrs.nick = Strophe.getNodeFromJid(converse.bare_jid);
                         }
-                        if (typeof jids === "undefined") {
+                        if (_.isUndefined(jids)) {
                             throw new TypeError('rooms.open: You need to provide at least one JID');
-                        } else if (typeof jids === "string") {
+                        } else if (_.isString(jids)) {
                             return converse.getWrappedChatRoom(jids, attrs, converse.createChatRoom);
                         }
                         return _.map(jids, _.partial(converse.getWrappedChatRoom, _, attrs, converse.createChatRoom));
                     },
                     'get': function (jids, attrs, create) {
-                        if (typeof attrs === "string") {
+                        if (_.isString(attrs)) {
                             attrs = {'nick': attrs};
-                        } else if (typeof attrs === "undefined") {
+                        } else if (_.isUndefined(attrs)) {
                             attrs = {};
                         }
-                        if (typeof jids === "undefined") {
+                        if (_.isUndefined(jids)) {
                             var result = [];
                             converse.chatboxes.each(function (chatbox) {
                                 if (chatbox.get('type') === 'chatroom') {
@@ -2442,7 +2458,7 @@
                         if (!attrs.nick) {
                             attrs.nick = Strophe.getNodeFromJid(converse.bare_jid);
                         }
-                        if (typeof jids === "string") {
+                        if (_.isString(jids)) {
                             return converse.getWrappedChatRoom(jids, attrs, fetcher);
                         }
                         return _.map(jids, _.partial(converse.getWrappedChatRoom, _, attrs, fetcher));

+ 6 - 6
src/converse-notification.js

@@ -68,7 +68,7 @@
                     return false;
                 }
                 var mentioned = (new RegExp("\\b"+room.get('nick')+"\\b")).test($body.text());
-                notify_all = notify_all === true || (_.isArray(notify_all) && _.contains(notify_all, room_jid));
+                notify_all = notify_all === true || (_.isArray(notify_all) && _.includes(notify_all, room_jid));
                 if (sender === room.get('nick') || (!notify_all && !mentioned)) {
                     return false;
                 }
@@ -102,7 +102,7 @@
                 // feature, but no browser currently supports it.
                 // https://developer.mozilla.org/en-US/docs/Web/API/notification/sound
                 var audio;
-                if (converse.play_sounds && typeof Audio !== "undefined") {
+                if (converse.play_sounds && !_.isUndefined(Audio)) {
                     audio = new Audio(converse.sounds_path+"msg_received.ogg");
                     if (audio.canPlayType('/audio/ogg')) {
                         audio.play();
@@ -130,7 +130,7 @@
                  */
                 var n, title, contact_jid, roster_item,
                     from_jid = $message.attr('from');
-                if ($message.attr('type') === 'headline' || from_jid.indexOf('@') === -1) {
+                if ($message.attr('type') === 'headline' || !_.includes(from_jid, '@')) {
                     // XXX: 2nd check is workaround for Prosody which doesn't
                     // give type "headline"
                     title = __(___("Notification from %1$s"), from_jid);
@@ -138,7 +138,7 @@
                     if ($message.attr('type') === 'groupchat') {
                         title = __(___("%1$s says"), Strophe.getResourceFromJid(from_jid));
                     } else {
-                        if (typeof converse.roster === 'undefined') {
+                        if (_.isUndefined(converse.roster)) {
                             converse.log("Could not send notification, because roster is undefined", "error");
                             return;
                         }
@@ -159,7 +159,7 @@
                 /* Creates an HTML5 Notification to inform of a change in a
                  * contact's chat state.
                  */
-                if (_.contains(converse.chatstate_notification_blacklist, contact.jid)) {
+                if (_.includes(converse.chatstate_notification_blacklist, contact.jid)) {
                     // Don't notify if the user is being ignored.
                     return;
                 }
@@ -243,7 +243,7 @@
 
             converse.requestPermission = function (evt) {
                 if (converse.supports_html5_notification &&
-                    ! _.contains(['denied', 'granted'], Notification.permission)) {
+                    ! _.includes(['denied', 'granted'], Notification.permission)) {
                     // Ask user to enable HTML5 notifications
                     Notification.requestPermission();
                 }

+ 15 - 16
src/converse-otr.js

@@ -30,14 +30,13 @@
     // For translations
     var __ = utils.__.bind(converse);
 
-    var HAS_CSPRNG = ((typeof crypto !== 'undefined') &&
-        ((typeof crypto.randomBytes === 'function') ||
-            (typeof crypto.getRandomValues === 'function')
+    var HAS_CSPRNG = ((!_.isUndefined(crypto)) &&
+        ((_.isFunction(crypto.randomBytes)) || (_.isFunction(crypto.getRandomValues))
     ));
     var HAS_CRYPTO = HAS_CSPRNG && (
-        (typeof CryptoJS !== "undefined") &&
-        (typeof otr.OTR !== "undefined") &&
-        (typeof otr.DSA !== "undefined")
+        (!_.isUndefined(CryptoJS)) &&
+        (!_.isUndefined(otr.OTR)) &&
+        (!_.isUndefined(otr.DSA))
     );
 
     var UNENCRYPTED = 0;
@@ -105,7 +104,7 @@
                      * "visible" OTR messages being exchanged.
                      */
                     return this.__super__.shouldPlayNotification.apply(this, arguments) &&
-                        !(utils.isOTRMessage($message[0]) && !_.contains([UNVERIFIED, VERIFIED], this.get('otr_status')));
+                        !(utils.isOTRMessage($message[0]) && !_.includes([UNVERIFIED, VERIFIED], this.get('otr_status')));
                 },
 
                 createMessage: function ($message, $delay, original_stanza) {
@@ -119,7 +118,7 @@
                     if (text.match(/^\?OTRv23?/)) {
                         this.initiateOTR(text);
                     } else {
-                        if (_.contains([UNVERIFIED, VERIFIED], this.get('otr_status'))) {
+                        if (_.includes([UNVERIFIED, VERIFIED], this.get('otr_status'))) {
                             this.otr.receiveMsg(text);
                         } else {
                             if (text.match(/^\?OTR/)) {
@@ -142,11 +141,11 @@
                     var pass, instance_tag, saved_key, pass_check;
                     if (converse.cache_otr_key) {
                         pass = converse.otr.getSessionPassphrase();
-                        if (typeof pass !== "undefined") {
+                        if (!_.isUndefined(pass)) {
                             instance_tag = window.sessionStorage[b64_sha1(this.id+'instance_tag')];
                             saved_key = window.sessionStorage[b64_sha1(this.id+'priv_key')];
                             pass_check = window.sessionStorage[b64_sha1(this.connection.jid+'pass_check')];
-                            if (saved_key && instance_tag && typeof pass_check !== 'undefined') {
+                            if (saved_key && instance_tag && !_.isUndefined(pass_check)) {
                                 var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass);
                                 var key = otr.DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1));
                                 if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') {
@@ -285,7 +284,7 @@
                     this.model.on('showReceivedOTRMessage', function (text) {
                         this.showMessage({'message': text, 'sender': 'them'});
                     }, this);
-                    if ((_.contains([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) || converse.use_otr_by_default) {
+                    if ((_.includes([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) || converse.use_otr_by_default) {
                         this.model.initiateOTR();
                     }
                 },
@@ -319,7 +318,7 @@
                             return this.model.initiateOTR();
                         }
                     }
-                    if (_.contains([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) {
+                    if (_.includes([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) {
                         // Off-the-record encryption is active
                         this.model.otr.sendMsg(text);
                         this.model.trigger('showSentOTRMessage', text);
@@ -372,7 +371,7 @@
                 },
 
                 endOTR: function (ev) {
-                    if (typeof ev !== "undefined") {
+                    if (!_.isUndefined(ev)) {
                         ev.preventDefault();
                         ev.stopPropagation();
                     }
@@ -485,7 +484,7 @@
             };
             _.extend(converse.default_settings, settings);
             _.extend(converse, settings);
-            _.extend(converse, _.pick(converse.user_settings, Object.keys(settings)));
+            _.extend(converse, _.pick(converse.user_settings, _.keys(settings)));
 
             // Only allow OTR if we have the capability
             converse.allow_otr = converse.allow_otr && HAS_CRYPTO;
@@ -500,7 +499,7 @@
                     if (converse.authentication === 'prebind') {
                         var key = b64_sha1(converse.connection.jid),
                             pass = window.sessionStorage[key];
-                        if (typeof pass === 'undefined') {
+                        if (_.isUndefined(pass)) {
                             pass = Math.floor(Math.random()*4294967295).toString();
                             window.sessionStorage[key] = pass;
                         }
@@ -516,7 +515,7 @@
                     if (converse.cache_otr_key) {
                         var cipher = CryptoJS.lib.PasswordBasedCipher;
                         var pass = this.getSessionPassphrase();
-                        if (typeof pass !== "undefined") {
+                        if (!_.isUndefined(pass)) {
                             // Encrypt the key and set in sessionStorage. Also store instance tag.
                             window.sessionStorage[b64_sha1(jid+'priv_key')] =
                                 cipher.encrypt(CryptoJS.algo.AES, key.packPrivate(), pass).toString();

+ 4 - 4
src/converse-ping.js

@@ -42,12 +42,12 @@
                 //
                 // var feature = converse.features.findWhere({'var': Strophe.NS.PING});
                 converse.lastStanzaDate = new Date();
-                if (typeof jid === 'undefined' || jid === null) {
+                if (_.isNil(jid)) {
                     jid = Strophe.getDomainFromJid(converse.bare_jid);
                 }
-                if (typeof timeout === 'undefined' ) { timeout = null; }
-                if (typeof success === 'undefined' ) { success = null; }
-                if (typeof error === 'undefined' ) { error = null; }
+                if (_.isUndefined(timeout) ) { timeout = null; }
+                if (_.isUndefined(success) ) { success = null; }
+                if (_.isUndefined(error) ) { error = null; }
                 if (converse.connection) {
                     converse.connection.ping.ping(jid, success, error, timeout);
                     return true;

+ 4 - 4
src/converse-register.js

@@ -51,7 +51,7 @@
 
     // Add Strophe Statuses
     var i = 0;
-    Object.keys(Strophe.Status).forEach(function (key) {
+    _.each(_.keys(Strophe.Status), function (key) {
         i = Math.max(i, Strophe.Status[key]);
     });
     Strophe.Status.REGIFAIL        = i + 1;
@@ -211,7 +211,7 @@
                     };
                     _.extend(this, defaults);
                     if (settings) {
-                        _.extend(this, _.pick(settings, Object.keys(defaults)));
+                        _.extend(this, _.pick(settings, _.keys(defaults)));
                     }
                 },
 
@@ -254,7 +254,7 @@
                 onRegistering: function (status, error) {
                     var that;
                     converse.log('onRegistering');
-                    if (_.contains([
+                    if (_.includes([
                                 Strophe.Status.DISCONNECTED,
                                 Strophe.Status.CONNFAIL,
                                 Strophe.Status.REGIFAIL,
@@ -320,7 +320,7 @@
                         }.bind(this));
                     } else {
                         // Show fields
-                        _.each(Object.keys(this.fields), function (key) {
+                        _.each(_.keys(this.fields), function (key) {
                             if (key === "username") {
                                 $input = converse.templates.form_username({
                                     domain: ' @'+this.domain,

+ 9 - 8
src/converse-rosterview.js

@@ -110,8 +110,8 @@
                 a = a.get('name');
                 b = b.get('name');
                 var special_groups = _.keys(HEADER_WEIGHTS);
-                var a_is_special = _.contains(special_groups, a);
-                var b_is_special = _.contains(special_groups, b);
+                var a_is_special = _.includes(special_groups, a);
+                var 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) {
@@ -345,7 +345,7 @@
                     query = query.toLowerCase();
                     if (type === 'groups') {
                         _.each(this.getAll(), function (view, idx) {
-                            if (view.model.get('name').toLowerCase().indexOf(query.toLowerCase()) === -1) {
+                            if (!_.includes(view.model.get('name').toLowerCase(), query.toLowerCase())) {
                                 view.hide();
                             } else if (view.model.contacts.length > 0) {
                                 view.show();
@@ -407,7 +407,7 @@
                     if (_.has(contact.changed, 'subscription')) {
                         if (contact.changed.subscription === 'from') {
                             this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
-                        } else if (_.contains(['both', 'to'], contact.get('subscription'))) {
+                        } else if (_.includes(['both', 'to'], contact.get('subscription'))) {
                             this.addExistingContact(contact);
                         }
                     }
@@ -550,6 +550,7 @@
                 },
 
                 render: function () {
+                    var that = this;
                     if (!this.mayBeShown()) {
                         this.$el.hide();
                         return this;
@@ -568,10 +569,10 @@
 
                     _.each(classes_to_remove,
                         function (cls) {
-                            if (this.el.className.indexOf(cls) !== -1) {
-                                this.$el.removeClass(cls);
+                            if (_.includes(that.el.className, cls)) {
+                                that.$el.removeClass(cls);
                             }
-                        }, this);
+                        });
                     this.$el.addClass(chat_status).data('status', chat_status);
 
                     if ((ask === 'subscribe') || (subscription === 'from')) {
@@ -861,7 +862,7 @@
                 },
 
                 onContactGroupChange: function (contact) {
-                    var in_this_group = _.contains(contact.get('groups'), this.model.get('name'));
+                    var in_this_group = _.includes(contact.get('groups'), this.model.get('name'));
                     var cid = contact.get('id');
                     var in_this_overview = !this.get(cid);
                     if (in_this_group && !in_this_overview) {

+ 1 - 0
src/converse.js

@@ -3,6 +3,7 @@
  * This file is used to tell require.js which components (or plugins) to load
  * when it generates a build.
  */
+/*global define */
 
 if (typeof define !== 'undefined') {
     /* When running tests, define is not defined. */

+ 1 - 0
src/jquery-private.js

@@ -1,3 +1,4 @@
+/*global define */
 define(['jquery'], function (jq) {
     return jq.noConflict( true );
 });

+ 0 - 25
src/jquery.eventemitter.js

@@ -1,25 +0,0 @@
-/*global $ */
-(function (root, factory) {
-    if (typeof define === 'function' && define.amd) {
-        define("converse", ["jquery"], function($) {
-            return factory($);
-        });
-    } else {
-        factory($);
-    }
-}(this, function ($) {
-    $.eventEmitter = {
-        emit: function(evt, data) {
-            $(this).trigger(evt, data);
-        },
-        once: function(evt, handler) {
-            $(this).one(evt, handler);
-        },
-        on: function(evt, handler) {
-            $(this).bind(evt, handler);
-        },
-        off: function(evt, handler) {
-            $(this).unbind(evt, handler);
-        }
-    };
-}));

+ 1 - 0
src/locales.js

@@ -6,6 +6,7 @@
  *
  * See also src/moment_locales.js
  */
+/*global define */
 (function (root, factory) {
     define("locales", ['jed',
         'text!af',

+ 6 - 6
src/utils.js

@@ -1,4 +1,4 @@
-/*global escape, locales, Jed */
+/*global define, escape, locales, Jed */
 (function (root, factory) {
     define([
         "jquery",
@@ -244,8 +244,8 @@
                 // check if an @ signal is included, and if not, we assume it's
                 // a headline message.
                 (   $message.attr('type') !== 'error' &&
-                    typeof from_jid !== 'undefined' &&
-                    from_jid.indexOf('@') === -1
+                    !_.isUndefined(from_jid) &&
+                    !_.includes(from_jid, '@')
                 )) {
                 return true;
             }
@@ -326,11 +326,11 @@
                 if (typeof attr === 'object') {
                     var value = false;
                     _.forEach(attr, function (a) {
-                        value = value || item.get(a).toLowerCase().indexOf(query.toLowerCase()) !== -1;
+                        value = value || _.includes(item.get(a).toLowerCase(), query.toLowerCase());
                     });
                     return value;
                 } else if (typeof attr === 'string') {
-                    return item.get(attr).toLowerCase().indexOf(query.toLowerCase()) !== -1;
+                    return _.includes(item.get(attr).toLowerCase(), query.toLowerCase());
                 } else {
                     throw new TypeError('contains: wrong attribute type. Must be string or array.');
                 }
@@ -360,7 +360,7 @@
                     options.push(tpl_select_option({
                         value: value,
                         label: $($options[j]).attr('label'),
-                        selected: (values.indexOf(value) >= 0),
+                        selected: _.startsWith(values, value),
                         required: $field.find('required').length
                     }));
                 }