Browse Source

Merge branch 'master' of github.com:jcbrand/converse.js

JC Brand 8 years ago
parent
commit
a717481b2c
100 changed files with 94358 additions and 84767 deletions
  1. 9 0
      .babelrc
  2. 5 2
      .eslintrc.json
  3. 10 6
      .gitignore
  4. 4 2
      3rdparty/lodash.fp.js
  5. 45 1
      CHANGES.md
  6. 12 0
      COPYRIGHT
  7. 48 30
      Makefile
  8. 0 13
      bower.json
  9. 3 0
      builds/README.rst
  10. 238 154
      css/converse.css
  11. 226 161
      css/inverse.css
  12. 1 1
      css/theme.css
  13. 10 11
      demo/without_bundled_dependencies.html
  14. 26281 25118
      dist/converse-mobile.js
  15. 1125 1739
      dist/converse-no-dependencies.js
  16. 26285 25122
      dist/converse.js
  17. 26285 25122
      dist/inverse.js
  18. 0 0
      dist/locales.js
  19. 1 1
      docs/source/_templates/layout.html
  20. 2 2
      docs/source/conf.py
  21. 27 6
      docs/source/configuration.rst
  22. 23 22
      docs/source/developer_api.rst
  23. 154 8
      docs/source/events.rst
  24. 3 1
      docs/source/plugin_development.rst
  25. 2 32
      index.html
  26. 2 2
      inverse.html
  27. 14 82
      locale/af/LC_MESSAGES/converse.json
  28. 212 266
      locale/af/LC_MESSAGES/converse.po
  29. 14 46
      locale/ca/LC_MESSAGES/converse.json
  30. 210 273
      locale/ca/LC_MESSAGES/converse.po
  31. 208 264
      locale/converse.pot
  32. 14 82
      locale/de/LC_MESSAGES/converse.json
  33. 212 267
      locale/de/LC_MESSAGES/converse.po
  34. 12 48
      locale/es/LC_MESSAGES/converse.json
  35. 208 270
      locale/es/LC_MESSAGES/converse.po
  36. 12 76
      locale/fr/LC_MESSAGES/converse.json
  37. 212 268
      locale/fr/LC_MESSAGES/converse.po
  38. 13 49
      locale/he/LC_MESSAGES/converse.json
  39. 209 271
      locale/he/LC_MESSAGES/converse.po
  40. 14 50
      locale/hu/LC_MESSAGES/converse.json
  41. 210 272
      locale/hu/LC_MESSAGES/converse.po
  42. 12 44
      locale/id/LC_MESSAGES/converse.json
  43. 208 271
      locale/id/LC_MESSAGES/converse.po
  44. 16 80
      locale/it/LC_MESSAGES/converse.json
  45. 212 267
      locale/it/LC_MESSAGES/converse.po
  46. 12 44
      locale/ja/LC_MESSAGES/converse.json
  47. 208 271
      locale/ja/LC_MESSAGES/converse.po
  48. 13 49
      locale/nb/LC_MESSAGES/converse.json
  49. 209 271
      locale/nb/LC_MESSAGES/converse.po
  50. 12 44
      locale/nl/LC_MESSAGES/converse.json
  51. 208 271
      locale/nl/LC_MESSAGES/converse.po
  52. 14 50
      locale/pl/LC_MESSAGES/converse.json
  53. 187 245
      locale/pl/LC_MESSAGES/converse.po
  54. 12 44
      locale/pt_BR/LC_MESSAGES/converse.json
  55. 208 271
      locale/pt_BR/LC_MESSAGES/converse.po
  56. 16 44
      locale/ru/LC_MESSAGES/converse.json
  57. 210 274
      locale/ru/LC_MESSAGES/converse.po
  58. 13 49
      locale/uk/LC_MESSAGES/converse.json
  59. 209 271
      locale/uk/LC_MESSAGES/converse.po
  60. 12 44
      locale/zh/LC_MESSAGES/converse.json
  61. 208 271
      locale/zh/LC_MESSAGES/converse.po
  62. 6976 0
      package-lock.json
  63. 8 3
      package.json
  64. 103 78
      sass/_chatbox.scss
  65. 17 11
      sass/_chatrooms.scss
  66. 17 11
      sass/_controlbox.scss
  67. 11 3
      sass/_core.scss
  68. 45 0
      sass/converse/_chatbox.scss
  69. 9 1
      sass/converse/_variables.scss
  70. 15 3
      sass/inverse/_chatbox.scss
  71. 4 3
      sass/inverse/_chatrooms.scss
  72. 6 1
      sass/inverse/_variables.scss
  73. 56 23
      spec/bookmarks.js
  74. 334 169
      spec/chatbox.js
  75. 524 435
      spec/chatroom.js
  76. 238 52
      spec/controlbox.js
  77. 39 29
      spec/converse.js
  78. 166 2
      spec/disco.js
  79. 7 2
      spec/headline.js
  80. 34 29
      spec/mam.js
  81. 41 20
      spec/minchats.js
  82. 90 68
      spec/notification.js
  83. 14 5
      spec/otr.js
  84. 12 2
      spec/ping.js
  85. 6 1
      spec/presence.js
  86. 50 15
      spec/protocol.js
  87. 2 3
      spec/register.js
  88. 71 57
      spec/roomslist.js
  89. 2 2
      spec/transcripts.js
  90. 3 3
      spec/utils.js
  91. 2 3
      spec/xmppstatus.js
  92. 9 0
      src/build-esnext.js
  93. 9 0
      src/build-inverse-esnext.js
  94. 22 0
      src/build-inverse.js
  95. 26 2
      src/build-no-dependencies.js
  96. 23 1
      src/build.js
  97. 21 8
      src/config.js
  98. 116 97
      src/converse-bookmarks.js
  99. 318 176
      src/converse-chatview.js
  100. 138 134
      src/converse-controlbox.js

+ 9 - 0
.babelrc

@@ -0,0 +1,9 @@
+{
+  "presets": [
+		["env", {
+			"targets": {
+				"browsers": ["last 2 versions", "safari >= 10", "IE 11"]
+			}
+		}]
+  ]
+}

+ 5 - 2
.eslintrc.json

@@ -1,4 +1,7 @@
 {
 {
+    "parserOptions": {
+        "ecmaVersion": 6
+    },
     "env": {
     "env": {
         "browser": true,
         "browser": true,
         "jasmine": true
         "jasmine": true
@@ -15,7 +18,7 @@
         "lodash/prefer-lodash-method": [2, {
         "lodash/prefer-lodash-method": [2, {
             "ignoreMethods": [
             "ignoreMethods": [
                 "find", "endsWith", "startsWith", "filter", "reduce",
                 "find", "endsWith", "startsWith", "filter", "reduce",
-                "map", "replace", "toLower", "split", "trim"
+                "map", "replace", "toLower", "split", "trim", "forEach", "toUpperCase"
             ]
             ]
         }],
         }],
         "lodash/prefer-startswith": "off",
         "lodash/prefer-startswith": "off",
@@ -56,7 +59,7 @@
         "dot-notation": [
         "dot-notation": [
             "error",
             "error",
             {
             {
-                "allowKeywords": false
+                "allowKeywords": true
             }
             }
         ],
         ],
         "eol-last": "error",
         "eol-last": "error",

+ 10 - 6
.gitignore

@@ -9,16 +9,24 @@
 .svn/
 .svn/
 .project
 .project
 .pydevproject
 .pydevproject
+.idea
+.su?
+builds/*
+
 analytics.js
 analytics.js
 inverse-analytics.js
 inverse-analytics.js
-.idea
+
+# python/buildout
 eggs
 eggs
 .Python
 .Python
+build
+parts
+*.pyc
+*.egg-info
 
 
 dev-jc.html
 dev-jc.html
 inverse-dev.html
 inverse-dev.html
 inverse-dev-jc.html
 inverse-dev-jc.html
-
 converse-logs/*.html
 converse-logs/*.html
 
 
 # Ruby/Sass/Bundler
 # Ruby/Sass/Bundler
@@ -30,7 +38,6 @@ bourbon
 Backbone.Overview
 Backbone.Overview
 tags
 tags
 stamp-npm
 stamp-npm
-stamp-bower
 stamp-bundler
 stamp-bundler
 
 
 # Sphinx
 # Sphinx
@@ -50,8 +57,5 @@ develop-eggs
 .DS_Store
 .DS_Store
 
 
 # Builds
 # Builds
-dist/*
-css/*.map
-css/*.min.css
 .sv?
 .sv?
 /vendor/
 /vendor/

+ 4 - 2
3rdparty/lodash.fp.js

@@ -69,7 +69,9 @@ return /******/ (function(modules) { // webpackBootstrap
 	}
 	}
 
 
 	if (typeof _ == 'function' && typeof _.runInContext == 'function') {
 	if (typeof _ == 'function' && typeof _.runInContext == 'function') {
-	  _ = browserConvert(_.runInContext());
+      // XXX: Customization in order to be able to run both _ and fp in the
+      // non-AMD usecase.
+	  fp = browserConvert(_.runInContext());
 	}
 	}
 	module.exports = browserConvert;
 	module.exports = browserConvert;
 
 
@@ -1037,4 +1039,4 @@ return /******/ (function(modules) { // webpackBootstrap
 /***/ }
 /***/ }
 /******/ ])
 /******/ ])
 });
 });
-;
+;

+ 45 - 1
CHANGES.md

@@ -1,6 +1,50 @@
 # Changelog
 # Changelog
 
 
-## 3.1.1 ((2017-07-12))
+## 3.2.0 (2017-08-09)
+
+### New Plugins
+- New plugin `converse-disco` which replaces the original support for
+  [XEP-0030](https://xmpp.org/extensions/xep-0030.html) and which has been
+  refactored to allow features for multiple entities to be stored.
+
+### New features and improvements
+- Add support for Emojis (either native, or via <a href="https://www.emojione.com/">Emojione</a>).
+- Add JID validation to the contact add form, the occupant invite form and the login form.
+- #896 Consistently use `XMPP username` in user-facing text (instead of JID, Jabber ID etc.).
+
+### New configuration settings
+* The `visible_toolbar_buttons.emoticons` configuration option is now changed to `visible_toolbar_buttons.emoji`.
+* [use_emojione](https://conversejs.org/docs/html/configurations.html#use-emojione)
+  is used to determine whether Emojione should be used to render emojis,
+  otherwise rendering falls back to native browser or OS support.
+* [emojione_image_path](https://conversejs.org/docs/html/configurations.html#emojione-image-path)
+  is used to specify from where Emojione will load images for rendering emojis.
+
+### New events
+* ['discoInitialized'](https://conversejs.org/docs/html/development.html#discoInitialized)
+* ['afterMessagesFetched'](https://conversejs.org/docs/html/development.html#afterMessagesFetched)
+
+### Code changes
+- Removed jQuery from `converse-core`, `converse-vcard` and `converse-roomslist`.
+- Remove `jquery.easing` from the full build. Was only being used by the
+  [conversejs.org](https://conversejs.org) website, which has been updated to not rely on it.
+- All promises are now native (or polyfilled) ES2015 Promises instead of jQuery's Deferred.
+- #866 Add babel in order to support ES2015 syntax
+
+#### Bugfixes:
+- The domain was queried for MAM:2 support, instead of the JID.
+- Roster filter is not shown when all groups are collapsed.
+- When filtering, contacts in closed groups appear.
+- Room name wasn't being updated after changing it in the configuration form.
+- Server disco features were "forgotten" after logging out and then logging in again.
+- Don't show duplicate sent groupchat messages in Slack chat rooms.
+- Bookmark icon shown in the open rooms list when `allow_bookmarks` is to `false`.
+- It wasn't possible to add or remove bookmarks via the "Open Rooms" list.
+- #879 Text in links are converted to smileys leading to non-clickable links.
+- #899: Only touch `stamp-npm` if `npm install` was successful
+- #902 `make build` dependends on non-existing files
+
+## 3.1.1 (2017-07-12)
 
 
 - Use a patched version of [awesomplete](https://github.com/LeaVerou/awesomplete)
 - Use a patched version of [awesomplete](https://github.com/LeaVerou/awesomplete)
   which doesn't render suggestions as HTML (possible XSS attack vector). [jcbrand]
   which doesn't render suggestions as HTML (possible XSS attack vector). [jcbrand]

+ 12 - 0
COPYRIGHT

@@ -0,0 +1,12 @@
+/** Converse.js
+ *
+ *  An XMPP chat client that runs in the browser.
+ *
+ *  Version: 3.2.0-rc
+ *
+ *  Copyright: JC Brand 2012-2017
+ *  Except for 3rd party dependencies.
+ *  Please refer to the unminified version of this file for details.
+ *
+ *  You can download it at: https://github.com/jcbrand/converse.js/releases
+ */

+ 48 - 30
Makefile

@@ -1,4 +1,5 @@
 # You can set these variables from the command line.
 # You can set these variables from the command line.
+UGLIFYJS		?= node_modules/.bin/uglifyjs
 BABEL			?= node_modules/.bin/babel
 BABEL			?= node_modules/.bin/babel
 BOURBON_TEMPLATES = ./node_modules/bourbon/app/assets/stylesheets/ 
 BOURBON_TEMPLATES = ./node_modules/bourbon/app/assets/stylesheets/ 
 BUILDDIR		= ./docs
 BUILDDIR		= ./docs
@@ -60,7 +61,7 @@ serve_bg: dev
 ########################################################################
 ########################################################################
 ## Translation machinery
 ## Translation machinery
 
 
-GETTEXT = xgettext --keyword=__ --keyword=___ --from-code=UTF-8 --output=locale/converse.pot src/*.js --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=3.1.0 -c
+GETTEXT = xgettext --keyword=__ --keyword=___ --from-code=UTF-8 --output=locale/converse.pot src/*.js --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=3.2.0-rc -c
 
 
 .PHONY: pot
 .PHONY: pot
 pot:
 pot:
@@ -79,6 +80,7 @@ po2json:
 
 
 .PHONY: release
 .PHONY: release
 release:
 release:
+	$(SED) -ri s/Version:\ [0-9]\+\.[0-9]\+\.[0-9]\+/Version:\ $(VERSION)/ COPYRIGHT
 	$(SED) -ri s/Version:\ [0-9]\+\.[0-9]\+\.[0-9]\+/Version:\ $(VERSION)/ src/start.frag
 	$(SED) -ri s/Version:\ [0-9]\+\.[0-9]\+\.[0-9]\+/Version:\ $(VERSION)/ src/start.frag
 	$(SED) -ri s/Project-Id-Version:\ Converse\.js\ [0-9]\+\.[0-9]\+\.[0-9]\+/Project-Id-Version:\ Converse.js\ $(VERSION)/ locale/converse.pot
 	$(SED) -ri s/Project-Id-Version:\ Converse\.js\ [0-9]\+\.[0-9]\+\.[0-9]\+/Project-Id-Version:\ Converse.js\ $(VERSION)/ locale/converse.pot
 	$(SED) -ri s/\"version\":\ \"[0-9]\+\.[0-9]\+\.[0-9]\+\"/\"version\":\ \"$(VERSION)\"/ bower.json
 	$(SED) -ri s/\"version\":\ \"[0-9]\+\.[0-9]\+\.[0-9]\+\"/\"version\":\ \"$(VERSION)\"/ bower.json
@@ -98,8 +100,7 @@ release:
 ## Install dependencies
 ## Install dependencies
 
 
 stamp-npm: package.json
 stamp-npm: package.json
-	npm install
-	touch stamp-npm
+	npm install && touch stamp-npm
 
 
 stamp-bundler: Gemfile
 stamp-bundler: Gemfile
 	mkdir -p .bundle
 	mkdir -p .bundle
@@ -109,7 +110,7 @@ stamp-bundler: Gemfile
 
 
 .PHONY: clean
 .PHONY: clean
 clean:
 clean:
-	-rm -f stamp-npm stamp-bundler
+	-rm -f stamp-npm stamp-bundler package-lock.json
 	-rm -rf node_modules .bundle
 	-rm -rf node_modules .bundle
 
 
 .PHONY: dev
 .PHONY: dev
@@ -147,49 +148,66 @@ css/mobile.min.css:: stamp-npm sass/*
 
 
 .PHONY: watch
 .PHONY: watch
 watch: stamp-bundler
 watch: stamp-bundler
-	$(SASS) --watch -I ./node_modules/bourbon/app/assets/stylesheets/ sass/converse.scss:css/converse.css sass/_muc_embedded.scss:css/converse-muc-embedded.css
+	$(SASS) --watch -I ./node_modules/bourbon/app/assets/stylesheets/ sass/converse/converse.scss:css/converse.css sass/_muc_embedded.scss:css/converse-muc-embedded.css sass/inverse/inverse.scss:css/inverse.css
 
 
 .PHONY: watchjs
 .PHONY: watchjs
 watchjs: stamp-npm
 watchjs: stamp-npm
-	$(BABEL) --source-maps --watch=./src --out-dir=./build
+	$(BABEL) --source-maps --watch=./src --out-dir=./builds
+
+.PHONY: transpile
+transpile: stamp-npm
+	$(BABEL) --source-maps --out-dir=./builds ./src
 
 
 BUILDS = dist/converse.js \
 BUILDS = dist/converse.js \
 		 dist/converse.min.js \
 		 dist/converse.min.js \
-         dist/inverse.js \
+		 dist/converse-esnext.js \
+		 dist/converse-esnext.min.js \
+		 dist/inverse.js \
 		 dist/inverse.min.js \
 		 dist/inverse.min.js \
-         dist/converse-mobile.js \
-         dist/converse-mobile.min.js \
-         dist/converse-muc-embedded.js \
-         dist/converse-muc-embedded.min.js \
-         dist/converse-no-jquery.js \
+		 dist/converse-mobile.js \
+		 dist/converse-mobile.min.js \
+		 dist/converse-muc-embedded.js \
+		 dist/converse-muc-embedded.min.js \
+		 dist/converse-no-jquery.js \
  		 dist/converse-no-jquery.min.js \
  		 dist/converse-no-jquery.min.js \
 		 dist/converse-no-dependencies.min.js \
 		 dist/converse-no-dependencies.min.js \
 		 dist/converse-no-dependencies.js
 		 dist/converse-no-dependencies.js
 
 
-dist/converse.min.js: src locale node_modules *.js
-	$(RJS) -o src/build.js include=converse out=dist/converse.min.js
-dist/converse.js: src locale node_modules *.js
+dist/converse.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build.js include=converse out=dist/converse.js optimize=none 
 	$(RJS) -o src/build.js include=converse out=dist/converse.js optimize=none 
-dist/inverse.js: src locale node_modules *.js
+dist/converse.min.js: src locale node_modules *.js
+	$(UGLIFYJS) --verbose dist/converse.js -o dist/converse.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse.min.js >> tmpfile && mv tmpfile dist/converse.min.js
+dist/converse-esnext.js: src locale node_modules *.js transpile
+	$(RJS) -o src/build-esnext.js include=converse out=dist/converse-esnext.js optimize=none 
+dist/converse-esnext.min.js: src locale node_modules *.js transpile
+	$(UGLIFYJS) --verbose dist/converse-esnext.js -o dist/converse-esnext.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse-esnext.min.js >> tmpfile && mv tmpfile dist/converse-esnext.min.js
+dist/inverse.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build-inverse.js include=inverse out=dist/inverse.js optimize=none 
 	$(RJS) -o src/build-inverse.js include=inverse out=dist/inverse.js optimize=none 
 dist/inverse.min.js: src locale node_modules *.js
 dist/inverse.min.js: src locale node_modules *.js
-	$(RJS) -o src/build-inverse.js include=inverse out=dist/inverse.min.js
-dist/converse-no-jquery.min.js: src locale node_modules *.js
-	$(RJS) -o src/build.js include=converse wrap.endFile=end-no-jquery.frag exclude=jquery exclude=jquery.noconflict out=dist/converse-no-jquery.min.js
-dist/converse-no-jquery.js: src locale node_modules *.js
+	$(UGLIFYJS) --verbose dist/inverse.js -o dist/inverse.min.js
+	cat COPYRIGHT > tmpfile && cat dist/inverse.min.js >> tmpfile && mv tmpfile dist/inverse.min.js
+dist/converse-no-jquery.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build.js include=converse wrap.endFile=end-no-jquery.frag exclude=jquery exclude=jquery.noconflict out=dist/converse-no-jquery.js optimize=none 
 	$(RJS) -o src/build.js include=converse wrap.endFile=end-no-jquery.frag exclude=jquery exclude=jquery.noconflict out=dist/converse-no-jquery.js optimize=none 
-dist/converse-no-dependencies.min.js: src locale node_modules *.js
-	$(RJS) -o src/build-no-dependencies.js
-dist/converse-no-dependencies.js: src locale node_modules *.js
+dist/converse-no-jquery.min.js: src locale node_modules *.js transpile
+	$(UGLIFYJS) --verbose dist/converse-no-jquery.js -o dist/converse-no-jquery.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse-no-jquery.min.js >> tmpfile && mv tmpfile dist/converse-no-jquery.min.js
+dist/converse-no-dependencies.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build-no-dependencies.js optimize=none out=dist/converse-no-dependencies.js
 	$(RJS) -o src/build-no-dependencies.js optimize=none out=dist/converse-no-dependencies.js
-dist/converse-mobile.min.js: src locale node_modules *.js
-	$(RJS) -o src/build.js paths.converse=src/converse-mobile include=converse out=dist/converse-mobile.min.js
-dist/converse-mobile.js: src locale node_modules *.js
+dist/converse-no-dependencies.min.js: src locale node_modules *.js
+	$(UGLIFYJS) --verbose dist/converse-no-dependencies.js -o dist/converse-no-dependencies.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse-no-dependencies.min.js >> tmpfile && mv tmpfile dist/converse-no-dependencies.min.js
+dist/converse-mobile.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build.js paths.converse=src/converse-mobile include=converse out=dist/converse-mobile.js optimize=none 
 	$(RJS) -o src/build.js paths.converse=src/converse-mobile include=converse out=dist/converse-mobile.js optimize=none 
-dist/converse-muc-embedded.min.js: src locale node_modules *.js
-	$(RJS) -o src/build.js paths.converse=src/converse-embedded include=converse out=dist/converse-muc-embedded.min.js
-dist/converse-muc-embedded.js: src locale node_modules *.js
+dist/converse-mobile.min.js: src locale node_modules *.js
+	$(UGLIFYJS) --verbose dist/converse-mobile.js -o dist/converse-mobile.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse-mobile.min.js >> tmpfile && mv tmpfile dist/converse-mobile.min.js
+dist/converse-muc-embedded.js: transpile src locale node_modules *.js
 	$(RJS) -o src/build.js paths.converse=src/converse-embedded include=converse out=dist/converse-muc-embedded.js optimize=none 
 	$(RJS) -o src/build.js paths.converse=src/converse-embedded include=converse out=dist/converse-muc-embedded.js optimize=none 
+dist/converse-muc-embedded.min.js: src locale node_modules *.js
+	$(UGLIFYJS) --verbose dist/converse-muc-embedded.js -o dist/converse-muc-embedded.min.js
+	cat COPYRIGHT > tmpfile && cat dist/converse-muc-embedded.min.js >> tmpfile && mv tmpfile dist/converse-muc-embedded.min.js
 
 
 .PHONY: jsmin
 .PHONY: jsmin
 jsmin: $(BUILDS)
 jsmin: $(BUILDS)
@@ -198,7 +216,7 @@ jsmin: $(BUILDS)
 dist:: build
 dist:: build
 
 
 .PHONY: build
 .PHONY: build
-build:: dev css
+build:: dev css transpile
 	$(GRUNT) json
 	$(GRUNT) json
 	make jsmin
 	make jsmin
 
 

+ 0 - 13
bower.json

@@ -1,13 +0,0 @@
-{
-  "name": "converse.js",
-  "description": "Web-based XMPP/Jabber chat client written in javascript",
-  "version": "3.1.1",
-  "license": "MPL-2.0",
-  "devDependencies": {},
-  "dependencies": {},
-  "exportsOverride": {},
-  "ignore": [
-    "docs",
-    "mockup"
-  ]
-}

+ 3 - 0
builds/README.rst

@@ -0,0 +1,3 @@
+This directory exists as a location for intermediate files generated by the
+Babel compiler, before they're bundled into distribution bundles in the
+`./dist/` directory.

+ 238 - 154
css/converse.css

@@ -1146,7 +1146,7 @@
 #converse-embedded-chat,
 #converse-embedded-chat,
 #conversejs {
 #conversejs {
   bottom: 0;
   bottom: 0;
-  color: #818479;
+  color: #777;
   direction: ltr;
   direction: ltr;
   display: block;
   display: block;
   font-family: "Helvetica", "Arial", sans-serif;
   font-family: "Helvetica", "Arial", sans-serif;
@@ -1201,9 +1201,6 @@
     -moz-user-select: none;
     -moz-user-select: none;
     -ms-user-select: none;
     -ms-user-select: none;
     user-select: none; }
     user-select: none; }
-  #converse-embedded-chat .emoticon,
-  #conversejs .emoticon {
-    font-size: 14px; }
 @keyframes fadein {
 @keyframes fadein {
   0% {
   0% {
     opacity: 0; }
     opacity: 0; }
@@ -1234,6 +1231,10 @@
   #conversejs .hidden {
   #conversejs .hidden {
     opacity: 0;
     opacity: 0;
     display: none; }
     display: none; }
+  #converse-embedded-chat .collapsed,
+  #conversejs .collapsed {
+    height: 0;
+    overflow: hidden; }
   #converse-embedded-chat .locked,
   #converse-embedded-chat .locked,
   #conversejs .locked {
   #conversejs .locked {
     padding-right: 22px; }
     padding-right: 22px; }
@@ -1260,6 +1261,9 @@
     -ms-transform: rotate(359deg);
     -ms-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     transform: rotate(359deg); } }
     transform: rotate(359deg); } }
+  #converse-embedded-chat .emojione,
+  #conversejs .emojione {
+    height: 20px; }
   #converse-embedded-chat .spinner,
   #converse-embedded-chat .spinner,
   #conversejs .spinner {
   #conversejs .spinner {
     -webkit-animation: spin 2s infinite, linear;
     -webkit-animation: spin 2s infinite, linear;
@@ -1332,6 +1336,9 @@
   #converse-embedded-chat .activated,
   #converse-embedded-chat .activated,
   #conversejs .activated {
   #conversejs .activated {
     display: block !important; }
     display: block !important; }
+  #converse-embedded-chat .pure-form-message,
+  #conversejs .pure-form-message {
+    padding: 0.5em 0; }
   #converse-embedded-chat .pure-button,
   #converse-embedded-chat .pure-button,
   #conversejs .pure-button {
   #conversejs .pure-button {
     border-radius: 4px; }
     border-radius: 4px; }
@@ -1346,14 +1353,14 @@
   #converse-embedded-chat .button-cancel,
   #converse-embedded-chat .button-cancel,
   #conversejs .button-cancel {
   #conversejs .button-cancel {
     color: white;
     color: white;
-    background-color: #818479; }
+    background-color: #777; }
   #converse-embedded-chat form.pure-form.converse-form,
   #converse-embedded-chat form.pure-form.converse-form,
   #conversejs form.pure-form.converse-form {
   #conversejs form.pure-form.converse-form {
     background: white;
     background: white;
     padding: 1em; }
     padding: 1em; }
     #converse-embedded-chat form.pure-form.converse-form legend,
     #converse-embedded-chat form.pure-form.converse-form legend,
     #conversejs form.pure-form.converse-form legend {
     #conversejs form.pure-form.converse-form legend {
-      color: #818479; }
+      color: #777; }
     #converse-embedded-chat form.pure-form.converse-form label,
     #converse-embedded-chat form.pure-form.converse-form label,
     #conversejs form.pure-form.converse-form label {
     #conversejs form.pure-form.converse-form label {
       margin-top: 1em;
       margin-top: 1em;
@@ -1380,7 +1387,7 @@
     #converse-embedded-chat form.pure-form.converse-form input.error,
     #converse-embedded-chat form.pure-form.converse-form input.error,
     #conversejs form.pure-form.converse-form input.error {
     #conversejs form.pure-form.converse-form input.error {
       border: 1px solid #A53214;
       border: 1px solid #A53214;
-      color: #818479; }
+      color: #777; }
     #converse-embedded-chat form.pure-form.converse-form .form-help,
     #converse-embedded-chat form.pure-form.converse-form .form-help,
     #conversejs form.pure-form.converse-form .form-help {
     #conversejs form.pure-form.converse-form .form-help {
       color: gray;
       color: gray;
@@ -1388,7 +1395,7 @@
       padding-top: 0.5em; }
       padding-top: 0.5em; }
       #converse-embedded-chat form.pure-form.converse-form .form-help:hover,
       #converse-embedded-chat form.pure-form.converse-form .form-help:hover,
       #conversejs form.pure-form.converse-form .form-help:hover {
       #conversejs form.pure-form.converse-form .form-help:hover {
-        color: #818479; }
+        color: #777; }
   #converse-embedded-chat form.pure-form.converse-centered-form,
   #converse-embedded-chat form.pure-form.converse-centered-form,
   #conversejs form.pure-form.converse-centered-form {
   #conversejs form.pure-form.converse-centered-form {
     text-align: center;
     text-align: center;
@@ -1518,10 +1525,11 @@
   #converse-embedded-chat .chatbox .chat-title,
   #converse-embedded-chat .chatbox .chat-title,
   #conversejs .chatbox .chat-title {
   #conversejs .chatbox .chat-title {
     color: white;
     color: white;
-    line-height: 15px;
     display: block;
     display: block;
+    line-height: 15px;
+    overflow: hidden;
     text-overflow: ellipsis;
     text-overflow: ellipsis;
-    overflow: hidden; }
+    white-space: nowrap; }
     #converse-embedded-chat .chatbox .chat-title a,
     #converse-embedded-chat .chatbox .chat-title a,
     #conversejs .chatbox .chat-title a {
     #conversejs .chatbox .chat-title a {
       color: white;
       color: white;
@@ -1547,7 +1555,7 @@
         border-bottom-right-radius: 0; } }
         border-bottom-right-radius: 0; } }
     #converse-embedded-chat .chatbox .chat-body p,
     #converse-embedded-chat .chatbox .chat-body p,
     #conversejs .chatbox .chat-body p {
     #conversejs .chatbox .chat-body p {
-      color: #818479;
+      color: #777;
       font-size: 14px;
       font-size: 14px;
       margin: 0;
       margin: 0;
       padding: 5px; }
       padding: 5px; }
@@ -1578,24 +1586,27 @@
       font-style: italic; }
       font-style: italic; }
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #conversejs .chatbox .chat-body .chat-message {
     #conversejs .chatbox .chat-body .chat-message {
-      margin: 0.3em; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {
+      overflow: auto; }
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-author,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-author {
         font-weight: bold;
         font-weight: bold;
         white-space: nowrap;
         white-space: nowrap;
         float: left;
         float: left;
         text-overflow: ellipsis;
         text-overflow: ellipsis;
         overflow: hidden; }
         overflow: hidden; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-them,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-them {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-them,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-them {
         color: #E77051; }
         color: #E77051; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-me,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-me {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-me,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-me {
         color: #578EA9; }
         color: #578EA9; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-content,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-content {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-content {
         max-width: 100%;
         max-width: 100%;
         word-wrap: break-word; }
         word-wrap: break-word; }
+        #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content .emojione,
+        #conversejs .chatbox .chat-body .chat-message .chat-msg-content .emojione {
+          margin-bottom: -6px; }
     #converse-embedded-chat .chatbox .chat-body .delayed .chat-msg-them,
     #converse-embedded-chat .chatbox .chat-body .delayed .chat-msg-them,
     #conversejs .chatbox .chat-body .delayed .chat-msg-them {
     #conversejs .chatbox .chat-body .delayed .chat-msg-them {
       color: #FB5D50; }
       color: #FB5D50; }
@@ -1619,7 +1630,7 @@
     position: relative;
     position: relative;
     padding: 0.5em;
     padding: 0.5em;
     font-size: 13px;
     font-size: 13px;
-    color: #818479;
+    color: #777;
     overflow-y: auto;
     overflow-y: auto;
     border: 0;
     border: 0;
     background-color: #ffffff;
     background-color: #ffffff;
@@ -1691,12 +1702,9 @@
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar a {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar a {
         font-size: 14px;
         font-size: 14px;
-        color: #818479;
+        color: #777;
         text-decoration: none;
         text-decoration: none;
         text-shadow: none; }
         text-shadow: none; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toolbar-picker-panel a,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toolbar-picker-panel a {
-        color: #578EA9; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text {
         font-size: 12px;
         font-size: 12px;
@@ -1705,11 +1713,11 @@
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted {
-        color: #818479; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-picker-panel a,
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-picker-panel a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-picker-panel a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-picker-panel a {
+        color: #777; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-menu a,
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-menu a,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-menu a,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-menu a {
           color: #578EA9; }
           color: #578EA9; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified,
@@ -1730,67 +1738,89 @@
         float: right; }
         float: right; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
+        cursor: pointer;
         display: inline-block;
         display: inline-block;
         list-style: none;
         list-style: none;
-        padding: 0 3px 0 3px;
-        cursor: pointer;
-        margin-top: 1px; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li:hover,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li:hover {
-        cursor: pointer; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul {
-        background: #fff;
-        bottom: 100%;
-        box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-        display: none;
-        font-size: 12px;
-        margin: 0;
-        position: absolute;
-        right: 0; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul li,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li {
-          cursor: pointer;
-          list-style: none;
-          position: relative; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul li a:hover,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li a:hover {
-            color: #8f2831; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
-        margin-left: 0; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley {
-        color: #818479;
-        padding-left: 5px; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul {
-          left: 0; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li {
-            font-size: 14px;
-            padding: 5px;
-            z-index: 98; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li:hover,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li:hover {
+        margin-top: 1px;
+        padding: 0 3px 0 3px; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li:hover,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li:hover {
+          cursor: pointer; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu {
+          background-color: #fff;
+          bottom: 100%;
+          box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
+          font-size: 12px;
+          margin: 0;
+          position: absolute;
+          right: 0; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu a,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu a {
+            color: #578EA9; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-picker {
+            height: 100px;
+            overflow: scroll;
+            padding: 0.5em; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar {
+            /* offset-x | offset-y | blur-radius | spread-radius | color */
+            box-shadow: 0 -1px 2px 0 rgba(0, 0, 0, 0.4); }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar {
+            overflow: hidden;
+            left: 0; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .picked,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .picked {
+              background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li {
+              height: 30px;
+              padding: 4px;
+              z-index: 98; }
+              #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li.emoji a,
+              #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li.emoji a {
+                font-size: 20px; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li {
+            margin-left: 0;
+            cursor: pointer;
+            list-style: none;
+            position: relative; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji {
+              padding: 0.3em; }
+              #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji:hover,
+              #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji:hover {
+                background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li a:hover,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li a:hover {
+              color: #8f2831; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-toolbar-menu,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-toolbar-menu {
+          color: #777; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley {
+          padding-left: 5px; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover {
             background-color: #DCF9F6; }
             background-color: #DCF9F6; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li {
-        padding: 7px;
-        background-color: white;
-        display: block;
-        z-index: 99; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li a {
-          -moz-transition: background-color 0.2s ease-in-out;
-          -webkit-transition: background-color 0.2s ease-in-out;
-          transition: background-color 0.2s ease-in-out;
-          display: block;
-          padding: 1px;
-          text-decoration: none; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li:hover,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li:hover {
-        background-color: #DCF9F6; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul {
+          z-index: 99; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li {
+            display: block;
+            padding: 7px; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li:hover,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li:hover {
+              background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li a,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li a {
+              display: block; }
   #converse-embedded-chat .chatbox .dragresize,
   #converse-embedded-chat .chatbox .dragresize,
   #conversejs .chatbox .dragresize {
   #conversejs .chatbox .dragresize {
     background: transparent;
     background: transparent;
@@ -1832,6 +1862,31 @@
     #conversejs .chat-head {
     #conversejs .chat-head {
       border-top-left-radius: 0;
       border-top-left-radius: 0;
       border-top-right-radius: 0; } }
       border-top-right-radius: 0; } }
+#converse-embedded-chat .chatbox .chat-body .chat-message,
+#conversejs .chatbox .chat-body .chat-message {
+  margin: 0.3em;
+  line-height: 20px; }
+  #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-author,
+  #conversejs .chatbox .chat-body .chat-message .chat-msg-author {
+    line-height: 20px; }
+  #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content,
+  #conversejs .chatbox .chat-body .chat-message .chat-msg-content {
+    line-height: 20px; }
+    #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content .emojione,
+    #conversejs .chatbox .chat-body .chat-message .chat-msg-content .emojione {
+      margin-bottom: -5px; }
+#converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar,
+#conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar {
+  width: 100%; }
+  #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .emoji-category,
+  #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .emoji-category {
+    float: left; }
+  #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li,
+  #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li {
+    padding: 2px; }
+#converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley ul li,
+#conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley ul li {
+  padding: 2px; }
 
 
 #conversejs #controlbox {
 #conversejs #controlbox {
   margin-right: 1em; }
   margin-right: 1em; }
@@ -1911,7 +1966,7 @@
       color: gray;
       color: gray;
       font-size: 85%; }
       font-size: 85%; }
       #conversejs #controlbox #converse-register .instructions:hover {
       #conversejs #controlbox #converse-register .instructions:hover {
-        color: #818479; }
+        color: #777; }
   #conversejs #controlbox #converse-register, #conversejs #controlbox #converse-login {
   #conversejs #controlbox #converse-register, #conversejs #controlbox #converse-login {
     margin-top: 2em; }
     margin-top: 2em; }
     #conversejs #controlbox #converse-register .login-anon, #conversejs #controlbox #converse-login .login-anon {
     #conversejs #controlbox #converse-register .login-anon, #conversejs #controlbox #converse-login .login-anon {
@@ -1936,7 +1991,7 @@
     margin: 0; }
     margin: 0; }
     #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle {
     #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle {
       display: block;
       display: block;
-      color: #818479;
+      color: #777;
       margin-top: 1em; }
       margin-top: 1em; }
       #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle:hover {
       #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle:hover {
         color: #585B51; }
         color: #585B51; }
@@ -1945,88 +2000,111 @@
       text-align: left; }
       text-align: left; }
       #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dt {
       #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dt {
         border: none;
         border: none;
-        color: #818479;
+        color: #777;
         font-weight: normal;
         font-weight: normal;
         padding: 0;
         padding: 0;
         padding-bottom: 0.5em;
         padding-bottom: 0.5em;
         text-shadow: 0 1px 0 #FAFAFA; }
         text-shadow: 0 1px 0 #FAFAFA; }
-      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom {
+      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom,
+      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom {
         border: none;
         border: none;
         clear: both;
         clear: both;
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         overflow: hidden;
         overflow: hidden;
         padding: 0.3em 0;
         padding: 0.3em 0;
         text-shadow: 0 1px 0 #FAFAFA;
         text-shadow: 0 1px 0 #FAFAFA;
         word-wrap: break-word; }
         word-wrap: break-word; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a:hover {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a:hover,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a:hover {
           color: #206485; }
           color: #206485; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom.unread-msgs .open-room {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom.unread-msgs .available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom.unread-msgs .open-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom.unread-msgs .available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom.unread-msgs .open-room {
           max-width: 55%;
           max-width: 55%;
           width: auto;
           width: auto;
           font-weight: bold; }
           font-weight: bold; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a.room-info:before {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.room-info:before,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.room-info:before {
           font-size: 15px; }
           font-size: 15px; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a.open-room {
-          float: left;
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.open-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.open-room {
           width: 68%;
           width: 68%;
+          float: left;
           overflow: hidden;
           overflow: hidden;
           text-overflow: ellipsis;
           text-overflow: ellipsis;
           white-space: nowrap;
           white-space: nowrap;
           padding-right: 0.5em; }
           padding-right: 0.5em; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.available-room {
+          width: 85%; }
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark {
           color: #A8ABA1; }
           color: #A8ABA1; }
-          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark.button-on {
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark.button-on {
             color: #578EA9; }
             color: #578EA9; }
-            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark.button-on:hover {
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark.button-on:hover {
               color: #206485; }
               color: #206485; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .room-info {
           font-size: 12px;
           font-size: 12px;
           font-style: normal;
           font-style: normal;
           font-weight: normal; }
           font-weight: normal; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom li.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom li.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom li.room-info {
           display: block;
           display: block;
           margin-left: 5px; }
           margin-left: 5px; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom p.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom p.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom p.room-info {
           line-height: 16px;
           line-height: 16px;
           margin: 0;
           margin: 0;
           display: block;
           display: block;
           white-space: normal; }
           white-space: normal; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom div.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom div.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom div.room-info {
           padding: 0.3em 0;
           padding: 0.3em 0;
           clear: left;
           clear: left;
           width: 100%; }
           width: 100%; }
-  #conversejs #controlbox .dropdown {
-    /* Custom addition for CSP */ }
-    #conversejs #controlbox .dropdown a {
-      width: 143px;
-      display: inline-block; }
-    #conversejs #controlbox .dropdown li {
-      list-style: none;
-      padding-left: 0; }
-    #conversejs #controlbox .dropdown dd ul {
-      padding: 0;
-      list-style: none;
+  #conversejs #controlbox .dropdown a {
+    width: 143px;
+    display: inline-block; }
+  #conversejs #controlbox .dropdown li {
+    list-style: none;
+    padding-left: 0; }
+  #conversejs #controlbox .dropdown dd ul {
+    padding: 0;
+    list-style: none;
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    z-index: 21;
+    background-color: #FCFDFD; }
+    #conversejs #controlbox .dropdown dd ul li:hover {
+      background-color: #DCF9F6; }
+  #conversejs #controlbox .dropdown dd.search-xmpp {
+    height: 0; }
+    #conversejs #controlbox .dropdown dd.search-xmpp .contact-form-container {
       position: absolute;
       position: absolute;
-      left: 0;
-      top: 0;
-      border: 1px solid #B1BFC4;
-      width: 100%;
-      z-index: 21;
+      z-index: 22; }
+      #conversejs #controlbox .dropdown dd.search-xmpp .contact-form-container form {
+        box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4);
+        background-color: white; }
+    #conversejs #controlbox .dropdown dd.search-xmpp li:hover {
       background-color: #FCFDFD; }
       background-color: #FCFDFD; }
-      #conversejs #controlbox .dropdown dd ul li:hover {
-        background-color: #DCF9F6; }
-    #conversejs #controlbox .dropdown dd.search-xmpp {
-      display: none;
-      width: 100%; }
-    #conversejs #controlbox .dropdown dd.search-xmpp ul {
-      box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4); }
-      #conversejs #controlbox .dropdown dd.search-xmpp ul li:hover {
-        background-color: #FCFDFD; }
-    #conversejs #controlbox .dropdown dt a span {
-      cursor: pointer;
-      display: block;
-      padding: 4px 7px 0 5px; }
+  #conversejs #controlbox .dropdown dt a span {
+    cursor: pointer;
+    display: block;
+    padding: 4px 7px 0 5px; }
   #conversejs #controlbox #select-xmpp-status {
   #conversejs #controlbox #select-xmpp-status {
     display: none;
     display: none;
     float: right;
     float: right;
@@ -2049,7 +2127,7 @@
         border-top-left-radius: 5px;
         border-top-left-radius: 5px;
         border-top-right-radius: 5px;
         border-top-right-radius: 5px;
         box-shadow: inset 2px -2px 20px rgba(0, 0, 0, 0.3);
         box-shadow: inset 2px -2px 20px rgba(0, 0, 0, 0.3);
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         font-size: 12px;
         font-size: 12px;
         height: 54px;
         height: 54px;
@@ -2067,7 +2145,7 @@
           border-top-right-radius: 5px;
           border-top-right-radius: 5px;
           float: right; }
           float: right; }
         #conversejs #controlbox #controlbox-tabs li a:hover {
         #conversejs #controlbox #controlbox-tabs li a:hover {
-          color: #818479; }
+          color: #777; }
           #conversejs #controlbox #controlbox-tabs li a:hover .msgs-indicator {
           #conversejs #controlbox #controlbox-tabs li a:hover .msgs-indicator {
             opacity: 1; }
             opacity: 1; }
         #conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover {
         #conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover {
@@ -2075,7 +2153,7 @@
           border-bottom: 0;
           border-bottom: 0;
           height: 55px;
           height: 55px;
           cursor: default;
           cursor: default;
-          color: #818479; }
+          color: #777; }
   #conversejs #controlbox .fancy-dropdown {
   #conversejs #controlbox .fancy-dropdown {
     border: 1px solid #B1BFC4;
     border: 1px solid #B1BFC4;
     height: 25px;
     height: 25px;
@@ -2143,7 +2221,6 @@
   #conversejs #controlbox #users {
   #conversejs #controlbox #users {
     overflow-y: hidden; }
     overflow-y: hidden; }
   #conversejs #controlbox .add-xmpp-contact {
   #conversejs #controlbox .add-xmpp-contact {
-    background: none;
     padding: 1em 0.5em; }
     padding: 1em 0.5em; }
     #conversejs #controlbox .add-xmpp-contact input {
     #conversejs #controlbox .add-xmpp-contact input {
       margin: 0 0 1rem;
       margin: 0 0 1rem;
@@ -2257,13 +2334,13 @@
     display: none; }
     display: none; }
     #conversejs #converse-roster .roster-contacts dt.roster-group {
     #conversejs #converse-roster .roster-contacts dt.roster-group {
       border: none;
       border: none;
-      color: #818479;
+      color: #777;
       display: none;
       display: none;
       font-weight: normal;
       font-weight: normal;
       margin: 1em 0 0.5em 0;
       margin: 1em 0 0.5em 0;
       text-shadow: 0 1px 0 #FAFAFA; }
       text-shadow: 0 1px 0 #FAFAFA; }
       #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle {
       #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle {
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         width: 100%; }
         width: 100%; }
         #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle:hover {
         #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle:hover {
@@ -2271,7 +2348,7 @@
     #conversejs #converse-roster .roster-contacts dd {
     #conversejs #converse-roster .roster-contacts dd {
       border: none;
       border: none;
       clear: both;
       clear: both;
-      color: #818479;
+      color: #777;
       display: block;
       display: block;
       height: 24px;
       height: 24px;
       overflow-y: hidden;
       overflow-y: hidden;
@@ -2367,17 +2444,24 @@
   #conversejs .chat-head-chatroom .chatbox-btn.button-on {
   #conversejs .chat-head-chatroom .chatbox-btn.button-on {
     background-color: white;
     background-color: white;
     color: #E77051; }
     color: #E77051; }
-  #converse-embedded-chat .chat-head-chatroom .chatroom-description,
-  #conversejs .chat-head-chatroom .chatroom-description {
-    color: white;
-    font-size: 80%;
-    font-style: italic;
-    height: 1.3em;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    margin: 0;
-    margin-top: 0.3em; }
+  #converse-embedded-chat .chat-head-chatroom .chat-title,
+  #conversejs .chat-head-chatroom .chat-title {
+    color: #FF977C; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-name,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-name {
+      color: white; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-jid,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-jid {
+      font-size: 12px; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-description,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-description {
+      color: white;
+      font-size: 80%;
+      font-style: italic;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      margin: 0.3em 0; }
 #converse-embedded-chat .chatroom,
 #converse-embedded-chat .chatroom,
 #conversejs .chatroom {
 #conversejs .chatroom {
   width: 300px; }
   width: 300px; }
@@ -2454,7 +2538,7 @@
         float: right;
         float: right;
         vertical-align: top;
         vertical-align: top;
         background-color: white;
         background-color: white;
-        border-left: 1px solid #818479;
+        border-left: 1px solid #777;
         border-bottom-right-radius: 4px;
         border-bottom-right-radius: 4px;
         width: 30%;
         width: 30%;
         height: 100%;
         height: 100%;
@@ -2537,7 +2621,7 @@
         border-bottom-left-radius: 4px;
         border-bottom-left-radius: 4px;
         border-bottom-right-radius: 4px;
         border-bottom-right-radius: 4px;
         border: 0;
         border: 0;
-        color: #818479;
+        color: #777;
         font-size: 14px;
         font-size: 14px;
         height: 289px;
         height: 289px;
         width: 100%;
         width: 100%;

+ 226 - 161
css/inverse.css

@@ -1146,7 +1146,7 @@
 #converse-embedded-chat,
 #converse-embedded-chat,
 #conversejs {
 #conversejs {
   bottom: 0;
   bottom: 0;
-  color: #818479;
+  color: #777;
   direction: ltr;
   direction: ltr;
   display: block;
   display: block;
   font-family: "Helvetica", "Arial", sans-serif;
   font-family: "Helvetica", "Arial", sans-serif;
@@ -1201,9 +1201,6 @@
     -moz-user-select: none;
     -moz-user-select: none;
     -ms-user-select: none;
     -ms-user-select: none;
     user-select: none; }
     user-select: none; }
-  #converse-embedded-chat .emoticon,
-  #conversejs .emoticon {
-    font-size: 16px; }
 @keyframes fadein {
 @keyframes fadein {
   0% {
   0% {
     opacity: 0; }
     opacity: 0; }
@@ -1234,6 +1231,10 @@
   #conversejs .hidden {
   #conversejs .hidden {
     opacity: 0;
     opacity: 0;
     display: none; }
     display: none; }
+  #converse-embedded-chat .collapsed,
+  #conversejs .collapsed {
+    height: 0;
+    overflow: hidden; }
   #converse-embedded-chat .locked,
   #converse-embedded-chat .locked,
   #conversejs .locked {
   #conversejs .locked {
     padding-right: 22px; }
     padding-right: 22px; }
@@ -1260,6 +1261,9 @@
     -ms-transform: rotate(359deg);
     -ms-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     transform: rotate(359deg); } }
     transform: rotate(359deg); } }
+  #converse-embedded-chat .emojione,
+  #conversejs .emojione {
+    height: 22px; }
   #converse-embedded-chat .spinner,
   #converse-embedded-chat .spinner,
   #conversejs .spinner {
   #conversejs .spinner {
     -webkit-animation: spin 2s infinite, linear;
     -webkit-animation: spin 2s infinite, linear;
@@ -1332,6 +1336,9 @@
   #converse-embedded-chat .activated,
   #converse-embedded-chat .activated,
   #conversejs .activated {
   #conversejs .activated {
     display: block !important; }
     display: block !important; }
+  #converse-embedded-chat .pure-form-message,
+  #conversejs .pure-form-message {
+    padding: 0.5em 0; }
   #converse-embedded-chat .pure-button,
   #converse-embedded-chat .pure-button,
   #conversejs .pure-button {
   #conversejs .pure-button {
     border-radius: 7px; }
     border-radius: 7px; }
@@ -1346,14 +1353,14 @@
   #converse-embedded-chat .button-cancel,
   #converse-embedded-chat .button-cancel,
   #conversejs .button-cancel {
   #conversejs .button-cancel {
     color: white;
     color: white;
-    background-color: #818479; }
+    background-color: #777; }
   #converse-embedded-chat form.pure-form.converse-form,
   #converse-embedded-chat form.pure-form.converse-form,
   #conversejs form.pure-form.converse-form {
   #conversejs form.pure-form.converse-form {
     background: white;
     background: white;
     padding: 1em; }
     padding: 1em; }
     #converse-embedded-chat form.pure-form.converse-form legend,
     #converse-embedded-chat form.pure-form.converse-form legend,
     #conversejs form.pure-form.converse-form legend {
     #conversejs form.pure-form.converse-form legend {
-      color: #818479; }
+      color: #777; }
     #converse-embedded-chat form.pure-form.converse-form label,
     #converse-embedded-chat form.pure-form.converse-form label,
     #conversejs form.pure-form.converse-form label {
     #conversejs form.pure-form.converse-form label {
       margin-top: 1em;
       margin-top: 1em;
@@ -1380,7 +1387,7 @@
     #converse-embedded-chat form.pure-form.converse-form input.error,
     #converse-embedded-chat form.pure-form.converse-form input.error,
     #conversejs form.pure-form.converse-form input.error {
     #conversejs form.pure-form.converse-form input.error {
       border: 1px solid #A53214;
       border: 1px solid #A53214;
-      color: #818479; }
+      color: #777; }
     #converse-embedded-chat form.pure-form.converse-form .form-help,
     #converse-embedded-chat form.pure-form.converse-form .form-help,
     #conversejs form.pure-form.converse-form .form-help {
     #conversejs form.pure-form.converse-form .form-help {
       color: gray;
       color: gray;
@@ -1388,7 +1395,7 @@
       padding-top: 0.5em; }
       padding-top: 0.5em; }
       #converse-embedded-chat form.pure-form.converse-form .form-help:hover,
       #converse-embedded-chat form.pure-form.converse-form .form-help:hover,
       #conversejs form.pure-form.converse-form .form-help:hover {
       #conversejs form.pure-form.converse-form .form-help:hover {
-        color: #818479; }
+        color: #777; }
   #converse-embedded-chat form.pure-form.converse-centered-form,
   #converse-embedded-chat form.pure-form.converse-centered-form,
   #conversejs form.pure-form.converse-centered-form {
   #conversejs form.pure-form.converse-centered-form {
     text-align: center;
     text-align: center;
@@ -1439,7 +1446,7 @@ body {
   #conversejs form.pure-form.converse-form {
   #conversejs form.pure-form.converse-form {
     margin: 1em; }
     margin: 1em; }
     #conversejs form.pure-form.converse-form legend {
     #conversejs form.pure-form.converse-form legend {
-      color: #818479; }
+      color: #777; }
     #conversejs form.pure-form.converse-form label {
     #conversejs form.pure-form.converse-form label {
       margin-top: 1em; }
       margin-top: 1em; }
     #conversejs form.pure-form.converse-form input[type=text],
     #conversejs form.pure-form.converse-form input[type=text],
@@ -1564,10 +1571,11 @@ body {
   #converse-embedded-chat .chatbox .chat-title,
   #converse-embedded-chat .chatbox .chat-title,
   #conversejs .chatbox .chat-title {
   #conversejs .chatbox .chat-title {
     color: white;
     color: white;
-    line-height: 15px;
     display: block;
     display: block;
+    line-height: 15px;
+    overflow: hidden;
     text-overflow: ellipsis;
     text-overflow: ellipsis;
-    overflow: hidden; }
+    white-space: nowrap; }
     #converse-embedded-chat .chatbox .chat-title a,
     #converse-embedded-chat .chatbox .chat-title a,
     #conversejs .chatbox .chat-title a {
     #conversejs .chatbox .chat-title a {
       color: white;
       color: white;
@@ -1593,7 +1601,7 @@ body {
         border-bottom-right-radius: 0; } }
         border-bottom-right-radius: 0; } }
     #converse-embedded-chat .chatbox .chat-body p,
     #converse-embedded-chat .chatbox .chat-body p,
     #conversejs .chatbox .chat-body p {
     #conversejs .chatbox .chat-body p {
-      color: #818479;
+      color: #777;
       font-size: 16px;
       font-size: 16px;
       margin: 0;
       margin: 0;
       padding: 5px; }
       padding: 5px; }
@@ -1624,24 +1632,27 @@ body {
       font-style: italic; }
       font-style: italic; }
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #conversejs .chatbox .chat-body .chat-message {
     #conversejs .chatbox .chat-body .chat-message {
-      margin: 0.3em; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {
+      overflow: auto; }
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-author,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-author {
         font-weight: bold;
         font-weight: bold;
         white-space: nowrap;
         white-space: nowrap;
         float: left;
         float: left;
         text-overflow: ellipsis;
         text-overflow: ellipsis;
         overflow: hidden; }
         overflow: hidden; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-them,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-them {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-them,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-them {
         color: #E77051; }
         color: #E77051; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-me,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-me {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-me,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-me {
         color: #578EA9; }
         color: #578EA9; }
-      #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-content,
-      #conversejs .chatbox .chat-body .chat-message span.chat-msg-content {
+      #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content,
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-content {
         max-width: 100%;
         max-width: 100%;
         word-wrap: break-word; }
         word-wrap: break-word; }
+        #converse-embedded-chat .chatbox .chat-body .chat-message .chat-msg-content .emojione,
+        #conversejs .chatbox .chat-body .chat-message .chat-msg-content .emojione {
+          margin-bottom: -6px; }
     #converse-embedded-chat .chatbox .chat-body .delayed .chat-msg-them,
     #converse-embedded-chat .chatbox .chat-body .delayed .chat-msg-them,
     #conversejs .chatbox .chat-body .delayed .chat-msg-them {
     #conversejs .chatbox .chat-body .delayed .chat-msg-them {
       color: #FB5D50; }
       color: #FB5D50; }
@@ -1665,7 +1676,7 @@ body {
     position: relative;
     position: relative;
     padding: 0.5em;
     padding: 0.5em;
     font-size: 13px;
     font-size: 13px;
-    color: #818479;
+    color: #777;
     overflow-y: auto;
     overflow-y: auto;
     border: 0;
     border: 0;
     background-color: #ffffff;
     background-color: #ffffff;
@@ -1737,12 +1748,9 @@ body {
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar a {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar a {
         font-size: 16px;
         font-size: 16px;
-        color: #818479;
+        color: #777;
         text-decoration: none;
         text-decoration: none;
         text-shadow: none; }
         text-shadow: none; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toolbar-picker-panel a,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toolbar-picker-panel a {
-        color: #578EA9; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .chat-toolbar-text {
         font-size: 12px;
         font-size: 12px;
@@ -1751,11 +1759,11 @@ body {
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted {
-        color: #818479; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-picker-panel a,
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-picker-panel a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-picker-panel a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-picker-panel a {
+        color: #777; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-menu a,
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-menu a,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted a .toolbar-menu a,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .unencrypted .toolbar-menu a {
           color: #578EA9; }
           color: #578EA9; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified a,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .unverified,
@@ -1776,67 +1784,89 @@ body {
         float: right; }
         float: right; }
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
       #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
       #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
+        cursor: pointer;
         display: inline-block;
         display: inline-block;
         list-style: none;
         list-style: none;
-        padding: 0 3px 0 3px;
-        cursor: pointer;
-        margin-top: 1px; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li:hover,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li:hover {
-        cursor: pointer; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul {
-        background: #fff;
-        bottom: 100%;
-        box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-        display: none;
-        font-size: 12px;
-        margin: 0;
-        position: absolute;
-        right: 0; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul li,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li {
-          cursor: pointer;
-          list-style: none;
-          position: relative; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar ul li a:hover,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li a:hover {
-            color: #8f2831; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li {
-        margin-left: 0; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley {
-        color: #818479;
-        padding-left: 5px; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul {
-          left: 0; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li {
-            font-size: 16px;
-            padding: 5px;
-            z-index: 98; }
-          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li:hover,
-          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li:hover {
+        margin-top: 1px;
+        padding: 0 3px 0 3px; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li:hover,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li:hover {
+          cursor: pointer; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu {
+          background-color: #fff;
+          bottom: 100%;
+          box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
+          font-size: 12px;
+          margin: 0;
+          position: absolute;
+          right: 0; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu a,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu a {
+            color: #578EA9; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-picker {
+            height: 150px;
+            overflow: scroll;
+            padding: 0.5em; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar {
+            /* offset-x | offset-y | blur-radius | spread-radius | color */
+            box-shadow: 0 -1px 2px 0 rgba(0, 0, 0, 0.4); }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar {
+            overflow: hidden;
+            left: 0; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .picked,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar .picked {
+              background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li {
+              height: 32px;
+              padding: 4px;
+              z-index: 98; }
+              #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li.emoji a,
+              #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul.emoji-toolbar li.emoji a {
+                font-size: 26px; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li {
+            margin-left: 0;
+            cursor: pointer;
+            list-style: none;
+            position: relative; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji {
+              padding: 0.3em; }
+              #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji:hover,
+              #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji:hover {
+                background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li a:hover,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li .toolbar-menu ul li a:hover {
+              color: #8f2831; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-toolbar-menu,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-toolbar-menu {
+          color: #777; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley {
+          padding-left: 5px; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover {
             background-color: #DCF9F6; }
             background-color: #DCF9F6; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li {
-        padding: 7px;
-        background-color: white;
-        display: block;
-        z-index: 99; }
-        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li a,
-        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li a {
-          -moz-transition: background-color 0.2s ease-in-out;
-          -webkit-transition: background-color 0.2s ease-in-out;
-          transition: background-color 0.2s ease-in-out;
-          display: block;
-          padding: 1px;
-          text-decoration: none; }
-      #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li:hover,
-      #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-otr ul li:hover {
-        background-color: #DCF9F6; }
+        #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul,
+        #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul {
+          z-index: 99; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li {
+            display: block;
+            padding: 7px; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li:hover,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li:hover {
+              background-color: #DCF9F6; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li a,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar li.toggle-otr ul li a {
+              display: block; }
   #converse-embedded-chat .chatbox .dragresize,
   #converse-embedded-chat .chatbox .dragresize,
   #conversejs .chatbox .dragresize {
   #conversejs .chatbox .dragresize {
     background: transparent;
     background: transparent;
@@ -1900,9 +1930,13 @@ body {
     border-top-left-radius: 7px;
     border-top-left-radius: 7px;
     border-top-right-radius: 7px; }
     border-top-right-radius: 7px; }
     #conversejs .chatbox .chat-body .chat-message {
     #conversejs .chatbox .chat-body .chat-message {
+      line-height: 22px;
       font-size: 14px;
       font-size: 14px;
-      line-height: 20px;
       margin: 0.5em 0; }
       margin: 0.5em 0; }
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-author {
+        line-height: 22px; }
+      #conversejs .chatbox .chat-body .chat-message .chat-msg-content {
+        line-height: 22px; }
   #conversejs .chatbox .chat-content {
   #conversejs .chatbox .chat-content {
     padding: 0 1em 1em 1em;
     padding: 0 1em 1em 1em;
     border-top-left-radius: 7px;
     border-top-left-radius: 7px;
@@ -1914,8 +1948,11 @@ body {
     width: 100%; }
     width: 100%; }
   #conversejs .chatbox form.sendXMPPMessage .toggle-smiley {
   #conversejs .chatbox form.sendXMPPMessage .toggle-smiley {
     padding-left: 0.5em; }
     padding-left: 0.5em; }
-    #conversejs .chatbox form.sendXMPPMessage .toggle-smiley ul li {
-      padding: 0.5em; }
+    #conversejs .chatbox form.sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category-picker {
+      margin-right: 5em; }
+    #conversejs .chatbox form.sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category {
+      padding-left: 10px;
+      padding-right: 10px; }
 
 
 #conversejs #controlbox {
 #conversejs #controlbox {
   margin-right: 1em; }
   margin-right: 1em; }
@@ -1995,7 +2032,7 @@ body {
       color: gray;
       color: gray;
       font-size: 85%; }
       font-size: 85%; }
       #conversejs #controlbox #converse-register .instructions:hover {
       #conversejs #controlbox #converse-register .instructions:hover {
-        color: #818479; }
+        color: #777; }
   #conversejs #controlbox #converse-register, #conversejs #controlbox #converse-login {
   #conversejs #controlbox #converse-register, #conversejs #controlbox #converse-login {
     margin-top: 2em; }
     margin-top: 2em; }
     #conversejs #controlbox #converse-register .login-anon, #conversejs #controlbox #converse-login .login-anon {
     #conversejs #controlbox #converse-register .login-anon, #conversejs #controlbox #converse-login .login-anon {
@@ -2020,7 +2057,7 @@ body {
     margin: 0; }
     margin: 0; }
     #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle {
     #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle {
       display: block;
       display: block;
-      color: #818479;
+      color: #777;
       margin-top: 1em; }
       margin-top: 1em; }
       #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle:hover {
       #conversejs #controlbox #chatrooms .rooms-list-container .rooms-toggle:hover {
         color: #585B51; }
         color: #585B51; }
@@ -2029,88 +2066,111 @@ body {
       text-align: left; }
       text-align: left; }
       #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dt {
       #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dt {
         border: none;
         border: none;
-        color: #818479;
+        color: #777;
         font-weight: normal;
         font-weight: normal;
         padding: 0;
         padding: 0;
         padding-bottom: 0.5em;
         padding-bottom: 0.5em;
         text-shadow: 0 1px 0 #FAFAFA; }
         text-shadow: 0 1px 0 #FAFAFA; }
-      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom {
+      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom,
+      #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom {
         border: none;
         border: none;
         clear: both;
         clear: both;
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         overflow: hidden;
         overflow: hidden;
         padding: 0.3em 0;
         padding: 0.3em 0;
         text-shadow: 0 1px 0 #FAFAFA;
         text-shadow: 0 1px 0 #FAFAFA;
         word-wrap: break-word; }
         word-wrap: break-word; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a:hover {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a:hover,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a:hover {
           color: #206485; }
           color: #206485; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom.unread-msgs .open-room {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom.unread-msgs .available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom.unread-msgs .open-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom.unread-msgs .available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom.unread-msgs .open-room {
           max-width: 55%;
           max-width: 55%;
           width: auto;
           width: auto;
           font-weight: bold; }
           font-weight: bold; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a.room-info:before {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.room-info:before,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.room-info:before {
           font-size: 15px; }
           font-size: 15px; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a.open-room {
-          float: left;
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.open-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.open-room {
           width: 68%;
           width: 68%;
+          float: left;
           overflow: hidden;
           overflow: hidden;
           text-overflow: ellipsis;
           text-overflow: ellipsis;
           white-space: nowrap;
           white-space: nowrap;
           padding-right: 0.5em; }
           padding-right: 0.5em; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom a.available-room,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom a.available-room {
+          width: 85%; }
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark {
           color: #A8ABA1; }
           color: #A8ABA1; }
-          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark.button-on {
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark.button-on,
+          #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark.button-on {
             color: #578EA9; }
             color: #578EA9; }
-            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .remove-bookmark.button-on:hover {
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .add-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .remove-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .add-bookmark.button-on:hover,
+            #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .remove-bookmark.button-on:hover {
               color: #206485; }
               color: #206485; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom .room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom .room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom .room-info {
           font-size: 14px;
           font-size: 14px;
           font-style: normal;
           font-style: normal;
           font-weight: normal; }
           font-weight: normal; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom li.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom li.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom li.room-info {
           display: block;
           display: block;
           margin-left: 5px; }
           margin-left: 5px; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom p.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom p.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom p.room-info {
           line-height: 22px;
           line-height: 22px;
           margin: 0;
           margin: 0;
           display: block;
           display: block;
           white-space: normal; }
           white-space: normal; }
-        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom div.room-info {
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .available-chatroom div.room-info,
+        #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list .open-chatroom div.room-info {
           padding: 0.3em 0;
           padding: 0.3em 0;
           clear: left;
           clear: left;
           width: 100%; }
           width: 100%; }
-  #conversejs #controlbox .dropdown {
-    /* Custom addition for CSP */ }
-    #conversejs #controlbox .dropdown a {
-      width: 143px;
-      display: inline-block; }
-    #conversejs #controlbox .dropdown li {
-      list-style: none;
-      padding-left: 0; }
-    #conversejs #controlbox .dropdown dd ul {
-      padding: 0;
-      list-style: none;
+  #conversejs #controlbox .dropdown a {
+    width: 143px;
+    display: inline-block; }
+  #conversejs #controlbox .dropdown li {
+    list-style: none;
+    padding-left: 0; }
+  #conversejs #controlbox .dropdown dd ul {
+    padding: 0;
+    list-style: none;
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    z-index: 21;
+    background-color: #FCFDFD; }
+    #conversejs #controlbox .dropdown dd ul li:hover {
+      background-color: #DCF9F6; }
+  #conversejs #controlbox .dropdown dd.search-xmpp {
+    height: 0; }
+    #conversejs #controlbox .dropdown dd.search-xmpp .contact-form-container {
       position: absolute;
       position: absolute;
-      left: 0;
-      top: 0;
-      border: 1px solid #B1BFC4;
-      width: 100%;
-      z-index: 21;
+      z-index: 22; }
+      #conversejs #controlbox .dropdown dd.search-xmpp .contact-form-container form {
+        box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4);
+        background-color: white; }
+    #conversejs #controlbox .dropdown dd.search-xmpp li:hover {
       background-color: #FCFDFD; }
       background-color: #FCFDFD; }
-      #conversejs #controlbox .dropdown dd ul li:hover {
-        background-color: #DCF9F6; }
-    #conversejs #controlbox .dropdown dd.search-xmpp {
-      display: none;
-      width: 100%; }
-    #conversejs #controlbox .dropdown dd.search-xmpp ul {
-      box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4); }
-      #conversejs #controlbox .dropdown dd.search-xmpp ul li:hover {
-        background-color: #FCFDFD; }
-    #conversejs #controlbox .dropdown dt a span {
-      cursor: pointer;
-      display: block;
-      padding: 4px 7px 0 5px; }
+  #conversejs #controlbox .dropdown dt a span {
+    cursor: pointer;
+    display: block;
+    padding: 4px 7px 0 5px; }
   #conversejs #controlbox #select-xmpp-status {
   #conversejs #controlbox #select-xmpp-status {
     display: none;
     display: none;
     float: right;
     float: right;
@@ -2133,7 +2193,7 @@ body {
         border-top-left-radius: 5px;
         border-top-left-radius: 5px;
         border-top-right-radius: 5px;
         border-top-right-radius: 5px;
         box-shadow: inset 2px -2px 20px rgba(0, 0, 0, 0.3);
         box-shadow: inset 2px -2px 20px rgba(0, 0, 0, 0.3);
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         font-size: 14px;
         font-size: 14px;
         height: 61px;
         height: 61px;
@@ -2151,7 +2211,7 @@ body {
           border-top-right-radius: 5px;
           border-top-right-radius: 5px;
           float: right; }
           float: right; }
         #conversejs #controlbox #controlbox-tabs li a:hover {
         #conversejs #controlbox #controlbox-tabs li a:hover {
-          color: #818479; }
+          color: #777; }
           #conversejs #controlbox #controlbox-tabs li a:hover .msgs-indicator {
           #conversejs #controlbox #controlbox-tabs li a:hover .msgs-indicator {
             opacity: 1; }
             opacity: 1; }
         #conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover {
         #conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover {
@@ -2159,7 +2219,7 @@ body {
           border-bottom: 0;
           border-bottom: 0;
           height: 62px;
           height: 62px;
           cursor: default;
           cursor: default;
-          color: #818479; }
+          color: #777; }
   #conversejs #controlbox .fancy-dropdown {
   #conversejs #controlbox .fancy-dropdown {
     border: 1px solid #B1BFC4;
     border: 1px solid #B1BFC4;
     height: 30px;
     height: 30px;
@@ -2227,7 +2287,6 @@ body {
   #conversejs #controlbox #users {
   #conversejs #controlbox #users {
     overflow-y: hidden; }
     overflow-y: hidden; }
   #conversejs #controlbox .add-xmpp-contact {
   #conversejs #controlbox .add-xmpp-contact {
-    background: none;
     padding: 1em 0.5em; }
     padding: 1em 0.5em; }
     #conversejs #controlbox .add-xmpp-contact input {
     #conversejs #controlbox .add-xmpp-contact input {
       margin: 0 0 1rem;
       margin: 0 0 1rem;
@@ -2379,13 +2438,13 @@ body {
     display: none; }
     display: none; }
     #conversejs #converse-roster .roster-contacts dt.roster-group {
     #conversejs #converse-roster .roster-contacts dt.roster-group {
       border: none;
       border: none;
-      color: #818479;
+      color: #777;
       display: none;
       display: none;
       font-weight: normal;
       font-weight: normal;
       margin: 1em 0 0.5em 0;
       margin: 1em 0 0.5em 0;
       text-shadow: 0 1px 0 #FAFAFA; }
       text-shadow: 0 1px 0 #FAFAFA; }
       #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle {
       #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle {
-        color: #818479;
+        color: #777;
         display: block;
         display: block;
         width: 100%; }
         width: 100%; }
         #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle:hover {
         #conversejs #converse-roster .roster-contacts dt.roster-group .group-toggle:hover {
@@ -2393,7 +2452,7 @@ body {
     #conversejs #converse-roster .roster-contacts dd {
     #conversejs #converse-roster .roster-contacts dd {
       border: none;
       border: none;
       clear: both;
       clear: both;
-      color: #818479;
+      color: #777;
       display: block;
       display: block;
       height: 24px;
       height: 24px;
       overflow-y: hidden;
       overflow-y: hidden;
@@ -2492,17 +2551,24 @@ body {
   #conversejs .chat-head-chatroom .chatbox-btn.button-on {
   #conversejs .chat-head-chatroom .chatbox-btn.button-on {
     background-color: white;
     background-color: white;
     color: #E77051; }
     color: #E77051; }
-  #converse-embedded-chat .chat-head-chatroom .chatroom-description,
-  #conversejs .chat-head-chatroom .chatroom-description {
-    color: white;
-    font-size: 80%;
-    font-style: italic;
-    height: 1.3em;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    margin: 0;
-    margin-top: 0.3em; }
+  #converse-embedded-chat .chat-head-chatroom .chat-title,
+  #conversejs .chat-head-chatroom .chat-title {
+    color: #FF977C; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-name,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-name {
+      color: white; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-jid,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-jid {
+      font-size: 14px; }
+    #converse-embedded-chat .chat-head-chatroom .chat-title .chatroom-description,
+    #conversejs .chat-head-chatroom .chat-title .chatroom-description {
+      color: white;
+      font-size: 80%;
+      font-style: italic;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      margin: 0.3em 0; }
 #converse-embedded-chat .chatroom,
 #converse-embedded-chat .chatroom,
 #conversejs .chatroom {
 #conversejs .chatroom {
   width: 300px; }
   width: 300px; }
@@ -2579,7 +2645,7 @@ body {
         float: right;
         float: right;
         vertical-align: top;
         vertical-align: top;
         background-color: white;
         background-color: white;
-        border-left: 1px solid #818479;
+        border-left: 1px solid #777;
         border-bottom-right-radius: 7px;
         border-bottom-right-radius: 7px;
         width: 30%;
         width: 30%;
         height: 100%;
         height: 100%;
@@ -2662,7 +2728,7 @@ body {
         border-bottom-left-radius: 7px;
         border-bottom-left-radius: 7px;
         border-bottom-right-radius: 7px;
         border-bottom-right-radius: 7px;
         border: 0;
         border: 0;
-        color: #818479;
+        color: #777;
         font-size: 16px;
         font-size: 16px;
         height: 289px;
         height: 289px;
         width: 100%;
         width: 100%;
@@ -2707,9 +2773,8 @@ body {
   font-size: 20px; }
   font-size: 20px; }
   #conversejs .chat-head-chatroom .close-chatbox-button:before {
   #conversejs .chat-head-chatroom .close-chatbox-button:before {
     content: "\e601"; }
     content: "\e601"; }
-  #conversejs .chat-head-chatroom .chatroom-description {
-    font-size: 66%;
-    margin-top: 3px; }
+  #conversejs .chat-head-chatroom .chat-title .chatroom-description {
+    font-size: 65%; }
 #conversejs .chatroom {
 #conversejs .chatroom {
   width: -webkit-calc(100% - 250px);
   width: -webkit-calc(100% - 250px);
   width: calc(100% - 250px); }
   width: calc(100% - 250px); }

+ 1 - 1
css/theme.css

@@ -344,5 +344,5 @@ ul.features {
 .sponsors {
 .sponsors {
     clear: both;
     clear: both;
     font-size: 1.1em;
     font-size: 1.1em;
-    padding: 6em 0 7em 0;
+    padding: 2em 0 7em 0;
 }
 }

+ 10 - 11
demo/without_bundled_dependencies.html

@@ -52,9 +52,10 @@
     <script type="text/javascript" src="../dist/locales.js"></script>
     <script type="text/javascript" src="../dist/locales.js"></script>
     <!-- END I18N -->
     <!-- END I18N -->
 
 
-    <script type="text/javascript" src="../node_modules/awesomplete/awesomplete.js"></script>
+    <script type="text/javascript" src="../node_modules/awesomplete-avoid-xss/awesomplete.js"></script>
     <script type="text/javascript" src="../node_modules/moment/min/moment-with-locales.js"></script>
     <script type="text/javascript" src="../node_modules/moment/min/moment-with-locales.js"></script>
 
 
+    <script type="text/javascript" src="../3rdparty/lodash.fp.js"></script>
 	<script src="../dist/converse-no-dependencies.js"></script>
 	<script src="../dist/converse-no-dependencies.js"></script>
 </head>
 </head>
 <body id="page-top" data-spy="scroll" data-target=".navbar-custom">
 <body id="page-top" data-spy="scroll" data-target=".navbar-custom">
@@ -80,16 +81,14 @@
 </body>
 </body>
 
 
 <script>
 <script>
-    require(['converse'], function (converse) {
-        converse.initialize({
-            bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
-            i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
-            prebind: false,
-            show_controlbox_by_default: true,
-            debug: true,
-            roster_groups: true,
-            keepalive: true
-        });
+    converse.initialize({
+        bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
+        i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
+        prebind: false,
+        show_controlbox_by_default: true,
+        debug: true,
+        roster_groups: true,
+        keepalive: true
     });
     });
 </script>
 </script>
 </html>
 </html>

File diff suppressed because it is too large
+ 26281 - 25118
dist/converse-mobile.js


File diff suppressed because it is too large
+ 1125 - 1739
dist/converse-no-dependencies.js


File diff suppressed because it is too large
+ 26285 - 25122
dist/converse.js


File diff suppressed because it is too large
+ 26285 - 25122
dist/inverse.js


File diff suppressed because it is too large
+ 0 - 0
dist/locales.js


+ 1 - 1
docs/source/_templates/layout.html

@@ -2,7 +2,7 @@
 {% extends "!layout.html" %}
 {% extends "!layout.html" %}
 
 
 {# Custom CSS overrides #}
 {# Custom CSS overrides #}
-{% set bootswatch_css_custom = ['_static/style.css', "../../css/converse.min.css"] %}
+{% set css_files = css_files + ['_static/style.css', "../../css/converse.min.css"] %}
 {% set script_files = script_files + ["../../dist/converse.min.js", "../../analytics.js"] %}
 {% set script_files = script_files + ["../../dist/converse.min.js", "../../analytics.js"] %}
 
 
 {# Add some extra stuff before and use existing with 'super()' call. #}
 {# Add some extra stuff before and use existing with 'super()' call. #}

+ 2 - 2
docs/source/conf.py

@@ -48,9 +48,9 @@ copyright = u'2014, JC Brand'
 # built documents.
 # built documents.
 #
 #
 # The short X.Y version.
 # The short X.Y version.
-version = '3.1.1'
+version = '3.2.0-rc'
 # The full version, including alpha/beta/rc tags.
 # The full version, including alpha/beta/rc tags.
-release = '3.1.1'
+release = '3.2.0-rc'
 
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 # for a list of supported languages.

+ 27 - 6
docs/source/configuration.rst

@@ -19,7 +19,7 @@ on your website.
 You'll most likely want to call the *initialize* method in your HTML page. For
 You'll most likely want to call the *initialize* method in your HTML page. For
 an example of how this is done, please see the bottom of the *./index.html* page.
 an example of how this is done, please see the bottom of the *./index.html* page.
 
 
-Please refer to the `Configuration variables`_ section below for info on
+Please refer to the `Configuration settings`_ section below for info on
 all the available configuration settings.
 all the available configuration settings.
 
 
 After you have configured *Converse.js*, you'll have to regenerate the minified
 After you have configured *Converse.js*, you'll have to regenerate the minified
@@ -516,6 +516,16 @@ domain_placeholder
 
 
 The placeholder text shown in the domain input on the registration form.
 The placeholder text shown in the domain input on the registration form.
 
 
+
+emojione_image_path
+-------------------
+
+* Default: ``'https://cdn.jsdelivr.net/emojione/assets/' + emojioneVersion + '/png/'``
+
+When `use_emojione`_ is set to ``true``, then this is the URL from where PNG image files for
+displaying emojis will be fetched.
+
+
 expose_rid_and_sid
 expose_rid_and_sid
 ------------------
 ------------------
 
 
@@ -993,6 +1003,17 @@ Notification will be shown in the following cases:
 
 
 Requires the `src/converse-notification.js` plugin.
 Requires the `src/converse-notification.js` plugin.
 
 
+use_emojione
+------------
+* Default: ``true``
+
+Determines whether `Emojione <https://www.emojione.com/>`_ should be used to
+render emojis. If set to ``false``, then rendering support will fall back to
+the operating system or browser (which might not support emoji).
+
+See also `emojione_image_path`_.
+
+
 show_only_online_users
 show_only_online_users
 ----------------------
 ----------------------
 
 
@@ -1088,7 +1109,7 @@ loaded), then an error will be raised.
 
 
 Otherwise a message will simply be logged and the override instruction ignored.
 Otherwise a message will simply be logged and the override instruction ignored.
 
 
-The Converse.js plugins architecture can have an ``optional_dependencies``
+The Converse.js plugins architecture can have an :ref:`optional_dependencies`
 plugin attribute. This enables you to specify an array of optional, or
 plugin attribute. This enables you to specify an array of optional, or
 "soft", dependencies. Converse.js (more specifically,
 "soft", dependencies. Converse.js (more specifically,
 `pluggable.js <https://jcbrand.github.io/pluggable.js/>`_) will try to first
 `pluggable.js <https://jcbrand.github.io/pluggable.js/>`_) will try to first
@@ -1154,7 +1175,7 @@ visible_toolbar_buttons
     {
     {
         call: false,
         call: false,
         clear: true,
         clear: true,
-        emoticons: true,
+        emoji: true,
         toggle_occupants: true
         toggle_occupants: true
     }
     }
 
 
@@ -1173,9 +1194,9 @@ Allows you to show or hide buttons on the chat boxes' toolbars.
         });
         });
 * *clear*:
 * *clear*:
     Provides a button for clearing messages from a chat box.
     Provides a button for clearing messages from a chat box.
-* *emoticons*:
-    Enables rendering of emoticons and provides a toolbar button for choosing them.
-* toggle_occupants:
+* *emoji*:
+    Enables rendering of emoji and provides a toolbar button for choosing them.
+* *toggle_occupants*:
     Shows a button for toggling (i.e. showing/hiding) the list of occupants in a chat room.
     Shows a button for toggling (i.e. showing/hiding) the list of occupants in a chat room.
 
 
 .. _`websocket-url`:
 .. _`websocket-url`:

+ 23 - 22
docs/source/developer_api.rst

@@ -188,16 +188,15 @@ two important ways:
 
 
 Converse.js has the following promises:
 Converse.js has the following promises:
 
 
-* cachedRoster
-* chatBoxesFetched
-* connected
-* pluginsInitialized
-* roster
-* rosterContactsFetched
-* rosterGroupsFetched
-* rosterInitialized
-* statusInitialized
-* roomsPanelRendered (only via the `converse-muc` plugin)
+* :ref:`cachedRoster`
+* :ref:`chatBoxesFetched`
+* :ref:`pluginsInitialized`
+* :ref:`roster`
+* :ref:`rosterContactsFetched`
+* :ref:`rosterGroupsFetched`
+* :ref:`rosterInitialized`
+* :ref:`statusInitialized`
+* :ref:`roomsPanelRendered` (only via the `converse-muc` plugin)
 
 
 Below is an example from `converse-muc.js <https://github.com/jcbrand/converse.js/blob/master/src/converse-muc.js>`_
 Below is an example from `converse-muc.js <https://github.com/jcbrand/converse.js/blob/master/src/converse-muc.js>`_
 where the `rosterContactsFetched` promise is waited on. The method
 where the `rosterContactsFetched` promise is waited on. The method
@@ -903,22 +902,24 @@ The **promises** grouping
 -------------------------
 -------------------------
 
 
 Converse.js and its plugins emit various events which you can listen to via the 
 Converse.js and its plugins emit various events which you can listen to via the 
-:refs:`listen-grouping`.
+:ref:`listen-grouping`.
 
 
-These events can also be turned into promises, and by default some already
-are.
+Some of these events are also available as `ES2015 Promises <http://es6-features.org/#PromiseUsage>`_,
+although not all of them could logically act as promises, since some events
+might be fired multpile times whereas promises are to be resolved (or
+rejected) only once.
 
 
 The core events, which are also promises are:
 The core events, which are also promises are:
 
 
-* cachedRoster
-* chatBoxesFetched
-* connected
-* pluginsInitialized
-* roster
-* rosterContactsFetched
-* rosterGroupsFetched
-* rosterInitialized
-* statusInitialized
+* :ref:`cachedRoster`
+* :ref:`chatBoxesFetched`
+* :ref:`pluginsInitialized`
+* :ref:`roster`
+* :ref:`rosterContactsFetched`
+* :ref:`rosterGroupsFetched`
+* :ref:`rosterInitialized`
+* :ref:`statusInitialized`
+* :ref:`roomsPanelRendered` (only via the `converse-muc` plugin)
 
 
 The various plugins might also provide promises, and they do this by using the
 The various plugins might also provide promises, and they do this by using the
 ``promises.add`` api method.
 ``promises.add`` api method.

+ 154 - 8
docs/source/events.rst

@@ -4,18 +4,41 @@
 
 
 .. _`events-API`:
 .. _`events-API`:
 
 
-Events emitted by converse.js
-=============================
+Events and promises
+===================
 
 
 .. contents:: Table of Contents
 .. contents:: Table of Contents
    :depth: 2
    :depth: 2
    :local:
    :local:
 
 
+Converse.js and its plugins emit various events which you can listen to via the
+:ref:`listen-grouping`.
 
 
-.. note:: see also :ref:`listen-grouping` above.
+Some of these events are also available as `ES2015 Promises <http://es6-features.org/#PromiseUsage>`_,
+although not all of them could logically act as promises, since some events
+might be fired multpile times whereas promises are to be resolved (or
+rejected) only once.
 
 
-Event Types
------------
+The core events, which are also promises are:
+
+* `cachedRoster`_
+* `chatBoxesFetched`_
+* `pluginsInitialized`_
+* `roster`_
+* `rosterContactsFetched`_
+* `rosterGroupsFetched`_
+* `rosterInitialized`_
+* `statusInitialized`_
+* `roomsPanelRendered`_ (only via the `converse-muc` plugin)
+
+For more info on how to use (or add promises), you can read the
+:ref:`promises-grouping` in the API documentation.
+
+Below we will now list all events and also specify whether they are available
+as promises.
+
+List of Events (and promises)
+-----------------------------
 
 
 Hooking into events that Converse.js emits is a great way to extend or
 Hooking into events that Converse.js emits is a great way to extend or
 customize its functionality.
 customize its functionality.
@@ -27,6 +50,22 @@ Refer to the :ref:`whitelisted_plugins` setting.
 
 
 Here follows the different events that are emitted:
 Here follows the different events that are emitted:
 
 
+afterMessagesFetched
+~~~~~~~~~~~~~~~~~~~~
+
+Emitted whenever a chat box has fetched its messages from ``sessionStorage`` and
+**NOT** from the server.
+
+This event is listened to by the ``converse-mam`` plugin to know when it can
+fetch archived messages from the server.
+
+The event handler is passed the ``Backbone.View`` instance of the relevant chat
+box.
+
+``_converse.on('afterMessagesFetched', function (chatboxview) { ... });``
+
+.. _`cachedRoster`:
+
 cachedRoster
 cachedRoster
 ~~~~~~~~~~~~
 ~~~~~~~~~~~~
 
 
@@ -34,6 +73,14 @@ The contacts roster has been retrieved from the local cache (`sessionStorage`).
 
 
 ``_converse.on('cachedRoster', function (items) { ... });``
 ``_converse.on('cachedRoster', function (items) { ... });``
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('cachedRoster').then(function () {
+        // Your code here...
+    });
+
 See also the `roster`_ event further down.
 See also the `roster`_ event further down.
 
 
 callButtonClicked
 callButtonClicked
@@ -43,6 +90,26 @@ When a call button (i.e. with class .toggle-call) on a chat box has been clicked
 
 
 ``_converse.on('callButtonClicked', function (connection, model) { ... });``
 ``_converse.on('callButtonClicked', function (connection, model) { ... });``
 
 
+.. _`chatBoxesFetched`:
+
+chatBoxesFetched
+~~~~~~~~~~~~~~~~
+
+Any open chat boxes (from this current session) has been retrieved from the local cache (`sessionStorage`).
+
+You should wait for this event or promise before attempting to do things
+related to open chat boxes.
+
+``_converse.on('chatBoxesFetched', function (items) { ... });``
+
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('chatBoxesFetched').then(function () {
+        // Your code here...
+    });
+
 chatBoxInitialized
 chatBoxInitialized
 ~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~
 
 
@@ -121,6 +188,15 @@ When a chat buddy's custom status message has changed.
 
 
 ``_converse.on('contactStatusMessageChanged', function (data) { ... });``
 ``_converse.on('contactStatusMessageChanged', function (data) { ... });``
 
 
+discoInitialized
+~~~~~~~~~~~~~~~~
+
+Emitted once the ``converse-disco`` plugin has been initialized and the
+``_converse.disco_entities`` collection will be available and populated with at
+least the service discovery features of the user's own server.
+
+``_converse.on('discoInitialized', function () { ... });``
+
 disconnected
 disconnected
 ~~~~~~~~~~~~
 ~~~~~~~~~~~~
 
 
@@ -151,7 +227,7 @@ Once a message has been added to a chat box. The passed in data object contains
 a `chatbox` attribute, referring to the chat box receiving the message, as well
 a `chatbox` attribute, referring to the chat box receiving the message, as well
 as a `message` attribute which refers to the Message model.
 as a `message` attribute which refers to the Message model.
 
 
-.. code-block:: javascript 
+.. code-block:: javascript
 
 
     _converse.on('messageAdded', function (data) {
     _converse.on('messageAdded', function (data) {
         // The message is at `data.message`
         // The message is at `data.message`
@@ -172,10 +248,12 @@ When keepalive=true but there aren't any stored prebind tokens.
 
 
 ``_converse.on('noResumeableSession', function () { ... });``
 ``_converse.on('noResumeableSession', function () { ... });``
 
 
+.. _`pluginsInitialized`:
+
 pluginsInitialized
 pluginsInitialized
 ~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~
 
 
-Once all plugins have been initialized. This is a useful event if you want to
+Emitted once all plugins have been initialized. This is a useful event if you want to
 register event handlers but would like your own handlers to be overridable by
 register event handlers but would like your own handlers to be overridable by
 plugins. In that case, you need to first wait until all plugins have been
 plugins. In that case, you need to first wait until all plugins have been
 initialized, so that their overrides are active. One example where this is used
 initialized, so that their overrides are active. One example where this is used
@@ -183,6 +261,14 @@ is in `converse-notifications.js <https://github.com/jcbrand/converse.js/blob/ma
 
 
 ``_converse.on('pluginsInitialized', function () { ... });``
 ``_converse.on('pluginsInitialized', function () { ... });``
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('pluginsInitialized').then(function () {
+        // Your code here...
+    });
+
 reconnecting
 reconnecting
 ~~~~~~~~~~~~
 ~~~~~~~~~~~~
 
 
@@ -212,6 +298,8 @@ After the user has sent out a direct invitation, to a roster contact, asking the
 
 
 ``_converse.on('roomInvite', function (data) { ... });``
 ``_converse.on('roomInvite', function (data) { ... });``
 
 
+.. _`roomsPanelRendered`:
+
 roomsPanelRendered
 roomsPanelRendered
 ~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~
 
 
@@ -221,6 +309,16 @@ render themselves in that panel.
 
 
 ``_converse.on('roomsPanelRendered', function (data) { ... });``
 ``_converse.on('roomsPanelRendered', function (data) { ... });``
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('roomsPanelRendered').then(function () {
+        // Your code here...
+    });
+
+.. _`roster`:
+
 roster
 roster
 ~~~~~~
 ~~~~~~
 
 
@@ -228,15 +326,35 @@ When the roster has been received from the XMPP server.
 
 
 ``_converse.on('roster', function (items) { ... });``
 ``_converse.on('roster', function (items) { ... });``
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('roster').then(function () {
+        // Your code here...
+    });
+
 See also the `cachedRoster` event further up, which gets called instead of
 See also the `cachedRoster` event further up, which gets called instead of
 `roster` if its already in `sessionStorage`.
 `roster` if its already in `sessionStorage`.
 
 
+.. _`rosterContactsFetched`:
+
 rosterContactsFetched
 rosterContactsFetched
 ~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~
 
 
 Triggered once roster contacts have been fetched. Used by the
 Triggered once roster contacts have been fetched. Used by the
 `converse-rosterview.js` plugin to know when it can start to show the roster.
 `converse-rosterview.js` plugin to know when it can start to show the roster.
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('rosterContactsFetched').then(function () {
+        // Your code here...
+    });
+
+.. _`rosterGroupsFetched`:
+
 rosterGroupsFetched
 rosterGroupsFetched
 ~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~
 
 
@@ -244,6 +362,16 @@ Triggered once roster groups have been fetched. Used by the
 `converse-rosterview.js` plugin to know when it can start alphabetically
 `converse-rosterview.js` plugin to know when it can start alphabetically
 position roster groups.
 position roster groups.
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('rosterGroupsFetched').then(function () {
+        // Your code here...
+    });
+
+.. _`rosterInitialized`:
+
 rosterInitialized
 rosterInitialized
 ~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~
 
 
@@ -252,6 +380,14 @@ but not yet populated with data.
 
 
 This event is useful when you want to create views for these collections.
 This event is useful when you want to create views for these collections.
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('rosterInitialized').then(function () {
+        // Your code here...
+    });
+
 rosterPush
 rosterPush
 ~~~~~~~~~~
 ~~~~~~~~~~
 
 
@@ -266,13 +402,23 @@ Similar to `rosterInitialized`, but instead pertaining to reconnection. This
 event indicates that the Backbone collections representing the roster and its
 event indicates that the Backbone collections representing the roster and its
 groups are now again available after converse.js has reconnected.
 groups are now again available after converse.js has reconnected.
 
 
+.. _`statusInitialized`:
+
 statusInitialized
 statusInitialized
 ~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~
 
 
-When own chat status has been initialized.
+When the user's own chat status has been initialized.
 
 
 ``_converse.on('statusInitialized', function (status) { ... });``
 ``_converse.on('statusInitialized', function (status) { ... });``
 
 
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('statusInitialized').then(function () {
+        // Your code here...
+    });
+
 statusChanged
 statusChanged
 ~~~~~~~~~~~~~
 ~~~~~~~~~~~~~
 
 

+ 3 - 1
docs/source/plugin_development.rst

@@ -167,6 +167,8 @@ A better approach is to listen to the events emitted by Converse.js, and to add
 your code in event handlers. This is however not always possible, in which case
 your code in event handlers. This is however not always possible, in which case
 the overrides are a powerful tool.
 the overrides are a powerful tool.
 
 
+.. _`optional_dependencies`:
+
 Optional plugin dependencies
 Optional plugin dependencies
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
@@ -189,7 +191,7 @@ In this case, you can't specify the plugin as a dependency in the ``define``
 statement at the top of the plugin, since it might not always be available,
 statement at the top of the plugin, since it might not always be available,
 which would cause ``require.js`` to throw an error.
 which would cause ``require.js`` to throw an error.
 
 
-To resolve this problem we thave the ``optional_dependencies`` Array attribute.
+To resolve this problem we have the ``optional_dependencies`` Array attribute.
 With this you can specify those dependencies which need to be loaded before
 With this you can specify those dependencies which need to be loaded before
 your plugin, if they exist. If they don't exist, they won't be ignored.
 your plugin, if they exist. If they don't exist, they won't be ignored.
 
 

+ 2 - 32
index.html

@@ -15,7 +15,8 @@
     <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.1.0/css/converse.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.1.0/css/converse.min.css" />
     <script type="text/javascript" src="analytics.js"></script>
     <script type="text/javascript" src="analytics.js"></script>
     <noscript><p><img src="//stats.opkode.com/piwik.php?idsite=1" style="border:0;" alt="" /></p></noscript>
     <noscript><p><img src="//stats.opkode.com/piwik.php?idsite=1" style="border:0;" alt="" /></p></noscript>
-    <![if gte IE 9]>
+	<script src="src/website.js"></script>
+    <![if gte IE 11]>
 	<script src="https://cdn.conversejs.org/3.1.0/dist/converse.min.js"></script>
 	<script src="https://cdn.conversejs.org/3.1.0/dist/converse.min.js"></script>
     <![endif]>
     <![endif]>
 </head>
 </head>
@@ -223,37 +224,6 @@
 </body>
 </body>
 
 
 <script>
 <script>
-    (function () {
-        /* XXX: This function initializes jquery.easing for the https://conversejs.org
-        * website. This code is only useful in the context of the converse.js
-        * website and converse.js itself is NOT dependent on it.
-        */
-        var $ = converse.env.jQuery;
-        $.extend( $.easing, {
-            easeInOutExpo: function (x, t, b, c, d) {
-                if (t==0) return b;
-                if (t==d) return b+c;
-                if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
-                return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
-            },
-        });
-
-        $(window).scroll(function() {
-            if ($(".navbar").offset().top > 50) {
-                $(".navbar-fixed-top").addClass("top-nav-collapse");
-            } else {
-                $(".navbar-fixed-top").removeClass("top-nav-collapse");
-            }
-        });
-        //jQuery for page scrolling feature - requires jQuery Easing plugin
-        $('.page-scroll a').bind('click', function(event) {
-            var $anchor = $(this);
-            $('html, body').stop().animate({
-                scrollTop: $($anchor.attr('href')).offset().top
-            }, 700, 'easeInOutExpo');
-            event.preventDefault();
-        });
-    })();
     converse.initialize({
     converse.initialize({
         // Please use this connection manager only for testing purposes
         // Please use this connection manager only for testing purposes
         bosh_service_url: 'https://conversejs.org/http-bind/',
         bosh_service_url: 'https://conversejs.org/http-bind/',

+ 2 - 2
inverse.html

@@ -8,7 +8,7 @@
     <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.1.0/css/inverse.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.1.0/css/inverse.min.css" />
     <script type="text/javascript" src="inverse-analytics.js"></script>
     <script type="text/javascript" src="inverse-analytics.js"></script>
     <noscript><p><img src="//stats.opkode.com/piwik.php?idsite=5" style="border:0;" alt="" /></p></noscript>
     <noscript><p><img src="//stats.opkode.com/piwik.php?idsite=5" style="border:0;" alt="" /></p></noscript>
-    <script src="https://cdn.conversejs.org/3.1.0/dist/inverse.min.js"></script>
+    <script src="dist/inverse.min.js"></script>
 </head>
 </head>
 <body>
 <body>
     <div class="content">
     <div class="content">
@@ -20,7 +20,7 @@
     converse.initialize({
     converse.initialize({
         authentication: 'login',
         authentication: 'login',
         auto_away: 300,
         auto_away: 300,
-        blacklisted_plugins: ['converse-minimize', 'converse-dragresize', 'converse-otr'],
+        blacklisted_plugins: ['converse-minimize', 'converse-dragresize'],
         whitelisted_plugins: ['converse-inverse', 'converse-singleton'],
         whitelisted_plugins: ['converse-inverse', 'converse-singleton'],
         auto_reconnect: true,
         auto_reconnect: true,
         bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
         bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes

+ 14 - 82
locale/af/LC_MESSAGES/converse.json

@@ -31,6 +31,10 @@
             null,
             null,
             "Kanseleer"
             "Kanseleer"
          ],
          ],
+         "Are you sure you want to remove the bookmark \"%1$s\"?": [
+            null,
+            "Is u seker u wil die boekmerk \"%1$s\" verwyder?"
+         ],
          "Sorry, something went wrong while trying to save your bookmark.": [
          "Sorry, something went wrong while trying to save your bookmark.": [
             null,
             null,
             "Jammer, 'n fout het voorgekom tydens storing van u boekmerk."
             "Jammer, 'n fout het voorgekom tydens storing van u boekmerk."
@@ -39,10 +43,6 @@
             null,
             null,
             "Klik om die boekmerklys te skakel"
             "Klik om die boekmerklys te skakel"
          ],
          ],
-         "Are you sure you want to remove the bookmark \"%1$s\"?": [
-            null,
-            "Is u seker u wil die boekmerk \"%1$s\" verwyder?"
-         ],
          "Remove this bookmark": [
          "Remove this bookmark": [
             null,
             null,
             "Verwyder hierdie boekmerk"
             "Verwyder hierdie boekmerk"
@@ -255,54 +255,6 @@
             null,
             null,
             "Geen gebruikers gevind"
             "Geen gebruikers gevind"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Klik om as kletskontak by te voeg"
-         ],
-         "Toggle chat": [
-            null,
-            "Klets"
-         ],
-         "Reconnecting": [
-            null,
-            "Herkonnekteer"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            "Die konneksie is onderbreek, probeer tans tans om te herkonnekteer."
-         ],
-         "Connection error": [
-            null,
-            "Fout tydens verbinding"
-         ],
-         "An error occurred while connecting to the chat server.": [
-            null,
-            "A fout het voorgekom tydens verbinding met die kletsbediener."
-         ],
-         "Connecting": [
-            null,
-            "Verbind tans"
-         ],
-         "Authenticating": [
-            null,
-            "Besig om te bekragtig"
-         ],
-         "Authentication Failed": [
-            null,
-            "Bekragtiging het gefaal"
-         ],
-         "Connection failed": [
-            null,
-            "Verbinding het gefaal"
-         ],
-         "An error occurred while connecting to the chat server: ": [
-            null,
-            "A fout het voorgekom tydens verbinding met die kletsbediener: "
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Jammer, 'n ander fout het voorgekom tydens byvoeging. "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Hierdie klient laat nie beskikbaarheidsinskrywings toe nie"
             "Hierdie klient laat nie beskikbaarheidsinskrywings toe nie"
@@ -431,9 +383,9 @@
             null,
             null,
             "Verskuil die lys van deelnemers"
             "Verskuil die lys van deelnemers"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Fout: die \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -535,9 +487,9 @@
             null,
             null,
             "Die gegewe rede is: \"%1$s\"."
             "Die gegewe rede is: \"%1$s\"."
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Die gegewe rede is: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -587,10 +539,6 @@
             null,
             null,
             "Onderwerp deur %1$s bygewerk na: %2$s"
             "Onderwerp deur %1$s bygewerk na: %2$s"
          ],
          ],
-         "Click to mention ": [
-            null,
-            "Klik om te noem "
-         ],
          "This user is a moderator.": [
          "This user is a moderator.": [
             null,
             null,
             "Hierdie gebruiker is 'n moderator."
             "Hierdie gebruiker is 'n moderator."
@@ -683,10 +631,6 @@
             null,
             null,
             "Hierdie kletskamer word gemodereer"
             "Hierdie kletskamer word gemodereer"
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
-            null,
-            "Alle ander deelnemers can u Jabber ID sien"
-         ],
          "Anyone can join this room": [
          "Anyone can join this room": [
             null,
             null,
             "Enige iemand kan hierdie kletskamer binnekom"
             "Enige iemand kan hierdie kletskamer binnekom"
@@ -703,10 +647,6 @@
             null,
             null,
             "Hierdie kletskamer is publiek opspoorbaar"
             "Hierdie kletskamer is publiek opspoorbaar"
          ],
          ],
-         "Only moderators can see your Jabber ID": [
-            null,
-            "Slegs moderators kan u Jabber ID sien"
-         ],
          "This room will disappear once the last person leaves": [
          "This room will disappear once the last person leaves": [
             null,
             null,
             "Hierdie kletskamer sal verdwyn sodra die laaste persoon dit verlaat"
             "Hierdie kletskamer sal verdwyn sodra die laaste persoon dit verlaat"
@@ -727,6 +667,10 @@
             null,
             null,
             "U mag na keuse 'n boodskap insluit, om bv. die rede vir die uitnodiging te staaf."
             "U mag na keuse 'n boodskap insluit, om bv. die rede vir die uitnodiging te staaf."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Kamer naam"
             "Kamer naam"
@@ -751,17 +695,13 @@
             null,
             null,
             "Geen kletskamers op %1$s"
             "Geen kletskamers op %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Kletskamers op %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Beskrywing:"
             "Beskrywing:"
          ],
          ],
-         "Server:": [
+         "Room Address (JID):": [
             null,
             null,
-            "Bediener:"
+            ""
          ],
          ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
@@ -987,10 +927,6 @@
             null,
             null,
             "Suksesvol geregistreer"
             "Suksesvol geregistreer"
          ],
          ],
-         "Return": [
-            null,
-            "Terug"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "Die verskaffer het u registrasieversoek verwerp. Kontrolleer asb. jou gegewe waardes vir korrektheid."
             "Die verskaffer het u registrasieversoek verwerp. Kontrolleer asb. jou gegewe waardes vir korrektheid."
@@ -1079,10 +1015,6 @@
             null,
             null,
             "Is u seker u wil hierdie gespreksmaat verwyder?"
             "Is u seker u wil hierdie gespreksmaat verwyder?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Jammer, 'n fout het voorgekom tydens die verwydering van "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Is u seker dat u hierdie persoon se versoek wil afkeur?"
             "Is u seker dat u hierdie persoon se versoek wil afkeur?"

File diff suppressed because it is too large
+ 212 - 266
locale/af/LC_MESSAGES/converse.po


+ 14 - 46
locale/ca/LC_MESSAGES/converse.json

@@ -235,34 +235,6 @@
             null,
             null,
             "No s'ha trobat cap usuari"
             "No s'ha trobat cap usuari"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Feu clic per afegir com a contacte del xat"
-         ],
-         "Toggle chat": [
-            null,
-            "Canvia de xat"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "S'està establint la connexió"
-         ],
-         "Authenticating": [
-            null,
-            "S'està efectuant l'autenticació"
-         ],
-         "Authentication Failed": [
-            null,
-            "Error d'autenticació"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "S'ha produït un error en intentar afegir "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Aquest client no admet les subscripcions de presència"
             "Aquest client no admet les subscripcions de presència"
@@ -343,9 +315,9 @@
             null,
             null,
             "Amaga la llista d'ocupants"
             "Amaga la llista d'ocupants"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Error: el \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -427,9 +399,9 @@
             null,
             null,
             "Envia"
             "Envia"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "El motiu indicat és: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -495,7 +467,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -503,7 +475,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -519,6 +491,10 @@
             null,
             null,
             "Teniu l'opció d'incloure un missatge per explicar el motiu de la invitació."
             "Teniu l'opció d'incloure un missatge per explicar el motiu de la invitació."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nom de la sala"
             "Nom de la sala"
@@ -543,14 +519,14 @@
             null,
             null,
             "No hi ha cap sala a %1$s"
             "No hi ha cap sala a %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Sales a %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Descripció:"
             "Descripció:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Ocupants:"
             "Ocupants:"
@@ -771,10 +747,6 @@
             null,
             null,
             "Registre correcte"
             "Registre correcte"
          ],
          ],
-         "Return": [
-            null,
-            "Torna"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "El proveïdor ha rebutjat l'intent de registre. Comproveu que els valors que heu introduït siguin correctes."
             "El proveïdor ha rebutjat l'intent de registre. Comproveu que els valors que heu introduït siguin correctes."
@@ -863,10 +835,6 @@
             null,
             null,
             "Segur que voleu eliminar aquest contacte?"
             "Segur que voleu eliminar aquest contacte?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "S'ha produït un error en intentar eliminar "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Segur que voleu rebutjar aquesta sol·licitud de contacte?"
             "Segur que voleu rebutjar aquesta sol·licitud de contacte?"

File diff suppressed because it is too large
+ 210 - 273
locale/ca/LC_MESSAGES/converse.po


File diff suppressed because it is too large
+ 208 - 264
locale/converse.pot


+ 14 - 82
locale/de/LC_MESSAGES/converse.json

@@ -31,6 +31,10 @@
             null,
             null,
             "Abbrechen"
             "Abbrechen"
          ],
          ],
+         "Are you sure you want to remove the bookmark \"%1$s\"?": [
+            null,
+            "Wollen Sie dieses Lesezeichen wirklich entfernen \"%1$s\"?"
+         ],
          "Sorry, something went wrong while trying to save your bookmark.": [
          "Sorry, something went wrong while trying to save your bookmark.": [
             null,
             null,
             "Etwas ging beim Versuch des Abspeicherns des Lesezeichens schief."
             "Etwas ging beim Versuch des Abspeicherns des Lesezeichens schief."
@@ -39,10 +43,6 @@
             null,
             null,
             "Zum Aus-/Einklappen klicken"
             "Zum Aus-/Einklappen klicken"
          ],
          ],
-         "Are you sure you want to remove the bookmark \"%1$s\"?": [
-            null,
-            "Wollen Sie dieses Lesezeichen wirklich entfernen \"%1$s\"?"
-         ],
          "Remove this bookmark": [
          "Remove this bookmark": [
             null,
             null,
             "Dieses Lesezeichen entfernen"
             "Dieses Lesezeichen entfernen"
@@ -255,54 +255,6 @@
             null,
             null,
             "Keine Benutzer gefunden"
             "Keine Benutzer gefunden"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Hier klicken um als Kontakt hinzuzufügen"
-         ],
-         "Toggle chat": [
-            null,
-            "Chat ein-/ausblenden"
-         ],
-         "Reconnecting": [
-            null,
-            "Verbindung wiederherstellen"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            "Die Verbindung wurde unterbrochen. Versuche neu zu verbinden."
-         ],
-         "Connection error": [
-            null,
-            "Verbindungsfehler"
-         ],
-         "An error occurred while connecting to the chat server.": [
-            null,
-            "Beim Speichern des Formulars ist ein Fehler aufgetreten."
-         ],
-         "Connecting": [
-            null,
-            "Verbindungsaufbau"
-         ],
-         "Authenticating": [
-            null,
-            "Authentifizierung"
-         ],
-         "Authentication Failed": [
-            null,
-            "Authentifizierung gescheitert"
-         ],
-         "Connection failed": [
-            null,
-            "Verbindung fehlgeschlagen"
-         ],
-         "An error occurred while connecting to the chat server: ": [
-            null,
-            "Es trat ein Fehler während des Verbindungsvorgangs mit dem Chat-Server auf: "
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Entschuldige, es kam zu einem Fehler bei der Hinzufügung von "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Dieser Kontakt erlaubt die Abonnieren des Status nicht"
             "Dieser Kontakt erlaubt die Abonnieren des Status nicht"
@@ -431,9 +383,9 @@
             null,
             null,
             "Teilnehmerliste ausblenden"
             "Teilnehmerliste ausblenden"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Fehler: Das \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -535,9 +487,9 @@
             null,
             null,
             "Die angegebene Begründung lautet: \"%1$s\"."
             "Die angegebene Begründung lautet: \"%1$s\"."
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Die angegebene Begründung lautet: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -587,10 +539,6 @@
             null,
             null,
             "%1$s hat das Thema zu \"%2$s\" geändert"
             "%1$s hat das Thema zu \"%2$s\" geändert"
          ],
          ],
-         "Click to mention ": [
-            null,
-            "Drücke um zu erwähnen "
-         ],
          "This user is a moderator.": [
          "This user is a moderator.": [
             null,
             null,
             "Dieser Benutzer ist ein Moderator."
             "Dieser Benutzer ist ein Moderator."
@@ -683,10 +631,6 @@
             null,
             null,
             "Dieser Raum ist moderiert"
             "Dieser Raum ist moderiert"
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
-            null,
-            "Jeder in dem Raum kann deine Jabber ID sehen"
-         ],
          "Anyone can join this room": [
          "Anyone can join this room": [
             null,
             null,
             "Jeder kann diesen Raum betreten"
             "Jeder kann diesen Raum betreten"
@@ -703,10 +647,6 @@
             null,
             null,
             "Dieser Raum ist per Suche auffindbar"
             "Dieser Raum ist per Suche auffindbar"
          ],
          ],
-         "Only moderators can see your Jabber ID": [
-            null,
-            "Nur Moderatoren können deine Jabber ID sehen"
-         ],
          "This room will disappear once the last person leaves": [
          "This room will disappear once the last person leaves": [
             null,
             null,
             "Dieser Raum verschwindet sobald diesen die letzte Person verlassen hat"
             "Dieser Raum verschwindet sobald diesen die letzte Person verlassen hat"
@@ -727,6 +667,10 @@
             null,
             null,
             "Du kannst optional eine Nachricht mit den Gründen der Einladung mitsenden."
             "Du kannst optional eine Nachricht mit den Gründen der Einladung mitsenden."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Raumname"
             "Raumname"
@@ -751,17 +695,13 @@
             null,
             null,
             "Keine Räume auf %1$s"
             "Keine Räume auf %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Räume auf %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Beschreibung:"
             "Beschreibung:"
          ],
          ],
-         "Server:": [
+         "Room Address (JID):": [
             null,
             null,
-            "Server:"
+            ""
          ],
          ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
@@ -987,10 +927,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            "Zurück"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -1079,10 +1015,6 @@
             null,
             null,
             "Wollen Sie diesen Kontakt wirklich entfernen?"
             "Wollen Sie diesen Kontakt wirklich entfernen?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Wollen Sie diese Kontaktanfrage wirklich ablehnen?"
             "Wollen Sie diese Kontaktanfrage wirklich ablehnen?"

File diff suppressed because it is too large
+ 212 - 267
locale/de/LC_MESSAGES/converse.po


+ 12 - 48
locale/es/LC_MESSAGES/converse.json

@@ -203,38 +203,6 @@
             null,
             null,
             "Sin usuarios encontrados"
             "Sin usuarios encontrados"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Haga click para agregar como contacto de chat"
-         ],
-         "Toggle chat": [
-            null,
-            "Chat"
-         ],
-         "Reconnecting": [
-            null,
-            "Reconectando"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Conectando"
-         ],
-         "Authenticating": [
-            null,
-            "Autenticando"
-         ],
-         "Authentication Failed": [
-            null,
-            "La autenticación falló"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -311,7 +279,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -387,7 +355,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -459,7 +427,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -467,7 +435,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -483,6 +451,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nombre de sala"
             "Nombre de sala"
@@ -503,14 +475,14 @@
             null,
             null,
             "Sin salas en %1$s"
             "Sin salas en %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Salas en %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Descripción"
             "Descripción"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Ocupantes:"
             "Ocupantes:"
@@ -695,10 +667,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -786,10 +754,6 @@
          "Are you sure you want to remove this contact?": [
          "Are you sure you want to remove this contact?": [
             null,
             null,
             "¿Esta seguro de querer eliminar este contacto?"
             "¿Esta seguro de querer eliminar este contacto?"
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 270
locale/es/LC_MESSAGES/converse.po


+ 12 - 76
locale/fr/LC_MESSAGES/converse.json

@@ -251,54 +251,6 @@
             null,
             null,
             "Aucun utilisateur trouvé"
             "Aucun utilisateur trouvé"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Cliquer pour ajouter aux contacts"
-         ],
-         "Toggle chat": [
-            null,
-            "Ouvrir IM"
-         ],
-         "Reconnecting": [
-            null,
-            "Reconnexion"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            "La connexion a été perdue, tentative de reconnexion en cours."
-         ],
-         "Connection error": [
-            null,
-            "Erreur de connexion"
-         ],
-         "An error occurred while connecting to the chat server.": [
-            null,
-            "Une erreur est survenue lors de la connexion au serveur de discussion."
-         ],
-         "Connecting": [
-            null,
-            "Connexion"
-         ],
-         "Authenticating": [
-            null,
-            "Authentification"
-         ],
-         "Authentication Failed": [
-            null,
-            "L’authentification a échoué"
-         ],
-         "Connection failed": [
-            null,
-            "La connexion a échoué"
-         ],
-         "An error occurred while connecting to the chat server: ": [
-            null,
-            "Une erreur est survenue lors de la connexion au serveur de discussion : "
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Désolé, il y a eu une erreur lors de la tentative d’ajout "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Ce client ne permet pas les mises à jour de disponibilité"
             "Ce client ne permet pas les mises à jour de disponibilité"
@@ -427,9 +379,9 @@
             null,
             null,
             "Cacher la liste des participants"
             "Cacher la liste des participants"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Erreur : \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -531,9 +483,9 @@
             null,
             null,
             "La raison indiquée est : « %1$s »."
             "La raison indiquée est : « %1$s »."
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "La raison indiquée est : \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -555,10 +507,6 @@
             null,
             null,
             "Le sujet « %2$s » a été défini par %1$s"
             "Le sujet « %2$s » a été défini par %1$s"
          ],
          ],
-         "Click to mention ": [
-            null,
-            "Cliquer pour citer "
-         ],
          "This user is a moderator.": [
          "This user is a moderator.": [
             null,
             null,
             "Cet utilisateur est un modérateur."
             "Cet utilisateur est un modérateur."
@@ -647,10 +595,6 @@
             null,
             null,
             "Ce salon est modéré"
             "Ce salon est modéré"
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
-            null,
-            "Tous les autres occupants de ce salon peuvent voir votre ID Jabber"
-         ],
          "Anyone can join this room": [
          "Anyone can join this room": [
             null,
             null,
             "N’importe qui peut rejoindre ce salon"
             "N’importe qui peut rejoindre ce salon"
@@ -659,10 +603,6 @@
             null,
             null,
             "Ce salon nécessite un mot de passe pour y accéder"
             "Ce salon nécessite un mot de passe pour y accéder"
          ],
          ],
-         "Only moderators can see your Jabber ID": [
-            null,
-            "Seuls les modérateurs peuvent voir votre identifiant Jabber"
-         ],
          "This room will disappear once the last person leaves": [
          "This room will disappear once the last person leaves": [
             null,
             null,
             "Ce salon disparaîtra au départ de la dernière personne"
             "Ce salon disparaîtra au départ de la dernière personne"
@@ -683,6 +623,10 @@
             null,
             null,
             "Vous pouvez facultativement ajouter un message, expliquant la raison de cette invitation."
             "Vous pouvez facultativement ajouter un message, expliquant la raison de cette invitation."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nom du salon"
             "Nom du salon"
@@ -707,14 +651,14 @@
             null,
             null,
             "Aucun salon dans %1$s"
             "Aucun salon dans %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Salons dans %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Description :"
             "Description :"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Participants :"
             "Participants :"
@@ -939,10 +883,6 @@
             null,
             null,
             "Enregistré avec succès"
             "Enregistré avec succès"
          ],
          ],
-         "Return": [
-            null,
-            "Retourner"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "Le fournisseur a rejeté votre demande d’enregistrement."
             "Le fournisseur a rejeté votre demande d’enregistrement."
@@ -1031,10 +971,6 @@
             null,
             null,
             "Voulez-vous vraiment supprimer ce contact ?"
             "Voulez-vous vraiment supprimer ce contact ?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Désolé, il y a eu une erreur lors de la tentative de retrait "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Voulez-vous vraiment refuser cette demande de contact ?"
             "Voulez-vous vraiment refuser cette demande de contact ?"

File diff suppressed because it is too large
+ 212 - 268
locale/fr/LC_MESSAGES/converse.po


+ 13 - 49
locale/he/LC_MESSAGES/converse.json

@@ -231,38 +231,6 @@
             null,
             null,
             "לא נמצאו משתמשים"
             "לא נמצאו משתמשים"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "לחץ כדי להוסיף בתור איש קשר שיחה"
-         ],
-         "Toggle chat": [
-            null,
-            "הפעל שיח"
-         ],
-         "Reconnecting": [
-            null,
-            "כעת מתחבר"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "כעת מתחבר"
-         ],
-         "Authenticating": [
-            null,
-            "כעת מאמת"
-         ],
-         "Authentication Failed": [
-            null,
-            "אימות נכשל"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "מצטערים, היתה שגיאה במהלך ניסיון הוספת "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "לקוח זה לא מתיר הרשמות נוכחות"
             "לקוח זה לא מתיר הרשמות נוכחות"
@@ -339,7 +307,7 @@
             null,
             null,
             "הודעה"
             "הודעה"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -419,9 +387,9 @@
             null,
             null,
             "שלח"
             "שלח"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "הסיבה שניתנה היא: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -491,7 +459,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -499,7 +467,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -515,6 +483,10 @@
             null,
             null,
             "באפשרותך להכליל הודעה, אשר  מסבירה את הסיבה להזמנה."
             "באפשרותך להכליל הודעה, אשר  מסבירה את הסיבה להזמנה."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "שם חדר"
             "שם חדר"
@@ -539,14 +511,14 @@
             null,
             null,
             "אין חדרים על %1$s"
             "אין חדרים על %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "חדרים על %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "תיאור:"
             "תיאור:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "נוכחים:"
             "נוכחים:"
@@ -767,10 +739,6 @@
             null,
             null,
             "נרשם בהצלחה"
             "נרשם בהצלחה"
          ],
          ],
-         "Return": [
-            null,
-            "חזור"
-         ],
          "Retry": [
          "Retry": [
             null,
             null,
             ""
             ""
@@ -855,10 +823,6 @@
             null,
             null,
             "האם אתה בטוח כי ברצונך להסיר את איש קשר זה?"
             "האם אתה בטוח כי ברצונך להסיר את איש קשר זה?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "מצטערים, היתה שגיאה במהלך ניסיון להסיר את "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "האם אתה בטוח כי ברצונך לסרב את בקשת איש קשר זה?"
             "האם אתה בטוח כי ברצונך לסרב את בקשת איש קשר זה?"

File diff suppressed because it is too large
+ 209 - 271
locale/he/LC_MESSAGES/converse.po


+ 14 - 50
locale/hu/LC_MESSAGES/converse.json

@@ -234,38 +234,6 @@
             null,
             null,
             "Nincs felhasználó"
             "Nincs felhasználó"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Felvétel a csevegőpartnerek közé"
-         ],
-         "Toggle chat": [
-            null,
-            "Csevegőablak"
-         ],
-         "Reconnecting": [
-            null,
-            "Kapcsolódás"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Kapcsolódás"
-         ],
-         "Authenticating": [
-            null,
-            "Azonosítás"
-         ],
-         "Authentication Failed": [
-            null,
-            "Azonosítási hiba"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Sajnáljuk, hiba történt a hozzáadás során"
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Ez a kliens nem engedélyezi a jelenlét követését"
             "Ez a kliens nem engedélyezi a jelenlét követését"
@@ -346,9 +314,9 @@
             null,
             null,
             "A résztvevők listájának elrejtése"
             "A résztvevők listájának elrejtése"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Hiba: a \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -430,9 +398,9 @@
             null,
             null,
             "Küldés"
             "Küldés"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Az indok: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -502,7 +470,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -510,7 +478,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -526,6 +494,10 @@
             null,
             null,
             "Megadhat egy üzenet a meghívás okaként."
             "Megadhat egy üzenet a meghívás okaként."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Szoba neve"
             "Szoba neve"
@@ -550,14 +522,14 @@
             null,
             null,
             "Nincs csevegőszoba a(z) %1$s szerveren"
             "Nincs csevegőszoba a(z) %1$s szerveren"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Csevegőszobák a(z) %1$s szerveren:"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Leírás:"
             "Leírás:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Jelenlevők:"
             "Jelenlevők:"
@@ -778,10 +750,6 @@
             null,
             null,
             "Sikeres regisztráció"
             "Sikeres regisztráció"
          ],
          ],
-         "Return": [
-            null,
-            "Visza"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "A szolgáltató visszautasította a regisztrációs kérelmet. Kérem ellenőrízze a bevitt adatok pontosságát."
             "A szolgáltató visszautasította a regisztrációs kérelmet. Kérem ellenőrízze a bevitt adatok pontosságát."
@@ -870,10 +838,6 @@
             null,
             null,
             "Valóban törölni szeretné a csevegőpartnerét?"
             "Valóban törölni szeretné a csevegőpartnerét?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Sajnáljuk, hiba történt a törlés során"
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Valóban elutasítja ezt a partnerkérelmet?"
             "Valóban elutasítja ezt a partnerkérelmet?"

File diff suppressed because it is too large
+ 210 - 272
locale/hu/LC_MESSAGES/converse.po


+ 12 - 44
locale/id/LC_MESSAGES/converse.json

@@ -202,34 +202,6 @@
             null,
             null,
             "Pengguna tak ditemukan"
             "Pengguna tak ditemukan"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Klik untuk menambahkan sebagai teman"
-         ],
-         "Toggle chat": [
-            null,
-            ""
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Menyambung"
-         ],
-         "Authenticating": [
-            null,
-            "Melakukan otentikasi"
-         ],
-         "Authentication Failed": [
-            null,
-            "Otentikasi gagal"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -302,7 +274,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -374,7 +346,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -442,7 +414,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -450,7 +422,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -466,6 +438,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nama ruangan"
             "Nama ruangan"
@@ -486,14 +462,14 @@
             null,
             null,
             "Tak ada ruangan di %1$s"
             "Tak ada ruangan di %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Ruangan di %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Keterangan:"
             "Keterangan:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Penghuni:"
             "Penghuni:"
@@ -682,10 +658,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -769,10 +741,6 @@
          "Name": [
          "Name": [
             null,
             null,
             ""
             ""
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 271
locale/id/LC_MESSAGES/converse.po


+ 16 - 80
locale/it/LC_MESSAGES/converse.json

@@ -31,6 +31,10 @@
             null,
             null,
             "Annulla"
             "Annulla"
          ],
          ],
+         "Are you sure you want to remove the bookmark \"%1$s\"?": [
+            null,
+            "Sei sicuro di voler rimuovere il segnalibro \"%1$s\"?"
+         ],
          "Sorry, something went wrong while trying to save your bookmark.": [
          "Sorry, something went wrong while trying to save your bookmark.": [
             null,
             null,
             "Si è verificato un errore nel salvataggio del bookmark."
             "Si è verificato un errore nel salvataggio del bookmark."
@@ -39,10 +43,6 @@
             null,
             null,
             "Clicca per aprire/chiudere la lista bookmarks"
             "Clicca per aprire/chiudere la lista bookmarks"
          ],
          ],
-         "Are you sure you want to remove the bookmark \"%1$s\"?": [
-            null,
-            "Sei sicuro di voler rimuovere il segnalibro \"%1$s\"?"
-         ],
          "Remove this bookmark": [
          "Remove this bookmark": [
             null,
             null,
             "Rimuovi questo bookmark"
             "Rimuovi questo bookmark"
@@ -255,54 +255,6 @@
             null,
             null,
             "Nessun utente trovato"
             "Nessun utente trovato"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Clicca per aggiungere il contatto alla chat"
-         ],
-         "Toggle chat": [
-            null,
-            "Attiva/disattiva chat"
-         ],
-         "Reconnecting": [
-            null,
-            "Riconnessione"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            "La connessione è caduta, attendi la riconnessione."
-         ],
-         "Connection error": [
-            null,
-            "Errore di connessione"
-         ],
-         "An error occurred while connecting to the chat server.": [
-            null,
-            "Si è verificato un errore durante la connessione al server."
-         ],
-         "Connecting": [
-            null,
-            "Connessione in corso"
-         ],
-         "Authenticating": [
-            null,
-            "Autenticazione in corso"
-         ],
-         "Authentication Failed": [
-            null,
-            "Autenticazione fallita"
-         ],
-         "Connection failed": [
-            null,
-            "Errore di connessione"
-         ],
-         "An error occurred while connecting to the chat server: ": [
-            null,
-            "Si è verificato un errore durante la connessione al server. "
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Si è verificato un errore durante il tentativo di aggiunta "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Questo client non consente sottoscrizioni di presenza"
             "Questo client non consente sottoscrizioni di presenza"
@@ -431,9 +383,9 @@
             null,
             null,
             "Nascondi la lista degli occupanti"
             "Nascondi la lista degli occupanti"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Errore: il \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -527,9 +479,9 @@
             null,
             null,
             "Invia"
             "Invia"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "La ragione data è: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -551,10 +503,6 @@
             null,
             null,
             "Topic impostato da %1$s a: %2$s"
             "Topic impostato da %1$s a: %2$s"
          ],
          ],
-         "Click to mention ": [
-            null,
-            "Clicca per citare "
-         ],
          "This user is a moderator.": [
          "This user is a moderator.": [
             null,
             null,
             "Questo utente è un moderatore."
             "Questo utente è un moderatore."
@@ -643,10 +591,6 @@
             null,
             null,
             "Questa stanza è moderata"
             "Questa stanza è moderata"
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
-            null,
-            "Tutti gli occupanti della stanza possono vedere il tuo Jabber ID"
-         ],
          "Anyone can join this room": [
          "Anyone can join this room": [
             null,
             null,
             "Chiunque può collegarsi a questa stanza"
             "Chiunque può collegarsi a questa stanza"
@@ -659,10 +603,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
-            null,
-            "Solo il moderatore può vedere il tuo Jabber ID"
-         ],
          "This room will disappear once the last person leaves": [
          "This room will disappear once the last person leaves": [
             null,
             null,
             ""
             ""
@@ -683,6 +623,10 @@
             null,
             null,
             "Puoi includere un messaggio per spiegare le ragioni dell'invito."
             "Puoi includere un messaggio per spiegare le ragioni dell'invito."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nome stanza"
             "Nome stanza"
@@ -707,14 +651,14 @@
             null,
             null,
             "Nessuna stanza su %1$s"
             "Nessuna stanza su %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Stanze su %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Descrizione:"
             "Descrizione:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Utenti presenti:"
             "Utenti presenti:"
@@ -939,10 +883,6 @@
             null,
             null,
             "Registrazione riuscita"
             "Registrazione riuscita"
          ],
          ],
-         "Return": [
-            null,
-            "Ritorna"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "Il provider ha respinto il tentativo di registrazione. Controlla i dati inseriti."
             "Il provider ha respinto il tentativo di registrazione. Controlla i dati inseriti."
@@ -1031,10 +971,6 @@
             null,
             null,
             "Sei sicuro di voler rimuovere questo contatto?"
             "Sei sicuro di voler rimuovere questo contatto?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Si è verificato un errore durante il tentativo di rimozione "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Sei sicuro dirifiutare questa richiesta di contatto?"
             "Sei sicuro dirifiutare questa richiesta di contatto?"

File diff suppressed because it is too large
+ 212 - 267
locale/it/LC_MESSAGES/converse.po


+ 12 - 44
locale/ja/LC_MESSAGES/converse.json

@@ -203,34 +203,6 @@
             null,
             null,
             "ユーザーが見つかりません"
             "ユーザーが見つかりません"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "クリックしてチャットの相手先として追加"
-         ],
-         "Toggle chat": [
-            null,
-            ""
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "接続中です"
-         ],
-         "Authenticating": [
-            null,
-            "認証中"
-         ],
-         "Authentication Failed": [
-            null,
-            "認証に失敗"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -303,7 +275,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -375,7 +347,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -443,7 +415,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -451,7 +423,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -467,6 +439,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "談話室の名前"
             "談話室の名前"
@@ -487,14 +463,14 @@
             null,
             null,
             "%1$s に談話室はありません"
             "%1$s に談話室はありません"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "%1$s の談話室一覧"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "説明: "
             "説明: "
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "入室者:"
             "入室者:"
@@ -683,10 +659,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -770,10 +742,6 @@
          "Name": [
          "Name": [
             null,
             null,
             ""
             ""
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 271
locale/ja/LC_MESSAGES/converse.po


+ 13 - 49
locale/nb/LC_MESSAGES/converse.json

@@ -215,38 +215,6 @@
             null,
             null,
             "Ingen brukere funnet"
             "Ingen brukere funnet"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Klikk for å legge til som meldingskontakt"
-         ],
-         "Toggle chat": [
-            null,
-            "Endre chatten"
-         ],
-         "Reconnecting": [
-            null,
-            "Kobler til igjen"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Kobler til"
-         ],
-         "Authenticating": [
-            null,
-            "Godkjenner"
-         ],
-         "Authentication Failed": [
-            null,
-            "Godkjenning mislyktes"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -323,7 +291,7 @@
             null,
             null,
             "Melding"
             "Melding"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -399,9 +367,9 @@
             null,
             null,
             "Send"
             "Send"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Årsaken som er oppgitt er: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -471,7 +439,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -479,7 +447,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -495,6 +463,10 @@
             null,
             null,
             "Du kan eventuelt inkludere en melding og forklare årsaken til invitasjonen."
             "Du kan eventuelt inkludere en melding og forklare årsaken til invitasjonen."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Romnavn"
             "Romnavn"
@@ -515,14 +487,14 @@
             null,
             null,
             "Ingen rom på %1$s"
             "Ingen rom på %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Rom på %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Beskrivelse:"
             "Beskrivelse:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Brukere her:"
             "Brukere her:"
@@ -743,10 +715,6 @@
             null,
             null,
             "Registrering var vellykket"
             "Registrering var vellykket"
          ],
          ],
-         "Return": [
-            null,
-            "Tilbake"
-         ],
          "Retry": [
          "Retry": [
             null,
             null,
             ""
             ""
@@ -831,10 +799,6 @@
             null,
             null,
             "Er du sikker på at du vil fjerne denne kontakten?"
             "Er du sikker på at du vil fjerne denne kontakten?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Er du sikker på at du vil avslå denne kontaktforespørselen?"
             "Er du sikker på at du vil avslå denne kontaktforespørselen?"

File diff suppressed because it is too large
+ 209 - 271
locale/nb/LC_MESSAGES/converse.po


+ 12 - 44
locale/nl/LC_MESSAGES/converse.json

@@ -195,34 +195,6 @@
             null,
             null,
             "Geen gebruikers gevonden"
             "Geen gebruikers gevonden"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Klik om contact toe te voegen"
-         ],
-         "Toggle chat": [
-            null,
-            ""
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Verbinden"
-         ],
-         "Authenticating": [
-            null,
-            "Authenticeren"
-         ],
-         "Authentication Failed": [
-            null,
-            "Authenticeren mislukt"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -307,7 +279,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -379,7 +351,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -447,7 +419,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -455,7 +427,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -471,6 +443,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Room naam"
             "Room naam"
@@ -491,14 +467,14 @@
             null,
             null,
             "Geen room op %1$s"
             "Geen room op %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Room op %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Beschrijving"
             "Beschrijving"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Deelnemers:"
             "Deelnemers:"
@@ -695,10 +671,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -782,10 +754,6 @@
          "Name": [
          "Name": [
             null,
             null,
             ""
             ""
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 271
locale/nl/LC_MESSAGES/converse.po


+ 14 - 50
locale/pl/LC_MESSAGES/converse.json

@@ -247,38 +247,6 @@
             null,
             null,
             "Nie znaleziono użytkowników"
             "Nie znaleziono użytkowników"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Kliknij aby dodać jako kontakt"
-         ],
-         "Toggle chat": [
-            null,
-            "Przełącz rozmowę"
-         ],
-         "Reconnecting": [
-            null,
-            "Przywracam połączenie"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Łączę się"
-         ],
-         "Authenticating": [
-            null,
-            "Autoryzuję"
-         ],
-         "Authentication Failed": [
-            null,
-            "Autoryzacja nie powiodła się"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Wystąpił błąd w czasie próby dodania "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Klient nie umożliwia subskrybcji obecności"
             "Klient nie umożliwia subskrybcji obecności"
@@ -363,9 +331,9 @@
             null,
             null,
             "Ukryj listę rozmówców"
             "Ukryj listę rozmówców"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Błąd: \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -455,9 +423,9 @@
             null,
             null,
             "Wyślij"
             "Wyślij"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Podana przyczyna to: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -539,7 +507,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -547,7 +515,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -563,6 +531,10 @@
             null,
             null,
             "Masz opcjonalną możliwość dołączenia wiadomości, która wyjaśni przyczynę zaproszenia."
             "Masz opcjonalną możliwość dołączenia wiadomości, która wyjaśni przyczynę zaproszenia."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nazwa pokoju"
             "Nazwa pokoju"
@@ -587,14 +559,14 @@
             null,
             null,
             "Brak jest pokojów na %1$s"
             "Brak jest pokojów na %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Pokoje na %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Opis:"
             "Opis:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Uczestnicy:"
             "Uczestnicy:"
@@ -819,10 +791,6 @@
             null,
             null,
             "Szczęśliwie zarejestrowany"
             "Szczęśliwie zarejestrowany"
          ],
          ],
-         "Return": [
-            null,
-            "Powrót"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "Dostawca odrzucił twoją próbę rejestracji. Sprawdź proszę poprawność danych które zostały wprowadzone."
             "Dostawca odrzucił twoją próbę rejestracji. Sprawdź proszę poprawność danych które zostały wprowadzone."
@@ -911,10 +879,6 @@
             null,
             null,
             "Czy potwierdzasz zamiar usnunięcia tego kontaktu?"
             "Czy potwierdzasz zamiar usnunięcia tego kontaktu?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Wystąpił błąd w trakcie próby usunięcia "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Czy potwierdzasz odrzucenie chęci nawiązania kontaktu?"
             "Czy potwierdzasz odrzucenie chęci nawiązania kontaktu?"

File diff suppressed because it is too large
+ 187 - 245
locale/pl/LC_MESSAGES/converse.po


+ 12 - 44
locale/pt_BR/LC_MESSAGES/converse.json

@@ -195,34 +195,6 @@
             null,
             null,
             "Não foram encontrados usuários"
             "Não foram encontrados usuários"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Clique para adicionar como um contato do chat"
-         ],
-         "Toggle chat": [
-            null,
-            "Alternar bate-papo"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Conectando"
-         ],
-         "Authenticating": [
-            null,
-            "Autenticando"
-         ],
-         "Authentication Failed": [
-            null,
-            "Falha de autenticação"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -295,7 +267,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -367,7 +339,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -435,7 +407,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -443,7 +415,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -459,6 +431,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Nome da sala"
             "Nome da sala"
@@ -479,14 +455,14 @@
             null,
             null,
             "Sem salas em %1$s"
             "Sem salas em %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Salas em %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Descrição:"
             "Descrição:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Ocupantes:"
             "Ocupantes:"
@@ -671,10 +647,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -758,10 +730,6 @@
          "Name": [
          "Name": [
             null,
             null,
             ""
             ""
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 271
locale/pt_BR/LC_MESSAGES/converse.po


+ 16 - 44
locale/ru/LC_MESSAGES/converse.json

@@ -230,34 +230,6 @@
             null,
             null,
             "Пользователи не найдены"
             "Пользователи не найдены"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Кликните, чтобы добавить контакт"
-         ],
-         "Toggle chat": [
-            null,
-            "Включить чат"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Соединение"
-         ],
-         "Authenticating": [
-            null,
-            "Авторизация"
-         ],
-         "Authentication Failed": [
-            null,
-            "Не удалось авторизоваться"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            "Возникла ошибка при добавлении "
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             "Программа не поддерживает уведомления о статусе"
             "Программа не поддерживает уведомления о статусе"
@@ -338,9 +310,9 @@
             null,
             null,
             "Спрятать список участников"
             "Спрятать список участников"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
-            "Ошибка:  \""
+            ""
          ],
          ],
          "Are you sure you want to clear the messages from this room?": [
          "Are you sure you want to clear the messages from this room?": [
             null,
             null,
@@ -418,6 +390,10 @@
             null,
             null,
             "Отправить"
             "Отправить"
          ],
          ],
+         "${notification.reason}": [
+            null,
+            ""
+         ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
             ""
             ""
@@ -486,7 +462,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -494,7 +470,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -510,6 +486,10 @@
             null,
             null,
             "Вы можете дополнительно вставить сообщение, объясняющее причину приглашения."
             "Вы можете дополнительно вставить сообщение, объясняющее причину приглашения."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Имя чата"
             "Имя чата"
@@ -534,14 +514,14 @@
             null,
             null,
             "Нет чатов %1$s"
             "Нет чатов %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Чаты %1$s:"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Описание:"
             "Описание:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Участники:"
             "Участники:"
@@ -746,10 +726,6 @@
             null,
             null,
             "Зарегистрирован успешно"
             "Зарегистрирован успешно"
          ],
          ],
-         "Return": [
-            null,
-            "Назад"
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             "Провайдер отклонил вашу попытку зарегистрироваться. Пожалуйста, проверьте, правильно ли введены значения."
             "Провайдер отклонил вашу попытку зарегистрироваться. Пожалуйста, проверьте, правильно ли введены значения."
@@ -838,10 +814,6 @@
             null,
             null,
             "Вы уверены, что хотите удалить этот контакт?"
             "Вы уверены, что хотите удалить этот контакт?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            "Возникла ошибка при удалении "
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Вы уверены, что хотите отклонить запрос от этого контакта?"
             "Вы уверены, что хотите отклонить запрос от этого контакта?"

File diff suppressed because it is too large
+ 210 - 274
locale/ru/LC_MESSAGES/converse.po


+ 13 - 49
locale/uk/LC_MESSAGES/converse.json

@@ -227,38 +227,6 @@
             null,
             null,
             "Жодного користувача не знайдено"
             "Жодного користувача не знайдено"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "Клацніть, щоб додати як чат-контакт"
-         ],
-         "Toggle chat": [
-            null,
-            "Включити чат"
-         ],
-         "Reconnecting": [
-            null,
-            "Перепід'єднуюсь"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "Під'єднуюсь"
-         ],
-         "Authenticating": [
-            null,
-            "Автентикуюсь"
-         ],
-         "Authentication Failed": [
-            null,
-            "Автентикація невдала"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -335,7 +303,7 @@
             null,
             null,
             "Повідомлення"
             "Повідомлення"
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -415,9 +383,9 @@
             null,
             null,
             "Надіслати"
             "Надіслати"
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
-            "Причиною вказано: \""
+            ""
          ],
          ],
          " has left the room. \"": [
          " has left the room. \"": [
             null,
             null,
@@ -487,7 +455,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -495,7 +463,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -511,6 +479,10 @@
             null,
             null,
             "Ви можете опціонально додати повідомлення, щоб пояснити причину запрошення."
             "Ви можете опціонально додати повідомлення, щоб пояснити причину запрошення."
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "Назва кімнати"
             "Назва кімнати"
@@ -535,14 +507,14 @@
             null,
             null,
             "Жодної кімнати на %1$s"
             "Жодної кімнати на %1$s"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "Кімнати на %1$s"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "Опис:"
             "Опис:"
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "Присутні:"
             "Присутні:"
@@ -763,10 +735,6 @@
             null,
             null,
             "Успішно зареєстровано"
             "Успішно зареєстровано"
          ],
          ],
-         "Return": [
-            null,
-            "Вернутися"
-         ],
          "Retry": [
          "Retry": [
             null,
             null,
             ""
             ""
@@ -851,10 +819,6 @@
             null,
             null,
             "Ви впевнені, що хочете видалити цей контакт?"
             "Ви впевнені, що хочете видалити цей контакт?"
          ],
          ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
-         ],
          "Are you sure you want to decline this contact request?": [
          "Are you sure you want to decline this contact request?": [
             null,
             null,
             "Ви впевнені, що хочете відхилити цей запит контакту?"
             "Ви впевнені, що хочете відхилити цей запит контакту?"

File diff suppressed because it is too large
+ 209 - 271
locale/uk/LC_MESSAGES/converse.po


+ 12 - 44
locale/zh/LC_MESSAGES/converse.json

@@ -202,34 +202,6 @@
             null,
             null,
             "未找到用户"
             "未找到用户"
          ],
          ],
-         "Click to add as a chat contact": [
-            null,
-            "点击添加为好友"
-         ],
-         "Toggle chat": [
-            null,
-            "折叠聊天窗口"
-         ],
-         "The connection has dropped, attempting to reconnect.": [
-            null,
-            ""
-         ],
-         "Connecting": [
-            null,
-            "连接中"
-         ],
-         "Authenticating": [
-            null,
-            "验证中"
-         ],
-         "Authentication Failed": [
-            null,
-            "验证失败"
-         ],
-         "Sorry, there was an error while trying to add ": [
-            null,
-            ""
-         ],
          "This client does not allow presence subscriptions": [
          "This client does not allow presence subscriptions": [
             null,
             null,
             ""
             ""
@@ -302,7 +274,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Error: the \"": [
+         "${command}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -374,7 +346,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "The reason given is: \"": [
+         "${notification.reason}": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -442,7 +414,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "All other room occupants can see your Jabber ID": [
+         "All other room occupants can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -450,7 +422,7 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Only moderators can see your Jabber ID": [
+         "Only moderators can see your XMPP username": [
             null,
             null,
             ""
             ""
          ],
          ],
@@ -466,6 +438,10 @@
             null,
             null,
             ""
             ""
          ],
          ],
+         "Please enter a valid XMPP username": [
+            null,
+            ""
+         ],
          "Room name": [
          "Room name": [
             null,
             null,
             "聊天室名称"
             "聊天室名称"
@@ -486,14 +462,14 @@
             null,
             null,
             "%1$s 上没有聊天室"
             "%1$s 上没有聊天室"
          ],
          ],
-         "Rooms on %1$s": [
-            null,
-            "%1$s 上的聊天室"
-         ],
          "Description:": [
          "Description:": [
             null,
             null,
             "描述: "
             "描述: "
          ],
          ],
+         "Room Address (JID):": [
+            null,
+            ""
+         ],
          "Occupants:": [
          "Occupants:": [
             null,
             null,
             "成员:"
             "成员:"
@@ -678,10 +654,6 @@
             null,
             null,
             ""
             ""
          ],
          ],
-         "Return": [
-            null,
-            ""
-         ],
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
          "The provider rejected your registration attempt. Please check the values you entered for correctness.": [
             null,
             null,
             ""
             ""
@@ -765,10 +737,6 @@
          "Name": [
          "Name": [
             null,
             null,
             ""
             ""
-         ],
-         "Sorry, there was an error while trying to remove ": [
-            null,
-            ""
          ]
          ]
       }
       }
    }
    }

File diff suppressed because it is too large
+ 208 - 271
locale/zh/LC_MESSAGES/converse.po


+ 6976 - 0
package-lock.json

@@ -0,0 +1,6976 @@
+{
+  "name": "converse.js",
+  "version": "3.2.0",
+  "lockfileVersion": 1,
+  "dependencies": {
+    "@greenkeeper/flags": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@greenkeeper/flags/-/flags-3.0.1.tgz",
+      "integrity": "sha1-N7of8sdiohFCvX6oxmzjTbP0zAk=",
+      "dev": true
+    },
+    "@greenkeeper/rc": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@greenkeeper/rc/-/rc-2.0.1.tgz",
+      "integrity": "sha1-Ruh7aU/hSEEAWrZSGlo5sIMK+IE=",
+      "dev": true
+    },
+    "abbrev": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+      "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+      "dev": true
+    },
+    "acorn": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz",
+      "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+      "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+      "dev": true,
+      "dependencies": {
+        "acorn": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+          "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+          "dev": true
+        }
+      }
+    },
+    "ajv": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+      "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+      "dev": true
+    },
+    "ajv-keywords": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
+      "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
+      "dev": true
+    },
+    "align-text": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+      "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+      "dev": true
+    },
+    "almond": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz",
+      "integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA=",
+      "dev": true
+    },
+    "ansi-align": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
+      "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
+          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true
+        }
+      }
+    },
+    "ansi-escapes": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+      "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+      "dev": true
+    },
+    "ansicolors": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz",
+      "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=",
+      "dev": true
+    },
+    "anymatch": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
+      "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=",
+      "dev": true,
+      "optional": true
+    },
+    "aproba": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz",
+      "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==",
+      "dev": true
+    },
+    "archy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+      "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+      "dev": true
+    },
+    "are-we-there-yet": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+      "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+      "dev": true
+    },
+    "argparse": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+      "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+      "dev": true
+    },
+    "arr-diff": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+      "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+      "dev": true,
+      "optional": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true,
+      "optional": true
+    },
+    "array-find-index": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "dev": true
+    },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+      "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+      "dev": true,
+      "optional": true
+    },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+      "dev": true
+    },
+    "asn1": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+      "dev": true
+    },
+    "assert-plus": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+      "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+      "dev": true
+    },
+    "async": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+      "dev": true
+    },
+    "async-each": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+      "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+      "dev": true,
+      "optional": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
+    "awesomplete-avoid-xss": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/awesomplete-avoid-xss/-/awesomplete-avoid-xss-1.1.2.tgz",
+      "integrity": "sha1-+f4vrzmNZNniQYMlC3xKMTGcfGQ=",
+      "dev": true
+    },
+    "aws-sign2": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+      "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+      "dev": true
+    },
+    "aws4": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+      "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+      "dev": true
+    },
+    "babel-cli": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz",
+      "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM=",
+      "dev": true
+    },
+    "babel-code-frame": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
+      "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
+      "dev": true
+    },
+    "babel-core": {
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
+      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=",
+      "dev": true
+    },
+    "babel-generator": {
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
+      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=",
+      "dev": true
+    },
+    "babel-helper-builder-binary-assignment-operator-visitor": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
+      "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
+      "dev": true
+    },
+    "babel-helper-call-delegate": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+      "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+      "dev": true
+    },
+    "babel-helper-define-map": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz",
+      "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=",
+      "dev": true
+    },
+    "babel-helper-explode-assignable-expression": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz",
+      "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
+      "dev": true
+    },
+    "babel-helper-function-name": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+      "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+      "dev": true
+    },
+    "babel-helper-get-function-arity": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+      "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+      "dev": true
+    },
+    "babel-helper-hoist-variables": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+      "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+      "dev": true
+    },
+    "babel-helper-optimise-call-expression": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+      "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+      "dev": true
+    },
+    "babel-helper-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz",
+      "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
+      "dev": true
+    },
+    "babel-helper-remap-async-to-generator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz",
+      "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=",
+      "dev": true
+    },
+    "babel-helper-replace-supers": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+      "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+      "dev": true
+    },
+    "babel-helpers": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+      "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
+      "dev": true
+    },
+    "babel-messages": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+      "dev": true
+    },
+    "babel-plugin-check-es2015-constants": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+      "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+      "dev": true
+    },
+    "babel-plugin-syntax-async-functions": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
+      "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
+      "dev": true
+    },
+    "babel-plugin-syntax-exponentiation-operator": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
+      "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
+      "dev": true
+    },
+    "babel-plugin-syntax-trailing-function-commas": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz",
+      "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=",
+      "dev": true
+    },
+    "babel-plugin-transform-async-to-generator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz",
+      "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-arrow-functions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+      "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-block-scoped-functions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+      "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-block-scoping": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz",
+      "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-classes": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+      "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-computed-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+      "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-destructuring": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+      "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-duplicate-keys": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
+      "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-for-of": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+      "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-function-name": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+      "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-literals": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+      "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-amd": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
+      "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-commonjs": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
+      "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-systemjs": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
+      "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-umd": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
+      "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-object-super": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+      "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-parameters": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+      "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-shorthand-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+      "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-spread": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+      "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-sticky-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
+      "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-template-literals": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+      "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-typeof-symbol": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
+      "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-unicode-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
+      "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
+      "dev": true
+    },
+    "babel-plugin-transform-exponentiation-operator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
+      "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=",
+      "dev": true
+    },
+    "babel-plugin-transform-regenerator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz",
+      "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=",
+      "dev": true
+    },
+    "babel-plugin-transform-strict-mode": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+      "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+      "dev": true
+    },
+    "babel-polyfill": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
+      "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
+      "dev": true
+    },
+    "babel-preset-env": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.0.tgz",
+      "integrity": "sha512-OVgtQRuOZKckrILgMA5rvctvFZPv72Gua9Rt006AiPoB0DJKGN07UmaQA+qRrYgK71MVct8fFhT0EyNWYorVew==",
+      "dev": true
+    },
+    "babel-preset-es2015": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
+      "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
+      "dev": true
+    },
+    "babel-preset-es2016": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz",
+      "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=",
+      "dev": true
+    },
+    "babel-preset-es2017": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2017/-/babel-preset-es2017-6.24.1.tgz",
+      "integrity": "sha1-WXvq37n38gi8/YoS6bKym4svFNE=",
+      "dev": true
+    },
+    "babel-preset-latest": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-latest/-/babel-preset-latest-6.24.1.tgz",
+      "integrity": "sha1-Z33gaRVKdIXC0lxXfAL2JLhbheg=",
+      "dev": true
+    },
+    "babel-register": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz",
+      "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=",
+      "dev": true
+    },
+    "babel-runtime": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
+      "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+      "dev": true
+    },
+    "babel-template": {
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
+      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=",
+      "dev": true
+    },
+    "babel-traverse": {
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
+      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=",
+      "dev": true
+    },
+    "babel-types": {
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
+      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=",
+      "dev": true
+    },
+    "babylon": {
+      "version": "6.17.4",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
+      "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw==",
+      "dev": true
+    },
+    "backbone": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.3.3.tgz",
+      "integrity": "sha1-TMgOp8sWMaxHSInOQPL4vGg7KZk=",
+      "dev": true
+    },
+    "backbone.browserStorage": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/backbone.browserStorage/-/backbone.browserStorage-0.0.3.tgz",
+      "integrity": "sha1-ikIi8I2bHQslLR14/1CUuNCKc2s=",
+      "dev": true
+    },
+    "backbone.overview": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/backbone.overview/-/backbone.overview-0.0.3.tgz",
+      "integrity": "sha1-RW8stg8xkWVeMYOCpl2VZUTLZrI=",
+      "dev": true,
+      "dependencies": {
+        "backbone": {
+          "version": "1.2.3",
+          "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.2.3.tgz",
+          "integrity": "sha1-wiz9B/yG676uYdGJKe0RXpmdZbk=",
+          "dev": true
+        }
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+      "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+      "dev": true,
+      "optional": true
+    },
+    "binary-extensions": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz",
+      "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=",
+      "dev": true,
+      "optional": true
+    },
+    "boom": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+      "dev": true
+    },
+    "bootstrap": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz",
+      "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=",
+      "dev": true
+    },
+    "bourbon": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/bourbon/-/bourbon-4.3.4.tgz",
+      "integrity": "sha1-TaOAAp6SwMj5dkx3lFGhNLEefMM=",
+      "dev": true
+    },
+    "boxen": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.0.tgz",
+      "integrity": "sha512-tfKK3nq0qXXOxvXEYW1k1XNRrDuQzO2oFPvLD3Fs1I58n0leuTNlftBmu3seUCyZvDfiqgRaxlqZs9WJAbSA7g==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz",
+          "integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz",
+          "integrity": "sha512-Mp+FXEI+FrwY/XYV45b2YD3E8i3HwnEAoFcM0qlZzq/RZ9RwWitt2Y/c7cqRAz70U7hfekqx6qNYthuKFO6K0g==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
+          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz",
+          "integrity": "sha512-Ts0Mu/A1S1aZxEJNG88I4Oc9rcZSBFNac5e27yh4j2mqbhZSSzR1Ah79EYwSn9Zuh7lrlGD2cVGzw1RKGzyLSg==",
+          "dev": true
+        }
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+      "dev": true
+    },
+    "braces": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+      "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+      "dev": true,
+      "optional": true
+    },
+    "browserslist": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.1.5.tgz",
+      "integrity": "sha1-6IJVDfPRzW1IHBo+ADjyuvE6RxE=",
+      "dev": true
+    },
+    "builtin-modules": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+      "dev": true
+    },
+    "caller-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+      "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
+      "dev": true
+    },
+    "callsites": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+      "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+      "dev": true
+    },
+    "camelcase": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+      "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+      "dev": true
+    },
+    "camelcase-keys": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+      "dev": true,
+      "dependencies": {
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        }
+      }
+    },
+    "caniuse-lite": {
+      "version": "1.0.30000700",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000700.tgz",
+      "integrity": "sha1-YISHHsdcb6YjJ96XYiUU+V2dsmo=",
+      "dev": true
+    },
+    "capture-stack-trace": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
+      "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
+      "dev": true
+    },
+    "cardinal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz",
+      "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "dev": true
+    },
+    "center-align": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+      "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+      "dev": true
+    },
+    "chalk": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+      "dev": true
+    },
+    "char-spinner": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz",
+      "integrity": "sha1-5upnvSR+EHESmDt6sEee02KAAIE=",
+      "dev": true
+    },
+    "chokidar": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
+      "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+      "dev": true,
+      "optional": true
+    },
+    "circular-json": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz",
+      "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=",
+      "dev": true
+    },
+    "clean-css": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.6.tgz",
+      "integrity": "sha1-Wke+tSaZTLT3vzYYilXtO0VSjws=",
+      "dev": true
+    },
+    "clean-css-cli": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/clean-css-cli/-/clean-css-cli-4.1.6.tgz",
+      "integrity": "sha1-PvKidGsHT1XuBTkihxCnDbeng4k=",
+      "dev": true
+    },
+    "cli": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+      "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
+      "dev": true
+    },
+    "cli-boxes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+      "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+      "dev": true
+    },
+    "cli-cursor": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+      "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+      "dev": true
+    },
+    "cli-md": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/cli-md/-/cli-md-1.2.0.tgz",
+      "integrity": "sha1-qZsfS6GkdrGmVPD+lX4ZAsSM2e4=",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz",
+          "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz",
+          "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
+          "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=",
+          "dev": true
+        },
+        "has-ansi": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
+          "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
+          "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz",
+          "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=",
+          "dev": true
+        }
+      }
+    },
+    "cli-table": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
+      "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=",
+      "dev": true
+    },
+    "cli-width": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz",
+      "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
+      "dev": true
+    },
+    "clite": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/clite/-/clite-0.3.0.tgz",
+      "integrity": "sha1-5/y8jMW9Pn+LhO1I2xLpR0zHNEE=",
+      "dev": true,
+      "dependencies": {
+        "boxen": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.3.1.tgz",
+          "integrity": "sha1-p9iYJDrmIvertrtgTXQKdsalRhs=",
+          "dev": true
+        },
+        "cliui": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+          "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+          "dev": true
+        },
+        "es6-promise": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+          "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+          "dev": true
+        },
+        "update-notifier": {
+          "version": "0.6.3",
+          "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.6.3.tgz",
+          "integrity": "sha1-d23sjaoT6WKjQeih2YNUMGtnrgg=",
+          "dev": true
+        },
+        "window-size": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz",
+          "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=",
+          "dev": true
+        },
+        "yargs": {
+          "version": "4.8.1",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz",
+          "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=",
+          "dev": true
+        }
+      }
+    },
+    "cliui": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+      "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+      "dev": true,
+      "dependencies": {
+        "wordwrap": {
+          "version": "0.0.2",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+          "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
+          "dev": true
+        }
+      }
+    },
+    "clone-deep": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz",
+      "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=",
+      "dev": true,
+      "dependencies": {
+        "for-own": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
+          "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
+          "dev": true
+        }
+      }
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+      "dev": true
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
+    },
+    "coffee-script": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz",
+      "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=",
+      "dev": true
+    },
+    "color-convert": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
+      "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
+      "dev": true
+    },
+    "color-name": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.2.tgz",
+      "integrity": "sha1-XIq3K2S9IhXWF66VWeuxSEdc+Y0=",
+      "dev": true
+    },
+    "colors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
+      "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
+      "dev": true
+    },
+    "combined-stream": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+      "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+      "dev": true
+    },
+    "commander": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "concat-stream": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+      "dev": true
+    },
+    "configstore": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz",
+      "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=",
+      "dev": true,
+      "dependencies": {
+        "uuid": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+          "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+          "dev": true
+        }
+      }
+    },
+    "console-browserify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+      "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+      "dev": true
+    },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "dev": true
+    },
+    "convert-source-map": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz",
+      "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=",
+      "dev": true
+    },
+    "core-js": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
+      "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=",
+      "dev": true
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "dev": true
+    },
+    "corser": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
+      "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
+      "dev": true
+    },
+    "create-error-class": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+      "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+      "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+      "dev": true
+    },
+    "cryptiles": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+      "dev": true
+    },
+    "cssfilter": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.9.tgz",
+      "integrity": "sha1-j1zrOqvXaNtTnaRYKyFS1j73cV4=",
+      "dev": true
+    },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true
+    },
+    "d": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
+      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+      "dev": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dev": true,
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+          "dev": true
+        }
+      }
+    },
+    "date-now": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+      "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+      "dev": true
+    },
+    "dateformat": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
+      "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.8",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+      "dev": true
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "deep-extend": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
+      "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
+      "dev": true
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "del": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+      "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+      "dev": true
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true
+    },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+      "dev": true
+    },
+    "detect-indent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+      "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+      "dev": true
+    },
+    "diff": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz",
+      "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==",
+      "dev": true
+    },
+    "doctrine": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
+      "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+      "dev": true
+    },
+    "dom-serializer": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+      "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+      "dev": true,
+      "dependencies": {
+        "domelementtype": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+          "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
+          "dev": true
+        },
+        "entities": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
+          "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
+          "dev": true
+        }
+      }
+    },
+    "domelementtype": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+      "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
+      "dev": true
+    },
+    "domhandler": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+      "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
+      "dev": true
+    },
+    "domutils": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+      "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+      "dev": true
+    },
+    "dot-prop": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
+      "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
+      "dev": true
+    },
+    "duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
+      "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
+      "dev": true
+    },
+    "ecc-jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+      "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+      "dev": true,
+      "optional": true
+    },
+    "ecstatic": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-2.2.1.tgz",
+      "integrity": "sha512-ztE4WqheoWLh3wv+HQwy7dACnvNY620coWpa+XqY6R2cVWgaAT2lUISU1Uf7JpdLLJCURktJOaA9av2AOzsyYQ==",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "electron-to-chromium": {
+      "version": "1.3.15",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz",
+      "integrity": "sha1-CDl5NIkcvPrrvRi4KpW1pIETg2k=",
+      "dev": true
+    },
+    "emojione": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/emojione/-/emojione-3.1.1.tgz",
+      "integrity": "sha1-9WgPvuLLKlbpkEKxTZNePrhhswA=",
+      "dev": true
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true
+    },
+    "end-of-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+      "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=",
+      "dev": true,
+      "dependencies": {
+        "once": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+          "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+          "dev": true
+        }
+      }
+    },
+    "entities": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+      "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
+      "dev": true
+    },
+    "env-paths": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz",
+      "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=",
+      "dev": true
+    },
+    "error-ex": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+      "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+      "dev": true
+    },
+    "es5-ext": {
+      "version": "0.10.24",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz",
+      "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=",
+      "dev": true
+    },
+    "es6-iterator": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
+      "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
+      "dev": true
+    },
+    "es6-map": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
+      "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
+      "dev": true
+    },
+    "es6-promise": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz",
+      "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==",
+      "dev": true
+    },
+    "es6-set": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
+      "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
+      "dev": true
+    },
+    "es6-symbol": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+      "dev": true
+    },
+    "es6-weak-map": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
+      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+      "dev": true
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "escope": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
+      "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
+      "dev": true
+    },
+    "eslint": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
+      "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
+      "dev": true,
+      "dependencies": {
+        "user-home": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
+          "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-lodash": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.4.4.tgz",
+      "integrity": "sha1-7MeyFv3Uh98vVCc06hIVIDy1Y+w=",
+      "dev": true
+    },
+    "espree": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
+      "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
+      "dev": true
+    },
+    "esprima": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+      "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
+      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+      "dev": true
+    },
+    "esrecurse": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
+      "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
+      "dev": true
+    },
+    "estraverse": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+      "dev": true
+    },
+    "event-emitter": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "dev": true
+    },
+    "eventemitter2": {
+      "version": "0.4.14",
+      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
+      "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=",
+      "dev": true
+    },
+    "eventemitter3": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
+      "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=",
+      "dev": true
+    },
+    "execa": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "dev": true
+    },
+    "exit": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+      "dev": true
+    },
+    "exit-hook": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
+      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
+      "dev": true
+    },
+    "expand-brackets": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+      "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+      "dev": true,
+      "optional": true
+    },
+    "expand-range": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+      "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+      "dev": true,
+      "optional": true
+    },
+    "extend": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+      "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
+      "dev": true
+    },
+    "external-editor": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz",
+      "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=",
+      "dev": true
+    },
+    "extglob": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+      "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+      "dev": true,
+      "optional": true
+    },
+    "extsprintf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+      "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "figures": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+      "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+      "dev": true
+    },
+    "file-entry-cache": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+      "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
+      "dev": true
+    },
+    "filename-regex": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+      "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
+      "dev": true,
+      "optional": true
+    },
+    "fill-range": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
+      "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
+      "dev": true,
+      "optional": true
+    },
+    "filled-array": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/filled-array/-/filled-array-1.1.0.tgz",
+      "integrity": "sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=",
+      "dev": true
+    },
+    "find-up": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+      "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+      "dev": true
+    },
+    "findup-sync": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz",
+      "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=",
+      "dev": true,
+      "dependencies": {
+        "glob": {
+          "version": "5.0.15",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+          "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+          "dev": true
+        }
+      }
+    },
+    "flat-cache": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
+      "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
+      "dev": true
+    },
+    "font-awesome": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+      "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
+      "dev": true
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "for-own": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+      "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+      "dev": true,
+      "optional": true
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "dev": true
+    },
+    "form-data": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+      "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+      "dev": true
+    },
+    "formatio": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz",
+      "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=",
+      "dev": true
+    },
+    "fs-readdir-recursive": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz",
+      "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=",
+      "dev": true
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "fsevents": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
+      "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+          "dev": true,
+          "optional": true
+        },
+        "ajv": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+          "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+          "dev": true,
+          "optional": true
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "aproba": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
+          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
+          "dev": true,
+          "optional": true
+        },
+        "are-we-there-yet": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+          "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+          "dev": true,
+          "optional": true
+        },
+        "asn1": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+          "dev": true,
+          "optional": true
+        },
+        "assert-plus": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+          "dev": true,
+          "optional": true
+        },
+        "asynckit": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+          "dev": true,
+          "optional": true
+        },
+        "aws-sign2": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+          "dev": true,
+          "optional": true
+        },
+        "aws4": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+          "dev": true,
+          "optional": true
+        },
+        "balanced-match": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+          "dev": true
+        },
+        "bcrypt-pbkdf": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+          "dev": true,
+          "optional": true
+        },
+        "block-stream": {
+          "version": "0.0.9",
+          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+          "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+          "dev": true
+        },
+        "boom": {
+          "version": "2.10.1",
+          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+          "dev": true
+        },
+        "brace-expansion": {
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
+          "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=",
+          "dev": true
+        },
+        "buffer-shims": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+          "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
+          "dev": true
+        },
+        "caseless": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+          "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+          "dev": true,
+          "optional": true
+        },
+        "co": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+          "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+          "dev": true,
+          "optional": true
+        },
+        "code-point-at": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+          "dev": true
+        },
+        "combined-stream": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+          "dev": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+          "dev": true
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+          "dev": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "cryptiles": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+          "dev": true,
+          "optional": true
+        },
+        "dashdash": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "debug": {
+          "version": "2.6.8",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+          "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+          "dev": true,
+          "optional": true
+        },
+        "deep-extend": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
+          "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
+          "dev": true,
+          "optional": true
+        },
+        "delayed-stream": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+          "dev": true
+        },
+        "delegates": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+          "dev": true,
+          "optional": true
+        },
+        "ecc-jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+          "dev": true,
+          "optional": true
+        },
+        "extend": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+          "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
+          "dev": true,
+          "optional": true
+        },
+        "extsprintf": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+          "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=",
+          "dev": true
+        },
+        "forever-agent": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+          "dev": true,
+          "optional": true
+        },
+        "form-data": {
+          "version": "2.1.4",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+          "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+          "dev": true,
+          "optional": true
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+          "dev": true
+        },
+        "fstream": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+          "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+          "dev": true
+        },
+        "fstream-ignore": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
+          "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
+          "dev": true,
+          "optional": true
+        },
+        "gauge": {
+          "version": "2.7.4",
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+          "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+          "dev": true,
+          "optional": true
+        },
+        "getpass": {
+          "version": "0.1.7",
+          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+          "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "glob": {
+          "version": "7.1.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+          "dev": true
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+          "dev": true
+        },
+        "har-schema": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+          "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+          "dev": true,
+          "optional": true
+        },
+        "har-validator": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+          "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+          "dev": true,
+          "optional": true
+        },
+        "has-unicode": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+          "dev": true,
+          "optional": true
+        },
+        "hawk": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+          "dev": true,
+          "optional": true
+        },
+        "hoek": {
+          "version": "2.16.3",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+          "dev": true
+        },
+        "http-signature": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+          "dev": true,
+          "optional": true
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "ini": {
+          "version": "1.3.4",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+          "dev": true,
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true
+        },
+        "is-typedarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+          "dev": true,
+          "optional": true
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "isstream": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+          "dev": true,
+          "optional": true
+        },
+        "jodid25519": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+          "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+          "dev": true,
+          "optional": true
+        },
+        "jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+          "dev": true,
+          "optional": true
+        },
+        "json-schema": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+          "dev": true,
+          "optional": true
+        },
+        "json-stable-stringify": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+          "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+          "dev": true,
+          "optional": true
+        },
+        "json-stringify-safe": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+          "dev": true,
+          "optional": true
+        },
+        "jsonify": {
+          "version": "0.0.0",
+          "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+          "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+          "dev": true,
+          "optional": true
+        },
+        "jsprim": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
+          "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "mime-db": {
+          "version": "1.27.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
+          "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=",
+          "dev": true
+        },
+        "mime-types": {
+          "version": "2.1.15",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
+          "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+          "dev": true
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true,
+          "optional": true
+        },
+        "node-pre-gyp": {
+          "version": "0.6.36",
+          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz",
+          "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=",
+          "dev": true,
+          "optional": true
+        },
+        "nopt": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+          "dev": true,
+          "optional": true
+        },
+        "npmlog": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz",
+          "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==",
+          "dev": true,
+          "optional": true
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+          "dev": true
+        },
+        "oauth-sign": {
+          "version": "0.8.2",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+          "dev": true,
+          "optional": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true,
+          "optional": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+          "dev": true
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+          "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+          "dev": true,
+          "optional": true
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+          "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+          "dev": true,
+          "optional": true
+        },
+        "osenv": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+          "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+          "dev": true,
+          "optional": true
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+          "dev": true
+        },
+        "performance-now": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+          "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+          "dev": true,
+          "optional": true
+        },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+          "dev": true
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true,
+          "optional": true
+        },
+        "qs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+          "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+          "dev": true,
+          "optional": true
+        },
+        "rc": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
+          "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "minimist": {
+              "version": "1.2.0",
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "readable-stream": {
+          "version": "2.2.9",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
+          "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=",
+          "dev": true
+        },
+        "request": {
+          "version": "2.81.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+          "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+          "dev": true,
+          "optional": true
+        },
+        "rimraf": {
+          "version": "2.6.1",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+          "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+          "dev": true
+        },
+        "safe-buffer": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+          "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "dev": true,
+          "optional": true
+        },
+        "set-blocking": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+          "dev": true,
+          "optional": true
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "dev": true,
+          "optional": true
+        },
+        "sntp": {
+          "version": "1.0.9",
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+          "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+          "dev": true,
+          "optional": true
+        },
+        "sshpk": {
+          "version": "1.13.0",
+          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
+          "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "string_decoder": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
+          "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true
+        },
+        "stringstream": {
+          "version": "0.0.5",
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+          "dev": true,
+          "optional": true
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true
+        },
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+          "dev": true,
+          "optional": true
+        },
+        "tar": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+          "dev": true
+        },
+        "tar-pack": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz",
+          "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=",
+          "dev": true,
+          "optional": true
+        },
+        "tough-cookie": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+          "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+          "dev": true,
+          "optional": true
+        },
+        "tunnel-agent": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+          "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+          "dev": true,
+          "optional": true
+        },
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+          "dev": true,
+          "optional": true
+        },
+        "uid-number": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+          "dev": true,
+          "optional": true
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+          "dev": true
+        },
+        "uuid": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
+          "dev": true,
+          "optional": true
+        },
+        "verror": {
+          "version": "1.3.6",
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+          "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+          "dev": true,
+          "optional": true
+        },
+        "wide-align": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
+          "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+          "dev": true,
+          "optional": true
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true
+        }
+      }
+    },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "dev": true
+    },
+    "generate-function": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+      "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+      "dev": true
+    },
+    "generate-object-property": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+      "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+      "dev": true
+    },
+    "get-caller-file": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
+      "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
+      "dev": true
+    },
+    "get-stdin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+      "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true
+    },
+    "getobject": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz",
+      "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dev": true,
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+          "dev": true
+        }
+      }
+    },
+    "gettext-parser": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.1.0.tgz",
+      "integrity": "sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=",
+      "dev": true
+    },
+    "gitconfiglocal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.0.1.tgz",
+      "integrity": "sha1-tQCSyQUF05d7TZnXZPn1mcmA53Q=",
+      "dev": true
+    },
+    "github-slug": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/github-slug/-/github-slug-2.0.0.tgz",
+      "integrity": "sha1-yUdYSFYHVV+Xf/Ltf5Di8lltplI=",
+      "dev": true
+    },
+    "github-url-from-git": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz",
+      "integrity": "sha1-+YX+3MCpqledyI16/waNVcxiUaA=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true
+    },
+    "glob-base": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+      "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+      "dev": true,
+      "optional": true
+    },
+    "glob-parent": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+      "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+      "dev": true
+    },
+    "globals": {
+      "version": "9.18.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+      "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+      "dev": true
+    },
+    "globby": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+      "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+      "dev": true
+    },
+    "got": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
+      "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
+      "dev": true
+    },
+    "graceful-fs": {
+      "version": "4.1.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+      "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+      "dev": true
+    },
+    "graceful-readlink": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+      "dev": true
+    },
+    "greenkeeper": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/greenkeeper/-/greenkeeper-4.2.1.tgz",
+      "integrity": "sha1-VsjFPjF4OdcuB4H8tU+yORekIFI=",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "esprima": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+          "dev": true
+        },
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+          "dev": true
+        },
+        "inquirer": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-2.0.0.tgz",
+          "integrity": "sha1-4TUWh7kNFQykA86qPO+x4wZb70s=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "js-yaml": {
+          "version": "3.8.0",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.0.tgz",
+          "integrity": "sha1-9jYg0u5nY9z5UHi/e9uqAdjbTnM=",
+          "dev": true
+        },
+        "mute-stream": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz",
+          "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=",
+          "dev": true
+        },
+        "run-async": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+          "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
+          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "dev": true,
+          "dependencies": {
+            "strip-ansi": {
+              "version": "4.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+              "dev": true
+            }
+          }
+        }
+      }
+    },
+    "grunt": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz",
+      "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=",
+      "dev": true,
+      "dependencies": {
+        "esprima": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+          "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+          "dev": true
+        },
+        "glob": {
+          "version": "7.0.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
+          "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
+          "dev": true
+        },
+        "js-yaml": {
+          "version": "3.5.5",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz",
+          "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=",
+          "dev": true
+        },
+        "rimraf": {
+          "version": "2.2.8",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+          "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
+          "dev": true
+        }
+      }
+    },
+    "grunt-cli": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz",
+      "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=",
+      "dev": true,
+      "dependencies": {
+        "resolve": {
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+          "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+          "dev": true
+        }
+      }
+    },
+    "grunt-json": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/grunt-json/-/grunt-json-0.2.0.tgz",
+      "integrity": "sha1-+dgHhWMZiqXDPJkE/yXQiO5TLUE=",
+      "dev": true
+    },
+    "grunt-known-options": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz",
+      "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=",
+      "dev": true
+    },
+    "grunt-legacy-log": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz",
+      "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=",
+      "dev": true,
+      "dependencies": {
+        "colors": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+          "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+          "dev": true
+        },
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+          "dev": true
+        }
+      }
+    },
+    "grunt-legacy-log-utils": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz",
+      "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=",
+      "dev": true,
+      "dependencies": {
+        "lodash": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz",
+          "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=",
+          "dev": true
+        }
+      }
+    },
+    "grunt-legacy-util": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz",
+      "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=",
+      "dev": true,
+      "dependencies": {
+        "lodash": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz",
+          "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=",
+          "dev": true
+        }
+      }
+    },
+    "har-schema": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+      "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+      "dev": true
+    },
+    "har-validator": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+      "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+      "dev": true
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "dev": true
+    },
+    "has-color": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
+      "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+      "dev": true
+    },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+      "dev": true
+    },
+    "hasbin": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/hasbin/-/hasbin-1.2.3.tgz",
+      "integrity": "sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=",
+      "dev": true
+    },
+    "hawk": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+      "dev": true
+    },
+    "he": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+      "dev": true
+    },
+    "hide-secrets": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/hide-secrets/-/hide-secrets-1.1.0.tgz",
+      "integrity": "sha1-JAZoCU1cu+ZkAr4/rOMRwM7PrMI=",
+      "dev": true
+    },
+    "hoek": {
+      "version": "2.16.3",
+      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+      "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+      "dev": true
+    },
+    "home-or-tmp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+      "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
+      "dev": true
+    },
+    "hooker": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz",
+      "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=",
+      "dev": true
+    },
+    "hosted-git-info": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
+      "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
+      "dev": true
+    },
+    "htmlparser2": {
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+      "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
+      "dev": true,
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        }
+      }
+    },
+    "http-proxy": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz",
+      "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=",
+      "dev": true
+    },
+    "http-server": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.10.0.tgz",
+      "integrity": "sha1-sqRGsWqduH7TxiK6m+sbCFsSNKc=",
+      "dev": true
+    },
+    "http-signature": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+      "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+      "dev": true
+    },
+    "iconv-lite": {
+      "version": "0.4.18",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
+      "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==",
+      "dev": true
+    },
+    "ignore": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
+      "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
+      "dev": true
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+      "dev": true
+    },
+    "infinity-agent": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz",
+      "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+      "dev": true
+    },
+    "ini": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+      "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
+      "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
+      "dev": true
+    },
+    "install": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/install/-/install-0.8.9.tgz",
+      "integrity": "sha1-n0tcDRhR74cunfheT3Fi1OXc2+0=",
+      "dev": true
+    },
+    "interpret": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
+      "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=",
+      "dev": true
+    },
+    "invariant": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+      "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+      "dev": true
+    },
+    "invert-kv": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+      "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+      "dev": true,
+      "optional": true
+    },
+    "is-buffer": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
+      "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
+      "dev": true
+    },
+    "is-builtin-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+      "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+      "dev": true
+    },
+    "is-dotfile": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+      "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+      "dev": true,
+      "optional": true
+    },
+    "is-equal-shallow": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+      "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+      "dev": true,
+      "optional": true
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+      "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+      "dev": true
+    },
+    "is-finite": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+      "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+      "dev": true
+    },
+    "is-my-json-valid": {
+      "version": "2.16.0",
+      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
+      "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
+      "dev": true
+    },
+    "is-npm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+      "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+      "dev": true
+    },
+    "is-number": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+      "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+      "dev": true,
+      "optional": true
+    },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true
+    },
+    "is-path-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+      "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+      "dev": true
+    },
+    "is-path-in-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
+      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+      "dev": true
+    },
+    "is-path-inside": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
+      "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
+      "dev": true
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "dependencies": {
+        "isobject": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+          "dev": true
+        }
+      }
+    },
+    "is-posix-bracket": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+      "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
+      "dev": true,
+      "optional": true
+    },
+    "is-primitive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+      "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
+      "dev": true
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-property": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+      "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+      "dev": true
+    },
+    "is-redirect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+      "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+      "dev": true
+    },
+    "is-resolvable": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+      "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
+      "dev": true
+    },
+    "is-retry-allowed": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
+      "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "dev": true
+    },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+      "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+      "dev": true,
+      "optional": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "dev": true
+    },
+    "jasmine-core": {
+      "version": "2.6.4",
+      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz",
+      "integrity": "sha1-3skmzQqfoof7bbXHVfpIfnTOysU=",
+      "dev": true
+    },
+    "jed": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/jed/-/jed-0.5.4.tgz",
+      "integrity": "sha1-rtFA1cTWWs89qli5Gd1WElP2QcI=",
+      "dev": true
+    },
+    "jquery": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.3.tgz",
+      "integrity": "sha1-ReB+QZAzTeNsnhpktDsfE3PZF1g=",
+      "dev": true
+    },
+    "jquery.browser": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/jquery.browser/-/jquery.browser-0.1.0.tgz",
+      "integrity": "sha1-nHKmCV/SgUtER26o9xZne3Kmors=",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz",
+      "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==",
+      "dev": true
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "dev": true,
+      "optional": true
+    },
+    "jsesc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+      "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
+      "dev": true
+    },
+    "jshint": {
+      "version": "2.9.5",
+      "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz",
+      "integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=",
+      "dev": true,
+      "dependencies": {
+        "lodash": {
+          "version": "3.7.0",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz",
+          "integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=",
+          "dev": true
+        },
+        "shelljs": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
+          "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
+          "dev": true
+        },
+        "strip-json-comments": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+          "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=",
+          "dev": true
+        }
+      }
+    },
+    "json-preserve-indent": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/json-preserve-indent/-/json-preserve-indent-1.1.3.tgz",
+      "integrity": "sha1-gNN5mXVAAB+4VpTboSkzEePS69o=",
+      "dev": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "dev": true
+    },
+    "json-stable-stringify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+      "dev": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "dev": true
+    },
+    "json5": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+      "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+      "dev": true
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
+    "jsonpointer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+      "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+      "dev": true
+    },
+    "jsprim": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
+      "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+      "dev": true,
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+          "dev": true
+        }
+      }
+    },
+    "kind-of": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+      "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+      "dev": true
+    },
+    "latest-version": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz",
+      "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=",
+      "dev": true
+    },
+    "lazy-cache": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+      "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
+      "dev": true
+    },
+    "lazy-req": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/lazy-req/-/lazy-req-1.1.0.tgz",
+      "integrity": "sha1-va6+rTD42CQDnODOFJ1Nqge6H6w=",
+      "dev": true
+    },
+    "lcid": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+      "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true
+    },
+    "load-json-file": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+      "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+      "dev": true,
+      "dependencies": {
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "dev": true
+        }
+      }
+    },
+    "lodash": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
+      "dev": true
+    },
+    "lodash-template-loader": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/lodash-template-loader/-/lodash-template-loader-2.0.0.tgz",
+      "integrity": "sha1-jsqfgFXIzS6E4wzrPlzZFQGJpfo=",
+      "dev": true
+    },
+    "lodash.assign": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+      "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+      "dev": true
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "dev": true
+    },
+    "lodash.defaults": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+      "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
+      "dev": true
+    },
+    "lodash.defaultsdeep": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz",
+      "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=",
+      "dev": true
+    },
+    "lodash.mergewith": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz",
+      "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=",
+      "dev": true
+    },
+    "lodash.toarray": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
+      "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
+      "dev": true
+    },
+    "lolex": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz",
+      "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=",
+      "dev": true
+    },
+    "longest": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
+      "dev": true
+    },
+    "loose-envify": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+      "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+      "dev": true
+    },
+    "loud-rejection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+      "dev": true
+    },
+    "lowercase-keys": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+      "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+      "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+      "dev": true
+    },
+    "map-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+      "dev": true
+    },
+    "marked": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
+      "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=",
+      "dev": true
+    },
+    "marked-terminal": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.7.0.tgz",
+      "integrity": "sha1-yMRgiBx3LHYEtkNnAH7l938SWQQ=",
+      "dev": true
+    },
+    "meow": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "micromatch": {
+      "version": "2.3.11",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+      "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+      "dev": true,
+      "optional": true
+    },
+    "mime": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz",
+      "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=",
+      "dev": true
+    },
+    "mime-db": {
+      "version": "1.27.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
+      "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=",
+      "dev": true
+    },
+    "mime-types": {
+      "version": "2.1.15",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
+      "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+      "dev": true
+    },
+    "mixin-object": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
+      "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
+      "dev": true,
+      "dependencies": {
+        "for-in": {
+          "version": "0.1.8",
+          "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
+          "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
+          "dev": true
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "dev": true
+    },
+    "moment": {
+      "version": "2.18.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
+      "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=",
+      "dev": true
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "dev": true
+    },
+    "mute-stream": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
+      "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
+      "dev": true
+    },
+    "nan": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
+      "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=",
+      "dev": true,
+      "optional": true
+    },
+    "native-promise-only": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz",
+      "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=",
+      "dev": true
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "nconf": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.7.2.tgz",
+      "integrity": "sha1-oF/fItwBw3jdXE3yfy3JC5qouwA=",
+      "dev": true,
+      "dependencies": {
+        "async": {
+          "version": "0.9.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
+          "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
+          "dev": true
+        }
+      }
+    },
+    "nerf-dart": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz",
+      "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=",
+      "dev": true
+    },
+    "nested-error-stacks": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz",
+      "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=",
+      "dev": true
+    },
+    "node-emoji": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.7.0.tgz",
+      "integrity": "sha512-dYx345sjhPJUpWaVQKjP0/43y+nTcfBRTZfSciM3ZEbRGaU/9AKaHBPf7AJ9vOKcK0W3v67AgI4m4oo02NLHhQ==",
+      "dev": true
+    },
+    "node-status-codes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
+      "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=",
+      "dev": true
+    },
+    "nomnom": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz",
+      "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
+          "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
+          "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
+          "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=",
+          "dev": true
+        },
+        "underscore": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
+          "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+      "dev": true
+    },
+    "normalize-package-data": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+      "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+      "dev": true
+    },
+    "normalize-path": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+      "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+      "dev": true,
+      "optional": true
+    },
+    "npm": {
+      "version": "4.6.1",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-4.6.1.tgz",
+      "integrity": "sha1-+Osa0A3FilUUNjtBylNCgX8L1kY=",
+      "dev": true,
+      "dependencies": {
+        "abbrev": {
+          "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+          "dev": true
+        },
+        "ansi-regex": {
+          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "ansicolors": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
+          "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
+          "dev": true
+        },
+        "ansistyles": {
+          "version": "0.1.3",
+          "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz",
+          "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=",
+          "dev": true
+        },
+        "aproba": {
+          "version": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
+          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
+          "dev": true
+        },
+        "archy": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+          "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+          "dev": true
+        },
+        "asap": {
+          "version": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz",
+          "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=",
+          "dev": true
+        },
+        "bluebird": {
+          "version": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
+          "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=",
+          "dev": true
+        },
+        "call-limit": {
+          "version": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.0.tgz",
+          "integrity": "sha1-b9YbA/PaQqLNDsK2DwK9DnGZH+o=",
+          "dev": true
+        },
+        "chownr": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
+          "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
+          "dev": true
+        },
+        "cmd-shim": {
+          "version": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz",
+          "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=",
+          "dev": true
+        },
+        "columnify": {
+          "version": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
+          "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
+          "dev": true,
+          "dependencies": {
+            "wcwidth": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.0.tgz",
+              "integrity": "sha1-AtBZ/3qPx0Hg9rXaHmmytA2uym8=",
+              "dev": true,
+              "dependencies": {
+                "defaults": {
+                  "version": "1.0.3",
+                  "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+                  "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+                  "dev": true,
+                  "dependencies": {
+                    "clone": {
+                      "version": "1.0.2",
+                      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
+                      "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
+                      "dev": true
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "config-chain": {
+          "version": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
+          "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
+          "dev": true,
+          "dependencies": {
+            "proto-list": {
+              "version": "1.2.4",
+              "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+              "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+              "dev": true
+            }
+          }
+        },
+        "debuglog": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+          "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
+          "dev": true
+        },
+        "dezalgo": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+          "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+          "dev": true
+        },
+        "editor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz",
+          "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=",
+          "dev": true
+        },
+        "fs-vacuum": {
+          "version": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz",
+          "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
+          "dev": true
+        },
+        "fs-write-stream-atomic": {
+          "version": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+          "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+          "dev": true
+        },
+        "fstream": {
+          "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+          "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+          "dev": true
+        },
+        "fstream-npm": {
+          "version": "https://registry.npmjs.org/fstream-npm/-/fstream-npm-1.2.0.tgz",
+          "integrity": "sha1-0sPIkQE0aYLWTlcJHDhIe9qRb84=",
+          "dev": true,
+          "dependencies": {
+            "fstream-ignore": {
+              "version": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
+              "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
+              "dev": true,
+              "dependencies": {
+                "minimatch": {
+                  "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+                  "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
+                  "dev": true,
+                  "dependencies": {
+                    "brace-expansion": {
+                      "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+                      "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
+                      "dev": true,
+                      "dependencies": {
+                        "balanced-match": {
+                          "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+                          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+                          "dev": true
+                        },
+                        "concat-map": {
+                          "version": "0.0.1",
+                          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+                          "dev": true
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "glob": {
+          "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
+          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
+          "dev": true,
+          "dependencies": {
+            "fs.realpath": {
+              "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+              "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+              "dev": true
+            },
+            "minimatch": {
+              "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+              "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
+              "dev": true,
+              "dependencies": {
+                "brace-expansion": {
+                  "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+                  "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
+                  "dev": true,
+                  "dependencies": {
+                    "balanced-match": {
+                      "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+                      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+                      "dev": true
+                    },
+                    "concat-map": {
+                      "version": "0.0.1",
+                      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+                      "dev": true
+                    }
+                  }
+                }
+              }
+            },
+            "path-is-absolute": {
+              "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+              "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+              "dev": true
+            }
+          }
+        },
+        "graceful-fs": {
+          "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+          "dev": true
+        },
+        "has-unicode": {
+          "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+          "dev": true
+        },
+        "hosted-git-info": {
+          "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz",
+          "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=",
+          "dev": true
+        },
+        "iferr": {
+          "version": "0.1.5",
+          "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+          "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+          "dev": true
+        },
+        "imurmurhash": {
+          "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+          "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+          "dev": true
+        },
+        "inflight": {
+          "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "ini": {
+          "version": "1.3.4",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+          "dev": true
+        },
+        "init-package-json": {
+          "version": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.1.tgz",
+          "integrity": "sha1-zYc6FneWvvuZYSsodioLY5P9j2o=",
+          "dev": true,
+          "dependencies": {
+            "promzard": {
+              "version": "0.3.0",
+              "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz",
+              "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
+              "dev": true
+            }
+          }
+        },
+        "JSONStream": {
+          "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
+          "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
+          "dev": true,
+          "dependencies": {
+            "jsonparse": {
+              "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.0.tgz",
+              "integrity": "sha1-hfwkWx2SWazGlBlguQWt9k594Og=",
+              "dev": true
+            },
+            "through": {
+              "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+              "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+              "dev": true
+            }
+          }
+        },
+        "lazy-property": {
+          "version": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz",
+          "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=",
+          "dev": true
+        },
+        "lockfile": {
+          "version": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz",
+          "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=",
+          "dev": true
+        },
+        "lodash._baseindexof": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz",
+          "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=",
+          "dev": true
+        },
+        "lodash._baseuniq": {
+          "version": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
+          "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
+          "dev": true,
+          "dependencies": {
+            "lodash._createset": {
+              "version": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
+              "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=",
+              "dev": true
+            },
+            "lodash._root": {
+              "version": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
+              "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=",
+              "dev": true
+            }
+          }
+        },
+        "lodash._bindcallback": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
+          "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
+          "dev": true
+        },
+        "lodash._cacheindexof": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz",
+          "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=",
+          "dev": true
+        },
+        "lodash._createcache": {
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz",
+          "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=",
+          "dev": true
+        },
+        "lodash._getnative": {
+          "version": "3.9.1",
+          "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+          "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
+          "dev": true
+        },
+        "lodash.clonedeep": {
+          "version": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+          "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+          "dev": true
+        },
+        "lodash.restparam": {
+          "version": "3.6.1",
+          "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
+          "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
+          "dev": true
+        },
+        "lodash.union": {
+          "version": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+          "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
+          "dev": true
+        },
+        "lodash.uniq": {
+          "version": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+          "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+          "dev": true
+        },
+        "lodash.without": {
+          "version": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz",
+          "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=",
+          "dev": true
+        },
+        "mississippi": {
+          "version": "https://registry.npmjs.org/mississippi/-/mississippi-1.3.0.tgz",
+          "integrity": "sha1-0gFYPrEjJ+PFwWQqQEqcrPlONPU=",
+          "dev": true,
+          "dependencies": {
+            "concat-stream": {
+              "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+              "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+              "dev": true,
+              "dependencies": {
+                "typedarray": {
+                  "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+                  "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+                  "dev": true
+                }
+              }
+            },
+            "duplexify": {
+              "version": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
+              "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
+              "dev": true,
+              "dependencies": {
+                "end-of-stream": {
+                  "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                  "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=",
+                  "dev": true,
+                  "dependencies": {
+                    "once": {
+                      "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+                      "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+                      "dev": true
+                    }
+                  }
+                },
+                "stream-shift": {
+                  "version": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+                  "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+                  "dev": true
+                }
+              }
+            },
+            "end-of-stream": {
+              "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
+              "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=",
+              "dev": true,
+              "dependencies": {
+                "once": {
+                  "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+                  "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+                  "dev": true
+                }
+              }
+            },
+            "flush-write-stream": {
+              "version": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.2.tgz",
+              "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=",
+              "dev": true
+            },
+            "from2": {
+              "version": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+              "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+              "dev": true
+            },
+            "parallel-transform": {
+              "version": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
+              "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
+              "dev": true,
+              "dependencies": {
+                "cyclist": {
+                  "version": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
+                  "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
+                  "dev": true
+                }
+              }
+            },
+            "pump": {
+              "version": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz",
+              "integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=",
+              "dev": true
+            },
+            "pumpify": {
+              "version": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz",
+              "integrity": "sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=",
+              "dev": true
+            },
+            "stream-each": {
+              "version": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.0.tgz",
+              "integrity": "sha1-HpXUdXP1gNgU3A/4zQ9m8c5TyZE=",
+              "dev": true,
+              "dependencies": {
+                "stream-shift": {
+                  "version": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+                  "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+                  "dev": true
+                }
+              }
+            },
+            "through2": {
+              "version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+              "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+              "dev": true,
+              "dependencies": {
+                "xtend": {
+                  "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+                  "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "dev": true,
+          "dependencies": {
+            "minimist": {
+              "version": "0.0.8",
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+              "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+              "dev": true
+            }
+          }
+        },
+        "move-concurrently": {
+          "version": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+          "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+          "dev": true,
+          "dependencies": {
+            "copy-concurrently": {
+              "version": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.3.tgz",
+              "integrity": "sha1-Rft4ZiSaHKiJqlcI5svSc+dbslA=",
+              "dev": true
+            },
+            "run-queue": {
+              "version": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+              "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+              "dev": true
+            }
+          }
+        },
+        "node-gyp": {
+          "version": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.0.tgz",
+          "integrity": "sha1-dHT2OjoFARYd2gtjQfAi8UxCP6Y=",
+          "dev": true,
+          "dependencies": {
+            "minimatch": {
+              "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+              "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
+              "dev": true,
+              "dependencies": {
+                "brace-expansion": {
+                  "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+                  "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
+                  "dev": true,
+                  "dependencies": {
+                    "balanced-match": {
+                      "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+                      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+                      "dev": true
+                    },
+                    "concat-map": {
+                      "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+                      "dev": true
+                    }
+                  }
+                }
+              }
+            },
+            "nopt": {
+              "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+              "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+              "dev": true
+            }
+          }
+        },
+        "nopt": {
+          "version": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+          "dev": true,
+          "dependencies": {
+            "osenv": {
+              "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+              "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+              "dev": true,
+              "dependencies": {
+                "os-homedir": {
+                  "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+                  "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+                  "dev": true
+                },
+                "os-tmpdir": {
+                  "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+                  "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "normalize-git-url": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/normalize-git-url/-/normalize-git-url-3.0.2.tgz",
+          "integrity": "sha1-jl8Uvgva7bc+ByADEKpBbCc1D8Q=",
+          "dev": true
+        },
+        "normalize-package-data": {
+          "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz",
+          "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=",
+          "dev": true,
+          "dependencies": {
+            "is-builtin-module": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+              "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+              "dev": true,
+              "dependencies": {
+                "builtin-modules": {
+                  "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+                  "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "npm-cache-filename": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz",
+          "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=",
+          "dev": true
+        },
+        "npm-install-checks": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.0.tgz",
+          "integrity": "sha1-1K7N/VGlPjcjt7L5Oy7ijjB7wNc=",
+          "dev": true
+        },
+        "npm-package-arg": {
+          "version": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-4.2.1.tgz",
+          "integrity": "sha1-WTMD/eqF98Qid18X+et2cPaA4+w=",
+          "dev": true
+        },
+        "npm-registry-client": {
+          "version": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.1.1.tgz",
+          "integrity": "sha1-gxR2RVQjygomXG/9thAPzAQrNs8=",
+          "dev": true,
+          "dependencies": {
+            "concat-stream": {
+              "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+              "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+              "dev": true,
+              "dependencies": {
+                "typedarray": {
+                  "version": "0.0.6",
+                  "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+                  "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "npm-user-validate": {
+          "version": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-0.1.5.tgz",
+          "integrity": "sha1-UkZdUMLSApSlcSW5lrrtv1bFAEs=",
+          "dev": true
+        },
+        "npmlog": {
+          "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
+          "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=",
+          "dev": true,
+          "dependencies": {
+            "are-we-there-yet": {
+              "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+              "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+              "dev": true,
+              "dependencies": {
+                "delegates": {
+                  "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+                  "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+                  "dev": true
+                }
+              }
+            },
+            "console-control-strings": {
+              "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+              "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+              "dev": true
+            },
+            "gauge": {
+              "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+              "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+              "dev": true,
+              "dependencies": {
+                "object-assign": {
+                  "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+                  "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+                  "dev": true
+                },
+                "signal-exit": {
+                  "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+                  "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+                  "dev": true
+                },
+                "string-width": {
+                  "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+                  "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+                  "dev": true,
+                  "dependencies": {
+                    "code-point-at": {
+                      "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+                      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+                      "dev": true
+                    },
+                    "is-fullwidth-code-point": {
+                      "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+                      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+                      "dev": true,
+                      "dependencies": {
+                        "number-is-nan": {
+                          "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+                          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+                          "dev": true
+                        }
+                      }
+                    }
+                  }
+                },
+                "wide-align": {
+                  "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
+                  "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=",
+                  "dev": true
+                }
+              }
+            },
+            "set-blocking": {
+              "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+              "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+              "dev": true
+            }
+          }
+        },
+        "once": {
+          "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+          "dev": true
+        },
+        "opener": {
+          "version": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz",
+          "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=",
+          "dev": true
+        },
+        "osenv": {
+          "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+          "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+          "dev": true,
+          "dependencies": {
+            "os-homedir": {
+              "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+              "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+              "dev": true
+            },
+            "os-tmpdir": {
+              "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+              "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+              "dev": true
+            }
+          }
+        },
+        "path-is-inside": {
+          "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+          "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+          "dev": true
+        },
+        "read": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
+          "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
+          "dev": true,
+          "dependencies": {
+            "mute-stream": {
+              "version": "0.0.5",
+              "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
+              "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
+              "dev": true
+            }
+          }
+        },
+        "read-cmd-shim": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz",
+          "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=",
+          "dev": true
+        },
+        "read-installed": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
+          "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=",
+          "dev": true,
+          "dependencies": {
+            "util-extend": {
+              "version": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
+              "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=",
+              "dev": true
+            }
+          }
+        },
+        "read-package-json": {
+          "version": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.5.tgz",
+          "integrity": "sha1-+Tpk5kFSnfaKCMZN5GOJ6KP4iEU=",
+          "dev": true,
+          "dependencies": {
+            "json-parse-helpfulerror": {
+              "version": "1.0.3",
+              "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz",
+              "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=",
+              "dev": true,
+              "dependencies": {
+                "jju": {
+                  "version": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz",
+                  "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "read-package-tree": {
+          "version": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.1.5.tgz",
+          "integrity": "sha1-rOfmOBx2hPlwqqmPx8XStmat2rY=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
+          "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=",
+          "dev": true,
+          "dependencies": {
+            "buffer-shims": {
+              "version": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+              "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
+              "dev": true
+            },
+            "core-util-is": {
+              "version": "1.0.2",
+              "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+              "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+              "dev": true
+            },
+            "isarray": {
+              "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+              "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+              "dev": true
+            },
+            "process-nextick-args": {
+              "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+              "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+              "dev": true
+            },
+            "string_decoder": {
+              "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz",
+              "integrity": "sha1-8G9BFXtmTYYGn4S9vcmw2KsoFmc=",
+              "dev": true
+            },
+            "util-deprecate": {
+              "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+              "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+              "dev": true
+            }
+          }
+        },
+        "readdir-scoped-modules": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz",
+          "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=",
+          "dev": true
+        },
+        "realize-package-specifier": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/realize-package-specifier/-/realize-package-specifier-3.0.3.tgz",
+          "integrity": "sha1-0N74gpUrjeP2frpekRmWYScfQfQ=",
+          "dev": true
+        },
+        "request": {
+          "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+          "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+          "dev": true,
+          "dependencies": {
+            "aws-sign2": {
+              "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+              "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+              "dev": true
+            },
+            "aws4": {
+              "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+              "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+              "dev": true
+            },
+            "caseless": {
+              "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+              "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+              "dev": true
+            },
+            "combined-stream": {
+              "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+              "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+              "dev": true,
+              "dependencies": {
+                "delayed-stream": {
+                  "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+                  "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+                  "dev": true
+                }
+              }
+            },
+            "extend": {
+              "version": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+              "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=",
+              "dev": true
+            },
+            "forever-agent": {
+              "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+              "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+              "dev": true
+            },
+            "form-data": {
+              "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
+              "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=",
+              "dev": true,
+              "dependencies": {
+                "asynckit": {
+                  "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+                  "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+                  "dev": true
+                }
+              }
+            },
+            "har-validator": {
+              "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+              "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+              "dev": true,
+              "dependencies": {
+                "ajv": {
+                  "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.4.tgz",
+                  "integrity": "sha1-6/OlXUsTLqYP9YR66F0u8GmWC0U=",
+                  "dev": true,
+                  "dependencies": {
+                    "co": {
+                      "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+                      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+                      "dev": true
+                    },
+                    "json-stable-stringify": {
+                      "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+                      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+                      "dev": true,
+                      "dependencies": {
+                        "jsonify": {
+                          "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+                          "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+                          "dev": true
+                        }
+                      }
+                    }
+                  }
+                },
+                "har-schema": {
+                  "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+                  "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+                  "dev": true
+                }
+              }
+            },
+            "hawk": {
+              "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+              "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+              "dev": true,
+              "dependencies": {
+                "boom": {
+                  "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+                  "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+                  "dev": true
+                },
+                "cryptiles": {
+                  "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+                  "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+                  "dev": true
+                },
+                "hoek": {
+                  "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+                  "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+                  "dev": true
+                },
+                "sntp": {
+                  "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+                  "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+                  "dev": true
+                }
+              }
+            },
+            "http-signature": {
+              "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+              "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+              "dev": true,
+              "dependencies": {
+                "assert-plus": {
+                  "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+                  "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+                  "dev": true
+                },
+                "jsprim": {
+                  "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
+                  "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=",
+                  "dev": true,
+                  "dependencies": {
+                    "extsprintf": {
+                      "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+                      "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=",
+                      "dev": true
+                    },
+                    "json-schema": {
+                      "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+                      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+                      "dev": true
+                    },
+                    "verror": {
+                      "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+                      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+                      "dev": true
+                    }
+                  }
+                },
+                "sshpk": {
+                  "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.11.0.tgz",
+                  "integrity": "sha1-LY1eu0pvqyj/ujf6YqkPSj6lnXc=",
+                  "dev": true,
+                  "dependencies": {
+                    "asn1": {
+                      "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+                      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+                      "dev": true
+                    },
+                    "assert-plus": {
+                      "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+                      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+                      "dev": true
+                    },
+                    "bcrypt-pbkdf": {
+                      "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+                      "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+                      "dev": true,
+                      "optional": true
+                    },
+                    "dashdash": {
+                      "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+                      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+                      "dev": true
+                    },
+                    "ecc-jsbn": {
+                      "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+                      "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+                      "dev": true,
+                      "optional": true
+                    },
+                    "getpass": {
+                      "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+                      "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
+                      "dev": true
+                    },
+                    "jodid25519": {
+                      "version": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+                      "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+                      "dev": true,
+                      "optional": true
+                    },
+                    "jsbn": {
+                      "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+                      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+                      "dev": true,
+                      "optional": true
+                    },
+                    "tweetnacl": {
+                      "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+                      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+                      "dev": true,
+                      "optional": true
+                    }
+                  }
+                }
+              }
+            },
+            "is-typedarray": {
+              "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+              "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+              "dev": true
+            },
+            "isstream": {
+              "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+              "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+              "dev": true
+            },
+            "json-stringify-safe": {
+              "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+              "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+              "dev": true
+            },
+            "mime-types": {
+              "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
+              "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4=",
+              "dev": true,
+              "dependencies": {
+                "mime-db": {
+                  "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
+                  "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8=",
+                  "dev": true
+                }
+              }
+            },
+            "oauth-sign": {
+              "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+              "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+              "dev": true
+            },
+            "performance-now": {
+              "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+              "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+              "dev": true
+            },
+            "qs": {
+              "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+              "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+              "dev": true
+            },
+            "safe-buffer": {
+              "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+              "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=",
+              "dev": true
+            },
+            "stringstream": {
+              "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+              "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+              "dev": true
+            },
+            "tough-cookie": {
+              "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+              "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+              "dev": true,
+              "dependencies": {
+                "punycode": {
+                  "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+                  "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+                  "dev": true
+                }
+              }
+            },
+            "tunnel-agent": {
+              "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+              "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+              "dev": true
+            }
+          }
+        },
+        "retry": {
+          "version": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+          "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+          "dev": true
+        },
+        "rimraf": {
+          "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+          "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+          "dev": true
+        },
+        "semver": {
+          "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "dev": true
+        },
+        "sha": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/sha/-/sha-2.0.1.tgz",
+          "integrity": "sha1-YDCCL70smCOUn49y7WQR7lzyWq4=",
+          "dev": true
+        },
+        "slide": {
+          "version": "1.1.6",
+          "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+          "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
+          "dev": true
+        },
+        "sorted-object": {
+          "version": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz",
+          "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=",
+          "dev": true
+        },
+        "sorted-union-stream": {
+          "version": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz",
+          "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=",
+          "dev": true,
+          "dependencies": {
+            "from2": {
+              "version": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz",
+              "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=",
+              "dev": true,
+              "dependencies": {
+                "readable-stream": {
+                  "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+                  "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+                  "dev": true,
+                  "dependencies": {
+                    "core-util-is": {
+                      "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+                      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+                      "dev": true
+                    },
+                    "isarray": {
+                      "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+                      "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+                      "dev": true
+                    },
+                    "string_decoder": {
+                      "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+                      "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+                      "dev": true
+                    }
+                  }
+                }
+              }
+            },
+            "stream-iterate": {
+              "version": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.1.1.tgz",
+              "integrity": "sha1-XX0ZeqUryeJxtEVHyeOIsrGzODY=",
+              "dev": true
+            }
+          }
+        },
+        "strip-ansi": {
+          "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true
+        },
+        "tar": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+          "dev": true,
+          "dependencies": {
+            "block-stream": {
+              "version": "0.0.8",
+              "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz",
+              "integrity": "sha1-Boj0baK7+c/wxPaCJaDLlcvopGs=",
+              "dev": true
+            }
+          }
+        },
+        "text-table": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+          "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+          "dev": true
+        },
+        "uid-number": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+          "dev": true
+        },
+        "umask": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz",
+          "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=",
+          "dev": true
+        },
+        "unique-filename": {
+          "version": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz",
+          "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=",
+          "dev": true,
+          "dependencies": {
+            "unique-slug": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
+              "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
+              "dev": true
+            }
+          }
+        },
+        "unpipe": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+          "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+          "dev": true
+        },
+        "update-notifier": {
+          "version": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.1.0.tgz",
+          "integrity": "sha1-7AweU1NrdmR6JLd8uDlm2TFRI9k=",
+          "dev": true,
+          "dependencies": {
+            "boxen": {
+              "version": "https://registry.npmjs.org/boxen/-/boxen-1.0.0.tgz",
+              "integrity": "sha1-smlLrx9gX3CP8Bd8Ehk7IvKaqqs=",
+              "dev": true,
+              "dependencies": {
+                "ansi-align": {
+                  "version": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz",
+                  "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=",
+                  "dev": true,
+                  "dependencies": {
+                    "string-width": {
+                      "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+                      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+                      "dev": true,
+                      "dependencies": {
+                        "code-point-at": {
+                          "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+                          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+                          "dev": true
+                        },
+                        "is-fullwidth-code-point": {
+                          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+                          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+                          "dev": true,
+                          "dependencies": {
+                            "number-is-nan": {
+                              "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+                              "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+                              "dev": true
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                },
+                "camelcase": {
+                  "version": "https://registry.npmjs.org/camelcase/-/camelcase-4.0.0.tgz",
+                  "integrity": "sha1-iw+Q1Evl4oG5A7mIc0m5JZXvB/I=",
+                  "dev": true
+                },
+                "cli-boxes": {
+                  "version": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+                  "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+                  "dev": true
+                },
+                "string-width": {
+                  "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz",
+                  "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=",
+                  "dev": true,
+                  "dependencies": {
+                    "is-fullwidth-code-point": {
+                      "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+                      "dev": true
+                    }
+                  }
+                },
+                "term-size": {
+                  "version": "https://registry.npmjs.org/term-size/-/term-size-0.1.1.tgz",
+                  "integrity": "sha1-hzYLljlsq1dgljcUzaDQy+7K2co=",
+                  "dev": true,
+                  "dependencies": {
+                    "execa": {
+                      "version": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz",
+                      "integrity": "sha1-TrZGejaglfq7KXD/nV4/t7zm68M=",
+                      "dev": true,
+                      "dependencies": {
+                        "cross-spawn-async": {
+                          "version": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz",
+                          "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=",
+                          "dev": true,
+                          "dependencies": {
+                            "lru-cache": {
+                              "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
+                              "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
+                              "dev": true,
+                              "dependencies": {
+                                "pseudomap": {
+                                  "version": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+                                  "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+                                  "dev": true
+                                },
+                                "yallist": {
+                                  "version": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz",
+                                  "integrity": "sha1-MGxUODXwnuGkyyO3vOmrNByRzdQ=",
+                                  "dev": true
+                                }
+                              }
+                            }
+                          }
+                        },
+                        "is-stream": {
+                          "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+                          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+                          "dev": true
+                        },
+                        "npm-run-path": {
+                          "version": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz",
+                          "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=",
+                          "dev": true
+                        },
+                        "object-assign": {
+                          "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+                          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+                          "dev": true
+                        },
+                        "path-key": {
+                          "version": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz",
+                          "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=",
+                          "dev": true
+                        },
+                        "strip-eof": {
+                          "version": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+                          "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+                          "dev": true
+                        }
+                      }
+                    }
+                  }
+                },
+                "widest-line": {
+                  "version": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
+                  "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
+                  "dev": true,
+                  "dependencies": {
+                    "string-width": {
+                      "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+                      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+                      "dev": true,
+                      "dependencies": {
+                        "code-point-at": {
+                          "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+                          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+                          "dev": true
+                        },
+                        "is-fullwidth-code-point": {
+                          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+                          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+                          "dev": true,
+                          "dependencies": {
+                            "number-is-nan": {
+                              "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+                              "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+                              "dev": true
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            },
+            "chalk": {
+              "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+              "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+              "dev": true,
+              "dependencies": {
+                "ansi-styles": {
+                  "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+                  "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+                  "dev": true
+                },
+                "escape-string-regexp": {
+                  "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+                  "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+                  "dev": true
+                },
+                "has-ansi": {
+                  "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+                  "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+                  "dev": true
+                },
+                "supports-color": {
+                  "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+                  "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+                  "dev": true
+                }
+              }
+            },
+            "configstore": {
+              "version": "https://registry.npmjs.org/configstore/-/configstore-3.0.0.tgz",
+              "integrity": "sha1-4bhmnBgDzMULVF6S+ObnmqgOAZY=",
+              "dev": true,
+              "dependencies": {
+                "dot-prop": {
+                  "version": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.1.1.tgz",
+                  "integrity": "sha1-qEk/C3te7sglJbXHWH+n3nyoWcE=",
+                  "dev": true,
+                  "dependencies": {
+                    "is-obj": {
+                      "version": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+                      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+                      "dev": true
+                    }
+                  }
+                },
+                "unique-string": {
+                  "version": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+                  "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
+                  "dev": true,
+                  "dependencies": {
+                    "crypto-random-string": {
+                      "version": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+                      "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
+                      "dev": true
+                    }
+                  }
+                }
+              }
+            },
+            "is-npm": {
+              "version": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+              "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+              "dev": true
+            },
+            "latest-version": {
+              "version": "https://registry.npmjs.org/latest-version/-/latest-version-3.0.0.tgz",
+              "integrity": "sha1-MQTwCMDDkQhBB/haNEvGHjiXBkk=",
+              "dev": true,
+              "dependencies": {
+                "package-json": {
+                  "version": "https://registry.npmjs.org/package-json/-/package-json-3.1.0.tgz",
+                  "integrity": "sha1-zigZAP6AUhUMxnCcbABsGP2y83k=",
+                  "dev": true,
+                  "dependencies": {
+                    "got": {
+                      "version": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+                      "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
+                      "dev": true,
+                      "dependencies": {
+                        "create-error-class": {
+                          "version": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+                          "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+                          "dev": true,
+                          "dependencies": {
+                            "capture-stack-trace": {
+                              "version": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
+                              "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
+                              "dev": true
+                            }
+                          }
+                        },
+                        "duplexer3": {
+                          "version": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+                          "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+                          "dev": true
+                        },
+                        "get-stream": {
+                          "version": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+                          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+                          "dev": true
+                        },
+                        "is-redirect": {
+                          "version": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+                          "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+                          "dev": true
+                        },
+                        "is-retry-allowed": {
+                          "version": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
+                          "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=",
+                          "dev": true
+                        },
+                        "is-stream": {
+                          "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+                          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+                          "dev": true
+                        },
+                        "lowercase-keys": {
+                          "version": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+                          "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+                          "dev": true
+                        },
+                        "safe-buffer": {
+                          "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+                          "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=",
+                          "dev": true
+                        },
+                        "timed-out": {
+                          "version": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+                          "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+                          "dev": true
+                        },
+                        "unzip-response": {
+                          "version": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
+                          "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
+                          "dev": true
+                        },
+                        "url-parse-lax": {
+                          "version": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+                          "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+                          "dev": true,
+                          "dependencies": {
+                            "prepend-http": {
+                              "version": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+                              "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+                              "dev": true
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "registry-auth-token": {
+                      "version": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.1.0.tgz",
+                      "integrity": "sha1-mXwIJW4MeZmDe5DpRNs52KeQJ2s=",
+                      "dev": true,
+                      "dependencies": {
+                        "rc": {
+                          "version": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
+                          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
+                          "dev": true,
+                          "dependencies": {
+                            "deep-extend": {
+                              "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
+                              "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
+                              "dev": true
+                            },
+                            "minimist": {
+                              "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+                              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+                              "dev": true
+                            },
+                            "strip-json-comments": {
+                              "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+                              "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+                              "dev": true
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "registry-url": {
+                      "version": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+                      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+                      "dev": true,
+                      "dependencies": {
+                        "rc": {
+                          "version": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
+                          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
+                          "dev": true,
+                          "dependencies": {
+                            "deep-extend": {
+                              "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
+                              "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
+                              "dev": true
+                            },
+                            "minimist": {
+                              "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+                              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+                              "dev": true
+                            },
+                            "strip-json-comments": {
+                              "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+                              "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+                              "dev": true
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            },
+            "lazy-req": {
+              "version": "https://registry.npmjs.org/lazy-req/-/lazy-req-2.0.0.tgz",
+              "integrity": "sha1-yUUKNj7N2i5vDHATKtTzf48G8rQ=",
+              "dev": true
+            },
+            "semver-diff": {
+              "version": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+              "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+              "dev": true
+            },
+            "xdg-basedir": {
+              "version": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+              "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
+              "dev": true
+            }
+          }
+        },
+        "uuid": {
+          "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
+          "dev": true
+        },
+        "validate-npm-package-license": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+          "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+          "dev": true,
+          "dependencies": {
+            "spdx-correct": {
+              "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+              "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+              "dev": true,
+              "dependencies": {
+                "spdx-license-ids": {
+                  "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.0.tgz",
+                  "integrity": "sha1-tUndD2Pct0Whfi6joHQC4OMy0eI=",
+                  "dev": true
+                }
+              }
+            },
+            "spdx-expression-parse": {
+              "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz",
+              "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=",
+              "dev": true,
+              "dependencies": {
+                "spdx-exceptions": {
+                  "version": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz",
+                  "integrity": "sha1-IguEI5EZrpBFqJLbgag/TOFvgP0=",
+                  "dev": true
+                },
+                "spdx-license-ids": {
+                  "version": "1.2.0",
+                  "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.0.tgz",
+                  "integrity": "sha1-tUndD2Pct0Whfi6joHQC4OMy0eI=",
+                  "dev": true
+                }
+              }
+            }
+          }
+        },
+        "validate-npm-package-name": {
+          "version": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+          "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+          "dev": true,
+          "dependencies": {
+            "builtins": {
+              "version": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+              "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
+              "dev": true
+            }
+          }
+        },
+        "which": {
+          "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+          "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+          "dev": true,
+          "dependencies": {
+            "isexe": {
+              "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+              "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+              "dev": true
+            }
+          }
+        },
+        "wrappy": {
+          "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true
+        },
+        "write-file-atomic": {
+          "version": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.3.tgz",
+          "integrity": "sha1-gx3SLUkb3BNRgLuZag6z+L9Yd5E=",
+          "dev": true
+        }
+      }
+    },
+    "npm-package-arg": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-4.2.1.tgz",
+      "integrity": "sha1-WTMD/eqF98Qid18X+et2cPaA4+w=",
+      "dev": true
+    },
+    "npm-registry-client": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-7.5.0.tgz",
+      "integrity": "sha1-D23W5dEUJM+pn85bkw/q8JtPfwQ=",
+      "dev": true
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true
+    },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "dev": true
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+      "dev": true
+    },
+    "oauth-sign": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+      "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+      "dev": true
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+      "dev": true
+    },
+    "object.omit": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+      "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+      "dev": true,
+      "optional": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true
+    },
+    "onetime": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+      "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+      "dev": true
+    },
+    "open": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz",
+      "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=",
+      "dev": true
+    },
+    "opener": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz",
+      "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=",
+      "dev": true
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "dev": true,
+      "dependencies": {
+        "wordwrap": {
+          "version": "0.0.3",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+          "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+          "dev": true
+        }
+      }
+    },
+    "optionator": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+      "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+      "dev": true
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+      "dev": true
+    },
+    "os-locale": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+      "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+      "dev": true
+    },
+    "os-name": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/os-name/-/os-name-1.0.3.tgz",
+      "integrity": "sha1-GzefZINa98Wn9JizV8uVIVwVnt8=",
+      "dev": true
+    },
+    "os-shim": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
+      "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=",
+      "dev": true
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
+    },
+    "osenv": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+      "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+      "dev": true
+    },
+    "osx-release": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/osx-release/-/osx-release-1.1.0.tgz",
+      "integrity": "sha1-8heRGigTaUmvG/kwiyQeJzfTzWw=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "otr": {
+      "version": "0.2.16",
+      "resolved": "https://registry.npmjs.org/otr/-/otr-0.2.16.tgz",
+      "integrity": "sha1-BKdTRPUi38sHeMVDjA9V5p0+i8E=",
+      "dev": true
+    },
+    "output-file-sync": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz",
+      "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=",
+      "dev": true
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "package-json": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
+      "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
+      "dev": true
+    },
+    "parse-glob": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+      "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+      "dev": true,
+      "optional": true
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true
+    },
+    "path-exists": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+      "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+      "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+      "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+      "dev": true,
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        }
+      }
+    },
+    "path-type": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+      "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+      "dev": true
+    },
+    "performance-now": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+      "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+      "dev": true
+    },
+    "pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+      "dev": true
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "dev": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "dev": true
+    },
+    "pluggable.js": {
+      "version": "git+https://github.com/jcbrand/pluggable.js.git#8f8c8235816f44cda0f855d6ca879445aaa486a1",
+      "dev": true
+    },
+    "pluralize": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz",
+      "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
+      "dev": true
+    },
+    "po2json": {
+      "version": "0.4.5",
+      "resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
+      "integrity": "sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=",
+      "dev": true
+    },
+    "portfinder": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz",
+      "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+      "dev": true
+    },
+    "preserve": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+      "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
+      "dev": true,
+      "optional": true
+    },
+    "private": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz",
+      "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+      "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+      "dev": true
+    },
+    "progress": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+      "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+      "dev": true
+    },
+    "promise": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "dev": true
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "punycode": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+      "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+      "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+      "dev": true
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+      "dev": true
+    },
+    "random-string": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/random-string/-/random-string-0.1.2.tgz",
+      "integrity": "sha1-LWr7YHZlExasW4pi1jEG2zHrjUQ=",
+      "dev": true
+    },
+    "randomatic": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
+      "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "is-number": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+          "dev": true,
+          "optional": true,
+          "dependencies": {
+            "kind-of": {
+              "version": "3.2.2",
+              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
+    "rc": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
+      "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "read-all-stream": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz",
+      "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=",
+      "dev": true
+    },
+    "read-pkg": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+      "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+      "dev": true
+    },
+    "read-pkg-up": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+      "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+      "dev": true
+    },
+    "readable-stream": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+      "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+      "dev": true
+    },
+    "readdirp": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
+      "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
+      "dev": true,
+      "optional": true
+    },
+    "readline2": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
+      "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
+      "dev": true
+    },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "dev": true
+    },
+    "redent": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+      "dev": true
+    },
+    "redeyed": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz",
+      "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=",
+      "dev": true,
+      "dependencies": {
+        "esprima": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.0.0.tgz",
+          "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k=",
+          "dev": true
+        }
+      }
+    },
+    "regenerate": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz",
+      "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=",
+      "dev": true
+    },
+    "regenerator-runtime": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+      "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+      "dev": true
+    },
+    "regenerator-transform": {
+      "version": "0.9.11",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz",
+      "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
+      "dev": true
+    },
+    "regex-cache": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz",
+      "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=",
+      "dev": true,
+      "optional": true
+    },
+    "regexpu-core": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
+      "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
+      "dev": true
+    },
+    "registry-auth-token": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz",
+      "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=",
+      "dev": true
+    },
+    "registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+      "dev": true
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+      "dev": true
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "dev": true,
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        }
+      }
+    },
+    "remove-trailing-separator": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz",
+      "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=",
+      "dev": true,
+      "optional": true
+    },
+    "repeat-element": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
+      "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "repeating": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+      "dev": true
+    },
+    "request": {
+      "version": "2.81.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+      "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+      "dev": true
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+      "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+      "dev": true
+    },
+    "require-uncached": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+      "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+      "dev": true
+    },
+    "requirejs": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.3.tgz",
+      "integrity": "sha1-qln9OgKH6vQHlZoTgigES13WpqM=",
+      "dev": true
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
+      "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
+      "dev": true
+    },
+    "resolve-from": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+      "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+      "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+      "dev": true
+    },
+    "retry": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+      "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+      "dev": true
+    },
+    "right-align": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+      "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+      "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+      "dev": true
+    },
+    "run-async": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+      "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
+      "dev": true
+    },
+    "run-headless-chromium": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/run-headless-chromium/-/run-headless-chromium-0.1.1.tgz",
+      "integrity": "sha1-eeYQoA3wIosztrNK5qP8ps6eWv8=",
+      "dev": true,
+      "dependencies": {
+        "rimraf": {
+          "version": "2.2.8",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+          "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
+          "dev": true
+        },
+        "which": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.0.5.tgz",
+          "integrity": "sha1-VjDWgZ3aaS8UZEYueVbLQsCEJzk=",
+          "dev": true
+        }
+      }
+    },
+    "rx": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
+      "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
+      "dev": true
+    },
+    "rx-lite": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
+      "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
+      "dev": true
+    },
+    "safe-buffer": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+      "dev": true
+    },
+    "samsam": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz",
+      "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=",
+      "dev": true
+    },
+    "semver": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+      "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+      "dev": true
+    },
+    "semver-diff": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+      "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+      "dev": true
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "set-immediate-shim": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+      "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
+      "dev": true,
+      "optional": true
+    },
+    "shallow-clone": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
+      "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=",
+      "dev": true,
+      "dependencies": {
+        "kind-of": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
+          "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=",
+          "dev": true
+        },
+        "lazy-cache": {
+          "version": "0.2.7",
+          "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
+          "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=",
+          "dev": true
+        }
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "shelljs": {
+      "version": "0.7.8",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
+      "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "sinon": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.8.tgz",
+      "integrity": "sha1-Md4G/tj7o6Zx5XbdltClhjeW8lw=",
+      "dev": true
+    },
+    "slash": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+      "dev": true
+    },
+    "sleep": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/sleep/-/sleep-3.0.1.tgz",
+      "integrity": "sha1-vk0XxXk2DgfgTtgXK6KxCmkFTfM=",
+      "dev": true,
+      "optional": true
+    },
+    "slice-ansi": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+      "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+      "dev": true
+    },
+    "slide": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+      "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
+      "dev": true
+    },
+    "sntp": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+      "dev": true
+    },
+    "snyk": {
+      "version": "1.36.2",
+      "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.36.2.tgz",
+      "integrity": "sha1-iL5yqNp1oC6SDyvn9YMEArXg60E=",
+      "dev": true,
+      "dependencies": {
+        "configstore": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz",
+          "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=",
+          "dev": true,
+          "dependencies": {
+            "uuid": {
+              "version": "2.0.3",
+              "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+              "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+              "dev": true
+            }
+          }
+        },
+        "es6-promise": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+          "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+          "dev": true
+        },
+        "got": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
+          "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=",
+          "dev": true,
+          "dependencies": {
+            "object-assign": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+              "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=",
+              "dev": true
+            }
+          }
+        },
+        "inquirer": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.0.3.tgz",
+          "integrity": "sha1-6+OglIVxvMRszMvi+bzsJR6YS9A=",
+          "dev": true
+        },
+        "latest-version": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz",
+          "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=",
+          "dev": true
+        },
+        "mute-stream": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz",
+          "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=",
+          "dev": true
+        },
+        "package-json": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz",
+          "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=",
+          "dev": true
+        },
+        "repeating": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz",
+          "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=",
+          "dev": true
+        },
+        "run-async": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+          "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+          "dev": true
+        },
+        "timed-out": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz",
+          "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=",
+          "dev": true
+        },
+        "update-notifier": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz",
+          "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=",
+          "dev": true
+        }
+      }
+    },
+    "snyk-config": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-1.0.1.tgz",
+      "integrity": "sha1-8nrsJJiyQCescZIUAmUhWRERUI8=",
+      "dev": true
+    },
+    "snyk-gradle-plugin": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-1.0.2.tgz",
+      "integrity": "sha512-mFGMmSLj3lIBZay0pJ+8+Y5QB6B55ywOaLYA85TJHQfsoRp3A5LpQvTd6jBWDCo+oZw65wrDQxu4EsGEew+Y6Q==",
+      "dev": true
+    },
+    "snyk-module": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-1.8.1.tgz",
+      "integrity": "sha1-MdUID7HA39b6hWfdNKUj/QK/H8o=",
+      "dev": true
+    },
+    "snyk-mvn-plugin": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-1.0.0.tgz",
+      "integrity": "sha512-23K3VGMKS3W2l53cyQyb+EnbScKW3idUmylKRJRYEcK95ACzghdpI7tU4XBv5FH9LX60QybOskHYs0Phkim2Aw==",
+      "dev": true
+    },
+    "snyk-policy": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.7.1.tgz",
+      "integrity": "sha1-5BO2vUr2BQxeX0RSh5CeTpigmyI=",
+      "dev": true,
+      "dependencies": {
+        "es6-promise": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+          "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+          "dev": true
+        }
+      }
+    },
+    "snyk-python-plugin": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.2.2.tgz",
+      "integrity": "sha512-JVQS77d7hRtsqGFPe7rVk8EsgqEPhBYJvtfLsRZzrLdk++T161rUVxXdYkWpUdrUO+uCUENqQX1ypi65Aj6tbg==",
+      "dev": true
+    },
+    "snyk-recursive-readdir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/snyk-recursive-readdir/-/snyk-recursive-readdir-2.0.0.tgz",
+      "integrity": "sha1-XLWelGmBaeAgWmDn1qUG0LTVL/M=",
+      "dev": true,
+      "dependencies": {
+        "minimatch": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz",
+          "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=",
+          "dev": true
+        }
+      }
+    },
+    "snyk-resolve": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.0.0.tgz",
+      "integrity": "sha1-u+kZbTf1fDklHmvnXM3Vsgl+maI=",
+      "dev": true
+    },
+    "snyk-resolve-deps": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-1.7.0.tgz",
+      "integrity": "sha1-E3Q6BYQ33/iQuq9DfDM8lmp0PLY=",
+      "dev": true,
+      "dependencies": {
+        "ansicolors": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
+          "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
+          "dev": true
+        },
+        "es6-promise": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+          "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+          "dev": true
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "snyk-sbt-plugin": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-1.0.2.tgz",
+      "integrity": "sha512-j+vVwqlrbMr6LSEM6sLOCykdOsRXhhJ23y8FdJHo22UfTVbRMCr6MFLrlMkNmsHcWhIFuzgqIdjkP5LMRdRktA==",
+      "dev": true
+    },
+    "snyk-tree": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/snyk-tree/-/snyk-tree-1.0.0.tgz",
+      "integrity": "sha1-D7cxdtvzLngvGRAClBYESPkRHMg=",
+      "dev": true
+    },
+    "snyk-try-require": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-1.2.0.tgz",
+      "integrity": "sha1-MPwrEcBwZFke41eAyCa+kTEvIUQ=",
+      "dev": true,
+      "dependencies": {
+        "es6-promise": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+          "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+          "dev": true
+        }
+      }
+    },
+    "source-map": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
+      "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
+      "dev": true
+    },
+    "source-map-support": {
+      "version": "0.4.15",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz",
+      "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=",
+      "dev": true
+    },
+    "spawn-sync": {
+      "version": "1.0.15",
+      "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
+      "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+      "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+      "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
+      "dev": true
+    },
+    "spdx-license-ids": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+      "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
+      "dev": true
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "sshpk": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+      "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+      "dev": true,
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+          "dev": true
+        }
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+      "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+      "dev": true
+    },
+    "string_decoder": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+      "dev": true
+    },
+    "string-length": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
+      "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=",
+      "dev": true
+    },
+    "string-width": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "dev": true
+    },
+    "string.prototype.codepointat": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz",
+      "integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg=",
+      "dev": true
+    },
+    "stringstream": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+      "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+      "dev": true
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "dev": true
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "strip-indent": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+      "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true
+    },
+    "strophe.js": {
+      "version": "1.2.14",
+      "resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.2.14.tgz",
+      "integrity": "sha1-fO7sUbMnLMXGxq53R0eApYQPGqc=",
+      "dev": true
+    },
+    "strophejs-plugin-disco": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/strophejs-plugin-disco/-/strophejs-plugin-disco-0.0.1.tgz",
+      "integrity": "sha1-JVoXdsZDFOnZ5OxS1XM+6vUWx/k=",
+      "dev": true
+    },
+    "strophejs-plugin-ping": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/strophejs-plugin-ping/-/strophejs-plugin-ping-0.0.1.tgz",
+      "integrity": "sha1-NXEmxTZZSwZmjhh4c4Ey+sNciJY=",
+      "dev": true
+    },
+    "strophejs-plugin-register": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/strophejs-plugin-register/-/strophejs-plugin-register-0.0.1.tgz",
+      "integrity": "sha1-0oXTPH1oK40co3ZHgJrCZ8CgiaM=",
+      "dev": true
+    },
+    "strophejs-plugin-rsm": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/strophejs-plugin-rsm/-/strophejs-plugin-rsm-0.0.1.tgz",
+      "integrity": "sha1-zBPqgHWmbJ2rdt04G37d/bG64Hs=",
+      "dev": true
+    },
+    "strophejs-plugin-vcard": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/strophejs-plugin-vcard/-/strophejs-plugin-vcard-0.0.1.tgz",
+      "integrity": "sha1-BDfoyYdZr5fKJ07GuAC1W52IciM=",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+      "dev": true
+    },
+    "table": {
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
+      "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
+          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true
+        }
+      }
+    },
+    "temp": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
+      "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=",
+      "dev": true,
+      "dependencies": {
+        "rimraf": {
+          "version": "2.2.8",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+          "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
+          "dev": true
+        }
+      }
+    },
+    "tempfile": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz",
+      "integrity": "sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I=",
+      "dev": true,
+      "dependencies": {
+        "uuid": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+          "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+          "dev": true
+        }
+      }
+    },
+    "term-size": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
+      "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
+      "dev": true
+    },
+    "text": {
+      "version": "github:requirejs/text#d04de4ffd7bf5ba6cb80cdca2d40d4f6f52a1b1f",
+      "dev": true
+    },
+    "text-encoding": {
+      "version": "0.6.4",
+      "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
+      "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
+      "dev": true
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "then-fs": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz",
+      "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=",
+      "dev": true
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "timed-out": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz",
+      "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=",
+      "dev": true
+    },
+    "tmp": {
+      "version": "0.0.29",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz",
+      "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=",
+      "dev": true
+    },
+    "to-fast-properties": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+      "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+      "dev": true
+    },
+    "tough-cookie": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+      "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+      "dev": true
+    },
+    "traverse": {
+      "version": "0.6.6",
+      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
+      "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=",
+      "dev": true
+    },
+    "trim-newlines": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "dev": true
+    },
+    "trim-right": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+      "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+      "dev": true
+    },
+    "tryit": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
+      "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
+      "dev": true
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "dev": true
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "dev": true,
+      "optional": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true
+    },
+    "type-detect": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz",
+      "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=",
+      "dev": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "uglify-es": {
+      "version": "3.0.24",
+      "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.0.24.tgz",
+      "integrity": "sha512-5BfuaLWF0idSXopD0iAK6osMvyo1jEyuQ8MtSpOdLvqUDjlfDDf18qEyz3M7athqorybUuAoKpKW1DhFIhxP2w==",
+      "dev": true,
+      "dependencies": {
+        "commander": {
+          "version": "2.9.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+          "dev": true
+        }
+      }
+    },
+    "undefsafe": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz",
+      "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=",
+      "dev": true
+    },
+    "underscore": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
+      "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
+      "dev": true
+    },
+    "underscore.string": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz",
+      "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=",
+      "dev": true
+    },
+    "union": {
+      "version": "0.4.6",
+      "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz",
+      "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=",
+      "dev": true,
+      "dependencies": {
+        "qs": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz",
+          "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=",
+          "dev": true
+        }
+      }
+    },
+    "unzip-response": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
+      "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=",
+      "dev": true
+    },
+    "update-notifier": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz",
+      "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=",
+      "dev": true,
+      "dependencies": {
+        "ansi-align": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz",
+          "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=",
+          "dev": true
+        },
+        "boxen": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz",
+          "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=",
+          "dev": true
+        },
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        }
+      }
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "dev": true,
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+          "dev": true
+        }
+      }
+    },
+    "url-join": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.2.tgz",
+      "integrity": "sha1-wHJ1aWetJLi1nldBVRyqx49QuLc=",
+      "dev": true
+    },
+    "url-parse-lax": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+      "dev": true
+    },
+    "user-home": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
+      "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
+      "dev": true
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
+    },
+    "uuid": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+      "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
+      "dev": true
+    },
+    "v8flags": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz",
+      "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=",
+      "dev": true
+    },
+    "valid-url": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
+      "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=",
+      "dev": true
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+      "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+      "dev": true
+    },
+    "validator": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
+      "integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g=",
+      "dev": true
+    },
+    "verror": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+      "dev": true
+    },
+    "wait-until-promise": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wait-until-promise/-/wait-until-promise-1.0.0.tgz",
+      "integrity": "sha1-03Uy1bfv9oJwIMtE2OyqIzyWeMU=",
+      "dev": true
+    },
+    "webworker-threads": {
+      "version": "0.4.13",
+      "resolved": "https://registry.npmjs.org/webworker-threads/-/webworker-threads-0.4.13.tgz",
+      "integrity": "sha1-1zvf0AIb9wkxy4XCTMP0zvvrH5g=",
+      "dependencies": {
+        "nan": {
+          "version": "0.8.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-0.8.0.tgz",
+          "integrity": "sha1-AiqPpen+hCCWSsH7PclOF/RJ9f0="
+        }
+      }
+    },
+    "which": {
+      "version": "1.2.14",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+      "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+      "dev": true
+    },
+    "which-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+      "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+      "dev": true
+    },
+    "wide-align": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
+      "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+      "dev": true
+    },
+    "widest-line": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
+      "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
+      "dev": true
+    },
+    "win-release": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz",
+      "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=",
+      "dev": true
+    },
+    "window-size": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
+      "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=",
+      "dev": true
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+      "dev": true
+    },
+    "wrap-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "write": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+      "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+      "dev": true
+    },
+    "write-file-atomic": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz",
+      "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=",
+      "dev": true
+    },
+    "xdg-basedir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz",
+      "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=",
+      "dev": true
+    },
+    "xss": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/xss/-/xss-0.3.3.tgz",
+      "integrity": "sha1-oBQ2De4QMXMx+edCWBQfftA/x4Q=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+      "dev": true
+    },
+    "xvfb": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/xvfb/-/xvfb-0.2.3.tgz",
+      "integrity": "sha1-VYipaHVFk5E/M8DA4su3N0EjWDI=",
+      "dev": true
+    },
+    "y18n": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+      "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+      "dev": true
+    },
+    "yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+      "dev": true
+    },
+    "yargs": {
+      "version": "3.15.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.15.0.tgz",
+      "integrity": "sha1-PZRG7yH7N5GzmFaQZi5LloPH8YE=",
+      "dev": true,
+      "dependencies": {
+        "camelcase": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+          "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
+          "dev": true
+        }
+      }
+    },
+    "yargs-parser": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
+      "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=",
+      "dev": true,
+      "dependencies": {
+        "camelcase": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+          "dev": true
+        }
+      }
+    }
+  }
+}

+ 8 - 3
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "converse.js",
   "name": "converse.js",
-  "version": "3.1.1",
+  "version": "3.2.0",
   "description": "Browser based XMPP instant messaging client",
   "description": "Browser based XMPP instant messaging client",
   "main": "main.js",
   "main": "main.js",
   "directories": {
   "directories": {
@@ -34,12 +34,16 @@
   "devDependencies": {
   "devDependencies": {
     "almond": "~0.3.3",
     "almond": "~0.3.3",
     "awesomplete-avoid-xss": "^1.1.2",
     "awesomplete-avoid-xss": "^1.1.2",
+    "babel-cli": "^6.18.0",
+    "babel-preset-env": "^1.5.2",
+    "babel-preset-latest": "^6.16.0",
     "backbone": "1.3.3",
     "backbone": "1.3.3",
     "backbone.browserStorage": "0.0.3",
     "backbone.browserStorage": "0.0.3",
     "backbone.overview": "0.0.3",
     "backbone.overview": "0.0.3",
     "bootstrap": "^3.3.7",
     "bootstrap": "^3.3.7",
     "bourbon": "^4.3.2",
     "bourbon": "^4.3.2",
     "clean-css-cli": "^4.0.10",
     "clean-css-cli": "^4.0.10",
+    "emojione": "^3.0.3",
     "es6-promise": "^4.1.0",
     "es6-promise": "^4.1.0",
     "eslint": "^3.19.0",
     "eslint": "^3.19.0",
     "eslint-plugin-lodash": "^2.3.3",
     "eslint-plugin-lodash": "^2.3.3",
@@ -53,7 +57,6 @@
     "jasmine-core": "2.6.4",
     "jasmine-core": "2.6.4",
     "jed": "0.5.4",
     "jed": "0.5.4",
     "jquery": "2.2.3",
     "jquery": "2.2.3",
-    "jquery-easing": "0.0.1",
     "jquery.browser": ">=0.1.0",
     "jquery.browser": ">=0.1.0",
     "jshint": "^2.9.4",
     "jshint": "^2.9.4",
     "lodash": "4.17.4",
     "lodash": "4.17.4",
@@ -74,7 +77,9 @@
     "strophejs-plugin-rsm": "0.0.1",
     "strophejs-plugin-rsm": "0.0.1",
     "strophejs-plugin-vcard": "0.0.1",
     "strophejs-plugin-vcard": "0.0.1",
     "text": "requirejs/text#2.0.15",
     "text": "requirejs/text#2.0.15",
-    "wait-until-promise": "^1.0.0"
+    "uglify-es": "^3.0.24",
+    "wait-until-promise": "^1.0.0",
+    "xss": "^0.3.3"
   },
   },
   "dependencies": {}
   "dependencies": {}
 }
 }

+ 103 - 78
sass/_chatbox.scss

@@ -100,10 +100,11 @@
         }
         }
         .chat-title {
         .chat-title {
             color: $chat-head-text-color;
             color: $chat-head-text-color;
-            line-height: 15px;
             display: block;
             display: block;
-            text-overflow: ellipsis;
+            line-height: 15px;
             overflow: hidden;
             overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
             a {
             a {
                 color: $chat-head-text-color;
                 color: $chat-head-text-color;
                 width: 100%;
                 width: 100%;
@@ -156,24 +157,25 @@
                 font-style: italic;
                 font-style: italic;
             }
             }
             .chat-message {
             .chat-message {
-                margin: 0.3em;
-                span {
-                    &.chat-msg-author {
-                        font-weight: bold;
-                        white-space: nowrap;
-                        float: left;
-                        text-overflow: ellipsis;
-                        overflow: hidden;
-                    }
-                    &.chat-msg-them {
-                        color: $message-them-color;
-                    }
-                    &.chat-msg-me {
-                        color: $link-color;
-                    }
-                    &.chat-msg-content {
-                        max-width: 100%;
-                        word-wrap: break-word;
+                overflow: auto; // Ensures that content stays inside
+                .chat-msg-author {
+                    font-weight: bold;
+                    white-space: nowrap;
+                    float: left;
+                    text-overflow: ellipsis;
+                    overflow: hidden;
+                }
+                .chat-msg-them {
+                    color: $message-them-color;
+                }
+                .chat-msg-me {
+                    color: $link-color;
+                }
+                .chat-msg-content {
+                    max-width: 100%;
+                    word-wrap: break-word;
+                    .emojione {
+                        margin-bottom: -6px;
                     }
                     }
                 }
                 }
             }
             }
@@ -272,11 +274,6 @@
                     text-decoration: none;
                     text-decoration: none;
                     text-shadow: none;
                     text-shadow: none;
                 }
                 }
-                .toolbar-picker-panel {
-                    a {
-                        color: $link-color;
-                    }
-                }
                 .chat-toolbar-text {
                 .chat-toolbar-text {
                     font-size: 12px;
                     font-size: 12px;
                     padding-right: 3px;
                     padding-right: 3px;
@@ -284,7 +281,7 @@
                 .unencrypted a,
                 .unencrypted a,
                 .unencrypted {
                 .unencrypted {
                     color: $text-color;
                     color: $text-color;
-                    .toolbar-picker-panel {
+                    .toolbar-menu {
                         a {
                         a {
                             color: $link-color;
                             color: $link-color;
                         }
                         }
@@ -301,73 +298,101 @@
                 .toggle-occupants,
                 .toggle-occupants,
                 .toggle-clear,
                 .toggle-clear,
                 .toggle-otr {
                 .toggle-otr {
+
                     float: right;
                     float: right;
                 }
                 }
                 li {
                 li {
+                    cursor: pointer;
                     display: inline-block;
                     display: inline-block;
                     list-style: none;
                     list-style: none;
-                    padding: 0 3px 0 3px;
-                    cursor: pointer;
                     margin-top: 1px;
                     margin-top: 1px;
-                }
-                li:hover {
-                    cursor: pointer;
-                }
-                ul {
-                    background: #fff;
-                    bottom: 100%;
-                    box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-                    display: none;
-                    font-size: 12px;
-                    margin: 0;
-                    position: absolute;
-                    right: 0;
-                    li {
+                    padding: 0 3px 0 3px;
+                    &:hover {
                         cursor: pointer;
                         cursor: pointer;
-                        list-style: none;
-                        position: relative;
-                        a:hover {
-                            color: #8f2831;
-                        }
                     }
                     }
-                }
-                li {
-                    margin-left: 0;
-                }
-                .toggle-smiley {
-                    color: $text-color;
-                    padding-left: 5px;
-                    ul {
-                        left: 0;
-                        li {
-                            font-size: $font-size;
-                            padding: 5px;
-                            z-index: 98;
+                    .toolbar-menu {
+                        background-color: #fff;
+                        bottom: 100%;
+                        box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
+                        font-size: 12px;
+                        margin: 0;
+                        position: absolute;
+                        right: 0;
+                        a {
+                            color: $link-color;
                         }
                         }
-                        li:hover {
-                            background-color: $highlight-color;
+                        ul {
+                            &.emoji-picker {
+                                height: $emoji-picker-height;
+                                overflow: scroll;
+                                padding: 0.5em;
+                            }
+                            &.emoji-toolbar {
+                                /* offset-x | offset-y | blur-radius | spread-radius | color */
+                                box-shadow: 0 -1px 2px 0 rgba(0, 0, 0, 0.4);
+                            }
+                            &.emoji-toolbar {
+                                overflow: hidden;
+                                left: 0;
+                                .picked {
+                                    background-color: $highlight-color;
+                                }
+                                li {
+                                    height: $emoji_height + 2*5px;
+                                    padding: 4px;
+                                    z-index: 98;
+                                    &.emoji {
+                                        a {
+                                            font-size: $font-size-huge;
+                                        }
+                                    }
+                                }
+                            }
+                            li {
+                                &.insert-emoji {
+                                    padding: 0.3em;
+                                    &:hover {
+                                        background-color: $highlight-color;
+                                    }
+                                }
+                                margin-left: 0;
+                                cursor: pointer;
+                                list-style: none;
+                                position: relative;
+                                a:hover {
+                                    color: #8f2831;
+                                }
+                            }
                         }
                         }
                     }
                     }
-                }
-                .toggle-otr {
-                    ul {
-                        li {
-                            padding: 7px;
-                            background-color: white;
-                            display: block;
+                    &.toggle-toolbar-menu {
+                        color: $text-color;
+                    }
+                    &.toggle-smiley {
+                        padding-left: 5px;
+                        .emoji-toolbar {
+                            .emoji-category-picker,
+                            .emoji-skintone-picker {
+                                li:hover {
+                                    background-color: $highlight-color;
+                                }
+                            }
+                        }
+                    }
+                    &.toggle-otr {
+                        ul {
                             z-index: 99;
                             z-index: 99;
-                            a {
-                                -moz-transition: background-color 0.2s ease-in-out;
-                                -webkit-transition: background-color 0.2s ease-in-out;
-                                transition: background-color 0.2s ease-in-out;
+                            li {
+                                &:hover {
+                                    background-color: $highlight-color;
+                                }
                                 display: block;
                                 display: block;
-                                padding: 1px;
-                                text-decoration: none;
+                                padding: 7px;
+                                a {
+                                    display: block;
+                                }
                             }
                             }
                         }
                         }
-                        li:hover {
-                            background-color: $highlight-color;
-                        }
                     }
                     }
                 }
                 }
             }
             }

+ 17 - 11
sass/_chatrooms.scss

@@ -19,17 +19,23 @@
                 color: $chatroom-head-color;
                 color: $chatroom-head-color;
             }
             }
         }
         }
-
-        .chatroom-description {
-            color: white;
-            font-size: 80%;
-            font-style: italic;
-            height: 1.3em;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-            margin: 0;
-            margin-top: 0.3em;
+        .chat-title {
+            color: $chatroom-color-lightest;
+            .chatroom-name {
+                color: white;
+            }
+            .chatroom-jid {
+                font-size: $font-size-small;
+            }
+            .chatroom-description {
+                color: white;
+                font-size: 80%;
+                font-style: italic;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                margin: 0.3em 0;
+            }
         }
         }
     }
     }
 
 

+ 17 - 11
sass/_controlbox.scss

@@ -152,7 +152,8 @@
                         padding-bottom: 0.5em;
                         padding-bottom: 0.5em;
                         text-shadow: 0 1px 0 $text-shadow-color;
                         text-shadow: 0 1px 0 $text-shadow-color;
                     }
                     }
-                    dd.available-chatroom {
+                    .available-chatroom,
+                    .open-chatroom {
                         border: none;
                         border: none;
                         clear: both;
                         clear: both;
                         color: $text-color;
                         color: $text-color;
@@ -165,6 +166,7 @@
                             color: $dark-link-color;
                             color: $dark-link-color;
                         }
                         }
                         &.unread-msgs {
                         &.unread-msgs {
+                            .available-room,
                             .open-room {
                             .open-room {
                                 max-width: 55%;
                                 max-width: 55%;
                                 width: auto;
                                 width: auto;
@@ -178,14 +180,18 @@
                                 }
                                 }
                             }
                             }
                             &.open-room {
                             &.open-room {
-                                float: left;
                                 width: 68%;
                                 width: 68%;
+                                float: left;
                                 overflow: hidden;
                                 overflow: hidden;
                                 text-overflow: ellipsis;
                                 text-overflow: ellipsis;
                                 white-space: nowrap;
                                 white-space: nowrap;
                                 padding-right: 0.5em;
                                 padding-right: 0.5em;
                             }
                             }
+                            &.available-room {
+                                width: 85%;
+                            }
                         }
                         }
+                        .add-bookmark,
                         .remove-bookmark {
                         .remove-bookmark {
                             &.button-on {
                             &.button-on {
                                 color: $link-color;
                                 color: $link-color;
@@ -236,7 +242,6 @@
                     position: absolute;
                     position: absolute;
                     left: 0;
                     left: 0;
                     top: 0;
                     top: 0;
-                    border: 1px solid $light-background-border-color;
                     width: 100%;
                     width: 100%;
                     z-index: 21;
                     z-index: 21;
                     background-color: $light-background-color;
                     background-color: $light-background-color;
@@ -246,14 +251,16 @@
                 }
                 }
             }
             }
 
 
-            /* Custom addition for CSP */
             dd.search-xmpp {
             dd.search-xmpp {
-                display: none;
-                width: 100%;
-            }
-
-            dd.search-xmpp ul {
-                box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4);
+                height: 0;
+                .contact-form-container {
+                    position: absolute;
+                    z-index: 22;
+                    form {
+                        box-shadow: 1px 4px 10px 1px rgba(0, 0, 0, 0.4);
+                        background-color: white;
+                    }
+                }
                 li:hover {
                 li:hover {
                     background-color: $light-background-color;
                     background-color: $light-background-color;
                 }
                 }
@@ -420,7 +427,6 @@
         }
         }
 
 
         .add-xmpp-contact {
         .add-xmpp-contact {
-            background: none;
             padding: 1em 0.5em;
             padding: 1em 0.5em;
             input {
             input {
                 margin: 0 0 1rem;
                 margin: 0 0 1rem;

+ 11 - 3
sass/_core.scss

@@ -52,9 +52,6 @@
         -webkit-touch-callout: none;
         -webkit-touch-callout: none;
         @include user-select(none);
         @include user-select(none);
     }
     }
-    .emoticon {
-        font-size: $font-size;
-    }
 
 
     @keyframes fadein {
     @keyframes fadein {
         0% { opacity: 0 }
         0% { opacity: 0 }
@@ -77,6 +74,10 @@
         opacity: 0;
         opacity: 0;
         display: none;
         display: none;
     }
     }
+    .collapsed {
+        height: 0;
+        overflow: hidden;
+    }
 
 
     .locked {
     .locked {
         padding-right: 22px;
         padding-right: 22px;
@@ -91,6 +92,10 @@
         }
         }
     }
     }
 
 
+    .emojione {
+        height: $emoji_height;
+    }
+
     .spinner {
     .spinner {
         @include animation(spin 2s infinite, linear);
         @include animation(spin 2s infinite, linear);
         display: block;
         display: block;
@@ -164,6 +169,9 @@
     .activated {
     .activated {
         display: block !important;
         display: block !important;
     }
     }
+    .pure-form-message {
+        padding: 0.5em 0;
+    }
     .pure-button {
     .pure-button {
         border-radius: $chatbox-border-radius;
         border-radius: $chatbox-border-radius;
     }
     }

+ 45 - 0
sass/converse/_chatbox.scss

@@ -12,4 +12,49 @@
             border-top-right-radius: 0;
             border-top-right-radius: 0;
         }
         }
     }
     }
+    .chatbox {
+        .chat-body {
+            .chat-message {
+                margin: 0.3em;
+                line-height: $line-height-large;
+                .chat-msg-author {
+                    line-height: $line-height-large;
+                }
+                .chat-msg-content {
+                    line-height: $line-height-large;
+                    .emojione {
+                        margin-bottom: -5px;
+                    }
+                }
+            }
+        }
+    }
+    .chatbox {
+        form.sendXMPPMessage {
+            .chat-toolbar {
+                li {
+                    .toolbar-menu {
+                        ul {
+                            &.emoji-toolbar {
+                                width: 100%;
+                                .emoji-category {
+                                    float: left;
+                                }
+                                li {
+                                    padding: 2px;
+                                }
+                            }
+                        }
+                    }
+                    &.toggle-smiley {
+                        ul {
+                            li {
+                                padding: 2px;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 }

+ 9 - 1
sass/converse/_variables.scss

@@ -42,7 +42,7 @@ $global-background-color: $light-blue !default;
 $inverse-link-color: white !default;
 $inverse-link-color: white !default;
 $link-shadow-color: #FAFAFA !default;
 $link-shadow-color: #FAFAFA !default;
 $text-shadow-color: #FAFAFA !default;
 $text-shadow-color: #FAFAFA !default;
-$text-color: #818479 !default;
+$text-color: #777 !default;
 $light-text-color: #A8ABA1 !default;
 $light-text-color: #A8ABA1 !default;
 $border-color: #CCC !default;
 $border-color: #CCC !default;
 $icon-color: $blue !default;
 $icon-color: $blue !default;
@@ -50,8 +50,11 @@ $save-button-color: $green !default;
 $chat-textarea-height: 70px !default;
 $chat-textarea-height: 70px !default;
 $send-button-height: 27px !default;
 $send-button-height: 27px !default;
 $send-button-margin: 3px !default;
 $send-button-margin: 3px !default;
+
 $message-them-color: $red !default;
 $message-them-color: $red !default;
 
 
+$emoji_height : 20px !default;
+
 $roster-height: 194px !default;
 $roster-height: 194px !default;
 $roster-item-height: 60px !default;
 $roster-item-height: 60px !default;
 
 
@@ -91,13 +94,17 @@ $font-size-tiny: 10px !default;
 $font-size-small: 12px !default;
 $font-size-small: 12px !default;
 $font-size: 14px !default;
 $font-size: 14px !default;
 $font-size-large: 16px !default;
 $font-size-large: 16px !default;
+$font-size-huge: 20px !default;
 $legend-font-size: 16px !default;
 $legend-font-size: 16px !default;
 
 
 $toolbar-height: 25px !default;
 $toolbar-height: 25px !default;
 $toolbar-color: $greenish-white !default;
 $toolbar-color: $greenish-white !default;
 
 
+$emoji-picker-height: 100px !default;
+
 $line-height-small:  14px !default;
 $line-height-small:  14px !default;
 $line-height:  16px !default;
 $line-height:  16px !default;
+$line-height-large:  20px !default;
 
 
 $controlbox-width: 200px !default;
 $controlbox-width: 200px !default;
 $chat-width: 200px !default;
 $chat-width: 200px !default;
@@ -114,6 +121,7 @@ $font-path: "../fonticons/fonts/" !default;
 $chatroom-width: 300px !default;
 $chatroom-width: 300px !default;
 $chatroom-head-color: $red !default;
 $chatroom-head-color: $red !default;
 $chatroom-color-light: $light-red !default;
 $chatroom-color-light: $light-red !default;
+$chatroom-color-lightest: $light-red !default;
 $chatroom-color-dark: $darkest-red !default;
 $chatroom-color-dark: $darkest-red !default;
 $chatroom-message-them-color: $green !default;
 $chatroom-message-them-color: $green !default;
 $chatroom-toolbar-color: $reddish-white !default;
 $chatroom-toolbar-color: $reddish-white !default;

+ 15 - 3
sass/inverse/_chatbox.scss

@@ -43,9 +43,15 @@
             border-top-right-radius: $chatbox-border-radius;
             border-top-right-radius: $chatbox-border-radius;
 
 
             .chat-message {
             .chat-message {
+                line-height: $line-height;
                 font-size: $font-size-small;
                 font-size: $font-size-small;
-                line-height: $line-height-small;
                 margin: 0.5em 0;
                 margin: 0.5em 0;
+                .chat-msg-author {
+                    line-height: $line-height;
+                }
+                .chat-msg-content {
+                    line-height: $line-height;
+                }
             }
             }
         }
         }
         .chat-content {
         .chat-content {
@@ -65,8 +71,14 @@
             .toggle-smiley {
             .toggle-smiley {
                 padding-left: 0.5em;
                 padding-left: 0.5em;
                 ul {
                 ul {
-                    li {
-                        padding: 0.5em;
+                    &.emoji-toolbar {
+                        .emoji-category-picker {
+                            margin-right: 5em;
+                        }
+                        .emoji-category {
+                            padding-left: 10px;
+                            padding-right: 10px;
+                        }
                     }
                     }
                 }
                 }
             }
             }

+ 4 - 3
sass/inverse/_chatrooms.scss

@@ -5,9 +5,10 @@
         .close-chatbox-button:before {
         .close-chatbox-button:before {
             content: "\e601"; // Leave icon
             content: "\e601"; // Leave icon
         }
         }
-        .chatroom-description {
-            font-size: 66%;
-            margin-top: 3px;
+        .chat-title {
+            .chatroom-description {
+                font-size: 65%;
+            }
         }
         }
     }
     }
 
 

+ 6 - 1
sass/inverse/_variables.scss

@@ -42,15 +42,19 @@ $global-background-color: $light-blue !default;
 $inverse-link-color: white !default;
 $inverse-link-color: white !default;
 $link-shadow-color: #FAFAFA !default;
 $link-shadow-color: #FAFAFA !default;
 $text-shadow-color: #FAFAFA !default;
 $text-shadow-color: #FAFAFA !default;
-$text-color: #818479 !default;
+$text-color: #777 !default;
 $light-text-color: #A8ABA1 !default;
 $light-text-color: #A8ABA1 !default;
 $border-color: #CCC !default;
 $border-color: #CCC !default;
 $icon-color: $blue !default;
 $icon-color: $blue !default;
 $save-button-color: $green !default;
 $save-button-color: $green !default;
 $send-button-height: 27px !default;
 $send-button-height: 27px !default;
 $send-button-margin: 3px !default;
 $send-button-margin: 3px !default;
+
 $message-them-color: $red !default;
 $message-them-color: $red !default;
 
 
+$emoji_height: 22px !default;
+$emoji-picker-height: 150px !default;
+
 $roster-height: 194px !default;
 $roster-height: 194px !default;
 $roster-item-height: 30px !default;
 $roster-item-height: 30px !default;
 
 
@@ -121,6 +125,7 @@ $chatroom-head-height: 62px !default;
 $chatroom-width: 300px !default;
 $chatroom-width: 300px !default;
 $chatroom-head-color: $red !default;
 $chatroom-head-color: $red !default;
 $chatroom-color-light: $light-red !default;
 $chatroom-color-light: $light-red !default;
+$chatroom-color-lightest: $light-red !default;
 $chatroom-color-dark: $darkest-red !default;
 $chatroom-color-dark: $darkest-red !default;
 $chatroom-message-them-color: $green !default;
 $chatroom-message-them-color: $green !default;
 $chatroom-toolbar-color: $reddish-white !default;
 $chatroom-toolbar-color: $reddish-white !default;

+ 56 - 23
spec/bookmarks.js

@@ -17,7 +17,9 @@
 
 
     describe("A chat room", function () {
     describe("A chat room", function () {
 
 
-        it("can be bookmarked", mock.initConverse(function (_converse) {
+        it("can be bookmarked", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
             var sent_stanza, IQ_id;
             var sent_stanza, IQ_id;
             var sendIQ = _converse.connection.sendIQ;
             var sendIQ = _converse.connection.sendIQ;
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -124,9 +126,12 @@
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             // We ignore this IQ stanza... (unless it's an error stanza), so
             // We ignore this IQ stanza... (unless it's an error stanza), so
             // nothing to test for here.
             // nothing to test for here.
+            done();
         }));
         }));
 
 
-        it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverse(function (_converse) {
+        it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
             var jid = 'lounge@localhost';
             var jid = 'lounge@localhost';
             _converse.bookmarks.create({
             _converse.bookmarks.create({
                 'jid': jid,
                 'jid': jid,
@@ -144,11 +149,14 @@
                 'nick': ' Othello'
                 'nick': ' Othello'
             });
             });
             expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
             expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
+            done();
         }));
         }));
 
 
         describe("when bookmarked", function () {
         describe("when bookmarked", function () {
 
 
-            it("displays that it's bookmarked through its bookmark icon", mock.initConverse(function (_converse) {
+            it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
                 test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
                 test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
                 var view = _converse.chatboxviews.get('lounge@localhost');
                 var view = _converse.chatboxviews.get('lounge@localhost');
                 var $bookmark_icon = view.$('.icon-pushpin');
                 var $bookmark_icon = view.$('.icon-pushpin');
@@ -157,9 +165,12 @@
                 expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
                 expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
                 view.model.set('bookmarked', false);
                 view.model.set('bookmarked', false);
                 expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
                 expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
+                done();
             }));
             }));
 
 
-            it("can be unbookmarked", mock.initConverse(function (_converse) {
+            it("can be unbookmarked", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
                 var sent_stanza, IQ_id;
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
                 test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
                 test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
@@ -216,6 +227,7 @@
                         "</pubsub>"+
                         "</pubsub>"+
                     "</iq>"
                     "</iq>"
                 );
                 );
+                done();
             }));
             }));
         });
         });
 
 
@@ -293,8 +305,9 @@
              */
              */
         }));
         }));
 
 
-        it("can be retrieved from the XMPP server",
-                mock.initConverseWithConnectionSpies(['send'], function (_converse) {
+        it("can be retrieved from the XMPP server", mock.initConverseWithPromises(
+            ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
+
             /* Client requests all items
             /* Client requests all items
              * -------------------------
              * -------------------------
              *
              *
@@ -306,7 +319,7 @@
              */
              */
             var IQ_id;
             var IQ_id;
             expect(_.filter(_converse.connection.send.calls.all(), function (call) {
             expect(_.filter(_converse.connection.send.calls.all(), function (call) {
-                var stanza = call.args[0]
+                var stanza = call.args[0];
                 if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                 if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                     return;
                     return;
                 }
                 }
@@ -366,14 +379,17 @@
             expect(_converse.bookmarks.models.length).toBe(2);
             expect(_converse.bookmarks.models.length).toBe(2);
             expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
             expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
             expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
             expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
+            done();
         }));
         }));
 
 
         describe("The rooms panel", function () {
         describe("The rooms panel", function () {
 
 
-            it("shows a list of bookmarks", mock.initConverseWithConnectionSpies(['send'], function (_converse) {
+            it("shows a list of bookmarks", mock.initConverseWithPromises(
+                ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
+
                 var IQ_id;
                 var IQ_id;
                 expect(_.filter(_converse.connection.send.calls.all(), function (call) {
                 expect(_.filter(_converse.connection.send.calls.all(), function (call) {
-                    var stanza = call.args[0]
+                    var stanza = call.args[0];
                     if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                     if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                         return;
                         return;
                     }
                     }
@@ -414,14 +430,21 @@
                                         'jid': 'another@conference.shakespeare.lit'
                                         'jid': 'another@conference.shakespeare.lit'
                                     }).c('nick').t('JC').up().up();
                                     }).c('nick').t('JC').up().up();
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                expect($('#chatrooms dl.bookmarks dd').length).toBe(3);
+
+                test_utils.waitUntil(function () {
+                    return $('#chatrooms dl.bookmarks dd').length;
+                }, 300).then(function () {
+                    expect($('#chatrooms dl.bookmarks dd').length).toBe(3);
+                    done();
+                });
             }));
             }));
 
 
-            it("remembers the toggle state of the bookmarks list",
-                    mock.initConverseWithConnectionSpies(['send'], function (_converse) {
+            it("remembers the toggle state of the bookmarks list", mock.initConverseWithPromises(
+                ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
+
                 var IQ_id;
                 var IQ_id;
                 expect(_.filter(_converse.connection.send.calls.all(), function (call) {
                 expect(_.filter(_converse.connection.send.calls.all(), function (call) {
-                    var stanza = call.args[0]
+                    var stanza = call.args[0];
                     if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                     if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
                         return;
                         return;
                     }
                     }
@@ -454,23 +477,32 @@
                     'nick': ''
                     'nick': ''
                 });
                 });
                 test_utils.openControlBox().openRoomsPanel(_converse);
                 test_utils.openControlBox().openRoomsPanel(_converse);
-                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
-                $('#chatrooms .bookmarks-toggle').click();
-                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(0);
-                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
-                $('#chatrooms .bookmarks-toggle').click();
-                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+
+                test_utils.waitUntil(function () {
+                    return $('#chatrooms dl.bookmarks dd:visible').length;
+                }, 300).then(function () {
+                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                    $('#chatrooms .bookmarks-toggle').click();
+                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(0);
+                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
+                    $('#chatrooms .bookmarks-toggle').click();
+                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                    done();
+                });
             }));
             }));
         });
         });
     });
     });
 
 
     describe("When hide_open_bookmarks is true and a bookmarked room is opened", function () {
     describe("When hide_open_bookmarks is true and a bookmarked room is opened", function () {
 
 
-        it("can be closed", mock.initConverse({ hide_open_bookmarks: true }, function (_converse) {
-            test_utils.openControlBox().openRoomsPanel(_converse);
+        it("can be closed", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'],
+            { hide_open_bookmarks: true },
+            function (done, _converse) {
 
 
+            test_utils.openControlBox().openRoomsPanel(_converse);
             // XXX Create bookmarks view here, otherwise we need to mock stanza
             // XXX Create bookmarks view here, otherwise we need to mock stanza
             // traffic for it to get created.
             // traffic for it to get created.
             _converse.bookmarksview = new _converse.BookmarksView(
             _converse.bookmarksview = new _converse.BookmarksView(
@@ -502,6 +534,7 @@
             view.close();
             view.close();
             room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
             room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
+            done();
         }));
         }));
     });
     });
 }));
 }));

File diff suppressed because it is too large
+ 334 - 169
spec/chatbox.js


File diff suppressed because it is too large
+ 524 - 435
spec/chatroom.js


+ 238 - 52
spec/controlbox.js

@@ -1,8 +1,7 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
-} (this, function (jasmine, mock, converse, test_utils) {
+    define(["jquery.noconflict", "jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function ($, jasmine, mock, converse, test_utils) {
     var _ = converse.env._;
     var _ = converse.env._;
-    var $ = converse.env.jQuery;
     var $pres = converse.env.$pres;
     var $pres = converse.env.$pres;
     var $msg = converse.env.$msg;
     var $msg = converse.env.$msg;
     var $iq = converse.env.$iq;
     var $iq = converse.env.$iq;
@@ -25,7 +24,11 @@
 
 
     describe("The Control Box", function () {
     describe("The Control Box", function () {
 
 
-        it("can be opened by clicking a DOM element with class 'toggle-controlbox'", mock.initConverse(function (_converse) {
+        it("can be opened by clicking a DOM element with class 'toggle-controlbox'",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             // This spec will only pass if the controlbox is not currently
             // This spec will only pass if the controlbox is not currently
             // open yet.
             // open yet.
             expect($("div#controlbox").is(':visible')).toBe(false);
             expect($("div#controlbox").is(':visible')).toBe(false);
@@ -39,18 +42,28 @@
             expect(_converse.controlboxtoggle.showControlBox).toHaveBeenCalled();
             expect(_converse.controlboxtoggle.showControlBox).toHaveBeenCalled();
             expect(_converse.emit).toHaveBeenCalledWith('controlBoxOpened', jasmine.any(Object));
             expect(_converse.emit).toHaveBeenCalledWith('controlBoxOpened', jasmine.any(Object));
             expect($("div#controlbox").is(':visible')).toBe(true);
             expect($("div#controlbox").is(':visible')).toBe(true);
+            done();
         }));
         }));
 
 
         describe("The Status Widget", function () {
         describe("The Status Widget", function () {
 
 
-            it("shows the user's chat status, which is online by default", mock.initConverse(function (_converse) {
+            it("shows the user's chat status, which is online by default",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 var view = _converse.xmppstatusview;
                 var view = _converse.xmppstatusview;
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am online');
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am online');
+                done();
             }));
             }));
 
 
-            it("can be used to set the current user's chat status", mock.initConverse(function (_converse) {
+            it("can be used to set the current user's chat status",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 var view = _converse.xmppstatusview;
                 var view = _converse.xmppstatusview;
                 spyOn(view, 'toggleOptions').and.callThrough();
                 spyOn(view, 'toggleOptions').and.callThrough();
@@ -68,9 +81,14 @@
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(false);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(false);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('dnd')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('dnd')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am busy');
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am busy');
+                done();
             }));
             }));
 
 
-            it("can be used to set a custom status message", mock.initConverse(function (_converse) {
+            it("can be used to set a custom status message",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 var view = _converse.xmppstatusview;
                 var view = _converse.xmppstatusview;
                 _converse.xmppstatus.save({'status': 'online'});
                 _converse.xmppstatus.save({'status': 'online'});
@@ -87,6 +105,7 @@
                 expect(_converse.emit).toHaveBeenCalledWith('statusMessageChanged', msg);
                 expect(_converse.emit).toHaveBeenCalledWith('statusMessageChanged', msg);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe(msg);
                 expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe(msg);
+                done();
             }));
             }));
         });
         });
     });
     });
@@ -95,7 +114,11 @@
 
 
         describe("The live filter", function () {
         describe("The live filter", function () {
 
 
-            it("will only appear when roster contacts flow over the visible area", mock.initConverseWithAsync(function (done, _converse) {
+            it("will only appear when roster contacts flow over the visible area",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var $filter = _converse.rosterview.$('.roster-filter');
                 var $filter = _converse.rosterview.$('.roster-filter');
                 var names = mock.cur_names;
                 var names = mock.cur_names;
                 test_utils.openControlBox();
                 test_utils.openControlBox();
@@ -120,13 +143,17 @@
                         } else {
                         } else {
                             return !$filter.is(':visible');
                             return !$filter.is(':visible');
                         }
                         }
-                }).then(function () {
-                    done();
-                });
+                    }).then(function () {
+                        done();
+                    });
                 });
                 });
             }));
             }));
 
 
-            it("can be used to filter the contacts shown", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be used to filter the contacts shown",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var $filter;
                 var $filter;
                 var $roster;
                 var $roster;
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
@@ -180,7 +207,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be used to filter the groups shown", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be used to filter the groups shown",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var $filter;
                 var $filter;
                 var $roster;
                 var $roster;
                 var $type;
                 var $type;
@@ -226,7 +257,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("has a button with which its contents can be cleared", mock.initConverseWithAsync(function (done, _converse) {
+            it("has a button with which its contents can be cleared",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 test_utils.createGroupedContacts(_converse);
                 test_utils.createGroupedContacts(_converse);
@@ -250,7 +285,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be used to filter contacts by their chat state", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be used to filter contacts by their chat state",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var $filter;
                 var $filter;
                 var $roster;
                 var $roster;
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
@@ -287,7 +326,11 @@
 
 
         describe("A Roster Group", function () {
         describe("A Roster Group", function () {
 
 
-            it("can be used to organize existing contacts", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be used to organize existing contacts",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
                 spyOn(_converse, 'emit');
                 spyOn(_converse, 'emit');
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
@@ -321,7 +364,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can share contacts with other roster groups", mock.initConverseWithAsync(function (done, _converse) {
+            it("can share contacts with other roster groups", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
                 var groups = ['colleagues', 'friends'];
                 var groups = ['colleagues', 'friends'];
                 spyOn(_converse, 'emit');
                 spyOn(_converse, 'emit');
@@ -351,7 +398,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("remembers whether it is closed or opened", mock.initConverse(function (_converse) {
+            it("remembers whether it is closed or opened",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.roster_groups = true;
                 _converse.roster_groups = true;
                 var i=0, j=0;
                 var i=0, j=0;
                 var groups = {
                 var groups = {
@@ -378,6 +429,7 @@
                 expect(view.model.get('state')).toBe('closed');
                 expect(view.model.get('state')).toBe('closed');
                 $toggle.click();
                 $toggle.click();
                 expect(view.model.get('state')).toBe('opened');
                 expect(view.model.get('state')).toBe('opened');
+                done();
             }));
             }));
         });
         });
 
 
@@ -388,7 +440,11 @@
                 test_utils.createContacts(_converse, 'pending').openControlBox().openContactsPanel(_converse);
                 test_utils.createContacts(_converse, 'pending').openControlBox().openContactsPanel(_converse);
             }
             }
 
 
-            it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be collapsed under their own header", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dd').length;
                         return _converse.rosterview.$el.find('dd').length;
@@ -399,7 +455,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be added to the roster", mock.initConverse(function (_converse) {
+            it("can be added to the roster",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 spyOn(_converse, 'emit');
                 spyOn(_converse, 'emit');
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 test_utils.openControlBox();
                 test_utils.openControlBox();
@@ -410,9 +470,14 @@
                     fullname: mock.pend_names[0]
                     fullname: mock.pend_names[0]
                 });
                 });
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.update).toHaveBeenCalled();
+                done();
             }));
             }));
 
 
-            it("are shown in the roster when show_only_online_users", mock.initConverseWithAsync(function (done, _converse) {
+            it("are shown in the roster when show_only_online_users", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.show_only_online_users = true;
                 _converse.show_only_online_users = true;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 _addContacts(_converse);
                 _addContacts(_converse);
@@ -428,7 +493,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("are shown in the roster when hide_offline_users", mock.initConverseWithAsync(function (done, _converse) {
+            it("are shown in the roster when hide_offline_users", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.hide_offline_users = true;
                 _converse.hide_offline_users = true;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 _addContacts(_converse);
                 _addContacts(_converse);
@@ -444,7 +513,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be removed by the user", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be removed by the user", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 var name = mock.pend_names[0];
                 var name = mock.pend_names[0];
                 var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
                 var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
@@ -474,7 +547,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
+            it("do not have a header if there aren't any", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 var name = mock.pend_names[0];
                 var name = mock.pend_names[0];
                 _converse.roster.create({
                 _converse.roster.create({
@@ -500,7 +577,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("will lose their own header once the last one has been removed", mock.initConverse(function (_converse) {
+            it("is shown when a new private message is received",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 var name;
                 var name;
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
@@ -510,9 +591,14 @@
                         .parent().siblings('.remove-xmpp-contact').click();
                         .parent().siblings('.remove-xmpp-contact').click();
                 }
                 }
                 expect(_converse.rosterview.$el.find('dt#pending-xmpp-contacts').is(':visible')).toBeFalsy();
                 expect(_converse.rosterview.$el.find('dt#pending-xmpp-contacts').is(':visible')).toBeFalsy();
+                done();
             }));
             }));
 
 
-            it("can be added to the roster and they will be sorted alphabetically", mock.initConverse(function (_converse) {
+            it("can be added to the roster and they will be sorted alphabetically",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var i, t;
                 var i, t;
                 spyOn(_converse, 'emit');
                 spyOn(_converse, 'emit');
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
@@ -530,6 +616,7 @@
                     return result + _.trim(value.textContent);
                     return result + _.trim(value.textContent);
                 }, '');
                 }, '');
                 expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
                 expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
+                done();
             }));
             }));
         });
         });
 
 
@@ -538,7 +625,11 @@
                 test_utils.createContacts(_converse, 'current').openControlBox().openContactsPanel(_converse);
                 test_utils.createContacts(_converse, 'current').openControlBox().openContactsPanel(_converse);
             };
             };
 
 
-            it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be collapsed under their own header", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dd:visible').length;
                         return _converse.rosterview.$el.find('dd:visible').length;
@@ -549,7 +640,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("will be hidden when appearing under a collapsed group", mock.initConverseWithAsync(function (done, _converse) {
+            it("will be hidden when appearing under a collapsed group", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _converse.roster_groups = false;
                 _converse.roster_groups = false;
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
@@ -572,7 +667,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be added to the roster and they will be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be added to the roster and they will be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 for (var i=0; i<mock.cur_names.length; i++) {
                 for (var i=0; i<mock.cur_names.length; i++) {
                     _converse.roster.create({
                     _converse.roster.create({
@@ -595,7 +694,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be removed by the user", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be removed by the user", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dd').length;
                         return _converse.rosterview.$el.find('dd').length;
@@ -620,7 +723,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
+            it("do not have a header if there aren't any", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var name = mock.cur_names[0];
                 var name = mock.cur_names[0];
                 var contact;
                 var contact;
                 contact = _converse.roster.create({
                 contact = _converse.roster.create({
@@ -650,7 +757,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can change their status to online and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can change their status to online and be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -674,7 +785,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can change their status to busy and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can change their status to busy and be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -698,7 +813,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can change their status to away and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can change their status to away and be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -722,7 +841,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can change their status to xa and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can change their status to xa and be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -746,7 +869,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can change their status to unavailable and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
+            it("can change their status to unavailable and be sorted alphabetically", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -770,7 +897,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("are ordered according to status: online, busy, away, xa, unavailable, offline", mock.initConverseWithAsync(function (done, _converse) {
+            it("are ordered according to status: online, busy, away, xa, unavailable, offline", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 _addContacts(_converse);
                 _addContacts(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -860,7 +991,11 @@
 
 
         describe("Requesting Contacts", function () {
         describe("Requesting Contacts", function () {
 
 
-            it("can be added to the roster and they will be sorted alphabetically", mock.initConverse(function (_converse) {
+            it("can be added to the roster and they will be sorted alphabetically",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 var i, children;
                 var i, children;
                 var names = [];
                 var names = [];
                 var addName = function (idx, item) {
                 var addName = function (idx, item) {
@@ -887,9 +1022,14 @@
                 names = [];
                 names = [];
                 children.each(addName);
                 children.each(addName);
                 expect(names.join('')).toEqual(mock.req_names.slice(0,mock.req_names.length+1).sort().join(''));
                 expect(names.join('')).toEqual(mock.req_names.slice(0,mock.req_names.length+1).sort().join(''));
+                done();
             }));
             }));
 
 
-            it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
+            it("do not have a header if there aren't any", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openContactsPanel(_converse);
                 test_utils.openContactsPanel(_converse);
                 var name = mock.req_names[0];
                 var name = mock.req_names[0];
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
@@ -914,7 +1054,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
+            it("can be collapsed under their own header", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -925,7 +1069,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can have their requests accepted by the user", mock.initConverseWithAsync(function (done, _converse) {
+            it("can have their requests accepted by the user", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -950,7 +1098,11 @@
                 });
                 });
             }));
             }));
 
 
-            it("can have their requests denied by the user", mock.initConverseWithAsync(function (done, _converse) {
+            it("can have their requests denied by the user", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                         return _converse.rosterview.$el.find('dt').length;
                         return _converse.rosterview.$el.find('dt').length;
@@ -973,7 +1125,9 @@
                 });
                 });
             }));
             }));
 
 
-            it("are persisted even if other contacts' change their presence ", mock.initConverse(function (_converse) {
+            it("are persisted even if other contacts' change their presence ", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
                 /* This is a regression test.
                 /* This is a regression test.
                  * https://github.com/jcbrand/_converse.js/issues/262
                  * https://github.com/jcbrand/_converse.js/issues/262
                  */
                  */
@@ -1009,12 +1163,17 @@
                 }).c('group').t('Friends');
                 }).c('group').t('Friends');
                 _converse.roster.onReceivedFromServer(stanza.tree());
                 _converse.roster.onReceivedFromServer(stanza.tree());
                 expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
                 expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
+                done();
             }));
             }));
         });
         });
 
 
         describe("All Contacts", function () {
         describe("All Contacts", function () {
 
 
-            it("are saved to, and can be retrieved from browserStorage", mock.initConverse(function (_converse) {
+            it("are saved to, and can be retrieved from browserStorage",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.openContactsPanel(_converse);
                 test_utils.openContactsPanel(_converse);
                 var new_attrs, old_attrs, attrs;
                 var new_attrs, old_attrs, attrs;
@@ -1036,9 +1195,14 @@
                     // comparison
                     // comparison
                     expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
                     expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
                 }
                 }
+                done();
             }));
             }));
 
 
-            it("will show fullname and jid properties on tooltip", mock.initConverseWithAsync(function (done, _converse) {
+            it("will show fullname and jid properties on tooltip", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.openContactsPanel(_converse);
                 test_utils.openContactsPanel(_converse);
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
@@ -1064,7 +1228,11 @@
 
 
     describe("The 'Add Contact' widget", function () {
     describe("The 'Add Contact' widget", function () {
 
 
-        it("opens up an add form when you click on it", mock.initConverse(function (_converse) {
+        it("opens up an add form when you click on it",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             var panel = _converse.chatboxviews.get('controlbox').contactspanel;
             var panel = _converse.chatboxviews.get('controlbox').contactspanel;
             spyOn(panel, 'toggleContactForm').and.callThrough();
             spyOn(panel, 'toggleContactForm').and.callThrough();
             panel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
             panel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@@ -1072,9 +1240,14 @@
             expect(panel.toggleContactForm).toHaveBeenCalled();
             expect(panel.toggleContactForm).toHaveBeenCalled();
             // XXX: Awaiting more tests, close it again for now...
             // XXX: Awaiting more tests, close it again for now...
             panel.$el.find('a.toggle-xmpp-contact-form').click();
             panel.$el.find('a.toggle-xmpp-contact-form').click();
+            done();
         }));
         }));
 
 
-        it("can be used to add contact and it checks for case-sensivity", mock.initConverseWithAsync(function (done, _converse) {
+        it("can be used to add contact and it checks for case-sensivity", 
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             spyOn(_converse, 'emit');
             spyOn(_converse, 'emit');
             spyOn(_converse.rosterview, 'update').and.callThrough();
             spyOn(_converse.rosterview, 'update').and.callThrough();
             test_utils.openControlBox();
             test_utils.openControlBox();
@@ -1101,12 +1274,15 @@
                 done();
                 done();
             });
             });
         }));
         }));
-
     });
     });
 
 
     describe("The Controlbox Tabs", function () {
     describe("The Controlbox Tabs", function () {
 
 
-        it("contains two tabs, 'Contacts' and 'ChatRooms'", mock.initConverse(function (_converse) {
+        it("contains two tabs, 'Contacts' and 'ChatRooms'",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.openControlBox();
             test_utils.openControlBox();
             var cbview = _converse.chatboxviews.get('controlbox');
             var cbview = _converse.chatboxviews.get('controlbox');
             var $panels = cbview.$el.find('.controlbox-panes');
             var $panels = cbview.$el.find('.controlbox-panes');
@@ -1115,9 +1291,14 @@
             expect($panels.children().first().is(':visible')).toBe(true);
             expect($panels.children().first().is(':visible')).toBe(true);
             expect($panels.children().last().attr('id')).toBe('chatrooms');
             expect($panels.children().last().attr('id')).toBe('chatrooms');
             expect($panels.children().last().is(':visible')).toBe(false);
             expect($panels.children().last().is(':visible')).toBe(false);
+            done();
         }));
         }));
 
 
-        it("remembers which tab was open last", mock.initConverse(function (_converse) {
+        it("remembers which tab was open last",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.openControlBox();
             test_utils.openControlBox();
             var cbview = _converse.chatboxviews.get('controlbox');
             var cbview = _converse.chatboxviews.get('controlbox');
             var $tabs = cbview.$el.find('#controlbox-tabs');
             var $tabs = cbview.$el.find('#controlbox-tabs');
@@ -1126,11 +1307,16 @@
             expect(cbview.model.get('active-panel')).toBe('chatrooms');
             expect(cbview.model.get('active-panel')).toBe('chatrooms');
             $tabs.find('li').first().find('a').click();
             $tabs.find('li').first().find('a').click();
             expect(cbview.model.get('active-panel')).toBe('users');
             expect(cbview.model.get('active-panel')).toBe('users');
+            done();
         }));
         }));
 
 
         describe("The \"Contacts\" Panel", function () {
         describe("The \"Contacts\" Panel", function () {
 
 
-            it("shows the number of unread mentions received", mock.initConverse(function (_converse) {
+            it("shows the number of unread mentions received",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.openContactsPanel(_converse);
                 test_utils.openContactsPanel(_converse);
 
 
@@ -1168,8 +1354,8 @@
                 chatview.model.set({'minimized': false});
                 chatview.model.set({'minimized': false});
                 expect(_.includes(contacts_panel.tab_el.firstChild.classList, 'unread-msgs')).toBeFalsy();
                 expect(_.includes(contacts_panel.tab_el.firstChild.classList, 'unread-msgs')).toBeFalsy();
                 expect(_.isNull(contacts_panel.tab_el.querySelector('.msgs-indicator'))).toBeTruthy();
                 expect(_.isNull(contacts_panel.tab_el.querySelector('.msgs-indicator'))).toBeTruthy();
+                done();
             }));
             }));
-
         });
         });
     });
     });
 }));
 }));

+ 39 - 29
spec/converse.js

@@ -7,7 +7,6 @@
 } (this, function (jasmine, converse, mock, test_utils) {
 } (this, function (jasmine, converse, mock, test_utils) {
     var b64_sha1 = converse.env.b64_sha1;
     var b64_sha1 = converse.env.b64_sha1;
     var _ = converse.env._;
     var _ = converse.env._;
-    var $ = converse.env.jQuery;
 
 
     describe("Converse", function() {
     describe("Converse", function() {
         
         
@@ -54,7 +53,11 @@
 
 
         describe("A chat state indication", function () {
         describe("A chat state indication", function () {
 
 
-            it("are sent out when the client becomes or stops being idle", mock.initConverse(function (_converse) {
+            it("are sent out when the client becomes or stops being idle",
+                mock.initConverseWithPromises(
+                    null, ['discoInitialized'], {},
+                    function (done, _converse) {
+
                 spyOn(_converse, 'sendCSI').and.callThrough();
                 spyOn(_converse, 'sendCSI').and.callThrough();
                 var sent_stanza;
                 var sent_stanza;
                 spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
                 spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
@@ -62,7 +65,7 @@
                 });
                 });
                 var i = 0;
                 var i = 0;
                 _converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
                 _converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
-                _converse.features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
+                _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
 
 
                 _converse.csi_waiting_time = 3; // The relevant config option
                 _converse.csi_waiting_time = 3; // The relevant config option
                 while (i <= _converse.csi_waiting_time) {
                 while (i <= _converse.csi_waiting_time) {
@@ -79,10 +82,10 @@
                 expect(sent_stanza.toLocaleString()).toBe(
                 expect(sent_stanza.toLocaleString()).toBe(
                     "<active xmlns='urn:xmpp:csi:0'/>"
                     "<active xmlns='urn:xmpp:csi:0'/>"
                 );
                 );
-
                 // Reset values
                 // Reset values
                 _converse.csi_waiting_time = 0;
                 _converse.csi_waiting_time = 0;
-                _converse.features['urn:xmpp:csi:0'] = false;
+                _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = false;
+                done();
             }));
             }));
         });
         });
 
 
@@ -272,32 +275,38 @@
 
 
         describe("The \"chats\" API", function() {
         describe("The \"chats\" API", function() {
 
 
-            it("has a method 'get' which returns a wrapped chat box", mock.initConverse(function (_converse) {
-                test_utils.createContacts(_converse, 'current');
-                // Test on chat that doesn't exist.
-                expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
-                var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                // Test on chat that's not open
-                var box = _converse.api.chats.get(jid);
-                expect(typeof box === 'undefined').toBeTruthy();
-                var chatboxview = _converse.chatboxviews.get(jid);
-                // Test for single JID
-                test_utils.openChatBoxFor(_converse, jid);
-                box = _converse.api.chats.get(jid);
-                expect(box instanceof Object).toBeTruthy();
-                expect(box.model.get('box_id')).toBe(b64_sha1(jid));
-                chatboxview = _converse.chatboxviews.get(jid);
-                expect(chatboxview.$el.is(':visible')).toBeTruthy();
-                // Test for multiple JIDs
-                var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-                test_utils.openChatBoxFor(_converse, jid2);
-                var list = _converse.api.chats.get([jid, jid2]);
-                expect(_.isArray(list)).toBeTruthy();
-                expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
-                expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
+            it("has a method 'get' which returns a wrapped chat box", mock.initConverseWithPromises(
+                null, ['rosterInitialized'], {}, function (done, _converse) {
+                    test_utils.openControlBox();
+                    test_utils.createContacts(_converse, 'current');
+                    // Test on chat that doesn't exist.
+                    expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
+                    var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                    // Test on chat that's not open
+                    var box = _converse.api.chats.get(jid);
+                    expect(typeof box === 'undefined').toBeTruthy();
+                    var chatboxview = _converse.chatboxviews.get(jid);
+                    // Test for single JID
+                    test_utils.openChatBoxFor(_converse, jid);
+                    box = _converse.api.chats.get(jid);
+                    expect(box instanceof Object).toBeTruthy();
+                    expect(box.model.get('box_id')).toBe(b64_sha1(jid));
+                    chatboxview = _converse.chatboxviews.get(jid);
+                    expect(chatboxview.$el.is(':visible')).toBeTruthy();
+                    // Test for multiple JIDs
+                    var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
+                    test_utils.openChatBoxFor(_converse, jid2);
+                    var list = _converse.api.chats.get([jid, jid2]);
+                    expect(_.isArray(list)).toBeTruthy();
+                    expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
+                    expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
+                    done();
             }));
             }));
 
 
-            it("has a method 'open' which opens and returns a wrapped chat box", mock.initConverse(function (_converse) {
+            it("has a method 'open' which opens and returns a wrapped chat box", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
+                test_utils.openControlBox();
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
                 var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 var chatboxview;
                 var chatboxview;
@@ -318,6 +327,7 @@
                 expect(_.isArray(list)).toBeTruthy();
                 expect(_.isArray(list)).toBeTruthy();
                 expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
                 expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
                 expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
                 expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
+                done();
             }));
             }));
         });
         });
 
 

+ 166 - 2
spec/disco.js

@@ -8,14 +8,178 @@
 } (this, function (jasmine, $, converse, mock, test_utils) {
 } (this, function (jasmine, $, converse, mock, test_utils) {
     "use strict";
     "use strict";
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
+    var $iq = converse.env.$iq;
+    var _ = converse.env._;
 
 
     describe("Service Discovery", function () {
     describe("Service Discovery", function () {
+
+        describe("Whenever converse.js queries a server for its features", function () {
+            it("stores the features it receives", mock.initConverseWithAsync(function (done, _converse) {
+                var IQ_stanzas = _converse.connection.IQ_stanzas;
+                var IQ_ids =  _converse.connection.IQ_ids;
+                test_utils.waitUntil(function () {
+                    return _.filter(IQ_stanzas, function (iq) {
+                        return iq.nodeTree.querySelector('query[xmlns="http://jabber.org/protocol/disco#info"]');
+                    }).length > 0;
+                }, 300).then(function () {
+                    /* <iq type='result'
+                     *      from='plays.shakespeare.lit'
+                     *      to='romeo@montague.net/orchard'
+                     *      id='info1'>
+                     *  <query xmlns='http://jabber.org/protocol/disco#info'>
+                     *      <identity
+                     *          category='server'
+                     *          type='im'/>
+                     *      <identity
+                     *          category='conference'
+                     *          type='text'
+                     *          name='Play-Specific Chatrooms'/>
+                     *      <identity
+                     *          category='directory'
+                     *          type='chatroom'
+                     *          name='Play-Specific Chatrooms'/>
+                     *      <feature var='http://jabber.org/protocol/disco#info'/>
+                     *      <feature var='http://jabber.org/protocol/disco#items'/>
+                     *      <feature var='http://jabber.org/protocol/muc'/>
+                     *      <feature var='jabber:iq:register'/>
+                     *      <feature var='jabber:iq:search'/>
+                     *      <feature var='jabber:iq:time'/>
+                     *      <feature var='jabber:iq:version'/>
+                     *  </query>
+                     *  </iq>
+                     */
+                    var info_IQ_id = IQ_ids[0];
+                    var stanza = $iq({
+                        'type': 'result',
+                        'from': 'localhost',
+                        'to': 'dummy@localhost/resource',
+                        'id': info_IQ_id
+                    }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
+                        .c('identity', {
+                            'category': 'server',
+                            'type': 'im'}).up()
+                        .c('identity', {
+                            'category': 'conference',
+                            'type': 'text',
+                            'name': 'Play-Specific Chatrooms'}).up()
+                        .c('identity', {
+                            'category': 'directory',
+                            'type': 'chatroom',
+                            'name': 'Play-Specific Chatrooms'}).up()
+                        .c('feature', {
+                            'var': 'http://jabber.org/protocol/disco#info'}).up()
+                        .c('feature', {
+                            'var': 'http://jabber.org/protocol/disco#items'}).up()
+                        .c('feature', {
+                            'var': 'jabber:iq:register'}).up()
+                        .c('feature', {
+                            'var': 'jabber:iq:time'}).up()
+                        .c('feature', {
+                            'var': 'jabber:iq:version'});
+                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                    var entities = _converse.disco_entities;
+                    expect(entities.length).toBe(1);
+                    expect(entities.get(_converse.domain).features.length).toBe(5);
+                    expect(entities.get(_converse.domain).identities.length).toBe(3);
+                    expect(entities.get('localhost').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
+                    expect(entities.get('localhost').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
+                    expect(entities.get('localhost').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
+                    expect(entities.get('localhost').features.where(
+                        {'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
+                    expect(entities.get('localhost').features.where(
+                        {'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
+
+
+                test_utils.waitUntil(function () {
+                    // Converse.js sees that the entity has a disco#items feature,
+                    // so it will make a query for it.
+                    return _.filter(IQ_stanzas, function (iq) {
+                        return iq.nodeTree.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]');
+                    }).length > 0;
+                }, 300).then(function () {
+                    /* <iq type='result'
+                     *     from='catalog.shakespeare.lit'
+                     *     to='romeo@montague.net/orchard'
+                     *     id='items2'>
+                     * <query xmlns='http://jabber.org/protocol/disco#items'>
+                     *     <item jid='people.shakespeare.lit'
+                     *         name='Directory of Characters'/>
+                     *     <item jid='plays.shakespeare.lit'
+                     *         name='Play-Specific Chatrooms'/>
+                     *     <item jid='mim.shakespeare.lit'
+                     *         name='Gateway to Marlowe IM'/>
+                     *     <item jid='words.shakespeare.lit'
+                     *         name='Shakespearean Lexicon'/>
+                     *
+                     *     <item jid='catalog.shakespeare.lit'
+                     *         node='books'
+                     *         name='Books by and about Shakespeare'/>
+                     *     <item jid='catalog.shakespeare.lit'
+                     *         node='clothing'
+                     *         name='Wear your literary taste with pride'/>
+                     *     <item jid='catalog.shakespeare.lit'
+                     *         node='music'
+                     *         name='Music from the time of Shakespeare'/>
+                     * </query>
+                     * </iq>
+                     */
+                   var items_IQ_id = IQ_ids.pop();
+                   stanza = $iq({
+                       'type': 'result',
+                       'from': 'localhost',
+                       'to': 'dummy@localhost/resource',
+                       'id': items_IQ_id
+                   }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
+                       .c('item', {
+                           'jid': 'people.shakespeare.lit',
+                           'name': 'Directory of Characters'}).up()
+                       .c('item', {
+                           'jid': 'plays.shakespeare.lit',
+                           'name': 'Play-Specific Chatrooms'}).up()
+                       .c('item', {
+                           'jid': 'words.shakespeare.lit',
+                           'name': 'Gateway to Marlowe IM'}).up()
+                       .c('item', {
+                           'jid': 'localhost',
+                           'name': 'Shakespearean Lexicon'}).up()
+
+                       .c('item', {
+                           'jid': 'localhost',
+                           'node': 'books',
+                           'name': 'Books by and about Shakespeare'}).up()
+                       .c('item', {
+                           'node': 'localhost',
+                           'name': 'Wear your literary taste with pride'}).up()
+                       .c('item', {
+                           'jid': 'localhost',
+                           'node': 'music',
+                           'name': 'Music from the time of Shakespeare'
+                       });
+                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                    entities = _converse.disco_entities;
+                    expect(entities.length).toBe(4);
+                    expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
+                    expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
+                    done();
+                });
+                });
+            }));
+        });
+
         describe("Whenever converse.js discovers a new server feature", function () {
         describe("Whenever converse.js discovers a new server feature", function () {
-           it("emits the serviceDiscovered event", mock.initConverse(function (_converse) {
+           it("emits the serviceDiscovered event",
+                mock.initConverseWithPromises(
+                    null, ['discoInitialized'], {},
+                    function (done, _converse) {
+
                 sinon.spy(_converse, 'emit');
                 sinon.spy(_converse, 'emit');
-                _converse.features.create({'var': Strophe.NS.MAM});
+                _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 expect(_converse.emit.called).toBe(true);
                 expect(_converse.emit.called).toBe(true);
+                expect(_converse.emit.args[0][0]).toBe('serviceDiscovered');
                 expect(_converse.emit.args[0][1].get('var')).toBe(Strophe.NS.MAM);
                 expect(_converse.emit.args[0][1].get('var')).toBe(Strophe.NS.MAM);
+                done();
             }));
             }));
         });
         });
     });
     });

+ 7 - 2
spec/headline.js

@@ -40,7 +40,9 @@
             utils.isHeadlineMessage.restore();
             utils.isHeadlineMessage.restore();
         }));
         }));
 
 
-        it("will open and display headline messages", mock.initConverse(function (_converse) {
+        it("will open and display headline messages", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
             /* <message from='notify.example.com'
             /* <message from='notify.example.com'
              *          to='romeo@im.example.com'
              *          to='romeo@im.example.com'
              *          type='headline'
              *          type='headline'
@@ -74,9 +76,12 @@
             expect(utils.isHeadlineMessage.called).toBeTruthy();
             expect(utils.isHeadlineMessage.called).toBeTruthy();
             expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
             expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
             utils.isHeadlineMessage.restore(); // unwraps
             utils.isHeadlineMessage.restore(); // unwraps
+            done();
         }));
         }));
 
 
-        it("will not show a headline messages from a full JID if allow_non_roster_messaging is false", mock.initConverse(function (_converse) {
+        it("will not show a headline messages from a full JID if allow_non_roster_messaging is false",
+            mock.initConverse(function (_converse) {
+
             _converse.allow_non_roster_messaging = false;
             _converse.allow_non_roster_messaging = false;
             sinon.spy(utils, 'isHeadlineMessage');
             sinon.spy(utils, 'isHeadlineMessage');
             var stanza = $msg({
             var stanza = $msg({

+ 34 - 29
spec/mam.js

@@ -1,9 +1,9 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
-} (this, function (jasmine, mock, converse, test_utils) {
+    define(["jquery.noconflict", "jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function ($, jasmine, mock, converse, test_utils) {
     "use strict";
     "use strict";
     var _ = converse.env._;
     var _ = converse.env._;
-    var $ = converse.env.jQuery;
+    var Backbone = converse.env.Backbone;
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
     var $iq = converse.env.$iq;
     var $iq = converse.env.$iq;
     var $msg = converse.env.$msg;
     var $msg = converse.env.$msg;
@@ -15,20 +15,25 @@
 
 
         describe("The archive.query API", function () {
         describe("The archive.query API", function () {
 
 
-           it("can be used to query for all archived messages", mock.initConverse(function (_converse) {
+           it("can be used to query for all archived messages",
+                mock.initConverseWithPromises(
+                    null, ['discoInitialized'], {},
+                    function (done, _converse) {
+
                 var sent_stanza, IQ_id;
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 _converse.api.archive.query();
                 _converse.api.archive.query();
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'><query xmlns='urn:xmpp:mam:2' queryid='"+queryid+"'/></iq>");
                     "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'><query xmlns='urn:xmpp:mam:2' queryid='"+queryid+"'/></iq>");
+                done();
             }));
             }));
 
 
            it("can be used to query for all messages to/from a particular JID", mock.initConverse(function (_converse) {
            it("can be used to query for all messages to/from a particular JID", mock.initConverse(function (_converse) {
@@ -38,8 +43,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 _converse.api.archive.query({'with':'juliet@capulet.lit'});
                 _converse.api.archive.query({'with':'juliet@capulet.lit'});
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
@@ -66,8 +71,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var start = '2010-06-07T00:00:00Z';
                 var start = '2010-06-07T00:00:00Z';
                 var end = '2010-07-07T13:23:54Z';
                 var end = '2010-07-07T13:23:54Z';
@@ -97,8 +102,8 @@
            }));
            }));
 
 
            it("throws a TypeError if an invalid date is provided", mock.initConverse(function (_converse) {
            it("throws a TypeError if an invalid date is provided", mock.initConverse(function (_converse) {
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 expect(_.partial(_converse.api.archive.query, {'start': 'not a real date'})).toThrow(
                 expect(_.partial(_converse.api.archive.query, {'start': 'not a real date'})).toThrow(
                     new TypeError('archive.query: invalid date provided for: start')
                     new TypeError('archive.query: invalid date provided for: start')
@@ -112,8 +117,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var start = '2010-06-07T00:00:00Z';
                 var start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query({'start': start});
                 _converse.api.archive.query({'start': start});
@@ -141,8 +146,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var start = '2010-06-07T00:00:00Z';
                 var start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query({'start': start, 'max':10});
                 _converse.api.archive.query({'start': start, 'max':10});
@@ -173,8 +178,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var start = '2010-06-07T00:00:00Z';
                 var start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query({
                 _converse.api.archive.query({
@@ -210,8 +215,8 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 _converse.api.archive.query({'before': '', 'max':10});
                 _converse.api.archive.query({'before': '', 'max':10});
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
                 var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
@@ -237,8 +242,8 @@
                 // and pass it in. However, in the callback method an RSM object is
                 // and pass it in. However, in the callback method an RSM object is
                 // returned which can be reused for easy paging. This test is
                 // returned which can be reused for easy paging. This test is
                 // more for that usecase.
                 // more for that usecase.
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var sent_stanza, IQ_id;
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
@@ -247,7 +252,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 var rsm =  new Strophe.RSM({'max': '10'});
                 var rsm =  new Strophe.RSM({'max': '10'});
-                rsm['with'] = 'romeo@montague.lit';
+                rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
                 rsm.start = '2010-06-07T00:00:00Z';
                 rsm.start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query(rsm);
                 _converse.api.archive.query(rsm);
 
 
@@ -275,8 +280,8 @@
            }));
            }));
 
 
            it("accepts a callback function, which it passes the messages and a Strophe.RSM object", mock.initConverse(function (_converse) {
            it("accepts a callback function, which it passes the messages and a Strophe.RSM object", mock.initConverse(function (_converse) {
-                if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) {
-                    _converse.features.create({'var': Strophe.NS.MAM});
+                if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
+                    _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
                 }
                 }
                 var sent_stanza, IQ_id;
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
@@ -351,7 +356,7 @@
                 expect(args[0].length).toBe(2);
                 expect(args[0].length).toBe(2);
                 expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML);
                 expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML);
                 expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML);
                 expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML);
-                expect(args[1]['with']).toBe('romeo@capulet.lit');
+                expect(args[1]['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
                 expect(args[1].max).toBe('10');
                 expect(args[1].max).toBe('10');
                 expect(args[1].count).toBe('16');
                 expect(args[1].count).toBe('16');
                 expect(args[1].first).toBe('23452-4534-1');
                 expect(args[1].first).toBe('23452-4534-1');
@@ -372,11 +377,11 @@
                 spyOn(_converse, 'onMAMPreferences').and.callThrough();
                 spyOn(_converse, 'onMAMPreferences').and.callThrough();
                 _converse.message_archiving = 'never';
                 _converse.message_archiving = 'never';
 
 
-                var feature = new _converse.Feature({
+                var feature = new Backbone.Model({
                     'var': Strophe.NS.MAM
                     'var': Strophe.NS.MAM
                 });
                 });
                 spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
                 spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
-                _converse.features.onFeatureAdded(feature);
+                _converse.disco_entities.get(_converse.domain).onFeatureAdded(feature);
 
 
                 expect(_converse.connection.sendIQ).toHaveBeenCalled();
                 expect(_converse.connection.sendIQ).toHaveBeenCalled();
                 expect(sent_stanza.toLocaleString()).toBe(
                 expect(sent_stanza.toLocaleString()).toBe(
@@ -430,7 +435,7 @@
                     .c('never').up();
                     .c('never').up();
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 expect(feature.save).toHaveBeenCalled();
                 expect(feature.save).toHaveBeenCalled();
-                expect(feature.get('preferences')['default']).toBe('never');
+                expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
 
 
                 // Restore
                 // Restore
                 _converse.message_archiving = 'never';
                 _converse.message_archiving = 'never';

+ 41 - 20
spec/minchats.js

@@ -6,7 +6,11 @@
 
 
     describe("The Minimized Chats Widget", function () {
     describe("The Minimized Chats Widget", function () {
 
 
-        it("shows chats that have been minimized",  mock.initConverse(function (_converse) {
+        it("shows chats that have been minimized",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.createContacts(_converse, 'current');
             test_utils.createContacts(_converse, 'current');
             test_utils.openControlBox();
             test_utils.openControlBox();
             test_utils.openContactsPanel(_converse);
             test_utils.openContactsPanel(_converse);
@@ -34,9 +38,14 @@
             expect(_converse.minimized_chats.$el.is(':visible')).toBeTruthy();
             expect(_converse.minimized_chats.$el.is(':visible')).toBeTruthy();
             expect(_converse.minimized_chats.keys().length).toBe(2);
             expect(_converse.minimized_chats.keys().length).toBe(2);
             expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
             expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
+            done();
         }));
         }));
 
 
-        it("can be toggled to hide or show minimized chats", mock.initConverse(function (_converse) {
+        it("can be toggled to hide or show minimized chats",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.createContacts(_converse, 'current');
             test_utils.createContacts(_converse, 'current');
             test_utils.openControlBox();
             test_utils.openControlBox();
             test_utils.openContactsPanel(_converse);
             test_utils.openContactsPanel(_converse);
@@ -56,9 +65,14 @@
             _converse.minimized_chats.$('#toggle-minimized-chats').click();
             _converse.minimized_chats.$('#toggle-minimized-chats').click();
             expect(_converse.minimized_chats.$('.minimized-chats-flyout').is(':visible')).toBeFalsy();
             expect(_converse.minimized_chats.$('.minimized-chats-flyout').is(':visible')).toBeFalsy();
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
+            done();
         }));
         }));
 
 
-        it("shows the number messages received to minimized chats", mock.initConverse(function (_converse) {
+        it("shows the number messages received to minimized chats",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.createContacts(_converse, 'current');
             test_utils.createContacts(_converse, 'current');
             test_utils.openControlBox();
             test_utils.openControlBox();
             test_utils.openContactsPanel(_converse);
             test_utils.openContactsPanel(_converse);
@@ -120,28 +134,35 @@
                 id: (new Date()).getTime()
                 id: (new Date()).getTime()
             }).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
             }).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
             expect(_converse.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
             expect(_converse.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
+            done();
         }));
         }));
 
 
-        it("shows the number messages received to minimized groupchats", mock.initConverse(function (_converse) {
+        it("shows the number messages received to minimized groupchats",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             var room_jid = 'kitchen@conference.shakespeare.lit';
             var room_jid = 'kitchen@conference.shakespeare.lit';
             test_utils.openAndEnterChatRoom(
             test_utils.openAndEnterChatRoom(
-                _converse, 'kitchen', 'conference.shakespeare.lit', 'fires');
-            var view = _converse.chatboxviews.get(room_jid);
-            view.model.set({'minimized': true});
+                _converse, 'kitchen', 'conference.shakespeare.lit', 'fires').then(function () {
+                var view = _converse.chatboxviews.get(room_jid);
+                view.model.set({'minimized': true});
 
 
-            var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
-            var message = 'fires: Your attention is required';
-            var nick = mock.chatroom_names[0],
-                msg = $msg({
-                    from: room_jid+'/'+nick,
-                    id: (new Date()).getTime(),
-                    to: 'dummy@localhost',
-                    type: 'groupchat'
-                }).c('body').t(message).tree();
-            view.handleMUCMessage(msg);
-
-            expect(_converse.minimized_chats.toggleview.$('.unread-message-count').is(':visible')).toBeTruthy();
-            expect(_converse.minimized_chats.toggleview.$('.unread-message-count').text()).toBe('1');
+                var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
+                var message = 'fires: Your attention is required';
+                var nick = mock.chatroom_names[0],
+                    msg = $msg({
+                        from: room_jid+'/'+nick,
+                        id: (new Date()).getTime(),
+                        to: 'dummy@localhost',
+                        type: 'groupchat'
+                    }).c('body').t(message).tree();
+                view.handleMUCMessage(msg);
+
+                expect(_converse.minimized_chats.toggleview.$('.unread-message-count').is(':visible')).toBeTruthy();
+                expect(_converse.minimized_chats.toggleview.$('.unread-message-count').text()).toBe('1');
+                done();
+            });
         }));
         }));
     });
     });
 }));
 }));

+ 90 - 68
spec/notification.js

@@ -12,7 +12,11 @@
             describe("And the desktop is not focused", function () {
             describe("And the desktop is not focused", function () {
                 describe("an HTML5 Notification", function () {
                 describe("an HTML5 Notification", function () {
 
 
-                    it("is shown when a new private message is received", mock.initConverse(function (_converse) {
+                    it("is shown when a new private message is received",
+                        mock.initConverseWithPromises(
+                            null, ['rosterGroupsFetched'], {},
+                            function (done, _converse) {
+
                         // TODO: not yet testing show_desktop_notifications setting
                         // TODO: not yet testing show_desktop_notifications setting
                         test_utils.createContacts(_converse, 'current');
                         test_utils.createContacts(_converse, 'current');
                         spyOn(_converse, 'showMessageNotification');
                         spyOn(_converse, 'showMessageNotification');
@@ -30,42 +34,53 @@
                         _converse.chatboxes.onMessage(msg); // This will emit 'message'
                         _converse.chatboxes.onMessage(msg); // This will emit 'message'
                         expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                         expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
+                        done();
                     }));
                     }));
 
 
-                    it("is shown when you are mentioned in a chat room", mock.initConverse(function (_converse) {
+                    it("is shown when you are mentioned in a chat room",
+                        mock.initConverseWithPromises(
+                            null, ['rosterGroupsFetched'], {},
+                            function (done, _converse) {
+
                         test_utils.createContacts(_converse, 'current');
                         test_utils.createContacts(_converse, 'current');
-                        test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
-                        var view = _converse.chatboxviews.get('lounge@localhost');
-                        if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
-                        var no_notification = false;
-                        if (typeof window.Notification === 'undefined') {
-                            no_notification = true;
-                            window.Notification = function () {
-                                return {
-                                    'close': function () {}
+                        test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
+                            var view = _converse.chatboxviews.get('lounge@localhost');
+                            if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
+                            var no_notification = false;
+                            if (typeof window.Notification === 'undefined') {
+                                no_notification = true;
+                                window.Notification = function () {
+                                    return {
+                                        'close': function () {}
+                                    };
                                 };
                                 };
-                            };
-                        }
-                        spyOn(_converse, 'showMessageNotification').and.callThrough();
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
-                        
-                        var message = 'dummy: This message will show a desktop notification';
-                        var nick = mock.chatroom_names[0],
-                            msg = $msg({
-                                from: 'lounge@localhost/'+nick,
-                                id: (new Date()).getTime(),
-                                to: 'dummy@localhost',
-                                type: 'groupchat'
-                            }).c('body').t(message).tree();
-                        _converse.chatboxes.onMessage(msg); // This will emit 'message'
-                        expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
-                        expect(_converse.showMessageNotification).toHaveBeenCalled();
-                        if (no_notification) {
-                            delete window.Notification;
-                        }
+                            }
+                            spyOn(_converse, 'showMessageNotification').and.callThrough();
+                            spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
+                            
+                            var message = 'dummy: This message will show a desktop notification';
+                            var nick = mock.chatroom_names[0],
+                                msg = $msg({
+                                    from: 'lounge@localhost/'+nick,
+                                    id: (new Date()).getTime(),
+                                    to: 'dummy@localhost',
+                                    type: 'groupchat'
+                                }).c('body').t(message).tree();
+                            _converse.chatboxes.onMessage(msg); // This will emit 'message'
+                            expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
+                            expect(_converse.showMessageNotification).toHaveBeenCalled();
+                            if (no_notification) {
+                                delete window.Notification;
+                            }
+                            done();
+                        });
                     }));
                     }));
 
 
-                    it("is shown for headline messages", mock.initConverse(function (_converse) {
+                    it("is shown for headline messages",
+                        mock.initConverseWithPromises(
+                            null, ['rosterGroupsFetched'], {},
+                            function (done, _converse) {
+
                         spyOn(_converse, 'showMessageNotification').and.callThrough();
                         spyOn(_converse, 'showMessageNotification').and.callThrough();
                         spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                         spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                         var stanza = $msg({
                         var stanza = $msg({
@@ -84,6 +99,7 @@
                                 'notify.example.com')
                                 'notify.example.com')
                             ).toBeTruthy();
                             ).toBeTruthy();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
+                        done();
                     }));
                     }));
 
 
                     it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse(function (_converse) {
                     it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse(function (_converse) {
@@ -137,44 +153,50 @@
         describe("When play_sounds is set to true", function () {
         describe("When play_sounds is set to true", function () {
             describe("A notification sound", function () {
             describe("A notification sound", function () {
 
 
-                it("is played when the current user is mentioned in a chat room", mock.initConverse(function (_converse) {
+                it("is played when the current user is mentioned in a chat room",
+                    mock.initConverseWithPromises(
+                        null, ['rosterGroupsFetched'], {},
+                        function (done, _converse) {
+
                     test_utils.createContacts(_converse, 'current');
                     test_utils.createContacts(_converse, 'current');
-                    test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
-                    _converse.play_sounds = true;
-                    spyOn(_converse, 'playSoundNotification');
-                    var view = _converse.chatboxviews.get('lounge@localhost');
-                    if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
-                    var text = 'This message will play a sound because it mentions dummy';
-                    var message = $msg({
-                        from: 'lounge@localhost/otheruser',
-                        id: '1',
-                        to: 'dummy@localhost',
-                        type: 'groupchat'
-                    }).c('body').t(text);
-                    view.onChatRoomMessage(message.nodeTree);
-                    expect(_converse.playSoundNotification).toHaveBeenCalled();
-
-                    text = "This message won't play a sound";
-                    message = $msg({
-                        from: 'lounge@localhost/otheruser',
-                        id: '2',
-                        to: 'dummy@localhost',
-                        type: 'groupchat'
-                    }).c('body').t(text);
-                    view.onChatRoomMessage(message.nodeTree);
-                    expect(_converse.playSoundNotification, 1);
-                    _converse.play_sounds = false;
-
-                    text = "This message won't play a sound because it is sent by dummy";
-                    message = $msg({
-                        from: 'lounge@localhost/dummy',
-                        id: '3',
-                        to: 'dummy@localhost',
-                        type: 'groupchat'
-                    }).c('body').t(text);
-                    view.onChatRoomMessage(message.nodeTree);
-                    expect(_converse.playSoundNotification, 1);
-                    _converse.play_sounds = false;
+                    test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
+                        _converse.play_sounds = true;
+                        spyOn(_converse, 'playSoundNotification');
+                        var view = _converse.chatboxviews.get('lounge@localhost');
+                        if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
+                        var text = 'This message will play a sound because it mentions dummy';
+                        var message = $msg({
+                            from: 'lounge@localhost/otheruser',
+                            id: '1',
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t(text);
+                        view.onChatRoomMessage(message.nodeTree);
+                        expect(_converse.playSoundNotification).toHaveBeenCalled();
+
+                        text = "This message won't play a sound";
+                        message = $msg({
+                            from: 'lounge@localhost/otheruser',
+                            id: '2',
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t(text);
+                        view.onChatRoomMessage(message.nodeTree);
+                        expect(_converse.playSoundNotification, 1);
+                        _converse.play_sounds = false;
+
+                        text = "This message won't play a sound because it is sent by dummy";
+                        message = $msg({
+                            from: 'lounge@localhost/dummy',
+                            id: '3',
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t(text);
+                        view.onChatRoomMessage(message.nodeTree);
+                        expect(_converse.playSoundNotification, 1);
+                        _converse.play_sounds = false;
+                        done();
+                    });
                 }));
                 }));
             });
             });
         });
         });

+ 14 - 5
spec/otr.js

@@ -1,13 +1,16 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
-} (this, function (jasmine, mock, converse, test_utils) {
-    var $ = converse.env.jQuery;
+    define(["jquery.noconflict", "jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function ($, jasmine, mock, converse, test_utils) {
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
     var b64_sha1 = converse.env.b64_sha1;
     var b64_sha1 = converse.env.b64_sha1;
 
 
     return describe("The OTR module", function() {
     return describe("The OTR module", function() {
 
 
-        it("will add processing hints to sent out encrypted <message> stanzas", mock.initConverse(function (_converse) {
+        it("will add processing hints to sent out encrypted <message> stanzas",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.openControlBox();
             test_utils.openControlBox();
             test_utils.openContactsPanel(_converse);
             test_utils.openContactsPanel(_converse);
             test_utils.createContacts(_converse, 'current');
             test_utils.createContacts(_converse, 'current');
@@ -25,11 +28,16 @@
             expect($hints.get(1).tagName).toBe('no-permanent-store');
             expect($hints.get(1).tagName).toBe('no-permanent-store');
             expect($hints.get(2).tagName).toBe('no-copy');
             expect($hints.get(2).tagName).toBe('no-copy');
             chatview.model.set('otr_status', UNENCRYPTED); // Reset again to UNENCRYPTED
             chatview.model.set('otr_status', UNENCRYPTED); // Reset again to UNENCRYPTED
+            done();
         }));
         }));
 
 
         describe("An OTR Chat Message", function () {
         describe("An OTR Chat Message", function () {
 
 
-            it("will not be carbon copied when it's sent out", mock.initConverse(function (_converse) {
+            it("will not be carbon copied when it's sent out",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 test_utils.openContactsPanel(_converse);
                 test_utils.openContactsPanel(_converse);
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
@@ -46,6 +54,7 @@
                 expect($sent.find('private').length).toBe(1);
                 expect($sent.find('private').length).toBe(1);
                 expect($sent.find('private').attr('xmlns')).toBe('urn:xmpp:carbons:2');
                 expect($sent.find('private').attr('xmlns')).toBe('urn:xmpp:carbons:2');
                 chatbox.set('otr_status', 0); // Reset again to UNENCRYPTED
                 chatbox.set('otr_status', 0); // Reset again to UNENCRYPTED
+                done();
             }));
             }));
         });
         });
     });
     });

+ 12 - 2
spec/ping.js

@@ -6,20 +6,30 @@
     describe("XMPP Ping", function () {
     describe("XMPP Ping", function () {
         describe("Ping and pong handlers", function () {
         describe("Ping and pong handlers", function () {
 
 
-            it("are registered when _converse.js is connected", mock.initConverse(function (_converse) {
+            it("are registered when _converse.js is connected",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 spyOn(_converse, 'registerPingHandler').and.callThrough();
                 spyOn(_converse, 'registerPingHandler').and.callThrough();
                 spyOn(_converse, 'registerPongHandler').and.callThrough();
                 spyOn(_converse, 'registerPongHandler').and.callThrough();
                 _converse.emit('connected');
                 _converse.emit('connected');
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
+                done();
             }));
             }));
 
 
-            it("are registered when _converse.js reconnected", mock.initConverse(function (_converse) {
+            it("are registered when _converse.js reconnected",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 spyOn(_converse, 'registerPingHandler').and.callThrough();
                 spyOn(_converse, 'registerPingHandler').and.callThrough();
                 spyOn(_converse, 'registerPongHandler').and.callThrough();
                 spyOn(_converse, 'registerPongHandler').and.callThrough();
                 _converse.emit('reconnected');
                 _converse.emit('reconnected');
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
+                done();
             }));
             }));
         });
         });
 
 

+ 6 - 1
spec/presence.js

@@ -49,7 +49,11 @@
 
 
     describe("A received presence stanza", function () {
     describe("A received presence stanza", function () {
 
 
-        it("has its priority taken into account", mock.initConverse(function (_converse) {
+        it("has its priority taken into account",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
             test_utils.openControlBox();
             test_utils.openControlBox();
             test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
             test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
             var contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@localhost';
             var contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@localhost';
@@ -218,6 +222,7 @@
             _converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
             _converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
             expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('offline');
             expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('offline');
             expect(_.keys(contact.get('resources')).length).toBe(0);
             expect(_.keys(contact.get('resources')).length).toBe(0);
+            done();
         }));
         }));
     });
     });
 }));
 }));

+ 50 - 15
spec/protocol.js

@@ -10,6 +10,7 @@
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
     var $iq = converse.env.$iq;
     var $iq = converse.env.$iq;
     var $pres = converse.env.$pres;
     var $pres = converse.env.$pres;
+    var _ = converse.env._;
     // See:
     // See:
     // https://xmpp.org/rfcs/rfc3921.html
     // https://xmpp.org/rfcs/rfc3921.html
 
 
@@ -47,11 +48,15 @@
              * that session. A client MUST acknowledge each roster push with an IQ
              * that session. A client MUST acknowledge each roster push with an IQ
              * stanza of type "result".
              * stanza of type "result".
              */
              */
-            it("Subscribe to contact, contact accepts and subscribes back", mock.initConverseWithAsync(function (done, _converse) {
+            it("Subscribe to contact, contact accepts and subscribes back",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'],
+                    { roster_groups: false },
+                    function (done, _converse) {
+
                 /* The process by which a user subscribes to a contact, including
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 * the interaction between roster items and subscription states.
                 */
                 */
-                _converse.roster_groups = false;
                 var contact, stanza, sent_stanza, IQ_id;
                 var contact, stanza, sent_stanza, IQ_id;
                 test_utils.openControlBox(_converse);
                 test_utils.openControlBox(_converse);
                 var panel = _converse.chatboxviews.get('controlbox').contactspanel;
                 var panel = _converse.chatboxviews.get('controlbox').contactspanel;
@@ -68,15 +73,20 @@
                 panel.delegateEvents(); // Rebind all events so that our spy gets called
                 panel.delegateEvents(); // Rebind all events so that our spy gets called
 
 
                 /* Add a new contact through the UI */
                 /* Add a new contact through the UI */
-                var $form = panel.$('form.add-xmpp-contact');
-                expect($form.is(":visible")).toBeFalsy();
+                var form = panel.el.querySelector('form.add-xmpp-contact');
+                expect(_.isNull(form)).toBeTruthy();
+
                 // Click the "Add a contact" link.
                 // Click the "Add a contact" link.
                 panel.$('.toggle-xmpp-contact-form').click();
                 panel.$('.toggle-xmpp-contact-form').click();
-                // Check that the $form appears
-                expect($form.is(":visible")).toBeTruthy();
+
+                // Check that the form appears
+                form = panel.el.querySelector('form.add-xmpp-contact');
+                expect(form.parentElement.offsetHeight).not.toBe(0);
+                expect(_.includes(form.parentElement.classList, 'collapsed')).toBeFalsy();
+
                 // Fill in the form and submit
                 // Fill in the form and submit
-                $form.find('input').val('contact@example.org');
-                $form.submit();
+                $(form).find('input').val('contact@example.org');
+                $(form).submit();
 
 
                 /* In preparation for being able to render the contact in the
                 /* In preparation for being able to render the contact in the
                 * user's client interface and for the server to keep track of the
                 * user's client interface and for the server to keep track of the
@@ -86,8 +96,11 @@
                 expect(panel.addContactFromForm).toHaveBeenCalled();
                 expect(panel.addContactFromForm).toHaveBeenCalled();
                 expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
                 expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
                 expect(_converse.roster.addContact).toHaveBeenCalled();
                 expect(_converse.roster.addContact).toHaveBeenCalled();
-                // The form should not be visible anymore.
-                expect($form.is(":visible")).toBeFalsy();
+
+                // The form should not be visible anymore (by virtue of its
+                // parent being collapsed)
+                expect(form.parentElement.offsetHeight).toBe(0);
+                expect(_.includes(form.parentElement.classList, 'collapsed')).toBeTrue;
 
 
                 /* _converse request consists of sending an IQ
                 /* _converse request consists of sending an IQ
                 * stanza of type='set' containing a <query/> element qualified by
                 * stanza of type='set' containing a <query/> element qualified by
@@ -134,8 +147,10 @@
                 * </iq>
                 * </iq>
                 */
                 */
                 var create = _converse.roster.create;
                 var create = _converse.roster.create;
+                var sent_stanzas = [];
                 spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
                 spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
                     sent_stanza = stanza;
                     sent_stanza = stanza;
+                    sent_stanzas.push(stanza.toLocaleString());
                 });
                 });
                 spyOn(_converse.roster, 'create').and.callFake(function () {
                 spyOn(_converse.roster, 'create').and.callFake(function () {
                     contact = create.apply(_converse.roster, arguments);
                     contact = create.apply(_converse.roster, arguments);
@@ -165,6 +180,11 @@
                 *
                 *
                 *  <presence to='contact@example.org' type='subscribe'/>
                 *  <presence to='contact@example.org' type='subscribe'/>
                 */
                 */
+
+                test_utils.waitUntil(function () {
+                    return sent_stanzas.length == 1;
+                }, 300).then(function () {
+
                 expect(contact.subscribe).toHaveBeenCalled();
                 expect(contact.subscribe).toHaveBeenCalled();
                 expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
                 expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
                     "<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'>"+
                     "<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'>"+
@@ -204,7 +224,8 @@
                 // contact in the roster.
                 // contact in the roster.
                 test_utils.waitUntil(function () {
                 test_utils.waitUntil(function () {
                     return $('a:contains("Pending contacts")').length;
                     return $('a:contains("Pending contacts")').length;
-                }).then(function () {
+                }, 300).then(function () {
+
                     var $header = $('a:contains("Pending contacts")');
                     var $header = $('a:contains("Pending contacts")');
                     expect($header.length).toBe(1);
                     expect($header.length).toBe(1);
                     expect($header.is(":visible")).toBeTruthy();
                     expect($header.is(":visible")).toBeTruthy();
@@ -347,9 +368,14 @@
                     expect($contacts.hasClass('both')).toBeTruthy();
                     expect($contacts.hasClass('both')).toBeTruthy();
                     done();
                     done();
                 });
                 });
+                });
             }));
             }));
 
 
-            it("Alternate Flow: Contact Declines Subscription Request", mock.initConverse(function (_converse) {
+            it("Alternate Flow: Contact Declines Subscription Request",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                    function (done, _converse) {
+
                 /* The process by which a user subscribes to a contact, including
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 * the interaction between roster items and subscription states.
                 */
                 */
@@ -429,11 +455,16 @@
                         "</query>"+
                         "</query>"+
                     "</iq>"
                     "</iq>"
                 );
                 );
+                done();
             }));
             }));
 
 
-            it("Unsubscribe to a contact when subscription is mutual", mock.initConverseWithAsync(function (done, _converse) {
+            it("Unsubscribe to a contact when subscription is mutual",
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'],
+                    { roster_groups: false },
+                    function (done, _converse) {
+
                 var sent_IQ, IQ_id, jid = 'annegreet.gomez@localhost';
                 var sent_IQ, IQ_id, jid = 'annegreet.gomez@localhost';
-                _converse.roster_groups = false;
                 test_utils.openControlBox(_converse);
                 test_utils.openControlBox(_converse);
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
@@ -490,7 +521,10 @@
                 });
                 });
             }));
             }));
 
 
-            it("Receiving a subscription request", mock.initConverse(function (_converse) {
+            it("Receiving a subscription request", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
                 test_utils.openControlBox(_converse);
                 test_utils.openControlBox(_converse);
                 test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
                 test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
                 spyOn(_converse, "emit");
                 spyOn(_converse, "emit");
@@ -516,6 +550,7 @@
                     expect($header.is(":visible")).toBeTruthy();
                     expect($header.is(":visible")).toBeTruthy();
                     var $contacts = $header.parent().nextUntil('dt', 'dd');
                     var $contacts = $header.parent().nextUntil('dt', 'dd');
                     expect($contacts.length).toBe(1);
                     expect($contacts.length).toBe(1);
+                    done();
                 });
                 });
             }));
             }));
         });
         });

+ 2 - 3
spec/register.js

@@ -1,7 +1,6 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
-} (this, function (jasmine, mock, converse, test_utils) {
-    var $ = converse.env.jQuery;
+    define(["jquery.noconflict", "jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function ($, jasmine, mock, converse, test_utils) {
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
     var $iq = converse.env.$iq;
     var $iq = converse.env.$iq;
 
 

+ 71 - 57
spec/roomslist.js

@@ -3,15 +3,17 @@
 } (this, function (jasmine, mock, converse, roomslist, test_utils) {
 } (this, function (jasmine, mock, converse, roomslist, test_utils) {
     var _ = converse.env._;
     var _ = converse.env._;
     var $msg = converse.env.$msg;
     var $msg = converse.env.$msg;
+    var Promise = converse.env.Promise;
 
 
     describe("The converse-roomslist plugin", function () {
     describe("The converse-roomslist plugin", function () {
 
 
-        it("is shown under a list of open rooms in the \"Rooms\" panel", mock.initConverse(
+        it("is shown under a list of open rooms in the \"Rooms\" panel", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'],
             { whitelisted_plugins: ['converse-roomslist'],
             { whitelisted_plugins: ['converse-roomslist'],
               allow_bookmarks: false // Makes testing easier, otherwise we
               allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
                                      // have to mock stanza traffic.
             },
             },
-            function (_converse) {
+            function (done, _converse) {
                 test_utils.openControlBox().openRoomsPanel(_converse);
                 test_utils.openControlBox().openRoomsPanel(_converse);
                 var controlbox = _converse.chatboxviews.get('controlbox');
                 var controlbox = _converse.chatboxviews.get('controlbox');
 
 
@@ -45,18 +47,20 @@
 
 
                 list = controlbox.el.querySelector('div.rooms-list-container');
                 list = controlbox.el.querySelector('div.rooms-list-container');
                 expect(_.includes(list.classList, 'hidden')).toBeTruthy();
                 expect(_.includes(list.classList, 'hidden')).toBeTruthy();
+                done();
             }
             }
         ));
         ));
     });
     });
 
 
     describe("An room shown in the rooms list", function () {
     describe("An room shown in the rooms list", function () {
 
 
-        it("can be closed", mock.initConverse(
+        it("can be closed", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'],
             { whitelisted_plugins: ['converse-roomslist'],
             { whitelisted_plugins: ['converse-roomslist'],
               allow_bookmarks: false // Makes testing easier, otherwise we
               allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
                                      // have to mock stanza traffic.
             },
             },
-            function (_converse) {
+            function (done, _converse) {
 
 
             spyOn(window, 'confirm').and.callFake(function () {
             spyOn(window, 'confirm').and.callFake(function () {
                 return true;
                 return true;
@@ -71,67 +75,77 @@
             var close_el = _converse.rooms_list_view.el.querySelector(".close-room");
             var close_el = _converse.rooms_list_view.el.querySelector(".close-room");
             close_el.click();
             close_el.click();
             expect(window.confirm).toHaveBeenCalledWith(
             expect(window.confirm).toHaveBeenCalledWith(
-                'Are you sure you want to leave the room ""?');
+                'Are you sure you want to leave the room "lounge"?');
             room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(0);
             expect(room_els.length).toBe(0);
             expect(_converse.chatboxes.length).toBe(1);
             expect(_converse.chatboxes.length).toBe(1);
+            done();
         }));
         }));
 
 
-        it("shows unread messages directed at the user", mock.initConverse(
+        it("shows unread messages directed at the user", mock.initConverseWithAsync(
             { whitelisted_plugins: ['converse-roomslist'],
             { whitelisted_plugins: ['converse-roomslist'],
               allow_bookmarks: false // Makes testing easier, otherwise we
               allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
                                      // have to mock stanza traffic.
-            }, function (_converse) {
-
-            var room_jid = 'kitchen@conference.shakespeare.lit';
-            test_utils.openAndEnterChatRoom(
-                _converse, 'kitchen', 'conference.shakespeare.lit', 'romeo');
-            var view = _converse.chatboxviews.get(room_jid);
-            view.model.set({'minimized': true});
-
-            var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
-            var nick = mock.chatroom_names[0];
-            view.handleMUCMessage(
-                $msg({
-                    from: room_jid+'/'+nick,
-                    id: (new Date()).getTime(),
-                    to: 'dummy@localhost',
-                    type: 'groupchat'
-                }).c('body').t('foo').tree());
-
-            // If the user isn't mentioned, the counter doesn't get incremented, but the text of the room is bold
-            var room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
-            expect(_.includes(room_el.classList, 'unread-msgs'));
-
-            // If the user is mentioned, the counter also gets updated
-            view.handleMUCMessage(
-                $msg({
-                    from: room_jid+'/'+nick,
-                    id: (new Date()).getTime(),
-                    to: 'dummy@localhost',
-                    type: 'groupchat'
-                }).c('body').t('romeo: Your attention is required').tree()
-            );
-            var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
-            expect(indicator_el.textContent).toBe('1');
-
-            view.handleMUCMessage(
-                $msg({
-                    from: room_jid+'/'+nick,
-                    id: (new Date()).getTime(),
-                    to: 'dummy@localhost',
-                    type: 'groupchat'
-                }).c('body').t('romeo: and another thing...').tree()
-            );
-            indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
-            expect(indicator_el.textContent).toBe('2');
-
-            // When the chat gets maximized again, the unread indicators are removed
-            view.model.set({'minimized': false});
-            indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
-            expect(_.isNull(indicator_el));
-            room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
-            expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
+            }, function (done, _converse) {
+
+            test_utils.waitUntil(function () {
+                    return !_.isUndefined(_converse.rooms_list_view)
+                }, 500)
+            .then(function () {
+                var room_jid = 'kitchen@conference.shakespeare.lit';
+                test_utils.openAndEnterChatRoom(
+                    _converse, 'kitchen', 'conference.shakespeare.lit', 'romeo').then(function () {
+
+                    var view = _converse.chatboxviews.get(room_jid);
+                    view.model.set({'minimized': true});
+                    var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
+                    var nick = mock.chatroom_names[0];
+                    view.handleMUCMessage(
+                        $msg({
+                            from: room_jid+'/'+nick,
+                            id: (new Date()).getTime(),
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t('foo').tree());
+
+                    // If the user isn't mentioned, the counter doesn't get incremented, but the text of the room is bold
+                    var room_el = _converse.rooms_list_view.el.querySelector(
+                        ".available-chatroom"
+                    );
+                    expect(_.includes(room_el.classList, 'unread-msgs'));
+
+                    // If the user is mentioned, the counter also gets updated
+                    view.handleMUCMessage(
+                        $msg({
+                            from: room_jid+'/'+nick,
+                            id: (new Date()).getTime(),
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t('romeo: Your attention is required').tree()
+                    );
+                    var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
+                    expect(indicator_el.textContent).toBe('1');
+
+                    view.handleMUCMessage(
+                        $msg({
+                            from: room_jid+'/'+nick,
+                            id: (new Date()).getTime(),
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t('romeo: and another thing...').tree()
+                    );
+                    indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
+                    expect(indicator_el.textContent).toBe('2');
+
+                    // When the chat gets maximized again, the unread indicators are removed
+                    view.model.set({'minimized': false});
+                    indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
+                    expect(_.isNull(indicator_el));
+                    room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
+                    expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
+                    done();
+                });
+            });
         }));
         }));
     });
     });
 }));
 }));

+ 2 - 2
spec/transcripts.js

@@ -1,5 +1,6 @@
 (function (root, factory) {
 (function (root, factory) {
     define([
     define([
+        "jquery.noconflict",
         "converse-core",
         "converse-core",
         "mock",
         "mock",
         "test_utils",
         "test_utils",
@@ -7,10 +8,9 @@
         "transcripts"
         "transcripts"
         ], factory
         ], factory
     );
     );
-} (this, function (converse, mock, test_utils, utils, transcripts) {
+} (this, function ($, converse, mock, test_utils, utils, transcripts) {
     var Strophe = converse.env.Strophe;
     var Strophe = converse.env.Strophe;
     var _ = converse.env._;
     var _ = converse.env._;
-    var $ = converse.env.jQuery;
     var IGNORED_TAGS = [
     var IGNORED_TAGS = [
         'stream:features',
         'stream:features',
         'auth',
         'auth',

+ 3 - 3
spec/utils.js

@@ -14,7 +14,7 @@
                 chatview_avatar_height: 32,
                 chatview_avatar_height: 32,
                 auto_join_rooms: [],
                 auto_join_rooms: [],
                 visible_toolbar_buttons: {
                 visible_toolbar_buttons: {
-                    'emoticons': true,
+                    'emojis': true,
                     'call': false,
                     'call': false,
                     'clear': true,
                     'clear': true,
                     'toggle_occupants': true
                     'toggle_occupants': true
@@ -31,7 +31,7 @@
                     'anonymous@conference.nomnom.im',
                     'anonymous@conference.nomnom.im',
                 ],
                 ],
                 visible_toolbar_buttons: {
                 visible_toolbar_buttons: {
-                    'emoticons': false,
+                    'emojis': false,
                     'call': false,
                     'call': false,
                     'toggle_occupants':false,
                     'toggle_occupants':false,
                     'invalid': false 
                     'invalid': false 
@@ -44,7 +44,7 @@
             expect(context.chatview_avatar_width).toBe(32);
             expect(context.chatview_avatar_width).toBe(32);
             expect(context.chatview_avatar_height).toBe(48);
             expect(context.chatview_avatar_height).toBe(48);
             expect(_.keys(context.visible_toolbar_buttons)).toEqual(_.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.emojis).toBeFalsy();
             expect(context.visible_toolbar_buttons.call).toBeFalsy();
             expect(context.visible_toolbar_buttons.call).toBeFalsy();
             expect(context.visible_toolbar_buttons.toggle_occupants).toBeFalsy();
             expect(context.visible_toolbar_buttons.toggle_occupants).toBeFalsy();
             expect(context.visible_toolbar_buttons.invalid).toBeFalsy();
             expect(context.visible_toolbar_buttons.invalid).toBeFalsy();

+ 2 - 3
spec/xmppstatus.js

@@ -1,7 +1,6 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
-} (this, function (jasmine, mock, converse, test_utils) {
-    var $ = converse.env.jQuery;
+    define(["jquery.noconflict", "jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function ($, jasmine, mock, converse, test_utils) {
 
 
     return describe("The XMPPStatus model", function() {
     return describe("The XMPPStatus model", function() {
 
 

+ 9 - 0
src/build-esnext.js

@@ -0,0 +1,9 @@
+({
+    baseUrl: "../",
+    name: "almond",
+    mainConfigFile: 'config.js',
+    wrap: {
+        startFile: "start.frag",
+        endFile: "end.frag"
+    }
+});

+ 9 - 0
src/build-inverse-esnext.js

@@ -0,0 +1,9 @@
+({
+    baseUrl: "../",
+    name: "almond",
+    mainConfigFile: 'config.js',
+    wrap: {
+        startFile: "start.frag",
+        endFile: "inverse-end.frag"
+    }
+})

+ 22 - 0
src/build-inverse.js

@@ -2,6 +2,28 @@
     baseUrl: "../",
     baseUrl: "../",
     name: "almond",
     name: "almond",
     mainConfigFile: 'config.js',
     mainConfigFile: 'config.js',
+    paths: {
+        "converse-bookmarks":       "builds/converse-bookmarks",
+        "converse-chatview":        "builds/converse-chatview",
+        "converse-controlbox":      "builds/converse-controlbox",
+        "converse-core":            "builds/converse-core",
+        "converse-dragresize":      "builds/converse-dragresize",
+        "converse-headline":        "builds/converse-headline",
+        "converse-inverse":         "builds/converse-inverse",
+        "converse-mam":             "builds/converse-mam",
+        "converse-minimize":        "builds/converse-minimize",
+        "converse-muc":             "builds/converse-muc",
+        "converse-muc-embedded":    "builds/converse-muc-embedded",
+        "converse-notification":    "builds/converse-notification",
+        "converse-otr":             "builds/converse-otr",
+        "converse-ping":            "builds/converse-ping",
+        "converse-register":        "builds/converse-register",
+        "converse-roomslist":       "builds/converse-roomslist",
+        "converse-rosterview":      "builds/converse-rosterview",
+        "converse-singleton":       "builds/converse-singleton",
+        "converse-vcard":           "builds/converse-vcard",
+        "utils":                    "builds/utils"
+    },
     wrap: {
     wrap: {
         startFile: "start.frag",
         startFile: "start.frag",
         endFile: "inverse-end.frag"
         endFile: "inverse-end.frag"

+ 26 - 2
src/build-no-dependencies.js

@@ -36,11 +36,35 @@
         "strophe.vcard",
         "strophe.vcard",
         "strophe.ping",
         "strophe.ping",
         "otr",
         "otr",
-        "lodash"
+        "lodash",
+        "lodash.converter",
+        "lodash.noconflict"
     ],
     ],
+    paths: {
+        "converse-bookmarks":       "builds/converse-bookmarks",
+        "converse-chatview":        "builds/converse-chatview",
+        "converse-controlbox":      "builds/converse-controlbox",
+        "converse-core":            "builds/converse-core",
+        "converse-dragresize":      "builds/converse-dragresize",
+        "converse-headline":        "builds/converse-headline",
+        "converse-inverse":         "builds/converse-inverse",
+        "converse-mam":             "builds/converse-mam",
+        "converse-minimize":        "builds/converse-minimize",
+        "converse-muc":             "builds/converse-muc",
+        "converse-muc-embedded":    "builds/converse-muc-embedded",
+        "converse-notification":    "builds/converse-notification",
+        "converse-otr":             "builds/converse-otr",
+        "converse-ping":            "builds/converse-ping",
+        "converse-register":        "builds/converse-register",
+        "converse-roomslist":       "builds/converse-roomslist",
+        "converse-rosterview":      "builds/converse-rosterview",
+        "converse-singleton":       "builds/converse-singleton",
+        "converse-vcard":           "builds/converse-vcard",
+        "utils":                    "builds/utils"
+    },
     wrap: {
     wrap: {
         startFile: "start.frag",
         startFile: "start.frag",
         endFile: "end-no-dependencies.frag"
         endFile: "end-no-dependencies.frag"
     },
     },
     mainConfigFile: "config.js"
     mainConfigFile: "config.js"
-})
+});

+ 23 - 1
src/build.js

@@ -2,8 +2,30 @@
     baseUrl: "../",
     baseUrl: "../",
     name: "almond",
     name: "almond",
     mainConfigFile: 'config.js',
     mainConfigFile: 'config.js',
+    paths: {
+        "converse-bookmarks":       "builds/converse-bookmarks",
+        "converse-chatview":        "builds/converse-chatview",
+        "converse-controlbox":      "builds/converse-controlbox",
+        "converse-core":            "builds/converse-core",
+        "converse-dragresize":      "builds/converse-dragresize",
+        "converse-headline":        "builds/converse-headline",
+        "converse-inverse":         "builds/converse-inverse",
+        "converse-mam":             "builds/converse-mam",
+        "converse-minimize":        "builds/converse-minimize",
+        "converse-muc":             "builds/converse-muc",
+        "converse-muc-embedded":    "builds/converse-muc-embedded",
+        "converse-notification":    "builds/converse-notification",
+        "converse-otr":             "builds/converse-otr",
+        "converse-ping":            "builds/converse-ping",
+        "converse-register":        "builds/converse-register",
+        "converse-roomslist":       "builds/converse-roomslist",
+        "converse-rosterview":      "builds/converse-rosterview",
+        "converse-singleton":       "builds/converse-singleton",
+        "converse-vcard":           "builds/converse-vcard",
+        "utils":                    "builds/utils"
+    },
     wrap: {
     wrap: {
         startFile: "start.frag",
         startFile: "start.frag",
         endFile: "end.frag"
         endFile: "end.frag"
     }
     }
-})
+});

+ 21 - 8
src/config.js

@@ -17,16 +17,20 @@ require.config({
     paths: {
     paths: {
         "almond":                   "node_modules/almond/almond",
         "almond":                   "node_modules/almond/almond",
         "awesomplete":              "node_modules/awesomplete-avoid-xss/awesomplete",
         "awesomplete":              "node_modules/awesomplete-avoid-xss/awesomplete",
+        "babel":                    "node_modules/requirejs-babel/babel-5.8.34.min",
         "backbone":                 "node_modules/backbone/backbone",
         "backbone":                 "node_modules/backbone/backbone",
-        "backbone.noconflict":      "src/backbone.noconflict",
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
+        "backbone.noconflict":      "src/backbone.noconflict",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
+        "emojione":                 "node_modules/emojione/lib/js/emojione",
+        "es6-promise":              "node_modules/es6-promise/dist/es6-promise.auto",
         "eventemitter":             "node_modules/otr/build/dep/eventemitter",
         "eventemitter":             "node_modules/otr/build/dep/eventemitter",
-        "es6-promise":              "node_modules/es6-promise/dist/es6-promise",
         "jquery":                   "node_modules/jquery/dist/jquery",
         "jquery":                   "node_modules/jquery/dist/jquery",
-        "jquery.noconflict":        "src/jquery.noconflict",
         "jquery.browser":           "node_modules/jquery.browser/dist/jquery.browser",
         "jquery.browser":           "node_modules/jquery.browser/dist/jquery.browser",
-        "jquery.easing":            "node_modules/jquery-easing/jquery.easing.1.3.umd", // XXX: Only required for https://conversejs.org website
+        "jquery.noconflict":        "src/jquery.noconflict",
+        "lodash":                   "node_modules/lodash/lodash",
+        "lodash.converter":         "3rdparty/lodash.fp",
+        "lodash.noconflict":        "src/lodash.noconflict",
         "pluggable":                "node_modules/pluggable.js/dist/pluggable",
         "pluggable":                "node_modules/pluggable.js/dist/pluggable",
         "polyfill":                 "src/polyfill",
         "polyfill":                 "src/polyfill",
         "sizzle":                   "node_modules/jquery/sizzle/dist/sizzle",
         "sizzle":                   "node_modules/jquery/sizzle/dist/sizzle",
@@ -38,11 +42,10 @@ require.config({
         "text":                     "node_modules/text/text",
         "text":                     "node_modules/text/text",
         "tpl":                      "node_modules/lodash-template-loader/loader",
         "tpl":                      "node_modules/lodash-template-loader/loader",
         "typeahead":                "components/typeahead.js/index",
         "typeahead":                "components/typeahead.js/index",
-        "lodash":                   "node_modules/lodash/lodash",
-        "lodash.converter":         "3rdparty/lodash.fp",
-        "lodash.noconflict":        "src/lodash.noconflict",
         "underscore":               "src/underscore-shim",
         "underscore":               "src/underscore-shim",
         "utils":                    "src/utils",
         "utils":                    "src/utils",
+        "xss.noconflict":           "src/xss.noconflict",
+        "xss":                      "node_modules/xss/dist/xss",
 
 
         // Converse
         // Converse
         "converse":                 "src/converse",
         "converse":                 "src/converse",
@@ -52,6 +55,7 @@ require.config({
         "converse-chatview":        "src/converse-chatview",
         "converse-chatview":        "src/converse-chatview",
         "converse-controlbox":      "src/converse-controlbox",
         "converse-controlbox":      "src/converse-controlbox",
         "converse-core":            "src/converse-core",
         "converse-core":            "src/converse-core",
+        "converse-disco":           "src/converse-disco",
         "converse-dragresize":      "src/converse-dragresize",
         "converse-dragresize":      "src/converse-dragresize",
         "converse-headline":        "src/converse-headline",
         "converse-headline":        "src/converse-headline",
         "converse-inverse":         "src/converse-inverse",
         "converse-inverse":         "src/converse-inverse",
@@ -136,6 +140,15 @@ require.config({
 
 
     // define module dependencies for modules not using define
     // define module dependencies for modules not using define
     shim: {
     shim: {
-        'awesomplete':          { exports: 'Awesomplete' }
+        'awesomplete':          { exports: 'Awesomplete'},
+        'emojione':             { exports: 'emojione'},
+        'xss':                  {
+            init: function (xss_noconflict) {
+                return {
+                    filterXSS: window.filterXSS,
+                    filterCSS: window.filterCSS
+                }
+            }
+        }
     }
     }
 });
 });

+ 116 - 97
src/converse-bookmarks.js

@@ -10,7 +10,8 @@
  * in XEP-0048.
  * in XEP-0048.
  */
  */
 (function (root, factory) {
 (function (root, factory) {
-    define(["utils",
+    define(["jquery.noconflict",
+            "utils",
             "converse-core",
             "converse-core",
             "converse-muc",
             "converse-muc",
             "tpl!chatroom_bookmark_form",
             "tpl!chatroom_bookmark_form",
@@ -20,6 +21,7 @@
         ],
         ],
         factory);
         factory);
 }(this, function (
 }(this, function (
+        $,
         utils,
         utils,
         converse,
         converse,
         muc,
         muc,
@@ -29,13 +31,7 @@
         tpl_bookmarks_list
         tpl_bookmarks_list
     ) {
     ) {
 
 
-    var $ = converse.env.jQuery,
-        Backbone = converse.env.Backbone,
-        Strophe = converse.env.Strophe,
-        $iq = converse.env.$iq,
-        b64_sha1 = converse.env.b64_sha1,
-        sizzle = converse.env.sizzle,
-        _ = converse.env._;
+    const { Backbone, Promise, Strophe, $iq, b64_sha1, sizzle, _ } = converse.env;
 
 
     converse.plugins.add('converse-bookmarks', {
     converse.plugins.add('converse-bookmarks', {
         overrides: {
         overrides: {
@@ -45,11 +41,12 @@
             //
             //
             // New functions which don't exist yet can also be added.
             // New functions which don't exist yet can also be added.
 
 
-            clearSession: function () {
+            clearSession () {
                 this.__super__.clearSession.apply(this, arguments);
                 this.__super__.clearSession.apply(this, arguments);
                 if (!_.isUndefined(this.bookmarks)) {
                 if (!_.isUndefined(this.bookmarks)) {
                     this.bookmarks.reset();
                     this.bookmarks.reset();
                     this.bookmarks.browserStorage._clear();
                     this.bookmarks.browserStorage._clear();
+                    window.sessionStorage.removeItem(this.bookmarks.fetched_flag);
                 }
                 }
             },
             },
 
 
@@ -58,20 +55,20 @@
                     'click .toggle-bookmark': 'toggleBookmark'
                     'click .toggle-bookmark': 'toggleBookmark'
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
                     this.__super__.initialize.apply(this, arguments);
                     this.__super__.initialize.apply(this, arguments);
                     this.model.on('change:bookmarked', this.onBookmarked, this);
                     this.model.on('change:bookmarked', this.onBookmarked, this);
                     this.setBookmarkState();
                     this.setBookmarkState();
                 },
                 },
 
 
-                generateHeadingHTML: function () {
-                    var _converse = this.__super__._converse,
-                        __ = _converse.__,
+                generateHeadingHTML () {
+                    const { _converse } = this.__super__,
+                        { __ } = _converse,
                         html = this.__super__.generateHeadingHTML.apply(this, arguments);
                         html = this.__super__.generateHeadingHTML.apply(this, arguments);
                     if (_converse.allow_bookmarks) {
                     if (_converse.allow_bookmarks) {
-                        var div = document.createElement('div');
+                        const div = document.createElement('div');
                         div.innerHTML = html;
                         div.innerHTML = html;
-                        var bookmark_button = tpl_chatroom_bookmark_toggle(
+                        const bookmark_button = tpl_chatroom_bookmark_toggle(
                             _.assignIn(
                             _.assignIn(
                                 this.model.toJSON(),
                                 this.model.toJSON(),
                                 {
                                 {
@@ -79,23 +76,23 @@
                                     bookmarked: this.model.get('bookmarked')
                                     bookmarked: this.model.get('bookmarked')
                                 }
                                 }
                             ));
                             ));
-                        var close_button = div.querySelector('.close-chatbox-button');
+                        const close_button = div.querySelector('.close-chatbox-button');
                         close_button.insertAdjacentHTML('afterend', bookmark_button);
                         close_button.insertAdjacentHTML('afterend', bookmark_button);
                         return div.innerHTML;
                         return div.innerHTML;
                     }
                     }
                     return html;
                     return html;
                 },
                 },
 
 
-                checkForReservedNick: function () {
+                checkForReservedNick () {
                     /* Check if the user has a bookmark with a saved nickanme
                     /* Check if the user has a bookmark with a saved nickanme
                      * for this room, and if so use it.
                      * for this room, and if so use it.
                      * Otherwise delegate to the super method.
                      * Otherwise delegate to the super method.
                      */
                      */
-                    var _converse = this.__super__._converse;
+                    const { _converse } = this.__super__;
                     if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) {
                     if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) {
                         return this.__super__.checkForReservedNick.apply(this, arguments);
                         return this.__super__.checkForReservedNick.apply(this, arguments);
                     }
                     }
-                    var model = _converse.bookmarks.findWhere({'jid': this.model.get('jid')});
+                    const model = _converse.bookmarks.findWhere({'jid': this.model.get('jid')});
                     if (!_.isUndefined(model) && model.get('nick')) {
                     if (!_.isUndefined(model) && model.get('nick')) {
                         this.join(model.get('nick'));
                         this.join(model.get('nick'));
                     } else {
                     } else {
@@ -103,7 +100,7 @@
                     }
                     }
                 },
                 },
 
 
-                onBookmarked: function () {
+                onBookmarked () {
                     if (this.model.get('bookmarked')) {
                     if (this.model.get('bookmarked')) {
                         this.$('.icon-pushpin').addClass('button-on');
                         this.$('.icon-pushpin').addClass('button-on');
                     } else {
                     } else {
@@ -111,12 +108,12 @@
                     }
                     }
                 },
                 },
 
 
-                setBookmarkState: function () {
+                setBookmarkState () {
                     /* Set whether the room is bookmarked or not.
                     /* Set whether the room is bookmarked or not.
                      */
                      */
-                    var _converse = this.__super__._converse;
+                    const { _converse } = this.__super__;
                     if (!_.isUndefined(_converse.bookmarks)) {
                     if (!_.isUndefined(_converse.bookmarks)) {
-                        var 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.model.save('bookmarked', false);
                             this.model.save('bookmarked', false);
                         } else {
                         } else {
@@ -125,9 +122,9 @@
                     }
                     }
                 },
                 },
 
 
-                renderBookmarkForm: function () {
-                    var _converse = this.__super__._converse,
-                        __ = _converse.__,
+                renderBookmarkForm () {
+                    const { _converse } = this.__super__,
+                        { __ } = _converse,
                         $body = this.$('.chatroom-body');
                         $body = this.$('.chatroom-body');
                     $body.children().addClass('hidden');
                     $body.children().addClass('hidden');
                     // Remove any existing forms
                     // Remove any existing forms
@@ -146,10 +143,10 @@
                     this.$('.chatroom-form .button-cancel').on('click', this.cancelConfiguration.bind(this));
                     this.$('.chatroom-form .button-cancel').on('click', this.cancelConfiguration.bind(this));
                 },
                 },
 
 
-                onBookmarkFormSubmitted: function (ev) {
+                onBookmarkFormSubmitted (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var _converse = this.__super__._converse;
-                    var $form = $(ev.target), that = this;
+                    const { _converse } = this.__super__;
+                    const $form = $(ev.target), that = this;
                     _converse.bookmarks.createBookmark({
                     _converse.bookmarks.createBookmark({
                         'jid': this.model.get('jid'),
                         'jid': this.model.get('jid'),
                         'autojoin': $form.find('input[name="autojoin"]').prop('checked'),
                         'autojoin': $form.find('input[name="autojoin"]').prop('checked'),
@@ -163,13 +160,13 @@
                         });
                         });
                 },
                 },
 
 
-                toggleBookmark: function (ev) {
+                toggleBookmark (ev) {
                     if (ev) {
                     if (ev) {
                         ev.preventDefault();
                         ev.preventDefault();
                         ev.stopPropagation();
                         ev.stopPropagation();
                     }
                     }
-                    var _converse = this.__super__._converse;
-                    var models = _converse.bookmarks.where({'jid': this.model.get('jid')});
+                    const { _converse } = this.__super__;
+                    const models = _converse.bookmarks.where({'jid': this.model.get('jid')});
                     if (!models.length) {
                     if (!models.length) {
                         this.renderBookmarkForm();
                         this.renderBookmarkForm();
                     } else {
                     } else {
@@ -182,13 +179,13 @@
             }
             }
         },
         },
 
 
-        initialize: function () {
+        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.
              */
              */
-            var _converse = this._converse,
-                __ = _converse.__,
-                ___ = _converse.___;
+            const { _converse } = this,
+                { __,
+                ___ } = _converse;
 
 
             // Configuration values for this plugin
             // Configuration values for this plugin
             // ====================================
             // ====================================
@@ -201,6 +198,31 @@
             // Promises exposed by this plugin
             // Promises exposed by this plugin
             _converse.api.promises.add('bookmarksInitialized');
             _converse.api.promises.add('bookmarksInitialized');
 
 
+            // Pure functions on the _converse object
+            _.extend(_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.openChatRoom({'jid': jid}, true);
+                    _converse.chatboxviews.get(jid).renderBookmarkForm();
+                },
+            });
+
             _converse.Bookmark = Backbone.Model;
             _converse.Bookmark = Backbone.Model;
 
 
             _converse.BookmarksList = Backbone.Model.extend({
             _converse.BookmarksList = Backbone.Model.extend({
@@ -212,56 +234,55 @@
             _converse.Bookmarks = Backbone.Collection.extend({
             _converse.Bookmarks = Backbone.Collection.extend({
                 model: _converse.Bookmark,
                 model: _converse.Bookmark,
 
 
-                initialize: function () {
+                initialize () {
                     this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked));
                     this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked));
                     this.on('remove', this.markRoomAsUnbookmarked, this);
                     this.on('remove', this.markRoomAsUnbookmarked, this);
                     this.on('remove', this.sendBookmarkStanza, this);
                     this.on('remove', this.sendBookmarkStanza, this);
 
 
-                    var cache_key = 'converse.room-bookmarks'+_converse.bare_jid;
-                    this.cached_flag = b64_sha1(cache_key+'fetched');
+                    const cache_key = `converse.room-bookmarks${_converse.bare_jid}`;
+                    this.fetched_flag = b64_sha1(cache_key+'fetched');
                     this.browserStorage = new Backbone.BrowserStorage[_converse.storage](
                     this.browserStorage = new Backbone.BrowserStorage[_converse.storage](
                         b64_sha1(cache_key)
                         b64_sha1(cache_key)
                     );
                     );
                 },
                 },
 
 
-                openBookmarkedRoom: function (bookmark) {
+                openBookmarkedRoom (bookmark) {
                     if (bookmark.get('autojoin')) {
                     if (bookmark.get('autojoin')) {
                         _converse.api.rooms.open(bookmark.get('jid'), bookmark.get('nick'));
                         _converse.api.rooms.open(bookmark.get('jid'), bookmark.get('nick'));
                     }
                     }
                     return bookmark;
                     return bookmark;
                 },
                 },
 
 
-                fetchBookmarks: function () {
-                    var deferred = new $.Deferred();
-                    var promise = deferred.promise();
-                    if (window.sessionStorage.getItem(this.browserStorage.name)) {
+                fetchBookmarks () {
+                    const deferred = utils.getWrappedPromise();
+                    if (this.browserStorage.records.length > 0) {
                         this.fetch({
                         this.fetch({
                             'success': _.bind(this.onCachedBookmarksFetched, this, deferred),
                             'success': _.bind(this.onCachedBookmarksFetched, this, deferred),
                             'error':  _.bind(this.onCachedBookmarksFetched, this, deferred)
                             'error':  _.bind(this.onCachedBookmarksFetched, this, deferred)
                         });
                         });
-                    } else if (! window.sessionStorage.getItem(this.cached_flag)) {
-                        // There aren't any cached bookmarks, and the cache is
-                        // not set to null. So we query the XMPP server.
+                    } else if (! window.sessionStorage.getItem(this.fetched_flag)) {
+                        // There aren't any cached bookmarks and the
+                        // `fetched_flag` is off, so we query the XMPP server.
                         // If nothing is returned from the XMPP server, we set
                         // If nothing is returned from the XMPP server, we set
-                        // the cache to null to avoid calling the server again.
+                        // the `fetched_flag` to avoid calling the server again.
                         this.fetchBookmarksFromServer(deferred);
                         this.fetchBookmarksFromServer(deferred);
                     } else {
                     } else {
                         deferred.resolve();
                         deferred.resolve();
                     }
                     }
-                    return promise;
+                    return deferred.promise;
                 },
                 },
 
 
-                onCachedBookmarksFetched: function (deferred) {
+                onCachedBookmarksFetched (deferred) {
                     return deferred.resolve();
                     return deferred.resolve();
                 },
                 },
 
 
-                createBookmark: function (options) {
+                createBookmark (options) {
                     _converse.bookmarks.create(options);
                     _converse.bookmarks.create(options);
                     _converse.bookmarks.sendBookmarkStanza();
                     _converse.bookmarks.sendBookmarkStanza();
                 },
                 },
 
 
-                sendBookmarkStanza: function () {
-                    var stanza = $iq({
+                sendBookmarkStanza () {
+                    let stanza = $iq({
                             'type': 'set',
                             'type': 'set',
                             'from': _converse.connection.jid,
                             'from': _converse.connection.jid,
                         })
                         })
@@ -288,7 +309,7 @@
                     _converse.connection.sendIQ(stanza, null, this.onBookmarkError.bind(this));
                     _converse.connection.sendIQ(stanza, null, this.onBookmarkError.bind(this));
                 },
                 },
 
 
-                onBookmarkError: function (iq) {
+                onBookmarkError (iq) {
                     _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR);
                     _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR);
                     _converse.log(iq);
                     _converse.log(iq);
                     // We remove all locally cached bookmarks and fetch them
                     // We remove all locally cached bookmarks and fetch them
@@ -298,8 +319,8 @@
                     window.alert(__("Sorry, something went wrong while trying to save your bookmark."));
                     window.alert(__("Sorry, something went wrong while trying to save your bookmark."));
                 },
                 },
 
 
-                fetchBookmarksFromServer: function (deferred) {
-                    var stanza = $iq({
+                fetchBookmarksFromServer (deferred) {
+                    const stanza = $iq({
                         'from': _converse.connection.jid,
                         'from': _converse.connection.jid,
                         'type': 'get',
                         'type': 'get',
                     }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
                     }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
@@ -311,25 +332,25 @@
                     );
                     );
                 },
                 },
 
 
-                markRoomAsBookmarked: function (bookmark) {
-                    var room = _converse.chatboxes.get(bookmark.get('jid'));
+                markRoomAsBookmarked (bookmark) {
+                    const room = _converse.chatboxes.get(bookmark.get('jid'));
                     if (!_.isUndefined(room)) {
                     if (!_.isUndefined(room)) {
                         room.save('bookmarked', true);
                         room.save('bookmarked', true);
                     }
                     }
                 },
                 },
 
 
-                markRoomAsUnbookmarked: function (bookmark) {
-                    var room = _converse.chatboxes.get(bookmark.get('jid'));
+                markRoomAsUnbookmarked (bookmark) {
+                    const room = _converse.chatboxes.get(bookmark.get('jid'));
                     if (!_.isUndefined(room)) {
                     if (!_.isUndefined(room)) {
                         room.save('bookmarked', false);
                         room.save('bookmarked', false);
                     }
                     }
                 },
                 },
 
 
-                onBookmarksReceived: function (deferred, iq) {
-                    var bookmarks = $(iq).find(
+                onBookmarksReceived (deferred, iq) {
+                    const bookmarks = $(iq).find(
                         'items[node="storage:bookmarks"] item[id="current"] storage conference'
                         'items[node="storage:bookmarks"] item[id="current"] storage conference'
                     );
                     );
-                    var that = this;
+                    const that = this;
                     _.forEach(bookmarks, function (bookmark) {
                     _.forEach(bookmarks, function (bookmark) {
                         that.create({
                         that.create({
                             'jid': bookmark.getAttribute('jid'),
                             'jid': bookmark.getAttribute('jid'),
@@ -343,10 +364,10 @@
                     }
                     }
                 },
                 },
 
 
-                onBookmarksReceivedError: function (deferred, iq) {
-                    window.sessionStorage.setItem(this.cached_flag, true);
+                onBookmarksReceivedError (deferred, iq) {
+                    window.sessionStorage.setItem(this.fetched_flag, true);
                     _converse.log('Error while fetching bookmarks', Strophe.LogLevel.ERROR);
                     _converse.log('Error while fetching bookmarks', Strophe.LogLevel.ERROR);
-                    _converse.log(iq);
+                    _converse.log(iq, Strophe.LogLevel.DEBUG);
                     if (!_.isNil(deferred)) {
                     if (!_.isNil(deferred)) {
                         return deferred.reject();
                         return deferred.reject();
                     }
                     }
@@ -357,17 +378,18 @@
                 tagName: 'div',
                 tagName: 'div',
                 className: 'bookmarks-list, rooms-list-container',
                 className: 'bookmarks-list, rooms-list-container',
                 events: {
                 events: {
-                    'click .remove-bookmark': 'removeBookmark',
-                    'click .bookmarks-toggle': 'toggleBookmarksList'
+                    'click .add-bookmark': 'addBookmark',
+                    'click .bookmarks-toggle': 'toggleBookmarksList',
+                    'click .remove-bookmark': 'removeBookmark'
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
                     this.model.on('add', this.renderBookmarkListElement, this);
                     this.model.on('add', this.renderBookmarkListElement, this);
                     this.model.on('remove', this.removeBookmarkListElement, this);
                     this.model.on('remove', this.removeBookmarkListElement, this);
                     _converse.chatboxes.on('add', this.renderBookmarkListElement, this);
                     _converse.chatboxes.on('add', this.renderBookmarkListElement, this);
                     _converse.chatboxes.on('remove', this.renderBookmarkListElement, this);
                     _converse.chatboxes.on('remove', this.renderBookmarkListElement, this);
 
 
-                    var cachekey = 'converse.room-bookmarks'+_converse.bare_jid+'-list-model';
+                    const cachekey = `converse.room-bookmarks${_converse.bare_jid}-list-model`;
                     this.list_model = new _converse.BookmarksList();
                     this.list_model = new _converse.BookmarksList();
                     this.list_model.id = cachekey;
                     this.list_model.id = cachekey;
                     this.list_model.browserStorage = new Backbone.BrowserStorage[_converse.storage](
                     this.list_model.browserStorage = new Backbone.BrowserStorage[_converse.storage](
@@ -377,7 +399,7 @@
                     this.render();
                     this.render();
                 },
                 },
 
 
-                render: function () {
+                render () {
                     this.$el.html(tpl_bookmarks_list({
                     this.$el.html(tpl_bookmarks_list({
                         'toggle_state': this.list_model.get('toggle-state'),
                         'toggle_state': this.list_model.get('toggle-state'),
                         'desc_bookmarks': __('Click to toggle the bookmarks list'),
                         'desc_bookmarks': __('Click to toggle the bookmarks list'),
@@ -387,23 +409,17 @@
                         this.$('.bookmarks').hide();
                         this.$('.bookmarks').hide();
                     }
                     }
                     this.model.each(this.renderBookmarkListElement.bind(this));
                     this.model.each(this.renderBookmarkListElement.bind(this));
-                    var controlboxview = _converse.chatboxviews.get('controlbox');
+                    const controlboxview = _converse.chatboxviews.get('controlbox');
                     if (!_.isUndefined(controlboxview)) {
                     if (!_.isUndefined(controlboxview)) {
                         this.$el.prependTo(controlboxview.$('#chatrooms'));
                         this.$el.prependTo(controlboxview.$('#chatrooms'));
                     }
                     }
                     return this.$el;
                     return this.$el;
                 },
                 },
 
 
-                removeBookmark: function (ev) {
-                    ev.preventDefault();
-                    var name = $(ev.target).data('bookmarkName');
-                    var jid = $(ev.target).data('roomJid');
-                    if (confirm(__(___("Are you sure you want to remove the bookmark \"%1$s\"?"), name))) {
-                        _.invokeMap(_converse.bookmarks.where({'jid': jid}), Backbone.Model.prototype.destroy);
-                    }
-                },
+                removeBookmark: _converse.removeBookmarkViaEvent,
+                addBookmark: _converse.addBookmarkViaEvent,
 
 
-                renderBookmarkListElement: function (item) {
+                renderBookmarkListElement (item) {
                     if (item instanceof _converse.ChatBox) {
                     if (item instanceof _converse.ChatBox) {
                         item = _.head(this.model.where({'jid': item.get('jid')}));
                         item = _.head(this.model.where({'jid': item.get('jid')}));
                         if (_.isNil(item)) {
                         if (_.isNil(item)) {
@@ -420,8 +436,8 @@
                         return;
                         return;
                     }
                     }
 
 
-                    var list_el = this.el.querySelector('.bookmarks');
-                    var div = document.createElement('div');
+                    const list_el = this.el.querySelector('.bookmarks');
+                    const div = document.createElement('div');
                     div.innerHTML = tpl_bookmark({
                     div.innerHTML = tpl_bookmark({
                         'bookmarked': true,
                         'bookmarked': true,
                         'info_leave_room': __('Leave this room'),
                         'info_leave_room': __('Leave this room'),
@@ -432,8 +448,8 @@
                         'name': item.get('name'),
                         'name': item.get('name'),
                         'open_title': __('Click to open this room')
                         'open_title': __('Click to open this room')
                     });
                     });
-                    var el = _.head(sizzle(
-                        '.available-chatroom[data-room-jid="'+item.get('jid')+'"]',
+                    const el = _.head(sizzle(
+                        `.available-chatroom[data-room-jid="${item.get('jid')}"]`,
                         list_el));
                         list_el));
 
 
                     if (el) {
                     if (el) {
@@ -444,19 +460,19 @@
                     this.show();
                     this.show();
                 },
                 },
 
 
-                show: function () {
+                show () {
                     if (!this.$el.is(':visible')) {
                     if (!this.$el.is(':visible')) {
                         this.$el.show();
                         this.$el.show();
                     }
                     }
                 },
                 },
 
 
-                hide: function () {
+                hide () {
                     this.$el.hide();
                     this.$el.hide();
                 },
                 },
 
 
-                removeBookmarkListElement: function (item) {
-                    var list_el = this.el.querySelector('.bookmarks');
-                    var el = _.head(sizzle('.available-chatroom[data-room-jid="'+item.get('jid')+'"]', list_el));
+                removeBookmarkListElement (item) {
+                    const list_el = this.el.querySelector('.bookmarks');
+                    const el = _.head(sizzle(`.available-chatroom[data-room-jid="${item.get('jid')}"]`, list_el));
                     if (el) {
                     if (el) {
                         list_el.removeChild(el);
                         list_el.removeChild(el);
                     }
                     }
@@ -465,9 +481,9 @@
                     }
                     }
                 },
                 },
 
 
-                toggleBookmarksList: function (ev) {
+                toggleBookmarksList (ev) {
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    var $el = $(ev.target);
+                    const $el = $(ev.target);
                     if ($el.hasClass("icon-opened")) {
                     if ($el.hasClass("icon-opened")) {
                         this.$('.bookmarks').slideUp('fast');
                         this.$('.bookmarks').slideUp('fast');
                         this.list_model.save({'toggle-state': _converse.CLOSED});
                         this.list_model.save({'toggle-state': _converse.CLOSED});
@@ -480,22 +496,25 @@
                 }
                 }
             });
             });
 
 
-            var initBookmarks = function () {
+            const initBookmarks = function () {
                 if (!_converse.allow_bookmarks) {
                 if (!_converse.allow_bookmarks) {
                     return;
                     return;
                 }
                 }
                 _converse.bookmarks = new _converse.Bookmarks();
                 _converse.bookmarks = new _converse.Bookmarks();
-                _converse.bookmarks.fetchBookmarks().always(function () {
+                _converse.bookmarks.fetchBookmarks().then(function () {
                     _converse.bookmarksview = new _converse.BookmarksView(
                     _converse.bookmarksview = new _converse.BookmarksView(
                         {'model': _converse.bookmarks}
                         {'model': _converse.bookmarks}
                     );
                     );
                     _converse.emit('bookmarksInitialized');
                     _converse.emit('bookmarksInitialized');
                 });
                 });
             };
             };
-            $.when(_converse.api.waitUntil('chatBoxesFetched'),
-                   _converse.api.waitUntil('roomsPanelRendered')).then(initBookmarks);
 
 
-            var afterReconnection = function () {
+            Promise.all([
+                _converse.api.waitUntil('chatBoxesFetched'),
+                _converse.api.waitUntil('roomsPanelRendered')
+            ]).then(initBookmarks);
+
+            const afterReconnection = function () {
                 if (!_converse.allow_bookmarks) {
                 if (!_converse.allow_bookmarks) {
                     return;
                     return;
                 }
                 }

+ 318 - 176
src/converse-chatview.js

@@ -8,10 +8,14 @@
 
 
 (function (root, factory) {
 (function (root, factory) {
     define([
     define([
+            "jquery.noconflict",
             "converse-core",
             "converse-core",
+            "emojione",
+            "xss",
             "tpl!chatbox",
             "tpl!chatbox",
             "tpl!new_day",
             "tpl!new_day",
             "tpl!action",
             "tpl!action",
+            "tpl!emojis",
             "tpl!message",
             "tpl!message",
             "tpl!help_message",
             "tpl!help_message",
             "tpl!toolbar",
             "tpl!toolbar",
@@ -19,10 +23,14 @@
             "tpl!spinner"
             "tpl!spinner"
     ], factory);
     ], factory);
 }(this, function (
 }(this, function (
+            $,
             converse,
             converse,
+            emojione,
+            xss,
             tpl_chatbox,
             tpl_chatbox,
             tpl_new_day,
             tpl_new_day,
             tpl_action,
             tpl_action,
+            tpl_emojis,
             tpl_message,
             tpl_message,
             tpl_help_message,
             tpl_help_message,
             tpl_toolbar,
             tpl_toolbar,
@@ -30,20 +38,13 @@
             tpl_spinner
             tpl_spinner
     ) {
     ) {
     "use strict";
     "use strict";
-    var $ = converse.env.jQuery,
-        $msg = converse.env.$msg,
-        Backbone = converse.env.Backbone,
-        Strophe = converse.env.Strophe,
-        _ = converse.env._,
-        moment = converse.env.moment,
-        utils = converse.env.utils;
-
-    var KEY = {
+    const { $msg, Backbone, Strophe, _, b64_sha1, moment, utils } = converse.env;
+
+    const KEY = {
         ENTER: 13,
         ENTER: 13,
         FORWARD_SLASH: 47
         FORWARD_SLASH: 47
     };
     };
 
 
-
     converse.plugins.add('converse-chatview', {
     converse.plugins.add('converse-chatview', {
 
 
         overrides: {
         overrides: {
@@ -52,11 +53,26 @@
             // relevant objects or classes.
             // relevant objects or classes.
             //
             //
             // New functions which don't exist yet can also be added.
             // New functions which don't exist yet can also be added.
+            //
+            registerGlobalEventHandlers: function () {
+                this.__super__.registerGlobalEventHandlers();
+                document.addEventListener(
+                    'click', function (ev) {
+                        if (_.includes(ev.target.classList, 'toggle-toolbar-menu') ||
+                            _.includes(ev.target.classList, 'insert-emoji')) {
+                            return;
+                        }
+                        utils.slideInAllElements(
+                            document.querySelectorAll('.toolbar-menu')
+                        )
+                    }
+                );
+            },
 
 
             ChatBoxViews: {
             ChatBoxViews: {
-                onChatBoxAdded: function (item) {
-                    var _converse = this.__super__._converse;
-                    var view = this.get(item.get('id'));
+                onChatBoxAdded (item) {
+                    const { _converse } = this.__super__;
+                    let view = this.get(item.get('id'));
                     if (!view) {
                     if (!view) {
                         view = new _converse.ChatBoxView({model: item});
                         view = new _converse.ChatBoxView({model: item});
                         this.add(item.get('id'), view);
                         this.add(item.get('id'), view);
@@ -68,35 +84,138 @@
             }
             }
         },
         },
 
 
-
-        initialize: function () {
+        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.
              */
              */
-            var _converse = this._converse,
-                __ = _converse.__;
+            const { _converse } = this,
+                { __ } = _converse;
 
 
             _converse.api.settings.update({
             _converse.api.settings.update({
-                chatview_avatar_height: 32,
-                chatview_avatar_width: 32,
-                show_toolbar: true,
-                time_format: 'HH:mm',
-                visible_toolbar_buttons: {
-                    'emoticons': true,
+                'use_emojione': true,
+                'emojione_image_path': emojione.imagePathPNG,
+                'chatview_avatar_height': 32,
+                'chatview_avatar_width': 32,
+                'show_toolbar': true,
+                'time_format': 'HH:mm',
+                'visible_toolbar_buttons': {
+                    'emoji': true,
                     'call': false,
                     'call': false,
                     'clear': true
                     'clear': true
                 },
                 },
             });
             });
+            emojione.imagePathPNG = _converse.emojione_image_path;
+            emojione.ascii = true;
 
 
-            var onWindowStateChanged = function (data) {
-                var state = data.state;
+            function onWindowStateChanged (data) {
                 _converse.chatboxviews.each(function (chatboxview) {
                 _converse.chatboxviews.each(function (chatboxview) {
-                    chatboxview.onWindowStateChanged(state);
-                })
-            };
-
+                    chatboxview.onWindowStateChanged(data.state);
+                });
+            }
             _converse.api.listen.on('windowStateChanged', onWindowStateChanged);
             _converse.api.listen.on('windowStateChanged', onWindowStateChanged);
 
 
+            _converse.EmojiPicker = Backbone.Model.extend({ 
+                defaults: {
+                    'current_category': 'people',
+                    'current_skintone': '',
+                    'scroll_position': 0
+                },
+                initialize () {
+                    const id = `converse.emoji-${_converse.bare_jid}`;
+                    this.id = id;
+                    this.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
+                }
+            });
+
+            _converse.EmojiPickerView = Backbone.View.extend({
+                className: 'emoji-picker-container toolbar-menu collapsed',
+                events: {
+                    'click .emoji-category-picker li.emoji-category': 'chooseCategory',
+                    'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone'
+                },
+
+                initialize () {
+                    this.model.on('change:current_skintone', this.render, this);
+                    this.model.on('change:current_category', this.render, this);
+                    this.setScrollPosition = _.debounce(this.setScrollPosition, 50);
+                },
+
+                render () {
+                    var emojis_html = tpl_emojis(
+                        _.extend(
+                            this.model.toJSON(), {
+                                'transform': _converse.use_emojione ? emojione.shortnameToImage : emojione.shortnameToUnicode,
+                                'emojis_by_category': utils.getEmojisByCategory(_converse, emojione),
+                                'toned_emojis': utils.getTonedEmojis(_converse),
+                                'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'],
+                                'shouldBeHidden': this.shouldBeHidden
+                            }
+                        ));
+                    this.el.innerHTML = emojis_html;
+                    _.forEach(this.el.querySelectorAll('.emoji-picker'), (el) => {
+                        el.addEventListener('scroll', this.setScrollPosition.bind(this));
+                    });
+                    this.restoreScrollPosition();
+                    return this;
+                },
+
+                shouldBeHidden (shortname, current_skintone, toned_emojis) {
+                    /* Helper method for the template which decides whether an
+                     * emoji should be hidden, based on which skin tone is
+                     * currently being applied.
+                     */
+                    if (_.includes(shortname, '_tone')) {
+                        if (!current_skintone || !_.includes(shortname, current_skintone)) {
+                            return true;
+                        }
+                    } else {
+                        if (current_skintone && _.includes(toned_emojis, shortname)) {
+                            return true;
+                        }
+                    }
+                    return false;
+                },
+
+                restoreScrollPosition () {
+                    const current_picker = _.difference(
+                        this.el.querySelectorAll('.emoji-picker'),
+                        this.el.querySelectorAll('.emoji-picker.hidden')
+                    );
+                    if (current_picker.length === 1 && this.model.get('scroll_position')) {
+                        current_picker[0].scrollTop = this.model.get('scroll_position');
+                    }
+                },
+
+                setScrollPosition (ev) {
+                    this.model.save('scroll_position', ev.target.scrollTop);
+                },
+
+                chooseSkinTone (ev) {
+                    ev.preventDefault();
+                    ev.stopPropagation();
+                    const target = ev.target.nodeName === 'IMG' ?
+                        ev.target.parentElement : ev.target;
+                    const skintone = target.getAttribute("data-skintone").trim();
+                    if (this.model.get('current_skintone') === skintone) {
+                        this.model.save({'current_skintone': ''});
+                    } else {
+                        this.model.save({'current_skintone': skintone});
+                    }
+                },
+
+                chooseCategory (ev) {
+                    ev.preventDefault();
+                    ev.stopPropagation();
+                    const target = ev.target.nodeName === 'IMG' ?
+                        ev.target.parentElement : ev.target;
+                    const category = target.getAttribute("data-category").trim();
+                    this.model.save({
+                        'current_category': category,
+                        'scroll_position': 0
+                    });
+                }
+            });
+
             _converse.ChatBoxView = Backbone.View.extend({
             _converse.ChatBoxView = Backbone.View.extend({
                 length: 200,
                 length: 200,
                 tagName: 'div',
                 tagName: 'div',
@@ -106,15 +225,18 @@
                 events: {
                 events: {
                     'click .close-chatbox-button': 'close',
                     'click .close-chatbox-button': 'close',
                     'keypress .chat-textarea': 'keyPressed',
                     'keypress .chat-textarea': 'keyPressed',
-                    'click .send-button': 'onSendButtonClicked',
-                    'click .toggle-smiley': 'toggleEmoticonMenu',
-                    'click .toggle-smiley ul li': 'insertEmoticon',
+                    'click .send-button': 'onFormSubmitted',
+                    'click .toggle-smiley': 'toggleEmojiMenu',
+                    'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
                     'click .toggle-clear': 'clearMessages',
                     'click .toggle-clear': 'clearMessages',
                     'click .toggle-call': 'toggleCall',
                     'click .toggle-call': 'toggleCall',
                     'click .new-msgs-indicator': 'viewUnreadMessages'
                     'click .new-msgs-indicator': 'viewUnreadMessages'
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
+                    this.markScrolled = _.debounce(this.markScrolled, 100);
+
+                    this.createEmojiPicker();
                     this.model.messages.on('add', this.onMessageAdded, this);
                     this.model.messages.on('add', this.onMessageAdded, this);
                     this.model.on('show', this.show, this);
                     this.model.on('show', this.show, this);
                     this.model.on('destroy', this.hide, this);
                     this.model.on('destroy', this.hide, this);
@@ -129,7 +251,7 @@
                     _converse.emit('chatBoxInitialized', this);
                     _converse.emit('chatBoxInitialized', this);
                 },
                 },
 
 
-                render: function () {
+                render () {
                     this.$el.attr('id', this.model.get('box_id'))
                     this.$el.attr('id', this.model.get('box_id'))
                         .html(tpl_chatbox(
                         .html(tpl_chatbox(
                                 _.extend(this.model.toJSON(), {
                                 _.extend(this.model.toJSON(), {
@@ -152,15 +274,26 @@
                     return this.showStatusMessage();
                     return this.showStatusMessage();
                 },
                 },
 
 
-                afterMessagesFetched: function () {
+                createEmojiPicker () {
+                    if (_.isUndefined(_converse.emojipicker)) {
+                        _converse.emojipicker = new _converse.EmojiPicker();
+                        _converse.emojipicker.fetch();
+                    }
+                    this.emoji_picker_view = new _converse.EmojiPickerView({
+                        'model': _converse.emojipicker
+                    });
+                },
+
+                afterMessagesFetched () {
                     this.insertIntoDOM();
                     this.insertIntoDOM();
                     this.scrollDown();
                     this.scrollDown();
                     // We only start listening for the scroll event after
                     // We only start listening for the scroll event after
                     // cached messages have been fetched
                     // cached messages have been fetched
                     this.$content.on('scroll', this.markScrolled.bind(this));
                     this.$content.on('scroll', this.markScrolled.bind(this));
+                    _converse.emit('afterMessagesFetched', this);
                 },
                 },
 
 
-                fetchMessages: function () {
+                fetchMessages () {
                     this.model.messages.fetch({
                     this.model.messages.fetch({
                         'add': true,
                         'add': true,
                         'success': this.afterMessagesFetched.bind(this),
                         'success': this.afterMessagesFetched.bind(this),
@@ -169,26 +302,26 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                insertIntoDOM: function () {
+                insertIntoDOM () {
                     /* This method gets overridden in src/converse-controlbox.js if
                     /* This method gets overridden in src/converse-controlbox.js if
                      * the controlbox plugin is active.
                      * the controlbox plugin is active.
                      */
                      */
-                    var container = document.querySelector('#conversejs');
+                    const container = document.querySelector('#conversejs');
                     if (this.el.parentNode !== container) {
                     if (this.el.parentNode !== container) {
                         container.insertBefore(this.el, container.firstChild);
                         container.insertBefore(this.el, container.firstChild);
                     }
                     }
                     return this;
                     return this;
                 },
                 },
 
 
-                clearStatusNotification: function () {
+                clearStatusNotification () {
                     this.$content.find('div.chat-event').remove();
                     this.$content.find('div.chat-event').remove();
                 },
                 },
 
 
-                showStatusNotification: function (message, keep_old, permanent) {
+                showStatusNotification (message, keep_old, permanent) {
                     if (!keep_old) {
                     if (!keep_old) {
                         this.clearStatusNotification();
                         this.clearStatusNotification();
                     }
                     }
-                    var $el = $('<div class="chat-info"></div>').text(message);
+                    const $el = $('<div class="chat-info"></div>').text(message);
                     if (!permanent) {
                     if (!permanent) {
                         $el.addClass('chat-event');
                         $el.addClass('chat-event');
                     }
                     }
@@ -196,19 +329,19 @@
                     this.scrollDown();
                     this.scrollDown();
                 },
                 },
 
 
-                addSpinner: function () {
+                addSpinner () {
                     if (_.isNull(this.el.querySelector('.spinner'))) {
                     if (_.isNull(this.el.querySelector('.spinner'))) {
                         this.$content.prepend(tpl_spinner);
                         this.$content.prepend(tpl_spinner);
                     }
                     }
                 },
                 },
 
 
-                clearSpinner: function () {
+                clearSpinner () {
                     if (this.$content.children(':first').is('span.spinner')) {
                     if (this.$content.children(':first').is('span.spinner')) {
                         this.$content.children(':first').remove();
                         this.$content.children(':first').remove();
                     }
                     }
                 },
                 },
 
 
-                insertDayIndicator: function (date, prepend) {
+                insertDayIndicator (date, prepend) {
                     /* Appends (or prepends if "prepend" is truthy) an indicator
                     /* Appends (or prepends if "prepend" is truthy) an indicator
                      * into the chat area, showing the day as given by the
                      * into the chat area, showing the day as given by the
                      * passed in date.
                      * passed in date.
@@ -216,15 +349,15 @@
                      * Parameters:
                      * Parameters:
                      *  (String) date - An ISO8601 date string.
                      *  (String) date - An ISO8601 date string.
                      */
                      */
-                    var day_date = moment(date).startOf('day');
-                    var insert = prepend ? this.$content.prepend: this.$content.append;
+                    const day_date = moment(date).startOf('day');
+                    const insert = prepend ? this.$content.prepend: this.$content.append;
                     insert.call(this.$content, tpl_new_day({
                     insert.call(this.$content, tpl_new_day({
                         isodate: day_date.format(),
                         isodate: day_date.format(),
                         datestring: day_date.format("dddd MMM Do YYYY")
                         datestring: day_date.format("dddd MMM Do YYYY")
                     }));
                     }));
                 },
                 },
 
 
-                insertMessage: function (attrs, prepend) {
+                insertMessage (attrs, prepend) {
                     /* Helper method which appends a message (or prepends if the
                     /* Helper method which appends a message (or prepends if the
                      * 2nd parameter is set to true) to the end of the chat box's
                      * 2nd parameter is set to true) to the end of the chat box's
                      * content area.
                      * content area.
@@ -232,18 +365,16 @@
                      * Parameters:
                      * Parameters:
                      *  (Object) attrs: An object containing the message attributes.
                      *  (Object) attrs: An object containing the message attributes.
                      */
                      */
-                    var that = this;
-                    var insert = prepend ? this.$content.prepend : this.$content.append;
-                    _.flow(
-                        function ($el) {
-                            insert.call(that.$content, $el);
+                    const insert = prepend ? this.$content.prepend : this.$content.append;
+                    _.flow(($el) => {
+                            insert.call(this.$content, $el);
                             return $el;
                             return $el;
                         },
                         },
                         this.scrollDown.bind(this)
                         this.scrollDown.bind(this)
                     )(this.renderMessage(attrs));
                     )(this.renderMessage(attrs));
                 },
                 },
 
 
-                showMessage: function (attrs) {
+                showMessage (attrs) {
                     /* Inserts a chat message into the content area of the chat box.
                     /* Inserts a chat message into the content area of the chat box.
                      * Will also insert a new day indicator if the message is on a
                      * Will also insert a new day indicator if the message is on a
                      * different day.
                      * different day.
@@ -255,11 +386,9 @@
                      *  (Object) attrs: An object containing the message
                      *  (Object) attrs: An object containing the message
                      *      attributes.
                      *      attributes.
                      */
                      */
-                    var msg_dates,
-                        $first_msg = this.$content.find('.chat-message:first'),
-                        first_msg_date = $first_msg.data('isodate'),
-                        current_msg_date = moment(attrs.time) || moment,
-                        last_msg_date = this.$content.find('.chat-message:last').data('isodate');
+                    let current_msg_date = moment(attrs.time) || moment;
+                    const $first_msg = this.$content.find('.chat-message:first'),
+                          first_msg_date = $first_msg.data('isodate');
 
 
                     if (!first_msg_date) {
                     if (!first_msg_date) {
                         // This is the first received message, so we insert a
                         // This is the first received message, so we insert a
@@ -268,6 +397,8 @@
                         this.insertMessage(attrs);
                         this.insertMessage(attrs);
                         return;
                         return;
                     }
                     }
+
+                    const last_msg_date = this.$content.find('.chat-message:last').data('isodate');
                     if (current_msg_date.isAfter(last_msg_date) ||
                     if (current_msg_date.isAfter(last_msg_date) ||
                             current_msg_date.isSame(last_msg_date)) {
                             current_msg_date.isSame(last_msg_date)) {
                         // The new message is after the last message
                         // The new message is after the last message
@@ -294,22 +425,24 @@
                     }
                     }
                     // Find the correct place to position the message
                     // Find the correct place to position the message
                     current_msg_date = current_msg_date.format();
                     current_msg_date = current_msg_date.format();
-                    msg_dates = _.map(this.$content.find('.chat-message'), function (el) {
-                        return $(el).data('isodate');
-                    });
+                    const msg_dates = _.map(
+                        this.$content.find('.chat-message'),
+                        (el) => $(el).data('isodate')
+                    );
                     msg_dates.push(current_msg_date);
                     msg_dates.push(current_msg_date);
                     msg_dates.sort();
                     msg_dates.sort();
-                    var idx = msg_dates.indexOf(current_msg_date)-1;
-                    var $latest_message = this.$content.find('.chat-message[data-isodate="'+msg_dates[idx]+'"]:last');
-                    _.flow(
-                        function ($el) {
+
+                    const idx = msg_dates.indexOf(current_msg_date)-1;
+                    const $latest_message = this.$content.find(`.chat-message[data-isodate="${msg_dates[idx]}"]:last`);
+                    _.flow(($el) => {
                             $el.insertAfter($latest_message);
                             $el.insertAfter($latest_message);
+                            return $el;
                         },
                         },
                         this.scrollDown.bind(this)
                         this.scrollDown.bind(this)
                     )(this.renderMessage(attrs));
                     )(this.renderMessage(attrs));
                 },
                 },
 
 
-                getExtraMessageTemplateAttributes: function () {
+                getExtraMessageTemplateAttributes () {
                     /* Provides a hook for sending more attributes to the
                     /* Provides a hook for sending more attributes to the
                      * message template.
                      * message template.
                      *
                      *
@@ -319,11 +452,11 @@
                     return {};
                     return {};
                 },
                 },
 
 
-                getExtraMessageClasses: function (attrs) {
+                getExtraMessageClasses (attrs) {
                     return attrs.delayed && 'delayed' || '';
                     return attrs.delayed && 'delayed' || '';
                 },
                 },
 
 
-                renderMessage: function (attrs) {
+                renderMessage (attrs) {
                     /* Renders a chat message based on the passed in attributes.
                     /* Renders a chat message based on the passed in attributes.
                      *
                      *
                      * Parameters:
                      * Parameters:
@@ -332,12 +465,11 @@
                      *  Returns:
                      *  Returns:
                      *      The DOM element representing the message.
                      *      The DOM element representing the message.
                      */
                      */
-                    var msg_time = moment(attrs.time) || moment,
-                        text = attrs.message,
-                        match = text.match(/^\/(.*?)(?: (.*))?$/),
+                    let text = attrs.message,
                         fullname = this.model.get('fullname') || attrs.fullname,
                         fullname = this.model.get('fullname') || attrs.fullname,
                         template, username;
                         template, username;
 
 
+                    const match = text.match(/^\/(.*?)(?: (.*))?$/);
                     if ((match) && (match[1] === 'me')) {
                     if ((match) && (match[1] === 'me')) {
                         text = text.replace(/^\/me/, '');
                         text = text.replace(/^\/me/, '');
                         template = tpl_action;
                         template = tpl_action;
@@ -361,7 +493,8 @@
                                "Output has been shortened."),
                                "Output has been shortened."),
                             true, true);
                             true, true);
                     }
                     }
-                    var $msg = $(template(
+                    const msg_time = moment(attrs.time) || moment;
+                    const $msg = $(template(
                         _.extend(this.getExtraMessageTemplateAttributes(attrs), {
                         _.extend(this.getExtraMessageTemplateAttributes(attrs), {
                             'msgid': attrs.msgid,
                             'msgid': attrs.msgid,
                             'sender': attrs.sender,
                             'sender': attrs.sender,
@@ -371,21 +504,21 @@
                             'extra_classes': this.getExtraMessageClasses(attrs)
                             'extra_classes': this.getExtraMessageClasses(attrs)
                         })
                         })
                     ));
                     ));
-                    $msg.find('.chat-msg-content').first()
-                        .text(text)
-                        .addHyperlinks()
-                        .addEmoticons(_converse.visible_toolbar_buttons.emoticons);
+                    const msg_content = $msg[0].querySelector('.chat-msg-content');
+                    msg_content.innerHTML = utils.addEmoji(
+                        _converse, emojione, utils.addHyperlinks(xss.filterXSS(text, {'whiteList': {}}))
+                    );
+                    utils.renderImageURLs(msg_content);
                     return $msg;
                     return $msg;
                 },
                 },
 
 
-                showHelpMessages: function (msgs, type, spinner) {
-                    var i, msgs_length = msgs.length;
-                    for (i=0; i<msgs_length; i++) {
+                showHelpMessages (msgs, type, spinner) {
+                    _.each(msgs, (msg) => {
                         this.$content.append($(tpl_help_message({
                         this.$content.append($(tpl_help_message({
                             'type': type||'info',
                             'type': type||'info',
-                            'message': msgs[i]
+                            'message': msgs
                         })));
                         })));
-                    }
+                    });
                     if (spinner === true) {
                     if (spinner === true) {
                         this.$content.append(tpl_spinner);
                         this.$content.append(tpl_spinner);
                     } else if (spinner === false) {
                     } else if (spinner === false) {
@@ -394,7 +527,7 @@
                     return this.scrollDown();
                     return this.scrollDown();
                 },
                 },
 
 
-                handleChatStateMessage: function (message) {
+                handleChatStateMessage (message) {
                     if (message.get('chat_state') === _converse.COMPOSING) {
                     if (message.get('chat_state') === _converse.COMPOSING) {
                         if (message.get('sender') === 'me') {
                         if (message.get('sender') === 'me') {
                             this.showStatusNotification(__('Typing from another device'));
                             this.showStatusNotification(__('Typing from another device'));
@@ -415,11 +548,11 @@
                     }
                     }
                 },
                 },
 
 
-                shouldShowOnTextMessage: function () {
+                shouldShowOnTextMessage () {
                     return !this.$el.is(':visible');
                     return !this.$el.is(':visible');
                 },
                 },
 
 
-                handleTextMessage: function (message) {
+                handleTextMessage (message) {
                     this.showMessage(_.clone(message.attributes));
                     this.showMessage(_.clone(message.attributes));
                     if (utils.isNewMessage(message) && message.get('sender') === 'me') {
                     if (utils.isNewMessage(message) && message.get('sender') === 'me') {
                         // We remove the "scrolled" flag so that the chat area
                         // We remove the "scrolled" flag so that the chat area
@@ -439,15 +572,15 @@
                     }
                     }
                 },
                 },
 
 
-                handleErrorMessage: function (message) {
-                    var $message = $('[data-msgid='+message.get('msgid')+']');
+                handleErrorMessage (message) {
+                    const $message = $(`[data-msgid=${message.get('msgid')}]`);
                     if ($message.length) {
                     if ($message.length) {
                         $message.after($('<div class="chat-info chat-error"></div>').text(message.get('message')));
                         $message.after($('<div class="chat-info chat-error"></div>').text(message.get('message')));
                         this.scrollDown();
                         this.scrollDown();
                     }
                     }
                 },
                 },
 
 
-                onMessageAdded: function (message) {
+                onMessageAdded (message) {
                     /* Handler that gets called when a new message object is created.
                     /* Handler that gets called when a new message object is created.
                      *
                      *
                      * Parameters:
                      * Parameters:
@@ -470,7 +603,7 @@
                     });
                     });
                 },
                 },
 
 
-                createMessageStanza: function (message) {
+                createMessageStanza (message) {
                     return $msg({
                     return $msg({
                                 from: _converse.connection.jid,
                                 from: _converse.connection.jid,
                                 to: this.model.get('jid'),
                                 to: this.model.get('jid'),
@@ -480,7 +613,7 @@
                             .c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
                             .c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
                 },
                 },
 
 
-                sendMessage: function (message) {
+                sendMessage (message) {
                     /* Responsible for sending off a text message.
                     /* Responsible for sending off a text message.
                      *
                      *
                      *  Parameters:
                      *  Parameters:
@@ -488,7 +621,7 @@
                      */
                      */
                     // TODO: We might want to send to specfic resources.
                     // TODO: We might want to send to specfic resources.
                     // Especially in the OTR case.
                     // Especially in the OTR case.
-                    var messageStanza = this.createMessageStanza(message);
+                    const messageStanza = this.createMessageStanza(message);
                     _converse.connection.send(messageStanza);
                     _converse.connection.send(messageStanza);
                     if (_converse.forward_messages) {
                     if (_converse.forward_messages) {
                         // Forward the message, so that other connected resources are also aware of it.
                         // Forward the message, so that other connected resources are also aware of it.
@@ -501,7 +634,7 @@
                     }
                     }
                 },
                 },
 
 
-                onMessageSubmitted: function (text) {
+                onMessageSubmitted (text) {
                     /* This method gets called once the user has typed a message
                     /* This method gets called once the user has typed a message
                      * and then pressed enter in a chat box.
                      * and then pressed enter in a chat box.
                      *
                      *
@@ -515,25 +648,26 @@
                             'error'
                             'error'
                         );
                         );
                     }
                     }
-                    var match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/), msgs;
+                    const match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/);
                     if (match) {
                     if (match) {
                         if (match[1] === "clear") {
                         if (match[1] === "clear") {
                             return this.clearMessages();
                             return this.clearMessages();
                         }
                         }
                         else if (match[1] === "help") {
                         else if (match[1] === "help") {
-                            msgs = [
-                                '<strong>/help</strong>:'+__('Show this menu')+'',
-                                '<strong>/me</strong>:'+__('Write in the third person')+'',
-                                '<strong>/clear</strong>:'+__('Remove messages')+''
+                            const msgs = [
+                                `<strong>/help</strong>:${__('Show this menu')}`,
+                                `<strong>/me</strong>:${__('Write in the third person')}`,
+                                `<strong>/clear</strong>:${__('Remove messages')}`
                                 ];
                                 ];
                             this.showHelpMessages(msgs);
                             this.showHelpMessages(msgs);
                             return;
                             return;
                         }
                         }
                     }
                     }
-                    var fullname = _converse.xmppstatus.get('fullname');
+                    let fullname = _converse.xmppstatus.get('fullname');
                     fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname;
                     fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname;
-                    var message = this.model.messages.create({
-                        fullname: fullname,
+
+                    const message = this.model.messages.create({
+                        fullname,
                         sender: 'me',
                         sender: 'me',
                         time: moment().format(),
                         time: moment().format(),
                         message: text
                         message: text
@@ -541,7 +675,7 @@
                     this.sendMessage(message);
                     this.sendMessage(message);
                 },
                 },
 
 
-                sendChatState: function () {
+                sendChatState () {
                     /* Sends a message with the status of the user in this chat session
                     /* Sends a message with the status of the user in this chat session
                      * as taken from the 'chat_state' attribute of the chat box.
                      * as taken from the 'chat_state' attribute of the chat box.
                      * See XEP-0085 Chat State Notifications.
                      * See XEP-0085 Chat State Notifications.
@@ -554,7 +688,7 @@
                     );
                     );
                 },
                 },
 
 
-                setChatState: function (state, no_save) {
+                setChatState (state, no_save) {
                     /* Mutator for setting the chat state of this chat session.
                     /* Mutator for setting the chat state of this chat session.
                      * Handles clearing of any chat state notification timeouts and
                      * Handles clearing of any chat state notification timeouts and
                      * setting new ones if necessary.
                      * setting new ones if necessary.
@@ -583,34 +717,10 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                keyPressed: function (ev) {
-                    /* Event handler for when a key is pressed in a chat box textarea.
-                     */
-                    var textarea = ev.target, message;
-                    if (ev.keyCode === KEY.ENTER) {
-                        ev.preventDefault();
-                        message = textarea.value;
-                        textarea.value = '';
-                        textarea.focus();
-                        if (message !== '') {
-                            this.onMessageSubmitted(message);
-                            _converse.emit('messageSend', message);
-                        }
-                        this.setChatState(_converse.ACTIVE);
-                    } else {
-                        // Set chat state to composing if keyCode is not a forward-slash
-                        // (which would imply an internal command and not a message).
-                        this.setChatState(_converse.COMPOSING, ev.keyCode === KEY.FORWARD_SLASH);
-                    }
-                },
-
-                onSendButtonClicked: function(ev) {
-                    /* Event handler for when a send button is clicked in a chat box textarea.
-                     */
+                onFormSubmitted (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var textarea = this.el.querySelector('.chat-textarea'),
-                        message = textarea.value;
-
+                    const textarea = this.el.querySelector('.chat-textarea'),
+                          message = textarea.value;
                     textarea.value = '';
                     textarea.value = '';
                     textarea.focus();
                     textarea.focus();
                     if (message !== '') {
                     if (message !== '') {
@@ -620,9 +730,21 @@
                     this.setChatState(_converse.ACTIVE);
                     this.setChatState(_converse.ACTIVE);
                 },
                 },
 
 
-                clearMessages: function (ev) {
+                keyPressed (ev) {
+                    /* Event handler for when a key is pressed in a chat box textarea.
+                     */
+                    if (ev.keyCode === KEY.ENTER) {
+                        this.onFormSubmitted(ev);
+                    } else {
+                        // Set chat state to composing if keyCode is not a forward-slash
+                        // (which would imply an internal command and not a message).
+                        this.setChatState(_converse.COMPOSING, ev.keyCode === KEY.FORWARD_SLASH);
+                    }
+                },
+
+                clearMessages (ev) {
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    var result = confirm(__("Are you sure you want to clear the messages from this chat box?"));
+                    const result = confirm(__("Are you sure you want to clear the messages from this chat box?"));
                     if (result === true) {
                     if (result === true) {
                         this.$content.empty();
                         this.$content.empty();
                         this.model.messages.reset();
                         this.model.messages.reset();
@@ -631,29 +753,44 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                insertIntoTextArea: function (value) {
-                    var $textbox = this.$el.find('textarea.chat-textarea');
-                    var existing = $textbox.val();
+                insertIntoTextArea (value) {
+                    const $textbox = this.$el.find('textarea.chat-textarea');
+                    let existing = $textbox.val();
                     if (existing && (existing[existing.length-1] !== ' ')) {
                     if (existing && (existing[existing.length-1] !== ' ')) {
                         existing = existing + ' ';
                         existing = existing + ' ';
                     }
                     }
                     $textbox.focus().val(existing+value+' ');
                     $textbox.focus().val(existing+value+' ');
                 },
                 },
 
 
-                insertEmoticon: function (ev) {
+                insertEmoji (ev) {
                     ev.stopPropagation();
                     ev.stopPropagation();
-                    this.$el.find('.toggle-smiley ul').slideToggle(200);
-                    var $target = $(ev.target);
-                    $target = $target.is('a') ? $target : $target.children('a');
-                    this.insertIntoTextArea($target.data('emoticon'));
+                    this.toggleEmojiMenu();
+                    const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
+                    this.insertIntoTextArea(target.getAttribute('data-emoji'));
                 },
                 },
 
 
-                toggleEmoticonMenu: function (ev) {
-                    ev.stopPropagation();
-                    this.$el.find('.toggle-smiley ul').slideToggle(200);
+                toggleEmojiMenu (ev) {
+                    if (!_.isUndefined(ev)) {
+                        ev.stopPropagation();
+                        if (ev.target.classList.contains('emoji-category-picker') ||
+                            ev.target.classList.contains('emoji-skintone-picker') ||
+                                ev.target.classList.contains('emoji-category')) {
+                            return;
+                        }
+                    }
+                    const elements = _.difference(
+                        document.querySelectorAll('.toolbar-menu'),
+                        [this.emoji_picker_view.el]
+                    );
+                    utils.slideInAllElements(elements).then(
+                        _.partial(
+                            utils.slideToggleElement,
+                            this.emoji_picker_view.el
+                        )
+                    );
                 },
                 },
 
 
-                toggleCall: function (ev) {
+                toggleCall (ev) {
                     ev.stopPropagation();
                     ev.stopPropagation();
                     _converse.emit('callButtonClicked', {
                     _converse.emit('callButtonClicked', {
                         connection: _converse.connection,
                         connection: _converse.connection,
@@ -661,9 +798,9 @@
                     });
                     });
                 },
                 },
 
 
-                onChatStatusChanged: function (item) {
-                    var chat_status = item.get('chat_status'),
-                        fullname = item.get('fullname');
+                onChatStatusChanged (item) {
+                    const chat_status = item.get('chat_status');
+                    let fullname = item.get('fullname');
                     fullname = _.isEmpty(fullname)? item.get('jid'): fullname;
                     fullname = _.isEmpty(fullname)? item.get('jid'): fullname;
                     if (this.$el.is(':visible')) {
                     if (this.$el.is(':visible')) {
                         if (chat_status === 'offline') {
                         if (chat_status === 'offline') {
@@ -678,7 +815,7 @@
                     }
                     }
                 },
                 },
 
 
-                onStatusChanged: function (item) {
+                onStatusChanged (item) {
                     this.showStatusMessage();
                     this.showStatusMessage();
                     _converse.emit('contactStatusMessageChanged', {
                     _converse.emit('contactStatusMessageChanged', {
                         'contact': item.attributes,
                         'contact': item.attributes,
@@ -686,7 +823,7 @@
                     });
                     });
                 },
                 },
 
 
-                showStatusMessage: function (msg) {
+                showStatusMessage (msg) {
                     msg = msg || this.model.get('status');
                     msg = msg || this.model.get('status');
                     if (_.isString(msg)) {
                     if (_.isString(msg)) {
                         this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
                         this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
@@ -694,7 +831,7 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                close: function (ev) {
+                close (ev) {
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (_converse.connection.connected) {
                     if (_converse.connection.connected) {
                         // Immediately sending the chat state, because the
                         // Immediately sending the chat state, because the
@@ -712,35 +849,39 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                getToolbarOptions: function (options) {
+                getToolbarOptions (options) {
                     return _.extend(options || {}, {
                     return _.extend(options || {}, {
                         'label_clear': __('Clear all messages'),
                         'label_clear': __('Clear all messages'),
                         'label_insert_smiley': __('Insert a smiley'),
                         'label_insert_smiley': __('Insert a smiley'),
                         'label_start_call': __('Start a call'),
                         'label_start_call': __('Start a call'),
                         'show_call_button': _converse.visible_toolbar_buttons.call,
                         'show_call_button': _converse.visible_toolbar_buttons.call,
                         'show_clear_button': _converse.visible_toolbar_buttons.clear,
                         'show_clear_button': _converse.visible_toolbar_buttons.clear,
-                        'show_emoticons': _converse.visible_toolbar_buttons.emoticons,
+                        'use_emoji': _converse.visible_toolbar_buttons.emoji,
                     });
                     });
                 },
                 },
 
 
-                renderToolbar: function (toolbar, options) {
+                renderToolbar (toolbar, options) {
                     if (!_converse.show_toolbar) { return; }
                     if (!_converse.show_toolbar) { return; }
                     toolbar = toolbar || tpl_toolbar;
                     toolbar = toolbar || tpl_toolbar;
-                    options = _.extend(
+                    options = _.assign(
                         this.model.toJSON(),
                         this.model.toJSON(),
                         this.getToolbarOptions(options || {})
                         this.getToolbarOptions(options || {})
                     );
                     );
-                    this.$el.find('.chat-toolbar').html(toolbar(options));
+                    this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options);
+
+                    var toggle = this.el.querySelector('.toggle-smiley');
+                    toggle.innerHTML = '';
+                    toggle.appendChild(this.emoji_picker_view.render().el);
                     return this;
                     return this;
                 },
                 },
 
 
-                renderAvatar: function () {
+                renderAvatar () {
                     if (!this.model.get('image')) {
                     if (!this.model.get('image')) {
                         return;
                         return;
                     }
                     }
-                    var width = _converse.chatview_avatar_width;
-                    var height = _converse.chatview_avatar_height;
-                    var img_src = 'data:'+this.model.get('image_type')+';base64,'+this.model.get('image'),
+                    const width = _converse.chatview_avatar_width;
+                    const height = _converse.chatview_avatar_height;
+                    const img_src = `data:${this.model.get('image_type')};base64,${this.model.get('image')}`,
                         canvas = $(tpl_avatar({
                         canvas = $(tpl_avatar({
                             'width': width,
                             'width': width,
                             'height': height
                             'height': height
@@ -749,10 +890,10 @@
                     if (!(canvas.getContext && canvas.getContext('2d'))) {
                     if (!(canvas.getContext && canvas.getContext('2d'))) {
                         return this;
                         return this;
                     }
                     }
-                    var ctx = canvas.getContext('2d');
-                    var img = new Image();   // Create new Image object
+                    const ctx = canvas.getContext('2d');
+                    const img = new Image();   // Create new Image object
                     img.onload = function () {
                     img.onload = function () {
-                        var ratio = img.width/img.height;
+                        const ratio = img.width/img.height;
                         if (ratio < 1) {
                         if (ratio < 1) {
                             ctx.drawImage(img, 0,0, width, height*(1/ratio));
                             ctx.drawImage(img, 0,0, width, height*(1/ratio));
                         } else {
                         } else {
@@ -765,19 +906,19 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                focus: function () {
+                focus () {
                     this.$el.find('.chat-textarea').focus();
                     this.$el.find('.chat-textarea').focus();
                     _converse.emit('chatBoxFocused', this);
                     _converse.emit('chatBoxFocused', this);
                     return this;
                     return this;
                 },
                 },
 
 
-                hide: function () {
+                hide () {
                     this.el.classList.add('hidden');
                     this.el.classList.add('hidden');
                     utils.refreshWebkit();
                     utils.refreshWebkit();
                     return this;
                     return this;
                 },
                 },
 
 
-                afterShown: function (focus) {
+                afterShown (focus) {
                     if (utils.isPersistableModel(this.model)) {
                     if (utils.isPersistableModel(this.model)) {
                         this.model.save();
                         this.model.save();
                     }
                     }
@@ -788,7 +929,7 @@
                     }
                     }
                 },
                 },
 
 
-                _show: function (focus) {
+                _show (focus) {
                     /* Inner show method that gets debounced */
                     /* Inner show method that gets debounced */
                     if (this.$el.is(':visible') && this.$el.css('opacity') === "1") {
                     if (this.$el.is(':visible') && this.$el.css('opacity') === "1") {
                         if (focus) { this.focus(); }
                         if (focus) { this.focus(); }
@@ -797,7 +938,7 @@
                     utils.fadeIn(this.el, _.bind(this.afterShown, this, focus));
                     utils.fadeIn(this.el, _.bind(this.afterShown, this, focus));
                 },
                 },
 
 
-                show: function (focus) {
+                show (focus) {
                     if (_.isUndefined(this.debouncedShow)) {
                     if (_.isUndefined(this.debouncedShow)) {
                         /* We wrap the method in a debouncer and set it on the
                         /* We wrap the method in a debouncer and set it on the
                          * instance, so that we have it debounced per instance.
                          * instance, so that we have it debounced per instance.
@@ -809,14 +950,14 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                hideNewMessagesIndicator: function () {
-                    var new_msgs_indicator = this.el.querySelector('.new-msgs-indicator');
+                hideNewMessagesIndicator () {
+                    const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator');
                     if (!_.isNull(new_msgs_indicator)) {
                     if (!_.isNull(new_msgs_indicator)) {
                         new_msgs_indicator.classList.add('hidden');
                         new_msgs_indicator.classList.add('hidden');
                     }
                     }
                 },
                 },
 
 
-                markScrolled: _.debounce(function (ev) {
+                markScrolled: function (ev) {
                     /* Called when the chat content is scrolled up or down.
                     /* Called when the chat content is scrolled up or down.
                      * We want to record when the user has scrolled away from
                      * We want to record when the user has scrolled away from
                      * the bottom, so that we don't automatically scroll away
                      * the bottom, so that we don't automatically scroll away
@@ -831,23 +972,24 @@
                         });
                         });
                         return;
                         return;
                     }
                     }
-                    var scrolled = true;
-                    var is_at_bottom =
+                    let scrolled = true;
+                    const is_at_bottom =
                         (this.$content.scrollTop() + this.$content.innerHeight()) >=
                         (this.$content.scrollTop() + this.$content.innerHeight()) >=
                             this.$content[0].scrollHeight-10;
                             this.$content[0].scrollHeight-10;
+
                     if (is_at_bottom) {
                     if (is_at_bottom) {
                         scrolled = false;
                         scrolled = false;
                         this.onScrolledDown();
                         this.onScrolledDown();
                     }
                     }
                     utils.safeSave(this.model, {'scrolled': scrolled});
                     utils.safeSave(this.model, {'scrolled': scrolled});
-                }, 150),
+                },
 
 
-                viewUnreadMessages: function () {
+                viewUnreadMessages () {
                     this.model.save('scrolled', false);
                     this.model.save('scrolled', false);
                     this.scrollDown();
                     this.scrollDown();
                 },
                 },
 
 
-                _scrollDown: function () {
+                _scrollDown () {
                     /* Inner method that gets debounced */
                     /* Inner method that gets debounced */
                     if (this.$content.is(':visible') && !this.model.get('scrolled')) {
                     if (this.$content.is(':visible') && !this.model.get('scrolled')) {
                         this.$content.scrollTop(this.$content[0].scrollHeight);
                         this.$content.scrollTop(this.$content[0].scrollHeight);
@@ -856,7 +998,7 @@
                     }
                     }
                 },
                 },
 
 
-                onScrolledDown: function() {
+                onScrolledDown() {
                     this.hideNewMessagesIndicator();
                     this.hideNewMessagesIndicator();
                     if (_converse.windowState !== 'hidden') {
                     if (_converse.windowState !== 'hidden') {
                         this.model.clearUnreadMsgCounter();
                         this.model.clearUnreadMsgCounter();
@@ -864,7 +1006,7 @@
                     _converse.emit('chatBoxScrolledDown', {'chatbox': this.model});
                     _converse.emit('chatBoxScrolledDown', {'chatbox': this.model});
                 },
                 },
 
 
-                scrollDown: function () {
+                scrollDown () {
                     if (_.isUndefined(this.debouncedScrollDown)) {
                     if (_.isUndefined(this.debouncedScrollDown)) {
                         /* We wrap the method in a debouncer and set it on the
                         /* We wrap the method in a debouncer and set it on the
                          * instance, so that we have it debounced per instance.
                          * instance, so that we have it debounced per instance.
@@ -876,7 +1018,7 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                onWindowStateChanged: function (state) {
+                onWindowStateChanged (state) {
                     if (this.model.get('num_unread', 0) && !this.model.newMessageWillBeHidden()) {
                     if (this.model.get('num_unread', 0) && !this.model.newMessageWillBeHidden()) {
                         this.model.clearUnreadMsgCounter();
                         this.model.clearUnreadMsgCounter();
                     }
                     }

+ 138 - 134
src/converse-controlbox.js

@@ -7,7 +7,8 @@
 /*global define */
 /*global define */
 
 
 (function (root, factory) {
 (function (root, factory) {
-    define(["converse-core",
+    define(["jquery.noconflict",
+            "converse-core",
             "tpl!add_contact_dropdown",
             "tpl!add_contact_dropdown",
             "tpl!add_contact_form",
             "tpl!add_contact_form",
             "tpl!change_status_message",
             "tpl!change_status_message",
@@ -25,6 +26,7 @@
             "converse-rosterview"
             "converse-rosterview"
     ], factory);
     ], factory);
 }(this, function (
 }(this, function (
+            $,
             converse,
             converse,
             tpl_add_contact_dropdown,
             tpl_add_contact_dropdown,
             tpl_add_contact_form,
             tpl_add_contact_form,
@@ -42,17 +44,9 @@
         ) {
         ) {
     "use strict";
     "use strict";
 
 
-    var USERS_PANEL_ID = 'users';
-    var CHATBOX_TYPE = 'chatbox';
-    // Strophe methods for building stanzas
-    var Strophe = converse.env.Strophe,
-        Backbone = converse.env.Backbone,
-        utils = converse.env.utils;
-    // Other necessary globals
-    var $ = converse.env.jQuery,
-        _ = converse.env._,
-        fp = converse.env.fp,
-        moment = converse.env.moment;
+    const USERS_PANEL_ID = 'users';
+    const CHATBOX_TYPE = 'chatbox';
+    const { Strophe, Backbone, utils, _, fp, moment } = converse.env;
 
 
 
 
     converse.plugins.add('converse-controlbox', {
     converse.plugins.add('converse-controlbox', {
@@ -64,19 +58,19 @@
             //
             //
             // New functions which don't exist yet can also be added.
             // New functions which don't exist yet can also be added.
 
 
-            initChatBoxes: function () {
+            initChatBoxes () {
                 this.__super__.initChatBoxes.apply(this, arguments);
                 this.__super__.initChatBoxes.apply(this, arguments);
                 this.controlboxtoggle = new this.ControlBoxToggle();
                 this.controlboxtoggle = new this.ControlBoxToggle();
             },
             },
 
 
-            initConnection: function () {
+            initConnection () {
                 this.__super__.initConnection.apply(this, arguments);
                 this.__super__.initConnection.apply(this, arguments);
                 if (this.connection) {
                 if (this.connection) {
                     this.addControlBox();
                     this.addControlBox();
                 }
                 }
             },
             },
 
 
-            _tearDown: function () {
+            _tearDown () {
                 this.__super__._tearDown.apply(this, arguments);
                 this.__super__._tearDown.apply(this, arguments);
                 if (this.rosterview) {
                 if (this.rosterview) {
                     // Removes roster groups
                     // Removes roster groups
@@ -89,9 +83,9 @@
                 }
                 }
             },
             },
 
 
-            clearSession: function () {
+            clearSession () {
                 this.__super__.clearSession.apply(this, arguments);
                 this.__super__.clearSession.apply(this, arguments);
-                var controlbox = this.chatboxes.get('controlbox');
+                const controlbox = this.chatboxes.get('controlbox');
                 if (controlbox &&
                 if (controlbox &&
                         controlbox.collection &&
                         controlbox.collection &&
                         controlbox.collection.browserStorage) {
                         controlbox.collection.browserStorage) {
@@ -100,13 +94,13 @@
             },
             },
 
 
             ChatBoxes: {
             ChatBoxes: {
-                chatBoxMayBeShown: function (chatbox) {
+                chatBoxMayBeShown (chatbox) {
                     return this.__super__.chatBoxMayBeShown.apply(this, arguments) &&
                     return this.__super__.chatBoxMayBeShown.apply(this, arguments) &&
                            chatbox.get('id') !== 'controlbox';
                            chatbox.get('id') !== 'controlbox';
                 },
                 },
 
 
-                onChatBoxesFetched: function (collection, resp) {
-                    var _converse = this.__super__._converse;
+                onChatBoxesFetched (collection, resp) {
+                    const { _converse } = this.__super__;
                     this.__super__.onChatBoxesFetched.apply(this, arguments);
                     this.__super__.onChatBoxesFetched.apply(this, arguments);
                     if (!_.includes(_.map(collection, 'id'), 'controlbox')) {
                     if (!_.includes(_.map(collection, 'id'), 'controlbox')) {
                         _converse.addControlBox();
                         _converse.addControlBox();
@@ -116,10 +110,10 @@
             },
             },
 
 
             ChatBoxViews: {
             ChatBoxViews: {
-                onChatBoxAdded: function (item) {
-                    var _converse = this.__super__._converse;
+                onChatBoxAdded (item) {
+                    const { _converse } = this.__super__;
                     if (item.get('box_id') === 'controlbox') {
                     if (item.get('box_id') === 'controlbox') {
-                        var view = this.get(item.get('id'));
+                        let view = this.get(item.get('id'));
                         if (view) {
                         if (view) {
                             view.model = item;
                             view.model = item;
                             view.initialize();
                             view.initialize();
@@ -133,8 +127,8 @@
                     }
                     }
                 },
                 },
 
 
-                closeAllChatBoxes: function () {
-                    var _converse = this.__super__._converse;
+                closeAllChatBoxes () {
+                    const { _converse } = this.__super__;
                     this.each(function (view) {
                     this.each(function (view) {
                         if (view.model.get('id') === 'controlbox' &&
                         if (view.model.get('id') === 'controlbox' &&
                                 (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) {
                                 (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) {
@@ -145,9 +139,9 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                getChatBoxWidth: function (view) {
-                    var _converse = this.__super__._converse;
-                    var controlbox = this.get('controlbox');
+                getChatBoxWidth (view) {
+                    const { _converse } = this.__super__;
+                    const controlbox = this.get('controlbox');
                     if (view.model.get('id') === 'controlbox') {
                     if (view.model.get('id') === 'controlbox') {
                         /* We return the width of the controlbox or its toggle,
                         /* We return the width of the controlbox or its toggle,
                          * depending on which is visible.
                          * depending on which is visible.
@@ -165,7 +159,7 @@
 
 
 
 
             ChatBox: {
             ChatBox: {
-                initialize: function () {
+                initialize () {
                     if (this.get('id') === 'controlbox') {
                     if (this.get('id') === 'controlbox') {
                         this.set({'time_opened': moment(0).valueOf()});
                         this.set({'time_opened': moment(0).valueOf()});
                     } else {
                     } else {
@@ -176,20 +170,20 @@
 
 
 
 
             ChatBoxView: {
             ChatBoxView: {
-                insertIntoDOM: function () {
-                    var _converse = this.__super__._converse;
+                insertIntoDOM () {
+                    const { _converse } = this.__super__;
                     this.$el.insertAfter(_converse.chatboxviews.get("controlbox").$el);
                     this.$el.insertAfter(_converse.chatboxviews.get("controlbox").$el);
                     return this;
                     return this;
                 }
                 }
             }
             }
         },
         },
 
 
-        initialize: function () {
+        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.
              */
              */
-            var _converse = this._converse,
-                __ = _converse.__;
+            const { _converse } = this,
+                { __ } = _converse;
 
 
             _converse.api.settings.update({
             _converse.api.settings.update({
                 allow_logout: true,
                 allow_logout: true,
@@ -200,15 +194,16 @@
                 xhr_user_search_url: ''
                 xhr_user_search_url: ''
             });
             });
 
 
-            var LABEL_CONTACTS = __('Contacts');
+            const LABEL_CONTACTS = __('Contacts');
 
 
-            _converse.addControlBox = function () {
-                return _converse.chatboxes.add({
+            _converse.addControlBox = () =>
+                _converse.chatboxes.add({
                     id: 'controlbox',
                     id: 'controlbox',
                     box_id: 'controlbox',
                     box_id: 'controlbox',
+                    type: 'controlbox',
                     closed: !_converse.show_controlbox_by_default
                     closed: !_converse.show_controlbox_by_default
-                });
-            };
+                })
+            ;
 
 
             _converse.ControlBoxView = _converse.ChatBoxView.extend({
             _converse.ControlBoxView = _converse.ChatBoxView.extend({
                 tagName: 'div',
                 tagName: 'div',
@@ -219,7 +214,7 @@
                     'click ul#controlbox-tabs li a': 'switchTab',
                     'click ul#controlbox-tabs li a': 'switchTab',
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
                     this.$el.insertAfter(_converse.controlboxtoggle.$el);
                     this.$el.insertAfter(_converse.controlboxtoggle.$el);
                     this.model.on('change:connected', this.onConnected, this);
                     this.model.on('change:connected', this.onConnected, this);
                     this.model.on('destroy', this.hide, this);
                     this.model.on('destroy', this.hide, this);
@@ -232,7 +227,7 @@
                     }
                     }
                 },
                 },
 
 
-                render: function () {
+                render () {
                     if (this.model.get('connected')) {
                     if (this.model.get('connected')) {
                         if (_.isUndefined(this.model.get('closed'))) {
                         if (_.isUndefined(this.model.get('closed'))) {
                             this.model.set('closed', !_converse.show_controlbox_by_default);
                             this.model.set('closed', !_converse.show_controlbox_by_default);
@@ -259,21 +254,21 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                onConnected: function () {
+                onConnected () {
                     if (this.model.get('connected')) {
                     if (this.model.get('connected')) {
                         this.render().insertRoster();
                         this.render().insertRoster();
                         this.model.save();
                         this.model.save();
                     }
                     }
                 },
                 },
 
 
-                insertRoster: function () {
+                insertRoster () {
                     /* Place the rosterview inside the "Contacts" panel.
                     /* Place the rosterview inside the "Contacts" panel.
                      */
                      */
                     this.contactspanel.$el.append(_converse.rosterview.$el);
                     this.contactspanel.$el.append(_converse.rosterview.$el);
                     return this;
                     return this;
                 },
                 },
 
 
-                renderLoginPanel: function () {
+                renderLoginPanel () {
                     this.loginpanel = new _converse.LoginPanel({
                     this.loginpanel = new _converse.LoginPanel({
                         '$parent': this.$el.find('.controlbox-panes'),
                         '$parent': this.$el.find('.controlbox-panes'),
                         'model': this
                         'model': this
@@ -282,7 +277,7 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                renderContactsPanel: function () {
+                renderContactsPanel () {
                     if (_.isUndefined(this.model.get('active-panel'))) {
                     if (_.isUndefined(this.model.get('active-panel'))) {
                         this.model.save({'active-panel': USERS_PANEL_ID});
                         this.model.save({'active-panel': USERS_PANEL_ID});
                     }
                     }
@@ -297,7 +292,7 @@
                     _converse.xmppstatusview.render();
                     _converse.xmppstatusview.render();
                 },
                 },
 
 
-                close: function (ev) {
+                close (ev) {
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (_converse.sticky_controlbox) {
                     if (_converse.sticky_controlbox) {
                         return;
                         return;
@@ -311,7 +306,7 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                ensureClosedState: function () {
+                ensureClosedState () {
                     if (this.model.get('closed')) {
                     if (this.model.get('closed')) {
                         this.hide();
                         this.hide();
                     } else {
                     } else {
@@ -319,7 +314,7 @@
                     }
                     }
                 },
                 },
 
 
-                hide: function (callback) {
+                hide (callback) {
                     if (_converse.sticky_controlbox) {
                     if (_converse.sticky_controlbox) {
                         return;
                         return;
                     }
                     }
@@ -333,8 +328,8 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                onControlBoxToggleHidden: function () {
-                    var that = this;
+                onControlBoxToggleHidden () {
+                    const that = this;
                     utils.fadeIn(this.el, function () {
                     utils.fadeIn(this.el, function () {
                         _converse.controlboxtoggle.updateOnlineCount();
                         _converse.controlboxtoggle.updateOnlineCount();
                         utils.refreshWebkit();
                         utils.refreshWebkit();
@@ -343,17 +338,17 @@
                     });
                     });
                 },
                 },
 
 
-                show: function () {
+                show () {
                     _converse.controlboxtoggle.hide(
                     _converse.controlboxtoggle.hide(
                         this.onControlBoxToggleHidden.bind(this)
                         this.onControlBoxToggleHidden.bind(this)
                     );
                     );
                     return this;
                     return this;
                 },
                 },
 
 
-                switchTab: function (ev) {
+                switchTab (ev) {
                     // TODO: automatically focus the relevant input
                     // TODO: automatically focus the relevant input
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    var $tab = $(ev.target),
+                    const $tab = $(ev.target),
                         $sibling = $tab.parent().siblings('li').children('a'),
                         $sibling = $tab.parent().siblings('li').children('a'),
                         $tab_panel = $($tab.attr('href'));
                         $tab_panel = $($tab.attr('href'));
                     $($sibling.attr('href')).addClass('hidden');
                     $($sibling.attr('href')).addClass('hidden');
@@ -366,7 +361,7 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                showHelpMessages: function () {
+                showHelpMessages () {
                     /* Override showHelpMessages in ChatBoxView, for now do nothing.
                     /* Override showHelpMessages in ChatBoxView, for now do nothing.
                      *
                      *
                      * Parameters:
                      * Parameters:
@@ -385,7 +380,7 @@
                     'submit form#converse-login': 'authenticate'
                     'submit form#converse-login': 'authenticate'
                 },
                 },
 
 
-                initialize: function (cfg) {
+                initialize (cfg) {
                     cfg.$parent.html(this.$el.html(
                     cfg.$parent.html(this.$el.html(
                         tpl_login_panel({
                         tpl_login_panel({
                             'ANONYMOUS': _converse.ANONYMOUS,
                             'ANONYMOUS': _converse.ANONYMOUS,
@@ -405,7 +400,7 @@
                     this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
                     this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
                 },
                 },
 
 
-                render: function () {
+                render () {
                     this.$tabs.append(tpl_login_tab({label_sign_in: __('Sign in')}));
                     this.$tabs.append(tpl_login_tab({label_sign_in: __('Sign in')}));
                     this.$el.find('input#jid').focus();
                     this.$el.find('input#jid').focus();
                     if (!this.$el.is(':visible')) {
                     if (!this.$el.is(':visible')) {
@@ -414,20 +409,21 @@
                     return this;
                     return this;
                 },
                 },
 
 
-                authenticate: function (ev) {
+                authenticate (ev) {
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    var $form = $(ev.target);
+                    const $form = $(ev.target);
                     if (_converse.authentication === _converse.ANONYMOUS) {
                     if (_converse.authentication === _converse.ANONYMOUS) {
                         this.connect($form, _converse.jid, null);
                         this.connect($form, _converse.jid, null);
                         return;
                         return;
                     }
                     }
-                    var $jid_input = $form.find('input[name=jid]'),
-                        jid = $jid_input.val(),
-                        $pw_input = $form.find('input[name=password]'),
-                        password = $pw_input.val(),
+                    const $jid_input = $form.find('input[name=jid]');
+                    const $pw_input = $form.find('input[name=password]');
+                    const password = $pw_input.val();
+
+                    let jid = $jid_input.val(),
                         errors = false;
                         errors = false;
 
 
-                    if (!jid) {
+                    if (!jid || _.filter(jid.split('@')).length < 2) {
                         errors = true;
                         errors = true;
                         $jid_input.addClass('error');
                         $jid_input.addClass('error');
                     }
                     }
@@ -445,8 +441,8 @@
                     return false;
                     return false;
                 },
                 },
 
 
-                connect: function ($form, jid, password) {
-                    var resource;
+                connect ($form, jid, password) {
+                    let resource;
                     if ($form) {
                     if ($form) {
                         $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
                         $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
                     }
                     }
@@ -462,7 +458,7 @@
                     _converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
                     _converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
                 },
                 },
 
 
-                remove: function () {
+                remove () {
                     this.$tabs.empty();
                     this.$tabs.empty();
                     this.$el.parent().empty();
                     this.$el.parent().empty();
                 }
                 }
@@ -478,19 +474,18 @@
                     "click .dropdown dd ul li a": "setStatus"
                     "click .dropdown dd ul li a": "setStatus"
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
                     this.model.on("change:status", this.updateStatusUI, this);
                     this.model.on("change:status", this.updateStatusUI, this);
                     this.model.on("change:status_message", this.updateStatusUI, this);
                     this.model.on("change:status_message", this.updateStatusUI, this);
                     this.model.on("update-status-ui", this.updateStatusUI, this);
                     this.model.on("update-status-ui", this.updateStatusUI, this);
                 },
                 },
 
 
-                render: function () {
+                render () {
                     // Replace the default dropdown with something nicer
                     // Replace the default dropdown with something nicer
-                    var $select = this.$el.find('select#select-xmpp-status'),
-                        chat_status = this.model.get('status') || 'offline',
-                        options = $('option', $select),
-                        $options_target,
-                        options_list = [];
+                    const $select = this.$el.find('select#select-xmpp-status');
+                    const chat_status = this.model.get('status') || 'offline';
+                    const options = $('option', $select);
+                    const options_list = [];
                     this.$el.html(tpl_choose_status());
                     this.$el.html(tpl_choose_status());
                     this.$el.find('#fancy-xmpp-status-select')
                     this.$el.find('#fancy-xmpp-status-select')
                             .html(tpl_chat_status({
                             .html(tpl_chat_status({
@@ -506,39 +501,42 @@
                             'text': this.text
                             'text': this.text
                         }));
                         }));
                     });
                     });
-                    $options_target = this.$el.find("#target dd ul").hide();
+                    const $options_target = this.$el.find("#target dd ul").hide();
                     $options_target.append(options_list.join(''));
                     $options_target.append(options_list.join(''));
                     $select.remove();
                     $select.remove();
                     return this;
                     return this;
                 },
                 },
 
 
-                toggleOptions: function (ev) {
+                toggleOptions (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
+                    utils.slideInAllElements(
+                        document.querySelectorAll('#conversejs .contact-form-container')
+                    );
                     $(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
                     $(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
                 },
                 },
 
 
-                renderStatusChangeForm: function (ev) {
+                renderStatusChangeForm (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var status_message = _converse.xmppstatus.get('status_message') || '';
-                    var input = tpl_change_status_message({
+                    const status_message = _converse.xmppstatus.get('status_message') || '';
+                    const input = tpl_change_status_message({
                         'status_message': status_message,
                         'status_message': status_message,
                         'label_custom_status': __('Custom status'),
                         'label_custom_status': __('Custom status'),
                         'label_save': __('Save')
                         'label_save': __('Save')
                     });
                     });
-                    var $xmppstatus = this.$el.find('.xmpp-status');
+                    const $xmppstatus = this.$el.find('.xmpp-status');
                     $xmppstatus.parent().addClass('no-border');
                     $xmppstatus.parent().addClass('no-border');
                     $xmppstatus.replaceWith(input);
                     $xmppstatus.replaceWith(input);
                     this.$el.find('.custom-xmpp-status').focus().focus();
                     this.$el.find('.custom-xmpp-status').focus().focus();
                 },
                 },
 
 
-                setStatusMessage: function (ev) {
+                setStatusMessage (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
                     this.model.setStatusMessage($(ev.target).find('input').val());
                     this.model.setStatusMessage($(ev.target).find('input').val());
                 },
                 },
 
 
-                setStatus: function (ev) {
+                setStatus (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var $el = $(ev.currentTarget),
+                    const $el = $(ev.currentTarget),
                         value = $el.attr('data-value');
                         value = $el.attr('data-value');
                     if (value === 'logout') {
                     if (value === 'logout') {
                         this.$el.find(".dropdown dd ul").hide();
                         this.$el.find(".dropdown dd ul").hide();
@@ -549,7 +547,7 @@
                     }
                     }
                 },
                 },
 
 
-                getPrettyStatus: function (stat) {
+                getPrettyStatus (stat) {
                     if (stat === 'chat') {
                     if (stat === 'chat') {
                         return __('online');
                         return __('online');
                     } else if (stat === 'dnd') {
                     } else if (stat === 'dnd') {
@@ -565,11 +563,11 @@
                     }
                     }
                 },
                 },
 
 
-                updateStatusUI: function (model) {
-                    var stat = model.get('status');
+                updateStatusUI (model) {
+                    const stat = model.get('status');
                     // For translators: the %1$s part gets replaced with the status
                     // For translators: the %1$s part gets replaced with the status
                     // Example, I am online
                     // Example, I am online
-                    var status_message = model.get('status_message') || __("I am %1$s", this.getPrettyStatus(stat));
+                    const status_message = model.get('status_message') || __("I am %1$s", this.getPrettyStatus(stat));
                     this.$el.find('#fancy-xmpp-status-select').removeClass('no-border').html(
                     this.$el.find('#fancy-xmpp-status-select').removeClass('no-border').html(
                         tpl_chat_status({
                         tpl_chat_status({
                             'chat_status': stat,
                             'chat_status': stat,
@@ -592,17 +590,17 @@
                     'click a.subscribe-to-user': 'addContactFromList'
                     'click a.subscribe-to-user': 'addContactFromList'
                 },
                 },
 
 
-                initialize: function (cfg) {
+                initialize (cfg) {
                     this.parent_el = cfg.$parent[0];
                     this.parent_el = cfg.$parent[0];
                     this.tab_el = document.createElement('li');
                     this.tab_el = document.createElement('li');
                     _converse.chatboxes.on('change:num_unread', this.renderTab, this);
                     _converse.chatboxes.on('change:num_unread', this.renderTab, this);
                     _converse.chatboxes.on('add', _.debounce(this.renderTab, 100), this);
                     _converse.chatboxes.on('add', _.debounce(this.renderTab, 100), this);
                 },
                 },
 
 
-                render: function () {
+                render () {
                     this.renderTab();
                     this.renderTab();
 
 
-                    var widgets = tpl_contacts_panel({
+                    let widgets = tpl_contacts_panel({
                         label_online: __('Online'),
                         label_online: __('Online'),
                         label_busy: __('Busy'),
                         label_busy: __('Busy'),
                         label_away: __('Away'),
                         label_away: __('Away'),
@@ -619,16 +617,16 @@
                     }
                     }
                     this.el.innerHTML = widgets;
                     this.el.innerHTML = widgets;
 
 
-                    var controlbox = _converse.chatboxes.get('controlbox');
+                    const controlbox = _converse.chatboxes.get('controlbox');
                     if (controlbox.get('active-panel') !== USERS_PANEL_ID) {
                     if (controlbox.get('active-panel') !== USERS_PANEL_ID) {
                         this.el.classList.add('hidden');
                         this.el.classList.add('hidden');
                     }
                     }
                     return this;
                     return this;
                 },
                 },
 
 
-                renderTab: function () {
-                    var controlbox = _converse.chatboxes.get('controlbox');
-                    var chats = fp.filter(_.partial(utils.isOfType, CHATBOX_TYPE), _converse.chatboxes.models);
+                renderTab () {
+                    const controlbox = _converse.chatboxes.get('controlbox');
+                    const chats = fp.filter(_.partial(utils.isOfType, CHATBOX_TYPE), _converse.chatboxes.models);
                     this.tab_el.innerHTML = tpl_contacts_tab({
                     this.tab_el.innerHTML = tpl_contacts_tab({
                         'label_contacts': LABEL_CONTACTS,
                         'label_contacts': LABEL_CONTACTS,
                         'is_current': controlbox.get('active-panel') === USERS_PANEL_ID,
                         'is_current': controlbox.get('active-panel') === USERS_PANEL_ID,
@@ -636,53 +634,54 @@
                     });
                     });
                 },
                 },
 
 
-                insertIntoDOM: function () {
+                insertIntoDOM () {
                     this.parent_el.appendChild(this.render().el);
                     this.parent_el.appendChild(this.render().el);
                     this.tabs = this.parent_el.parentNode.querySelector('#controlbox-tabs');
                     this.tabs = this.parent_el.parentNode.querySelector('#controlbox-tabs');
                     this.tabs.appendChild(this.tab_el);
                     this.tabs.appendChild(this.tab_el);
-                    this.$('.search-xmpp ul').append(
-                        this.generateAddContactHTML()
-                    );
                     return this;
                     return this;
                 },
                 },
 
 
-                generateAddContactHTML: function () {
+                generateAddContactHTML (settings={}) {
                     if (_converse.xhr_user_search) {
                     if (_converse.xhr_user_search) {
                         return tpl_search_contact({
                         return tpl_search_contact({
                             label_contact_name: __('Contact name'),
                             label_contact_name: __('Contact name'),
                             label_search: __('Search')
                             label_search: __('Search')
                         });
                         });
                     } else {
                     } else {
-                        return tpl_add_contact_form({
+                        return tpl_add_contact_form(_.assign({
+                            error_message: null,
                             label_contact_username: __('e.g. user@example.org'),
                             label_contact_username: __('e.g. user@example.org'),
-                            label_add: __('Add')
-                        });
+                            label_add: __('Add'),
+                            value: ''
+                        }, settings));
                     }
                     }
                 },
                 },
 
 
-                toggleContactForm: function (ev) {
+                toggleContactForm (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    this.$el.find('.search-xmpp').toggle('fast', function () {
-                        if ($(this).is(':visible')) {
-                            $(this).find('input.username').focus();
+                    this.el.querySelector('.search-xmpp div').innerHTML = this.generateAddContactHTML();
+                    var dropdown = this.el.querySelector('.contact-form-container');
+                    utils.slideToggleElement(dropdown).then(() => {
+                        if ($(dropdown).is(':visible')) {
+                            $(dropdown).find('input.username').focus();
                         }
                         }
                     });
                     });
                 },
                 },
 
 
-                searchContacts: function (ev) {
+                searchContacts (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
                     $.getJSON(_converse.xhr_user_search_url+ "?q=" + $(ev.target).find('input.username').val(), function (data) {
                     $.getJSON(_converse.xhr_user_search_url+ "?q=" + $(ev.target).find('input.username').val(), function (data) {
-                        var $ul= $('.search-xmpp ul');
+                        const $ul= $('.search-xmpp ul');
                         $ul.find('li.found-user').remove();
                         $ul.find('li.found-user').remove();
                         $ul.find('li.chat-info').remove();
                         $ul.find('li.chat-info').remove();
                         if (!data.length) {
                         if (!data.length) {
-                            $ul.append('<li class="chat-info">'+__('No users found')+'</li>');
+                            $ul.append(`<li class="chat-info">${__('No users found')}</li>`);
                         }
                         }
                         $(data).each(function (idx, obj) {
                         $(data).each(function (idx, obj) {
                             $ul.append(
                             $ul.append(
                                 $('<li class="found-user"></li>')
                                 $('<li class="found-user"></li>')
                                 .append(
                                 .append(
-                                    $('<a class="subscribe-to-user" href="#" title="'+__('Click to add as a chat contact')+'"></a>')
+                                    $(`<a class="subscribe-to-user" href="#" title="${__('Click to add as a chat contact')}"></a>`)
                                     .attr('data-recipient', Strophe.getNodeFromJid(obj.id)+"@"+Strophe.getDomainFromJid(obj.id))
                                     .attr('data-recipient', Strophe.getNodeFromJid(obj.id)+"@"+Strophe.getDomainFromJid(obj.id))
                                     .text(obj.fullname)
                                     .text(obj.fullname)
                                 )
                                 )
@@ -691,27 +690,32 @@
                     });
                     });
                 },
                 },
 
 
-                addContactFromForm: function (ev) {
+                addContactFromForm (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var $input = $(ev.target).find('input');
-                    var jid = $input.val();
-                    if (! jid) {
-                        // this is not a valid JID
-                        $input.addClass('error');
+                    const $input = $(ev.target).find('input');
+                    const jid = $input.val();
+                    if (!jid || _.filter(jid.split('@')).length < 2) {
+                        this.el.querySelector('.search-xmpp div').innerHTML =
+                            this.generateAddContactHTML({
+                                error_message: __('Please enter a valid XMPP username'),
+                                label_contact_username: __('e.g. user@example.org'),
+                                label_add: __('Add'),
+                                value: jid
+                            });
                         return;
                         return;
                     }
                     }
                     _converse.roster.addAndSubscribe(jid);
                     _converse.roster.addAndSubscribe(jid);
-                    $('.search-xmpp').hide();
+                    utils.slideIn(this.el.querySelector('.contact-form-container'));
                 },
                 },
 
 
-                addContactFromList: function (ev) {
+                addContactFromList (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
-                    var $target = $(ev.target),
+                    const $target = $(ev.target),
                         jid = $target.attr('data-recipient'),
                         jid = $target.attr('data-recipient'),
                         name = $target.text();
                         name = $target.text();
                     _converse.roster.addAndSubscribe(jid, name);
                     _converse.roster.addAndSubscribe(jid, name);
                     $target.parent().remove();
                     $target.parent().remove();
-                    $('.search-xmpp').hide();
+                    utils.slideIn(this.el.querySelector('.contact-form-container'));
                 }
                 }
             });
             });
 
 
@@ -727,10 +731,10 @@
                     'href': "#"
                     'href': "#"
                 },
                 },
 
 
-                initialize: function () {
+                initialize () {
                     _converse.chatboxviews.$el.prepend(this.render());
                     _converse.chatboxviews.$el.prepend(this.render());
                     this.updateOnlineCount();
                     this.updateOnlineCount();
-                    var that = this;
+                    const that = this;
                     _converse.on('initialized', function () {
                     _converse.on('initialized', function () {
                         _converse.roster.on("add", that.updateOnlineCount, that);
                         _converse.roster.on("add", that.updateOnlineCount, that);
                         _converse.roster.on('change', that.updateOnlineCount, that);
                         _converse.roster.on('change', that.updateOnlineCount, that);
@@ -739,7 +743,7 @@
                     });
                     });
                 },
                 },
 
 
-                render: function () {
+                render () {
                     // We let the render method of ControlBoxView decide whether
                     // We let the render method of ControlBoxView decide whether
                     // the ControlBox or the Toggle must be shown. This prevents
                     // the ControlBox or the Toggle must be shown. This prevents
                     // artifacts (i.e. on page load the toggle is shown only to then
                     // artifacts (i.e. on page load the toggle is shown only to then
@@ -755,24 +759,24 @@
                     if (_.isUndefined(_converse.roster)) {
                     if (_.isUndefined(_converse.roster)) {
                         return;
                         return;
                     }
                     }
-                    var $count = this.$('#online-count');
-                    $count.text('('+_converse.roster.getNumOnlineContacts()+')');
+                    const $count = this.$('#online-count');
+                    $count.text(`(${_converse.roster.getNumOnlineContacts()})`);
                     if (!$count.is(':visible')) {
                     if (!$count.is(':visible')) {
                         $count.show();
                         $count.show();
                     }
                     }
                 }, _converse.animate ? 100 : 0),
                 }, _converse.animate ? 100 : 0),
 
 
-                hide: function (callback) {
+                hide (callback) {
                     this.el.classList.add('hidden');
                     this.el.classList.add('hidden');
                     callback();
                     callback();
                 },
                 },
 
 
-                show: function (callback) {
+                show (callback) {
                     utils.fadeIn(this.el, callback);
                     utils.fadeIn(this.el, callback);
                 },
                 },
 
 
-                showControlBox: function () {
-                    var controlbox = _converse.chatboxes.get('controlbox');
+                showControlBox () {
+                    let controlbox = _converse.chatboxes.get('controlbox');
                     if (!controlbox) {
                     if (!controlbox) {
                         controlbox = _converse.addControlBox();
                         controlbox = _converse.addControlBox();
                     }
                     }
@@ -783,10 +787,10 @@
                     }
                     }
                 },
                 },
 
 
-                onClick: function (e) {
+                onClick (e) {
                     e.preventDefault();
                     e.preventDefault();
                     if ($("div#controlbox").is(':visible')) {
                     if ($("div#controlbox").is(':visible')) {
-                        var controlbox = _converse.chatboxes.get('controlbox');
+                        const controlbox = _converse.chatboxes.get('controlbox');
                         if (_converse.connection.connected) {
                         if (_converse.connection.connected) {
                             controlbox.save({closed: true});
                             controlbox.save({closed: true});
                         } else {
                         } else {
@@ -798,23 +802,23 @@
                 }
                 }
             });
             });
 
 
-            var disconnect =  function () {
+            const disconnect =  function () {
                 /* Upon disconnection, set connected to `false`, so that if
                 /* Upon disconnection, set connected to `false`, so that if
                  * we reconnect,
                  * we reconnect,
                  * "onConnected" will be called, to fetch the roster again and
                  * "onConnected" will be called, to fetch the roster again and
                  * to send out a presence stanza.
                  * to send out a presence stanza.
                  */
                  */
-                var view = _converse.chatboxviews.get('controlbox');
+                const view = _converse.chatboxviews.get('controlbox');
                 view.model.set({connected:false});
                 view.model.set({connected:false});
                 view.$('#controlbox-tabs').empty();
                 view.$('#controlbox-tabs').empty();
                 view.renderLoginPanel();
                 view.renderLoginPanel();
             };
             };
             _converse.on('disconnected', disconnect);
             _converse.on('disconnected', disconnect);
 
 
-            var afterReconnected = function () {
+            const afterReconnected = function () {
                 /* After reconnection makes sure the controlbox's is aware.
                 /* After reconnection makes sure the controlbox's is aware.
                  */
                  */
-                var view = _converse.chatboxviews.get('controlbox');
+                const view = _converse.chatboxviews.get('controlbox');
                 if (view.model.get('connected')) {
                 if (view.model.get('connected')) {
                     _converse.chatboxviews.get("controlbox").onConnected();
                     _converse.chatboxviews.get("controlbox").onConnected();
                 } else {
                 } else {

Some files were not shown because too many files changed in this diff