Bläddra i källkod

Merge branch 'master' into webpack

JC Brand 7 år sedan
förälder
incheckning
f7c3351e2b

+ 1 - 0
CHANGES.md

@@ -10,6 +10,7 @@
 - #968 Use nickname from VCard when joining a room
 - #1091 There's now only one CSS file for all view modes.
 - #1094 Show room members who aren't currently online
+- #1106 Support for Roster Versioning
 - It's now also possible to edit your VCard via the UI
 - Automatically grow/shrink input as text is entered/removed
 - MP4 and MP3 files when sent as XEP-0066 Out of Band Data, are now playable directly in chat

+ 0 - 210
bootstrap.py

@@ -1,210 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Bootstrap a buildout-based project
-
-Simply run this script in a directory containing a buildout.cfg.
-The script accepts buildout command-line options, so you can
-use the -c option to specify an alternate configuration file.
-"""
-
-import os
-import shutil
-import sys
-import tempfile
-
-from optparse import OptionParser
-
-__version__ = '2015-07-01'
-# See zc.buildout's changelog if this version is up to date.
-
-tmpeggs = tempfile.mkdtemp(prefix='bootstrap-')
-
-usage = '''\
-[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
-
-Bootstraps a buildout-based project.
-
-Simply run this script in a directory containing a buildout.cfg, using the
-Python that you want bin/buildout to use.
-
-Note that by using --find-links to point to local resources, you can keep
-this script from going over the network.
-'''
-
-parser = OptionParser(usage=usage)
-parser.add_option("--version",
-                  action="store_true", default=False,
-                  help=("Return bootstrap.py version."))
-parser.add_option("-t", "--accept-buildout-test-releases",
-                  dest='accept_buildout_test_releases',
-                  action="store_true", default=False,
-                  help=("Normally, if you do not specify a --version, the "
-                        "bootstrap script and buildout gets the newest "
-                        "*final* versions of zc.buildout and its recipes and "
-                        "extensions for you.  If you use this flag, "
-                        "bootstrap and buildout will get the newest releases "
-                        "even if they are alphas or betas."))
-parser.add_option("-c", "--config-file",
-                  help=("Specify the path to the buildout configuration "
-                        "file to be used."))
-parser.add_option("-f", "--find-links",
-                  help=("Specify a URL to search for buildout releases"))
-parser.add_option("--allow-site-packages",
-                  action="store_true", default=False,
-                  help=("Let bootstrap.py use existing site packages"))
-parser.add_option("--buildout-version",
-                  help="Use a specific zc.buildout version")
-parser.add_option("--setuptools-version",
-                  help="Use a specific setuptools version")
-parser.add_option("--setuptools-to-dir",
-                  help=("Allow for re-use of existing directory of "
-                        "setuptools versions"))
-
-options, args = parser.parse_args()
-if options.version:
-    print("bootstrap.py version %s" % __version__)
-    sys.exit(0)
-
-
-######################################################################
-# load/install setuptools
-
-try:
-    from urllib.request import urlopen
-except ImportError:
-    from urllib2 import urlopen
-
-ez = {}
-if os.path.exists('ez_setup.py'):
-    exec(open('ez_setup.py').read(), ez)
-else:
-    exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
-
-if not options.allow_site_packages:
-    # ez_setup imports site, which adds site packages
-    # this will remove them from the path to ensure that incompatible versions
-    # of setuptools are not in the path
-    import site
-    # inside a virtualenv, there is no 'getsitepackages'.
-    # We can't remove these reliably
-    if hasattr(site, 'getsitepackages'):
-        for sitepackage_path in site.getsitepackages():
-            # Strip all site-packages directories from sys.path that
-            # are not sys.prefix; this is because on Windows
-            # sys.prefix is a site-package directory.
-            if sitepackage_path != sys.prefix:
-                sys.path[:] = [x for x in sys.path
-                               if sitepackage_path not in x]
-
-setup_args = dict(to_dir=tmpeggs, download_delay=0)
-
-if options.setuptools_version is not None:
-    setup_args['version'] = options.setuptools_version
-if options.setuptools_to_dir is not None:
-    setup_args['to_dir'] = options.setuptools_to_dir
-
-ez['use_setuptools'](**setup_args)
-import setuptools
-import pkg_resources
-
-# This does not (always?) update the default working set.  We will
-# do it.
-for path in sys.path:
-    if path not in pkg_resources.working_set.entries:
-        pkg_resources.working_set.add_entry(path)
-
-######################################################################
-# Install buildout
-
-ws = pkg_resources.working_set
-
-setuptools_path = ws.find(
-    pkg_resources.Requirement.parse('setuptools')).location
-
-# Fix sys.path here as easy_install.pth added before PYTHONPATH
-cmd = [sys.executable, '-c',
-       'import sys; sys.path[0:0] = [%r]; ' % setuptools_path +
-       'from setuptools.command.easy_install import main; main()',
-       '-mZqNxd', tmpeggs]
-
-find_links = os.environ.get(
-    'bootstrap-testing-find-links',
-    options.find_links or
-    ('http://downloads.buildout.org/'
-     if options.accept_buildout_test_releases else None)
-    )
-if find_links:
-    cmd.extend(['-f', find_links])
-
-requirement = 'zc.buildout'
-version = options.buildout_version
-if version is None and not options.accept_buildout_test_releases:
-    # Figure out the most recent final version of zc.buildout.
-    import setuptools.package_index
-    _final_parts = '*final-', '*final'
-
-    def _final_version(parsed_version):
-        try:
-            return not parsed_version.is_prerelease
-        except AttributeError:
-            # Older setuptools
-            for part in parsed_version:
-                if (part[:1] == '*') and (part not in _final_parts):
-                    return False
-            return True
-
-    index = setuptools.package_index.PackageIndex(
-        search_path=[setuptools_path])
-    if find_links:
-        index.add_find_links((find_links,))
-    req = pkg_resources.Requirement.parse(requirement)
-    if index.obtain(req) is not None:
-        best = []
-        bestv = None
-        for dist in index[req.project_name]:
-            distv = dist.parsed_version
-            if _final_version(distv):
-                if bestv is None or distv > bestv:
-                    best = [dist]
-                    bestv = distv
-                elif distv == bestv:
-                    best.append(dist)
-        if best:
-            best.sort()
-            version = best[-1].version
-if version:
-    requirement = '=='.join((requirement, version))
-cmd.append(requirement)
-
-import subprocess
-if subprocess.call(cmd) != 0:
-    raise Exception(
-        "Failed to execute command:\n%s" % repr(cmd)[1:-1])
-
-######################################################################
-# Import and run buildout
-
-ws.add_entry(tmpeggs)
-ws.require(requirement)
-import zc.buildout.buildout
-
-if not [a for a in args if '=' not in a]:
-    args.append('bootstrap')
-
-# if -c was provided, we push it back into args for buildout' main function
-if options.config_file is not None:
-    args[0:0] = ['-c', options.config_file]
-
-zc.buildout.buildout.main(args)
-shutil.rmtree(tmpeggs)

+ 1 - 9
buildout.cfg

@@ -11,12 +11,4 @@ eggs =
     sphinx-bootstrap-theme
 
 [versions]
-docutils = 0.13.1
-Jinja2 = 2.9.5
-MarkupSafe = 0.23
-Pygments = 2.2.0
-six = 1.10.0
-setuptools = 28.6.1
-Sphinx = 1.5.2
-z3c.recipe.egg = 2.0.3
-zc.buildout = 2.5.3
+Sphinx = 1.7.5

+ 157 - 64
css/converse.css

@@ -6870,9 +6870,11 @@ body.reset {
   z-index: 1031; }
   #conversejs.converse-overlayed > .row {
     flex-direction: row-reverse; }
-  #conversejs.converse-fullscreen .converse-chatboxes {
+  #conversejs.converse-fullscreen .converse-chatboxes, #conversejs.converse-mobile .converse-chatboxes {
     width: 100vw;
     right: 15px; }
+  #conversejs.converse-overlayed {
+    height: 3em; }
   #conversejs .brand-heading {
     font-family: "Century Gothic", futura, "URW Gothic L", Verdana, sans-serif; }
     #conversejs .brand-heading .icon-conversejs {
@@ -6883,7 +6885,6 @@ body.reset {
     z-index: 1031;
     position: fixed;
     bottom: 0;
-    height: 3em;
     right: 0; }
   #conversejs ::-webkit-input-placeholder {
     /* Chrome/Opera/Safari */
@@ -7285,6 +7286,8 @@ body.reset {
 #conversejs #user-profile-modal label {
   font-weight: bold; }
 
+#conversejs .chatbox-navback {
+  display: none; }
 #conversejs .flyout {
   border-radius: 4px;
   position: absolute; }
@@ -7324,15 +7327,10 @@ body.reset {
     height: 36px;
     width: 36px;
     margin-right: 0.5em; }
+  #conversejs .chat-head .chatbox-title .chatroom-description {
+    font-size: 80%; }
   #conversejs .chat-head .chatbox-buttons {
     flex-direction: row-reverse;
-    position: relative;
-    width: 100%;
-    min-height: 1px;
-    padding-right: 15px;
-    padding-left: 15px;
-    flex: 0 0 33.3333333333%;
-    max-width: 33.3333333333%;
     padding: 0; }
   #conversejs .chat-head .user-custom-message {
     color: white;
@@ -7629,6 +7627,14 @@ body.reset {
     #conversejs.converse-overlayed .chat-head {
       border-top-left-radius: 0;
       border-top-right-radius: 0; } }
+  #conversejs.converse-embedded .chat-head .chatbox-title,
+  #conversejs.converse-overlayed .chat-head .chatbox-title {
+    flex: 0 0 66.6666666667%;
+    max-width: 66.6666666667%; }
+  #conversejs.converse-embedded .chat-head .chatbox-buttons,
+  #conversejs.converse-overlayed .chat-head .chatbox-buttons {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%; }
 #conversejs.converse-embedded .chatbox,
 #conversejs.converse-overlayed .chatbox {
   min-width: 250px !important;
@@ -7689,13 +7695,16 @@ body.reset {
   height: 62px;
   font-size: 20px;
   padding: 0; }
-  #conversejs.converse-fullscreen .chat-head .chatbox-buttons {
-    flex: 0 0 25%;
-    max-width: 25%; }
   #conversejs.converse-fullscreen .chat-head .user-custom-message {
     font-size: 50%;
     height: auto;
     line-height: 16px; }
+  #conversejs.converse-fullscreen .chat-head .chatbox-title {
+    flex: 0 0 83.3333333333%;
+    max-width: 83.3333333333%; }
+  #conversejs.converse-fullscreen .chat-head .chatbox-buttons {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%; }
 #conversejs.converse-fullscreen .chat-textarea {
   max-height: 400px; }
 #conversejs.converse-fullscreen .emoji-picker {
@@ -7751,23 +7760,43 @@ body.reset {
     padding-left: 10px;
     padding-right: 10px; }
 
-@media screen and (max-width: 767px) {
+@media (max-width: 767.98px) {
   #conversejs:not(.converse-embedded) > .row {
     flex-direction: row-reverse; }
   #conversejs:not(.converse-embedded) #converse-login-panel .converse-form {
     padding: 3em 2em 3em; }
-  #conversejs:not(.converse-embedded) .sidebar {
-    display: block; }
   #conversejs:not(.converse-embedded) .chatbox {
     width: calc(100% - 50px); }
     #conversejs:not(.converse-embedded) .chatbox .row .box-flyout {
       left: 50px;
       bottom: 0;
       height: 100vh;
-      box-shadow: none; } }
-@media screen and (max-width: 767px) {
-  #conversejs:not(.converse-embedded).converse-fullscreen .chatbox {
-    width: calc(100% - 50px); } }
+      box-shadow: none; }
+
+  #conversejs.converse-mobile .chatbox .box-flyout .chatbox-navback,
+  #conversejs.converse-overlayed .chatbox .box-flyout .chatbox-navback,
+  #conversejs.converse-embedded .chatbox .box-flyout .chatbox-navback,
+  #conversejs.converse-fullscreen .chatbox .box-flyout .chatbox-navback {
+    display: flex;
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%; }
+    #conversejs.converse-mobile .chatbox .box-flyout .chatbox-navback .fa-arrow-left:before,
+    #conversejs.converse-overlayed .chatbox .box-flyout .chatbox-navback .fa-arrow-left:before,
+    #conversejs.converse-embedded .chatbox .box-flyout .chatbox-navback .fa-arrow-left:before,
+    #conversejs.converse-fullscreen .chatbox .box-flyout .chatbox-navback .fa-arrow-left:before {
+      color: white; }
+  #conversejs.converse-mobile .chatbox .box-flyout .chatbox-title,
+  #conversejs.converse-overlayed .chatbox .box-flyout .chatbox-title,
+  #conversejs.converse-embedded .chatbox .box-flyout .chatbox-title,
+  #conversejs.converse-fullscreen .chatbox .box-flyout .chatbox-title {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%; }
+  #conversejs.converse-mobile .chatbox .box-flyout .chatbox-buttons,
+  #conversejs.converse-overlayed .chatbox .box-flyout .chatbox-buttons,
+  #conversejs.converse-embedded .chatbox .box-flyout .chatbox-buttons,
+  #conversejs.converse-fullscreen .chatbox .box-flyout .chatbox-buttons {
+    flex: 0 0 25%;
+    max-width: 25%; } }
 #conversejs .set-xmpp-status .fa-circle, #conversejs .xmpp-status .fa-circle, #conversejs .roster-contacts .fa-circle {
   color: #3AA569; }
 #conversejs .set-xmpp-status .fa-minus-circle, #conversejs .xmpp-status .fa-minus-circle, #conversejs .roster-contacts .fa-minus-circle {
@@ -8006,7 +8035,6 @@ body.reset {
       #conversejs:not(.converse-embedded) .converse-chatboxes .converse-chatroom {
         font-size: 14px; }
       #conversejs:not(.converse-embedded) .converse-chatboxes .chatbox .box-flyout {
-        top: -100vh;
         margin-left: 15px;
         left: 0;
         bottom: 0;
@@ -8024,21 +8052,21 @@ body.reset {
         display: none; }
       #conversejs:not(.converse-embedded) .converse-chatboxes.sidebar-open #controlbox .controlbox-pane {
         display: block; } }
-#conversejs:not(.converse-fullscreen) #controlbox {
+#conversejs.converse-overlayed #controlbox {
   order: -1;
   min-width: 250px !important;
   width: 250px; }
-  #conversejs:not(.converse-fullscreen) #controlbox .box-flyout {
+  #conversejs.converse-overlayed #controlbox .box-flyout {
     min-width: 250px !important;
     width: 250px; }
-  #conversejs:not(.converse-fullscreen) #controlbox:not(.logged-out) .controlbox-head {
+  #conversejs.converse-overlayed #controlbox:not(.logged-out) .controlbox-head {
     height: 15px; }
-  #conversejs:not(.converse-fullscreen) #controlbox .controlbox-head {
+  #conversejs.converse-overlayed #controlbox .controlbox-head {
     display: flex;
     flex-direction: row-reverse;
     flex-wrap: nowrap;
     justify-content: space-between; }
-    #conversejs:not(.converse-fullscreen) #controlbox .controlbox-head .brand-heading {
+    #conversejs.converse-overlayed #controlbox .controlbox-head .brand-heading {
       position: relative;
       width: 100%;
       min-height: 1px;
@@ -8048,19 +8076,20 @@ body.reset {
       max-width: 66.6666666667%;
       color: #666;
       font-size: 2em; }
-    #conversejs:not(.converse-fullscreen) #controlbox .controlbox-head .chatbox-btn {
+    #conversejs.converse-overlayed #controlbox .controlbox-head .chatbox-btn {
       color: #578EA9;
       margin: 0; }
-  #conversejs:not(.converse-fullscreen) #controlbox #converse-register, #conversejs:not(.converse-fullscreen) #controlbox #converse-login {
+  #conversejs.converse-overlayed #controlbox #converse-register, #conversejs.converse-overlayed #controlbox #converse-login {
     flex: 0 0 100%;
     max-width: 100%;
     padding-bottom: 0; }
-  #conversejs:not(.converse-fullscreen) #controlbox #converse-register .button-cancel {
+  #conversejs.converse-overlayed #controlbox #converse-register .button-cancel {
     font-size: 90%; }
-  #conversejs:not(.converse-fullscreen) #controlbox .controlbox-panes {
+  #conversejs.converse-overlayed #controlbox .controlbox-panes {
     border-radius: 4px; }
 
-#conversejs.converse-fullscreen #controlbox {
+#conversejs.converse-fullscreen #controlbox,
+#conversejs.converse-mobile #controlbox {
   position: relative;
   width: 100%;
   min-height: 1px;
@@ -8068,43 +8097,56 @@ body.reset {
   padding-left: 15px;
   margin: 0; }
   @media (min-width: 768px) {
-    #conversejs.converse-fullscreen #controlbox {
+    #conversejs.converse-fullscreen #controlbox,
+    #conversejs.converse-mobile #controlbox {
       flex: 0 0 25%;
       max-width: 25%; } }
   @media (min-width: 1200px) {
-    #conversejs.converse-fullscreen #controlbox {
+    #conversejs.converse-fullscreen #controlbox,
+    #conversejs.converse-mobile #controlbox {
       flex: 0 0 16.6666666667%;
       max-width: 16.6666666667%; } }
-  #conversejs.converse-fullscreen #controlbox.logged-out {
+  #conversejs.converse-fullscreen #controlbox.logged-out,
+  #conversejs.converse-mobile #controlbox.logged-out {
     flex: 0 0 100%;
     max-width: 100%; }
-  #conversejs.converse-fullscreen #controlbox .controlbox-pane {
+  #conversejs.converse-fullscreen #controlbox .controlbox-pane,
+  #conversejs.converse-mobile #controlbox .controlbox-pane {
     border-radius: 0; }
-  #conversejs.converse-fullscreen #controlbox .flyout {
+  #conversejs.converse-fullscreen #controlbox .flyout,
+  #conversejs.converse-mobile #controlbox .flyout {
     border-radius: 0; }
-  #conversejs.converse-fullscreen #controlbox #converse-login-panel {
+  #conversejs.converse-fullscreen #controlbox #converse-login-panel,
+  #conversejs.converse-mobile #controlbox #converse-login-panel {
     border-radius: 0; }
-    #conversejs.converse-fullscreen #controlbox #converse-login-panel .converse-form {
+    #conversejs.converse-fullscreen #controlbox #converse-login-panel .converse-form,
+    #conversejs.converse-mobile #controlbox #converse-login-panel .converse-form {
       padding: 3em 2em 3em; }
-  #conversejs.converse-fullscreen #controlbox .toggle-register-login {
+  #conversejs.converse-fullscreen #controlbox .toggle-register-login,
+  #conversejs.converse-mobile #controlbox .toggle-register-login {
     line-height: 24px; }
-  #conversejs.converse-fullscreen #controlbox .brand-heading-container {
+  #conversejs.converse-fullscreen #controlbox .brand-heading-container,
+  #conversejs.converse-mobile #controlbox .brand-heading-container {
     flex: 0 0 100%;
     max-width: 100%;
     text-align: center; }
-    #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-heading {
+    #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-heading,
+    #conversejs.converse-mobile #controlbox .brand-heading-container .brand-heading {
       font-size: 150%;
       font-size: 600%;
       padding: 0.7em 0 0 0;
       opacity: 0.8;
       color: #387592; }
-    #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-subtitle {
+    #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-subtitle,
+    #conversejs.converse-mobile #controlbox .brand-heading-container .brand-subtitle {
       font-size: 90%;
       padding: 0.5em; }
     @media screen and (max-width: 480px) {
-      #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-heading {
+      #conversejs.converse-fullscreen #controlbox .brand-heading-container .brand-heading,
+      #conversejs.converse-mobile #controlbox .brand-heading-container .brand-heading {
         font-size: 400%; } }
-  #conversejs.converse-fullscreen #controlbox.logged-out {
+  #conversejs.converse-fullscreen #controlbox.logged-out,
+  #conversejs.converse-mobile #controlbox.logged-out {
     flex: 0 0 100%;
     max-width: 100%;
     opacity: 0;
@@ -8122,16 +8164,21 @@ body.reset {
     -moz-animation-timing-function: ease;
     animation-timing-function: ease;
     width: 100%; }
-    #conversejs.converse-fullscreen #controlbox.logged-out .box-flyout {
+    #conversejs.converse-fullscreen #controlbox.logged-out .box-flyout,
+    #conversejs.converse-mobile #controlbox.logged-out .box-flyout {
       width: 100%; }
-  #conversejs.converse-fullscreen #controlbox .box-flyout {
+  #conversejs.converse-fullscreen #controlbox .box-flyout,
+  #conversejs.converse-mobile #controlbox .box-flyout {
     border: 0;
     width: 100%;
     z-index: 1;
     background-color: #578EA9; }
-    #conversejs.converse-fullscreen #controlbox .box-flyout .controlbox-head {
+    #conversejs.converse-fullscreen #controlbox .box-flyout .controlbox-head,
+    #conversejs.converse-mobile #controlbox .box-flyout .controlbox-head {
       display: none; }
-  #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login {
+  #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login,
+  #conversejs.converse-mobile #controlbox #converse-register,
+  #conversejs.converse-mobile #controlbox #converse-login {
     position: relative;
     width: 100%;
     min-height: 1px;
@@ -8141,25 +8188,39 @@ body.reset {
     max-width: 66.6666666667%;
     margin-left: 16.6666666667%; }
     @media (min-width: 576px) {
-      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login {
+      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login,
+      #conversejs.converse-mobile #controlbox #converse-register,
+      #conversejs.converse-mobile #controlbox #converse-login {
         flex: 0 0 66.6666666667%;
         max-width: 66.6666666667%;
         margin-left: 16.6666666667%; } }
     @media (min-width: 768px) {
-      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login {
+      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login,
+      #conversejs.converse-mobile #controlbox #converse-register,
+      #conversejs.converse-mobile #controlbox #converse-login {
         flex: 0 0 66.6666666667%;
         max-width: 66.6666666667%;
         margin-left: 16.6666666667%; } }
     @media (min-width: 992px) {
-      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login {
+      #conversejs.converse-fullscreen #controlbox #converse-register, #conversejs.converse-fullscreen #controlbox #converse-login,
+      #conversejs.converse-mobile #controlbox #converse-register,
+      #conversejs.converse-mobile #controlbox #converse-login {
         flex: 0 0 50%;
         max-width: 50%;
         margin-left: 25%; } }
-    #conversejs.converse-fullscreen #controlbox #converse-register .title, #conversejs.converse-fullscreen #controlbox #converse-register .instructions, #conversejs.converse-fullscreen #controlbox #converse-login .title, #conversejs.converse-fullscreen #controlbox #converse-login .instructions {
+    #conversejs.converse-fullscreen #controlbox #converse-register .title, #conversejs.converse-fullscreen #controlbox #converse-register .instructions, #conversejs.converse-fullscreen #controlbox #converse-login .title, #conversejs.converse-fullscreen #controlbox #converse-login .instructions,
+    #conversejs.converse-mobile #controlbox #converse-register .title,
+    #conversejs.converse-mobile #controlbox #converse-register .instructions,
+    #conversejs.converse-mobile #controlbox #converse-login .title,
+    #conversejs.converse-mobile #controlbox #converse-login .instructions {
       margin: 1em 0; }
     #conversejs.converse-fullscreen #controlbox #converse-register input[type=submit],
     #conversejs.converse-fullscreen #controlbox #converse-register input[type=button], #conversejs.converse-fullscreen #controlbox #converse-login input[type=submit],
-    #conversejs.converse-fullscreen #controlbox #converse-login input[type=button] {
+    #conversejs.converse-fullscreen #controlbox #converse-login input[type=button],
+    #conversejs.converse-mobile #controlbox #converse-register input[type=submit],
+    #conversejs.converse-mobile #controlbox #converse-register input[type=button],
+    #conversejs.converse-mobile #controlbox #converse-login input[type=submit],
+    #conversejs.converse-mobile #controlbox #converse-login input[type=button] {
       width: auto; }
 
 #conversejs .list-container {
@@ -8494,10 +8555,18 @@ body.reset {
         border-left: 1px solid #666;
         border-bottom-right-radius: 4px;
         padding: 0.5em; }
-        #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants .occupants-heading,
-        #conversejs .chatroom .box-flyout .chatroom-body .occupants .occupants-heading {
-          font-family: "Century Gothic", futura, "URW Gothic L", Verdana, sans-serif;
-          padding: 0.3em 0; }
+        #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants .occupants-header,
+        #conversejs .chatroom .box-flyout .chatroom-body .occupants .occupants-header {
+          display: flex;
+          flex-direction: column; }
+          #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants .occupants-header .hide-occupants,
+          #conversejs .chatroom .box-flyout .chatroom-body .occupants .occupants-header .hide-occupants {
+            align-self: flex-end;
+            cursor: pointer; }
+          #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants .occupants-header .occupants-heading,
+          #conversejs .chatroom .box-flyout .chatroom-body .occupants .occupants-header .occupants-heading {
+            font-family: "Century Gothic", futura, "URW Gothic L", Verdana, sans-serif;
+            padding: 0.3em 0; }
         #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants .chatroom-features,
         #conversejs .chatroom .box-flyout .chatroom-body .occupants .chatroom-features {
           width: 100%; }
@@ -8643,25 +8712,49 @@ body.reset {
     max-width: 66.6666666667%; }
     #conversejs.converse-overlayed .chatbox.chatroom .chatbox-title .chatroom-description {
       font-size: 80%; }
+  #conversejs.converse-overlayed .chatbox.chatroom .chatbox-buttons {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%; }
   #conversejs.converse-overlayed .chatbox.chatroom .chatroom-body .occupants .chatroom-features .feature {
     font-size: 12px; }
   #conversejs.converse-overlayed .chatbox.chatroom .chatroom-body .chat-area {
     min-width: 250px; }
 
-/* ******************* Fullpage styles *************************** */
+#conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-title {
+  flex: 0 0 75%;
+  max-width: 75%; }
+#conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-buttons {
+  flex: 0 0 25%;
+  max-width: 25%; }
+
+@media (max-width: 767.98px) {
+  #conversejs.converse-mobile .chatroom .box-flyout .chatbox-navback,
+  #conversejs.converse-overlayed .chatroom .box-flyout .chatbox-navback,
+  #conversejs.converse-embedded .chatroom .box-flyout .chatbox-navback,
+  #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-navback {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%; }
+  #conversejs.converse-mobile .chatroom .box-flyout .chatbox-title,
+  #conversejs.converse-overlayed .chatroom .box-flyout .chatbox-title,
+  #conversejs.converse-embedded .chatroom .box-flyout .chatbox-title,
+  #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-title {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%; }
+  #conversejs.converse-mobile .chatroom .box-flyout .chatbox-buttons,
+  #conversejs.converse-overlayed .chatroom .box-flyout .chatbox-buttons,
+  #conversejs.converse-embedded .chatroom .box-flyout .chatbox-buttons,
+  #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-buttons {
+    flex: 0 0 25%;
+    max-width: 25%; } }
 #conversejs.converse-fullscreen .chatroom .box-flyout,
 #conversejs.converse-mobile .chatroom .box-flyout {
   background-color: #E77051;
   border: 1.2em solid #E77051;
   border-top: 0.8em solid #E77051;
   width: 100%; }
-  #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-title,
-  #conversejs.converse-mobile .chatroom .box-flyout .chatbox-title {
-    flex: 0 0 75%;
-    max-width: 75%; }
-    #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-title .chatroom-description,
-    #conversejs.converse-mobile .chatroom .box-flyout .chatbox-title .chatroom-description {
-      font-size: 70%; }
+  #conversejs.converse-fullscreen .chatroom .box-flyout .chatbox-title .chatroom-description,
+  #conversejs.converse-mobile .chatroom .box-flyout .chatbox-title .chatroom-description {
+    font-size: 70%; }
   #conversejs.converse-fullscreen .chatroom .box-flyout .chatroom-body,
   #conversejs.converse-mobile .chatroom .box-flyout .chatroom-body {
     border-top-left-radius: 4px;

+ 1 - 5
dev.html

@@ -11,11 +11,7 @@
     <link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
     <link type="text/css" rel="stylesheet" media="screen" href="css/fullpage.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
-
-    <![if gte IE 11]>
-        <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
-        <script src="dist/converse.js"></script>
-    <![endif]>
+    <script src="dist/converse.js"></script>
 </head>
 
 <body class="reset">

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 228 - 191
dist/converse-no-dependencies.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 253 - 198
dist/converse.js


+ 1 - 43
docs/source/configuration.rst

@@ -672,7 +672,7 @@ geouri_regex
 Regular expression used to extract geo coordinates from links to openstreetmap.
 
 geouri_replacement
-----------------
+------------------
 
 * Default:  ``'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2'``
 
@@ -711,48 +711,6 @@ Makes sense to set this to ``true`` when also using the non-core
 ``converse-roomslist`` plugin, which shows a list of currently open (i.e.
 "joined") rooms.
 
-include_offline_state
----------------------
-
-* Default: `false`
-
-Originally, converse.js included an `offline` state which the user could
-choose (along with `online`, `busy` and `away`).
-
-Eventually it was however decided to remove this state, since the `offline`
-state doesn't propagate across tabs like the others do.
-
-What's meant by "propagate across tabs", is that when you set the state to
-`offline` in one tab, and you have instances of converse.js open in other tabs
-in your browser, then those instances will not have their states changed to
-`offline` as well. For the other statees the change is however propagated.
-
-The reason for this is that according to the XMPP spec, there is no `offline`
-state. The only defined states are:
-
-* away -- The entity or resource is temporarily away.
-* chat -- The entity or resource is actively interested in chattiIng.
-* dnd -- The entity or resource is busy (dnd = "Do Not Disturb").
-* xa -- The entity or resource is away for an extended period (xa = "eXtended Away").
-
-Read the `relevant section in the XMPP spec <https://xmpp.org/rfcs/rfc6121.html#presence-syntax-children-show>`_
-for more info.
-
-What used to happen in converse.js when the `offline` state was chosen, is
-that a presence stanza with a `type` of `unavailable` was sent out.
-
-This is actually exactly what happens when you log out of converse.js as well,
-with the notable exception that in the `offline` state, the connection is not
-terminated. So you can at any time change your state to something else and
-start chatting again.
-
-This might be useful to people, however the fact that the `offline` state
-doesn't propagate across tabs means that the user experience is inconsistent,
-confusing and appears "broken".
-
-If you are however aware of this issue and still want to allow the `offline`
-state, then you can set this option to `true` to enable it.
-
 .. _`i18n`:
 
 i18n

+ 39 - 22
docs/source/events.rst

@@ -7,7 +7,7 @@
 Events and promises
 ===================
 
-Converse.js and its plugins emit various events which you can listen to via the
+Converse and its plugins emit various events which you can listen to via the
 :ref:`listen-grouping`.
 
 Some of these events are also available as `ES2015 Promises <http://es6-features.org/#PromiseUsage>`_,
@@ -294,6 +294,25 @@ Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
         // Your code here...
     });
 
+privateChatsAutoJoined
+~~~~~~~~~~~~~~~~~~~~~~
+
+Emitted once any private chats have been automatically joined as specified by
+the _`auto_join_private_chats` settings.
+
+.. code-block:: javascript
+
+    _converse.api.listen.on('privateChatsAutoJoined', function () { ... });
+
+Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_.
+
+.. code-block:: javascript
+
+    _converse.api.waitUntil('privateChatsAutoJoined').then(function () {
+        // Your code here...
+    });
+
+
 reconnecting
 ~~~~~~~~~~~~
 
@@ -311,24 +330,14 @@ have to be registered anew.
 
     _converse.api.listen.on('reconnected', function () { ... });
 
+registeredGlobalEventHandlers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-privateChatsAutoJoined
-~~~~~~~~~~~~~~~~~~~~~~
-
-Emitted once any private chats have been automatically joined as specified by
-the _`auto_join_private_chats` settings.
+Called once Converse has registered its global event handlers (for events such
+as window resize or unload).
 
-.. code-block:: javascript
-
-    _converse.api.listen.on('privateChatsAutoJoined', function () { ... });
-
-Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_.
-
-.. code-block:: javascript
-
-    _converse.api.waitUntil('privateChatsAutoJoined').then(function () {
-        // Your code here...
-    });
+Plugins can listen to this event as cue to register their own global event
+handlers.
 
 roomsAutoJoined
 ---------------
@@ -466,6 +475,14 @@ Similar to `rosterInitialized`, but instead pertaining to reconnection. This
 event indicates that the Backbone collections representing the roster and its
 groups are now again available after converse.js has reconnected.
 
+serviceDiscovered
+~~~~~~~~~~~~~~~~~
+
+When converse.js has learned of a service provided by the XMPP server. See XEP-0030.
+
+``_converse.api.listen.on('serviceDiscovered', function (service) { ... });``
+
+
 .. _`statusInitialized`:
 
 statusInitialized
@@ -497,12 +514,12 @@ When own custom status message has changed.
 
 ``_converse.api.listen.on('statusMessageChanged', function (message) { ... });``
 
-serviceDiscovered
-~~~~~~~~~~~~~~~~~
-
-When converse.js has learned of a service provided by the XMPP server. See XEP-0030.
+streamFeaturesAdded
+~~~~~~~~~~~~~~~~~~~
 
-``_converse.api.listen.on('serviceDiscovered', function (service) { ... });``
+Emitted as soon as Converse has processed the stream features as advertised by
+the server. If you want to check whether a stream feature is supported before
+proceeding, then you'll first want to wait for this event.
 
 windowStateChanged
 ~~~~~~~~~~~~~~~~~~

+ 9 - 12
docs/source/features.rst

@@ -19,7 +19,7 @@ and a private chat with a URL fragment such as
 Off-the-record encryption
 =========================
 
-Converse.js supports `Off-the-record (OTR) <https://otr.cypherpunks.ca/>`_
+Converse supports `Off-the-record (OTR) <https://otr.cypherpunks.ca/>`_
 encrypted messaging.
 
 The OTR protocol not only **encrypts your messages**, it provides ways to
@@ -38,17 +38,17 @@ secure crypto.
 For harsh but fairly valid criticism of JavaScript cryptography, read:
 `JavaScript Cryptography Considered Harmful <http://www.matasano.com/articles/javascript-cryptography/>`_.
 
-To get an idea on how this applies to OTR support in Converse.js, please read
+To get an idea on how this applies to OTR support in Converse, please read
 `my thoughts on it <https://opkode.com/media/blog/2013/11/11/conversejs-otr-support>`_.
 
 For now, suffice to say that although its useful to have OTR support in
-Converse.js in order to avoid most eavesdroppers, if you need serious
+Converse in order to avoid most eavesdroppers, if you need serious
 communications privacy, then you're much better off using native software.
 
 Notifications
 =============
 
-From version 0.8.1 Converse.js can play a sound notification when you receive a
+From version 0.8.1 Converse can play a sound notification when you receive a
 message.
 
 For more info, refer to the :ref:`play-sounds` configuration setting.
@@ -61,13 +61,10 @@ For more info, refer to the :ref:`show-desktop-notifications` configuration sett
 Multilingual Support
 ====================
 
-Converse.js is translated into multiple languages. The default build,
-``converse.min.js``, includes all languages.
-
-Languages increase the size of the Converse.js significantly.
-
-If you only need one, or a subset of the available languages, it's better to
-make a custom build which includes only those languages that you need.
+Converse is translated into multiple languages. Translations are supplied in
+JSON format and are loaded on demand. Converse will expect to find the
+translations in the ``/locales`` path of your site. This can be changed via the
+:ref:`locales-url` configuration setting.
 
 Moderating chatrooms
 ====================
@@ -103,7 +100,7 @@ Here are the different commands that may be used to moderate a chatroom:
 Passwordless login with client certificates
 ===========================================
 
-Converse.js supports the SASL-EXTERNAL authentication mechanism, which can be
+Converse supports the SASL-EXTERNAL authentication mechanism, which can be
 used together with x509 client certificates to enable passwordless login or
 even 2-factor authentication.
 

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 230 - 115
package-lock.json


+ 1 - 1
package.json

@@ -73,7 +73,7 @@
     "sinon": "^2.1.0",
     "sizzle": "^2.3.3",
     "snabbdom": "0.7.1",
-    "strophe.js": "1.2.14",
+    "strophe.js": "1.2.15",
     "strophejs-plugin-ping": "0.0.1",
     "strophejs-plugin-register": "0.0.1",
     "strophejs-plugin-rsm": "0.0.1",

+ 1 - 0
requirements.txt

@@ -0,0 +1 @@
+zc.buildout==2.11.4

+ 43 - 16
sass/_chatbox.scss

@@ -1,4 +1,7 @@
 #conversejs {
+    .chatbox-navback {
+        display: none;
+    }
     .flyout {
         border-radius: $chatbox-border-radius;
         position: absolute;
@@ -51,10 +54,13 @@
             margin-right: 0.5em;
         }
 
+        .chatbox-title {
+            .chatroom-description {
+                font-size: 80%;
+            }
+        }
         .chatbox-buttons {
             flex-direction: row-reverse;
-            @include make-col-ready();
-            @include make-col(4);
             padding: 0;
         }
 
@@ -443,7 +449,14 @@
             border-top-left-radius: 0;
             border-top-right-radius: 0;
         }
+        .chatbox-title {
+            @include make-col(8);
+        }
+        .chatbox-buttons {
+            @include make-col(4);
+        }
     }
+
     .chatbox {
         min-width: $overlayed-chat-width!important;
         width: $overlayed-chat-width;
@@ -536,9 +549,6 @@
     }
     .chat-head {
         height: $fullpage-chat-head-height;
-        .chatbox-buttons {
-            @include make-col(3);
-        }
         font-size: $font-size-huge;
         padding: 0;
         .user-custom-message {
@@ -546,6 +556,12 @@
             height: auto;
             line-height: $line-height;
         }
+        .chatbox-title {
+            @include make-col(10);
+        }
+        .chatbox-buttons {
+            @include make-col(2);
+        }
     }
     .chat-textarea {
         max-height: $fullpage-max-chat-textarea-height;
@@ -623,22 +639,16 @@
     }
 }
 
-@media screen and (max-width: 767px) {
+@include media-breakpoint-down(sm) {
     #conversejs:not(.converse-embedded)  {
         > .row {
             flex-direction: row-reverse;
         }
-
         #converse-login-panel {
             .converse-form {
                 padding: 3em 2em 3em;
             }
         }
-
-        .sidebar {
-            display: block;
-        }
-
         .chatbox {
             width: calc(100% - 50px);
             .row {
@@ -651,12 +661,29 @@
             }
         }
     }
-}
 
-@media screen and (max-width: 767px) {
-    #conversejs:not(.converse-embedded).converse-fullscreen {
+    #conversejs.converse-mobile,
+    #conversejs.converse-overlayed,
+    #conversejs.converse-embedded,
+    #conversejs.converse-fullscreen {
         .chatbox {
-            width: calc(100% - 50px);
+            .box-flyout {
+                .chatbox-navback {
+                    display: flex;
+                    @include make-col(2);
+                    .fa-arrow-left {
+                        &:before {
+                            color: white;
+                        }
+                    }
+                }
+                .chatbox-title {
+                    @include make-col(7);
+                }
+                .chatbox-buttons {
+                    @include make-col(3);
+                }
+            }
         }
     }
 }

+ 47 - 5
sass/_chatrooms.scss

@@ -120,9 +120,17 @@
                     border-bottom-right-radius: $chatbox-border-radius;
                     padding: 0.5em;
 
-                    .occupants-heading {
-                        font-family: $heading-font; 
-                        padding: 0.3em 0;
+                    .occupants-header {
+                        display: flex;
+                        flex-direction: column;
+                        .hide-occupants {
+                            align-self: flex-end;
+                            cursor: pointer;
+                        }
+                        .occupants-heading {
+                            font-family: $heading-font; 
+                            padding: 0.3em 0;
+                        }
                     }
 
                     .chatroom-features {
@@ -292,6 +300,9 @@
                     font-size: 80%;
                 }
             }
+            .chatbox-buttons {
+                @include make-col(4);
+            }
             .chatroom-body {
                 .occupants {
                     .chatroom-features {
@@ -308,7 +319,39 @@
     }
 }
 
-/* ******************* Fullpage styles *************************** */
+#conversejs.converse-fullscreen {
+    .chatroom {
+        .box-flyout {
+            .chatbox-title {
+                @include make-col(9);
+            }
+            .chatbox-buttons {
+                @include make-col(3);
+            }
+        }
+    }
+}
+
+@include media-breakpoint-down(sm) {
+    #conversejs.converse-mobile,
+    #conversejs.converse-overlayed,
+    #conversejs.converse-embedded,
+    #conversejs.converse-fullscreen {
+        .chatroom {
+            .box-flyout {
+                .chatbox-navback {
+                    @include make-col(2);
+                }
+                .chatbox-title {
+                    @include make-col(7);
+                }
+                .chatbox-buttons {
+                    @include make-col(3);
+                }
+            }
+        }
+    }
+}
 
 #conversejs.converse-fullscreen,
 #conversejs.converse-mobile {
@@ -321,7 +364,6 @@
             width: 100%;
 
             .chatbox-title {
-                @include make-col(9);
                 .chatroom-description {
                     font-size: 70%;
                 }

+ 3 - 3
sass/_controlbox.scss

@@ -357,7 +357,6 @@
 
             .chatbox { 
                 .box-flyout {
-                    top: -100vh;
                     margin-left: 15px; // Counteracts Bootstrap margins, but
                                        // not clear why needed...
                     left: 0;
@@ -393,7 +392,7 @@
     }
 }
 
-#conversejs:not(.converse-fullscreen) {
+#conversejs.converse-overlayed {
     #controlbox {
         order: -1;
         min-width: $controlbox-width !important;
@@ -445,7 +444,8 @@
     }
 }
 
-#conversejs.converse-fullscreen {
+#conversejs.converse-fullscreen,
+#conversejs.converse-mobile {
     #controlbox {
         @include make-col-ready();
         @include media-breakpoint-up(md) {

+ 5 - 2
sass/_core.scss

@@ -67,12 +67,16 @@ body.reset {
         }
     }
 
-    &.converse-fullscreen {
+    &.converse-fullscreen,
+    &.converse-mobile {
         .converse-chatboxes {
             width: 100vw;
             right: 15px; // Hack due to padding added by bootstrap
         }
     }
+    &.converse-overlayed {
+        height: 3em;
+    }
 
     .brand-heading {
         font-family: $heading-font; 
@@ -89,7 +93,6 @@ body.reset {
         z-index: 1031; // One more than bootstrap navbar
         position: fixed;
         bottom: 0;
-        height: 3em;
         right: 0;
     }
 

+ 2 - 2
spec/chatroom.js

@@ -3170,7 +3170,7 @@
 
                 test_utils.openControlBox();
                 var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
-                roomspanel.el.querySelector('.trigger-add-chatrooms-modal').click();
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 test_utils.waitUntil(function () {
@@ -3205,7 +3205,7 @@
 
                 test_utils.openControlBox();
                 var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
-                roomspanel.el.querySelector('.trigger-list-chatrooms-modal').click();
+                roomspanel.el.querySelector('.show-list-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.list_rooms_modal;
                 test_utils.waitUntil(function () {

+ 1 - 1
spec/protocol.js

@@ -209,7 +209,7 @@
                      *  </iq>
                      */
                     spyOn(_converse.roster, "updateContact").and.callThrough();
-                    stanza = $iq({'type': 'set', 'from': 'dummy@localhost'})
+                    stanza = $iq({'type': 'set', 'from': _converse.connection.jid})
                         .c('query', {'xmlns': 'jabber:iq:roster'})
                         .c('item', {
                             'jid': 'contact@example.org',

+ 62 - 0
spec/roster.js

@@ -35,6 +35,68 @@
 
     describe("The Contacts Roster", function () {
 
+        it("supports roster versioning",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            var IQ_stanzas = _converse.connection.IQ_stanzas;
+            var stanza;
+
+            test_utils.waitUntil(() => {
+                const node = _.filter(IQ_stanzas, function (iq) {
+                    return iq.nodeTree.querySelector('iq query[xmlns="jabber:iq:roster"]');
+                }).pop();
+                if (node) {
+                    stanza = node.nodeTree;
+                    return true;
+                }
+            }).then(() => {
+                expect(_converse.roster.data.get('version')).toBeUndefined();
+                expect(stanza.outerHTML).toBe(
+                    `<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+
+                        `<query xmlns="jabber:iq:roster"/>`+
+                    `</iq>`);
+                let result = $iq({
+                    'to': _converse.connection.jid,
+                    'type': 'result',
+                    'id': stanza.getAttribute('id')
+                }).c('query', {
+                    'xmlns': 'jabber:iq:roster',
+                    'ver': 'ver7'
+                }).c('item', {'jid': 'nurse@example.com'}).up()
+                  .c('item', {'jid': 'romeo@example.com'})
+                _converse.connection._dataRecv(test_utils.createRequest(result));
+                expect(_converse.roster.data.get('version')).toBe('ver7');
+                expect(_converse.roster.models.length).toBe(2);
+
+                _converse.roster.fetchFromServer();
+                stanza = _converse.connection.IQ_stanzas.pop().nodeTree;
+                expect(stanza.outerHTML).toBe(
+                    `<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+
+                        `<query xmlns="jabber:iq:roster" ver="ver7"/>`+
+                    `</iq>`);
+
+                result = $iq({
+                    'to': _converse.connection.jid,
+                    'type': 'result',
+                    'id': stanza.getAttribute('id')
+                });
+                _converse.connection._dataRecv(test_utils.createRequest(result));
+
+                const roster_push = $iq({
+                    'to': _converse.connection.jid,
+                    'type': 'set',
+                }).c('query', {'xmlns': 'jabber:iq:roster', 'ver': 'ver34'})
+                    .c('item', {'jid': 'romeo@example.com', 'subscription': 'remove'});
+                _converse.connection._dataRecv(test_utils.createRequest(roster_push));
+                expect(_converse.roster.data.get('version')).toBe('ver34');
+                expect(_converse.roster.models.length).toBe(1);
+                expect(_converse.roster.at(0).get('jid')).toBe('nurse@example.com');
+                done();
+            });
+        }));
+
         describe("The live filter", function () {
 
             it("will only appear when roster contacts flow over the visible area",

+ 3 - 4
src/converse-chatboxes.js

@@ -62,7 +62,9 @@
             // Refer to docs/source/configuration.rst for explanations of these
             // configuration settings.
             _converse.api.settings.update({
-                auto_join_private_chats: [],
+                'filter_by_resource': false,
+                'auto_join_private_chats': [],
+                'forward_messages': false,
             });
             _converse.api.promises.add([
                 'chatBoxesFetched',
@@ -720,9 +722,6 @@
                                 _converse.root.appendChild(el);
                             }
                         }
-                        if (_.includes(['mobile', 'fullscreen'], _converse.view_mode)) {
-                            el.classList.add('fullscreen');
-                        }
                         el.innerHTML = '';
                         this.setElement(el, false);
                     } else {

+ 12 - 3
src/converse-chatview.js

@@ -100,10 +100,11 @@
                 { __ } = _converse;
 
             _converse.api.settings.update({
-                'use_emojione': false,
                 'emojione_image_path': emojione.imagePathPNG,
+                'show_send_button': false,
                 'show_toolbar': true,
                 'time_format': 'HH:mm',
+                'use_emojione': false,
                 'visible_toolbar_buttons': {
                     'call': false,
                     'clear': true,
@@ -320,16 +321,17 @@
 
                 events: {
                     'change input.fileupload': 'onFileSelection',
+                    'click .chatbox-navback': 'showControlBox',
                     'click .close-chatbox-button': 'close',
-                    'click .show-user-details-modal': 'showUserDetailsModal',
                     'click .new-msgs-indicator': 'viewUnreadMessages',
                     'click .send-button': 'onFormSubmitted',
+                    'click .show-user-details-modal': 'showUserDetailsModal',
+                    'click .spoiler-toggle': 'toggleSpoilerMessage',
                     'click .toggle-call': 'toggleCall',
                     'click .toggle-clear': 'clearMessages',
                     'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
                     'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
                     'click .toggle-smiley': 'toggleEmojiMenu',
-                    'click .spoiler-toggle': 'toggleSpoilerMessage',
                     'click .upload-file': 'toggleFileUpload',
                     'keypress .chat-textarea': 'keyPressed',
                     'input .chat-textarea': 'inputChanged'
@@ -412,6 +414,13 @@
                     this.renderToolbar();
                 },
 
+                showControlBox () {
+                    // Used in mobile view, to navigate back to the controlbox
+                    const view = _converse.chatboxviews.get('controlbox');
+                    view.show();
+                    this.hide();
+                },
+
                 showUserDetailsModal (ev) {
                     if (_.isUndefined(this.user_details_modal)) {
                         this.user_details_modal = new _converse.UserDetailsModal({model: this.model});

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
src/converse-core.js


+ 37 - 5
src/converse-disco.js

@@ -37,24 +37,24 @@
                     this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
 
                     this.dataforms = new Backbone.Collection();
-                    this.dataforms.browserStorage = new Backbone.BrowserStorage[_converse.storage](
+                    this.dataforms.browserStorage = new Backbone.BrowserStorage.session(
                         b64_sha1(`converse.dataforms-{this.get('jid')}`)
                     );
 
                     this.features = new Backbone.Collection();
-                    this.features.browserStorage = new Backbone.BrowserStorage[_converse.storage](
+                    this.features.browserStorage = new Backbone.BrowserStorage.session(
                         b64_sha1(`converse.features-${this.get('jid')}`)
                     );
                     this.features.on('add', this.onFeatureAdded, this);
 
                     this.identities = new Backbone.Collection();
-                    this.identities.browserStorage = new Backbone.BrowserStorage[_converse.storage](
+                    this.identities.browserStorage = new Backbone.BrowserStorage.session(
                         b64_sha1(`converse.identities-${this.get('jid')}`)
                     );
                     this.fetchFeatures();
 
                     this.items = new _converse.DiscoEntities();
-                    this.items.browserStorage = new Backbone.BrowserStorage[_converse.storage](
+                    this.items.browserStorage = new Backbone.BrowserStorage.session(
                         b64_sha1(`converse.disco-items-${this.get('jid')}`)
                     );
                     this.items.fetch();
@@ -217,12 +217,34 @@
                 return this;
             }
 
+            function initStreamFeatures () {
+                _converse.stream_features = new Backbone.Collection();
+                _converse.stream_features.browserStorage = new Backbone.BrowserStorage.session(
+                    b64_sha1(`converse.stream-features-${_converse.bare_jid}`)
+                );
+                _converse.stream_features.fetch({
+                    success (collection) {
+                        if (collection.length === 0 && _converse.connection.features) {
+                            _.forEach(
+                                _converse.connection.features.childNodes,
+                                (feature) => {
+                                    _converse.stream_features.create({
+                                        'name': feature.nodeName,
+                                        'xmlns': feature.getAttribute('xmlns')
+                                    });
+                                });
+                        }
+                    }
+                });
+                _converse.emit('streamFeaturesAdded');
+            }
+
             function initializeDisco () {
                 addClientFeatures();
                 _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
 
                 _converse.disco_entities = new _converse.DiscoEntities();
-                _converse.disco_entities.browserStorage = new Backbone.BrowserStorage[_converse.storage](
+                _converse.disco_entities.browserStorage = new Backbone.BrowserStorage.session(
                     b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)
                 );
 
@@ -236,6 +258,7 @@
                 }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
             }
 
+            _converse.api.listen.on('sessionInitialized', initStreamFeatures);
             _converse.api.listen.on('reconnected', initializeDisco);
             _converse.api.listen.on('connected', initializeDisco);
 
@@ -291,6 +314,15 @@
                  * @namespace
                  */
                 'disco': {
+                    'stream': {
+                        'getFeature': function (name, xmlns) {
+                            if (_.isNil(name) || _.isNil(xmlns)) {
+                                throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
+                            }
+                            return _converse.stream_features.findWhere({'name': name, 'xmlns': xmlns});
+                        }
+                    },
+
                     /**
                      * The "own" grouping
                      * @namespace

+ 9 - 10
src/converse-minimize.js

@@ -52,16 +52,6 @@
             //
             // New functions which don't exist yet can also be added.
 
-            registerGlobalEventHandlers () {
-                const { _converse } = this.__super__;
-                window.addEventListener("resize", _.debounce(function (ev) {
-                    if (_converse.connection.connected) {
-                        _converse.chatboxviews.trimChats();
-                    }
-                }, 200));
-                return this.__super__.registerGlobalEventHandlers.apply(this, arguments);
-            },
-
             ChatBox: {
                 initialize () {
                     this.__super__.initialize.apply(this, arguments);
@@ -541,6 +531,15 @@
                 _converse.emit('minimizedChatsInitialized');
             }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
 
+
+            _converse.on('registeredGlobalEventHandlers', function () {
+                window.addEventListener("resize", _.debounce(function (ev) {
+                    if (_converse.connection.connected) {
+                        _converse.chatboxviews.trimChats();
+                    }
+                }, 200));
+            });
+
             _converse.on('controlBoxOpened', function (chatbox) {
                 // Wrapped in anon method because at scan time, chatboxviews
                 // attr not set yet.

+ 24 - 5
src/converse-muc-views.js

@@ -492,8 +492,10 @@
                 is_chatroom: true,
                 events: {
                     'change input.fileupload': 'onFileSelection',
+                    'click .chatbox-navback': 'showControlBox',
                     'click .close-chatbox-button': 'close',
                     'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
+                    'click .hide-occupants': 'hideOccupants',
                     'click .new-msgs-indicator': 'viewUnreadMessages',
                     'click .occupant-nick': 'onOccupantClicked',
                     'click .send-button': 'onFormSubmitted',
@@ -697,15 +699,32 @@
                 setOccupantsVisibility () {
                     const icon_el = this.el.querySelector('.toggle-occupants');
                     if (this.model.get('hidden_occupants')) {
-                        this.el.querySelector('.chat-area').classList.add('full');
+                        u.removeClass('fa-angle-double-right', icon_el);
+                        u.addClass('fa-angle-double-left', icon_el);
+                        u.addClass('full', this.el.querySelector('.chat-area'));
                         u.hideElement(this.el.querySelector('.occupants'));
                     } else {
-                        this.el.querySelector('.chat-area').classList.remove('full');
-                        this.el.querySelector('.occupants').classList.remove('hidden');
+                        u.addClass('fa-angle-double-right', icon_el);
+                        u.removeClass('fa-angle-double-left', icon_el);
+                        u.removeClass('full', this.el.querySelector('.chat-area'));
+                        u.removeClass('hidden', this.el.querySelector('.occupants'));
                     }
                     this.occupantsview.setOccupantsHeight();
                 },
 
+                hideOccupants (ev, preserve_state) {
+                    /* Show or hide the right sidebar containing the chat
+                     * occupants (and the invite widget).
+                     */
+                    if (ev) {
+                        ev.preventDefault();
+                        ev.stopPropagation();
+                    }
+                    this.model.save({'hidden_occupants': true});
+                    this.setOccupantsVisibility();
+                    this.scrollDown();
+                },
+
                 toggleOccupants (ev, preserve_state) {
                     /* Show or hide the right sidebar containing the chat
                      * occupants (and the invite widget).
@@ -1535,8 +1554,8 @@
                 className: 'controlbox-section',
                 id: 'chatrooms',
                 events: {
-                    'click a.chatbox-btn.fa-users': 'showAddRoomModal',
-                    'click a.chatbox-btn.fa-list-ul': 'showListRoomsModal',
+                    'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal',
+                    'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal',
                     'click a.room-info': 'toggleRoomInfo'
                 },
 

+ 11 - 5
src/converse-muc.js

@@ -985,13 +985,18 @@
                 },
 
                 onAvatarChanged () {
-                    const vcard = _converse.vcards.findWhere({'jid': this.get('from')});
-                    if (!vcard) { return; }
-
                     const hash = this.get('image_hash');
-                    if (hash && vcard.get('image_hash') !== hash) {
-                        _converse.api.vcard.update(vcard);
+                    const vcards = [];
+                    if (this.get('jid')) {
+                        vcards.push(this.updateVCard(_converse.vcards.findWhere({'jid': this.get('jid')})));
                     }
+                    vcards.push(this.updateVCard(_converse.vcards.findWhere({'jid': this.get('from')})));
+
+                    _.forEach(_.filter(vcards, undefined), (vcard) => {
+                        if (hash && vcard.get('image_hash') !== hash) {
+                            _converse.api.vcard.update(vcard);
+                        }
+                    });
                 },
 
                 getDisplayName () {
@@ -1032,6 +1037,7 @@
                             // Remove absent occupants who've been removed from
                             // the members lists.
                             const occupant = this.findOccupant({'jid': removed_jid});
+                            if (!occupant) { return; }
                             if (occupant.get('show') === 'offline') {
                                 occupant.destroy();
                             }

+ 17 - 11
src/converse-register.js

@@ -62,16 +62,21 @@
 
             LoginPanel: {
 
-                render: function (cfg) {
+                insertRegisterLink () {
                     const { _converse } = this.__super__;
-                    this.__super__.render.apply(this, arguments);
-                    if (_converse.allow_registration) {
-                        if (_.isUndefined(this.registerlinkview)) {
-                            this.registerlinkview = new _converse.RegisterLinkView({'model': this.model});
-                            this.registerlinkview.render();
-                            this.el.querySelector('.buttons').insertAdjacentElement('beforeend', this.registerlinkview.el);
-                        }
+                    if (_.isUndefined(this.registerlinkview)) {
+                        this.registerlinkview = new _converse.RegisterLinkView({'model': this.model});
                         this.registerlinkview.render();
+                        this.el.querySelector('.buttons').insertAdjacentElement('beforeend', this.registerlinkview.el);
+                    }
+                    this.registerlinkview.render();
+                },
+
+                render (cfg) {
+                    const { _converse } = this.__super__;
+                    this.__super__.render.apply(this, arguments);
+                    if (_converse.allow_registration && !_converse.auto_login) {
+                        this.insertRegisterLink();
                     }
                     return this;
                 }
@@ -139,9 +144,10 @@
             _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
 
             _converse.api.settings.update({
-                allow_registration: true,
-                domain_placeholder: __(" e.g. conversejs.org"),  // Placeholder text shown in the domain input on the registration form
-                providers_link: 'https://xmpp.net/directory.php', // Link to XMPP providers shown on registration page
+                'allow_registration': true,
+                'domain_placeholder': __(" e.g. conversejs.org"),  // Placeholder text shown in the domain input on the registration form
+                'providers_link': 'https://xmpp.net/directory.php', // Link to XMPP providers shown on registration page
+                'registration_domain': ''
             });
 
 

+ 75 - 39
src/converse-roster.js

@@ -22,6 +22,12 @@
             const { _converse } = this,
                   { __ } = _converse;
 
+            _converse.api.settings.update({
+                'allow_contact_requests': true,
+                'auto_subscribe': false,
+                'synchronize_availability': true,
+            });
+
             _converse.api.promises.add([
                 'cachedRoster',
                 'roster',
@@ -48,6 +54,13 @@
                 _converse.roster = new _converse.RosterContacts();
                 _converse.roster.browserStorage = new Backbone.BrowserStorage[_converse.storage](
                     b64_sha1(`converse.contacts-${_converse.bare_jid}`));
+
+                _converse.roster.data = new Backbone.Model();
+                const id = b64_sha1(`converse-roster-model-${_converse.bare_jid}`);
+                _converse.roster.data.id = id;
+                _converse.roster.data.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
+                _converse.roster.data.fetch();
+
                 _converse.rostergroups = new _converse.RosterGroups();
                 _converse.rostergroups.browserStorage = new Backbone.BrowserStorage[_converse.storage](
                     b64_sha1(`converse.roster.groups${_converse.bare_jid}`));
@@ -330,28 +343,28 @@
 
                 onConnected () {
                     /* Called as soon as the connection has been established
-                    * (either after initial login, or after reconnection).
-                    *
-                    * Use the opportunity to register stanza handlers.
-                    */
+                     * (either after initial login, or after reconnection).
+                     *
+                     * Use the opportunity to register stanza handlers.
+                     */
                     this.registerRosterHandler();
                     this.registerRosterXHandler();
                 },
 
                 registerRosterHandler () {
                     /* Register a handler for roster IQ "set" stanzas, which update
-                    * roster contacts.
-                    */
-                    _converse.connection.addHandler(
-                        _converse.roster.onRosterPush.bind(_converse.roster),
-                        Strophe.NS.ROSTER, 'iq', "set"
-                    );
+                     * roster contacts.
+                     */
+                    _converse.connection.addHandler((iq) => {
+                        _converse.roster.onRosterPush(iq);
+                        return true;
+                    }, Strophe.NS.ROSTER, 'iq', "set");
                 },
 
                 registerRosterXHandler () {
                     /* Register a handler for RosterX message stanzas, which are
-                    * used to suggest roster contacts to a user.
-                    */
+                     * used to suggest roster contacts to a user.
+                     */
                     let t = 0;
                     _converse.connection.addHandler(
                         function (msg) {
@@ -375,12 +388,14 @@
                      * Returns a promise which resolves once the contacts have been
                      * fetched.
                      */
+                    const that = this;
                     return new Promise((resolve, reject) => {
                         this.fetch({
                             'add': true,
                             'silent': true,
                             success (collection) {
-                                if (collection.length === 0) {
+                                if (collection.length === 0 || 
+                                        (that.rosterVersioningSupported() && !_converse.session.get('roster_fetched'))) {
                                     _converse.send_initial_presence = true;
                                     _converse.roster.fetchFromServer().then(resolve).catch(reject);
                                 } else {
@@ -506,30 +521,44 @@
 
                 onRosterPush (iq) {
                     /* Handle roster updates from the XMPP server.
-                    * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
-                    *
-                    * Parameters:
-                    *    (XMLElement) IQ - The IQ stanza received from the XMPP server.
-                    */
+                     * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
+                     *
+                     * Parameters:
+                     *    (XMLElement) IQ - The IQ stanza received from the XMPP server.
+                     */
                     const id = iq.getAttribute('id');
                     const from = iq.getAttribute('from');
-                    if (from && from !== "" && Strophe.getBareJidFromJid(from) !== _converse.bare_jid) {
-                        // Receiving client MUST ignore stanza unless it has no from or from = user's bare JID.
-                        // XXX: Some naughty servers apparently send from a full
-                        // JID so we need to explicitly compare bare jids here.
-                        // https://github.com/jcbrand/converse.js/issues/493
-                        _converse.connection.send(
-                            $iq({type: 'error', id, from: _converse.connection.jid})
-                                .c('error', {'type': 'cancel'})
-                                .c('service-unavailable', {'xmlns': Strophe.NS.ROSTER })
-                        );
-                        return true;
+                    if (from && from !== _converse.connection.jid) {
+                        // https://tools.ietf.org/html/rfc6121#page-15
+                        // 
+                        // A receiving client MUST ignore the stanza unless it has no 'from'
+                        // attribute (i.e., implicitly from the bare JID of the user's
+                        // account) or it has a 'from' attribute whose value matches the
+                        // user's bare JID <user@domainpart>.
+                        return;
                     }
                     _converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid}));
-                    const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
-                    _.each(items, this.updateContact.bind(this));
+
+                    const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
+                    this.data.save('version', query.getAttribute('ver'));
+
+                    const items = sizzle(`item`, query);
+                    if (items.length > 1) {
+                        _converse.log(iq, Strophe.LogLevel.ERROR);
+                        throw new Error('Roster push query may not contain more than one "item" element.');
+                    }
+                    if (items.length === 0) {
+                        _converse.log(iq, Strophe.LogLevel.WARN);
+                        _converse.log('Received a roster push stanza without an "item" element.', Strophe.LogLevel.WARN);
+                        return;
+                    }
+                    this.updateContact(items.pop());
                     _converse.emit('rosterPush', iq);
-                    return true;
+                    return;
+                },
+
+                rosterVersioningSupported () {
+                    return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version');
                 },
 
                 fetchFromServer () {
@@ -539,7 +568,9 @@
                             'type': 'get',
                             'id': _converse.connection.getUniqueId('roster')
                         }).c('query', {xmlns: Strophe.NS.ROSTER});
-
+                        if (this.rosterVersioningSupported()) {
+                            iq.attrs({'ver': this.data.get('version')});
+                        }
                         const callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
                         const errback = function (iq) {
                             const errmsg = "Error while trying to fetch roster from the server";
@@ -552,17 +583,22 @@
 
                 onReceivedFromServer (iq) {
                     /* An IQ stanza containing the roster has been received from
-                    * the XMPP server.
-                    */
-                    const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
-                    _.each(items, this.updateContact.bind(this));
+                     * the XMPP server.
+                     */
+                    const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
+                    if (query) {
+                        const items = sizzle(`item`, query);
+                        _.each(items, (item) => this.updateContact(item));
+                        this.data.save('version', query.getAttribute('ver'));
+                        _converse.session.save('roster_fetched', true);
+                    }
                     _converse.emit('roster', iq);
                 },
 
                 updateContact (item) {
                     /* Update or create RosterContact models based on items
-                    * received in the IQ from the server.
-                    */
+                     * received in the IQ from the server.
+                     */
                     const jid = item.getAttribute('jid');
                     if (this.isSelf(jid)) { return; }
 

+ 2 - 0
src/converse-rosterview.js

@@ -80,7 +80,9 @@
             _converse.api.settings.update({
                 'allow_chat_pending_contacts': true,
                 'allow_contact_removal': true,
+                'hide_offline_users': false,
                 'roster_groups': true,
+                'show_only_online_users': false,
                 'show_toolbar': true,
                 'xhr_user_search_url': null
             });

+ 25 - 16
src/converse-singleton.js

@@ -1,10 +1,8 @@
-// Converse.js (A browser based XMPP chat client)
+// Converse.js
 // http://conversejs.org
 //
-// Copyright (c) 2012-2017, JC Brand <jc@opkode.com>
+// Copyright (c) 2012-2018, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
-//
-/*global Backbone, define, window, JSON */
 
 /* converse-singleton
  * ******************
@@ -39,10 +37,6 @@
         // NB: These plugins need to have already been loaded via require.js.
         dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'],
 
-        enabled (_converse) {
-            return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
-        },
-
         overrides: {
             // overrides mentioned here will be picked up by converse.js's
             // plugin architecture they will replace existing methods on the
@@ -50,21 +44,32 @@
             //
             // new functions which don't exist yet can also be added.
             ChatBoxes: {
+
                 chatBoxMayBeShown (chatbox) {
-                    return !chatbox.get('hidden');
+                    if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
+                        return !chatbox.get('hidden');
+                    } else {
+                        return this.__super__.chatBoxMayBeShown.apply(this, arguments);
+                    }
                 },
 
                 createChatBox (jid, attrs) {
                     /* Make sure new chat boxes are hidden by default. */
-                    attrs = attrs || {};
-                    attrs.hidden = true;
+                    if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
+                        attrs = attrs || {};
+                        attrs.hidden = true;
+                    }
                     return this.__super__.createChatBox.call(this, jid, attrs);
                 }
             },
 
             ChatBoxView: {
                 shouldShowOnTextMessage () {
-                    return false;
+                    if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
+                        return false;
+                    } else { 
+                        return this.__super__.shouldShowOnTextMessage.apply(this, arguments);
+                    }
                 },
 
                 _show (focus) {
@@ -72,16 +77,20 @@
                      * time. So before opening a chat, we make sure all other
                      * chats are hidden.
                      */
-                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
-                    this.model.set('hidden', false);
+                    if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
+                        _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
+                        this.model.set('hidden', false);
+                    }
                     return this.__super__._show.apply(this, arguments);
                 }
             },
 
             ChatRoomView: {
                 show (focus) {
-                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
-                    this.model.set('hidden', false);
+                    if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
+                        _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
+                        this.model.set('hidden', false);
+                    }
                     return this.__super__.show.apply(this, arguments);
                 }
             }

+ 16 - 9
src/converse-vcard.js

@@ -1,7 +1,7 @@
 // Converse.js
 // http://conversejs.org
 //
-// Copyright (c) 2012-2018, the Converse.js developers
+// Copyright (c) 2013-2018, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
 
 (function (root, factory) {
@@ -68,7 +68,9 @@
                         'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
                         'url': _.get(vcard.querySelector('URL'), 'textContent'),
                         'role': _.get(vcard.querySelector('ROLE'), 'textContent'),
-                        'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent')
+                        'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent'),
+                        'vcard_updated': moment().format(),
+                        'vcard_error': undefined
                     };
                 }
                 if (result.image) {
@@ -82,7 +84,11 @@
 
             function onVCardError (_converse, jid, iq, errback) {
                 if (errback) {
-                    errback({'stanza': iq, 'jid': jid});
+                    errback({
+                        'stanza': iq,
+                        'jid': jid,
+                        'vcard_error': moment().format()
+                    });
                 }
             }
 
@@ -141,8 +147,11 @@
                     'get' (model, force) {
                         if (_.isString(model)) {
                             return getVCard(_converse, model);
-                        } else if (!model.get('vcard_updated') || force) {
-                            const jid = model.get('jid') || model.get('muc_jid');
+                        } else if (force ||
+                                !model.get('vcard_updated') ||
+                                !moment(model.get('vcard_error')).isSame(new Date(), "day")) {
+
+                            const jid = model.get('jid');
                             if (!jid) {
                                 throw new Error("No JID to get vcard for!");
                             }
@@ -155,10 +164,8 @@
                     'update' (model, force) {
                         return new Promise((resolve, reject) => {
                             this.get(model, force).then((vcard) => {
-                                model.save(_.extend(
-                                    _.pick(vcard, ['fullname', 'nickname', 'email', 'url', 'role', 'image_type', 'image', 'image_hash']),
-                                    {'vcard_updated': moment().format()}
-                                ));
+                                delete vcard['stanza']
+                                model.save(vcard);
                                 resolve();
                             });
                         });

+ 2 - 1
src/templates/chatbox_head.html

@@ -1,5 +1,6 @@
 <div class="chat-head chat-head-chatbox row no-gutters">
-    <div class="col">
+    <div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>
+    <div class="chatbox-title">
         <div class="row no-gutters">
             <canvas class="avatar" height="36" width="36"></canvas>
             <div class="col chat-title" title="{{{o.jid}}}">

+ 1 - 0
src/templates/chatroom_head.html

@@ -1,3 +1,4 @@
+<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>
 <div class="chatbox-title">
     <div class="chat-title" title="{{{o.jid}}}">
         {[ if (o.name && o.name !== o.Strophe.getNodeFromJid(o.jid)) { ]}

+ 4 - 1
src/templates/chatroom_sidebar.html

@@ -1,5 +1,8 @@
 <!-- <div class="occupants"> -->
-<p class="occupants-heading">{{{o.label_occupants}}}</p>
+<div class="occupants-header">
+    <i class="hide-occupants fa fa-times"></i>
+    <p class="occupants-heading">{{{o.label_occupants}}}</p>
+</div>
 <ul class="occupant-list"></ul>
 <div class="chatroom-features"></div>
 <!-- </div> -->

+ 1 - 1
src/templates/chatroom_toolbar.html

@@ -8,5 +8,5 @@
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 {[ } ]}
 {[ if (o.show_occupants_toggle)  { ]}
-<li class="toggle-occupants fa fa-users" title="{{{o.label_hide_occupants}}}"></li>
+<li class="toggle-occupants fa fa-angle-double-right" title="{{{o.label_hide_occupants}}}"></li>
 {[ } ]}

+ 2 - 2
src/templates/room_panel.html

@@ -1,8 +1,8 @@
 <!-- <div id="chatrooms"> -->
 <div class="d-flex controlbox-padded">
     <span class="w-100 controlbox-heading">{{{o.heading_chatrooms}}}</span>
-    <a class="chatbox-btn trigger-list-chatrooms-modal fa fa-list-ul" title="{{{o.title_list_rooms}}}" data-toggle="modal" data-target="#list-chatrooms-modal"></a>
-    <a class="chatbox-btn trigger-add-chatrooms-modal fa fa-users" title="{{{o.title_new_room}}}" data-toggle="modal" data-target="#add-chatrooms-modal"></a>
+    <a class="chatbox-btn show-list-muc-modal fa fa-list-ul" title="{{{o.title_list_rooms}}}" data-toggle="modal" data-target="#list-chatrooms-modal"></a>
+    <a class="chatbox-btn show-add-muc-modal fa fa-plus" title="{{{o.title_new_room}}}" data-toggle="modal" data-target="#add-chatrooms-modal"></a>
 </div>
 <div class="list-container open-rooms-list rooms-list-container"></div>
 <div class="list-container bookmarks-list rooms-list-container"></div>

+ 18 - 2
tests/mock.js

@@ -4,6 +4,7 @@
     var _ = converse.env._;
     var Promise = converse.env.Promise;
     var Strophe = converse.env.Strophe;
+    var moment = converse.env.moment;
     var $iq = converse.env.$iq;
     var mock = {};
 
@@ -62,6 +63,18 @@
                 this.IQ_ids.push(id);
                 return id;
             }
+            c.features = Strophe.xmlHtmlNode(
+                '<stream:features xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">'+
+                    '<ver xmlns="urn:xmpp:features:rosterver"/>'+
+                    '<csi xmlns="urn:xmpp:csi:0"/>'+
+                    '<c xmlns="http://jabber.org/protocol/caps" ver="UwBpfJpEt3IoLYfWma/o/p3FFRo=" hash="sha-1" node="http://prosody.im"/>'+
+                    '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+
+                        '<required/>'+
+                    '</bind>'+
+                    '<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+
+                        '<optional/>'+
+                    '</session>'+
+                '</stream:features>').firstChild;
 
             c._proto._connect = function () {
                 c.authenticated = true;
@@ -99,6 +112,7 @@
         }, settings || {}));
 
         _converse.ChatBoxViews.prototype.trimChat = function () {};
+
         _converse.api.vcard.get = function (model, force) {
             return new Promise((resolve, reject) => {
                 let jid;
@@ -120,11 +134,13 @@
                 }
                 var vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
                 var result = {
-                    'stanza': vcard,
+                    'vcard': vcard,
                     'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
                     'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
                     'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
-                    'url': _.get(vcard.querySelector('URL'), 'textContent')
+                    'url': _.get(vcard.querySelector('URL'), 'textContent'),
+                    'vcard_updated': moment().format(),
+                    'vcard_error': undefined
                 };
                 resolve(result);
             }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));

+ 1 - 1
tests/utils.js

@@ -107,7 +107,7 @@
         return new Promise(function (resolve, reject) {
             utils.openControlBox(_converse);
             var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
-            roomspanel.el.querySelector('.trigger-add-chatrooms-modal').click();
+            roomspanel.el.querySelector('.show-add-muc-modal').click();
             utils.closeControlBox(_converse);
             const modal = roomspanel.add_room_modal;
             utils.waitUntil(function () {

Vissa filer visades inte eftersom för många filer har ändrats