Commit f7c3351e authored by JC Brand's avatar JC Brand

Merge branch 'master' into webpack

parents 659f70b2 8948be1d
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
- #968 Use nickname from VCard when joining a room - #968 Use nickname from VCard when joining a room
- #1091 There's now only one CSS file for all view modes. - #1091 There's now only one CSS file for all view modes.
- #1094 Show room members who aren't currently online - #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 - It's now also possible to edit your VCard via the UI
- Automatically grow/shrink input as text is entered/removed - 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 - MP4 and MP3 files when sent as XEP-0066 Out of Band Data, are now playable directly in chat
......
##############################################################################
#
# 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)
...@@ -11,12 +11,4 @@ eggs = ...@@ -11,12 +11,4 @@ eggs =
sphinx-bootstrap-theme sphinx-bootstrap-theme
[versions] [versions]
docutils = 0.13.1 Sphinx = 1.7.5
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
This diff is collapsed.
...@@ -11,11 +11,7 @@ ...@@ -11,11 +11,7 @@
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/> <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/fullpage.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="dist/converse.js"></script>
<![if gte IE 11]>
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="dist/converse.js"></script>
<![endif]>
</head> </head>
<body class="reset"> <body class="reset">
......
This diff is collapsed.
This diff is collapsed.
...@@ -672,7 +672,7 @@ geouri_regex ...@@ -672,7 +672,7 @@ geouri_regex
Regular expression used to extract geo coordinates from links to openstreetmap. Regular expression used to extract geo coordinates from links to openstreetmap.
geouri_replacement geouri_replacement
---------------- ------------------
* Default: ``'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2'`` * 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 ...@@ -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. ``converse-roomslist`` plugin, which shows a list of currently open (i.e.
"joined") rooms. "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`:
i18n i18n
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
Events and promises 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`. :ref:`listen-grouping`.
Some of these events are also available as `ES2015 Promises <http://es6-features.org/#PromiseUsage>`_, 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>`_: ...@@ -294,6 +294,25 @@ Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
// Your code here... // 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 reconnecting
~~~~~~~~~~~~ ~~~~~~~~~~~~
...@@ -311,24 +330,14 @@ have to be registered anew. ...@@ -311,24 +330,14 @@ have to be registered anew.
_converse.api.listen.on('reconnected', function () { ... }); _converse.api.listen.on('reconnected', function () { ... });
registeredGlobalEventHandlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
privateChatsAutoJoined Called once Converse has registered its global event handlers (for events such
~~~~~~~~~~~~~~~~~~~~~~ as window resize or unload).
Emitted once any private chats have been automatically joined as specified by
the _`auto_join_private_chats` settings.
.. code-block:: javascript Plugins can listen to this event as cue to register their own global event
handlers.
_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...
});
roomsAutoJoined roomsAutoJoined
--------------- ---------------
...@@ -466,6 +475,14 @@ Similar to `rosterInitialized`, but instead pertaining to reconnection. This ...@@ -466,6 +475,14 @@ 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.
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`:
statusInitialized statusInitialized
...@@ -497,12 +514,12 @@ When own custom status message has changed. ...@@ -497,12 +514,12 @@ When own custom status message has changed.
``_converse.api.listen.on('statusMessageChanged', function (message) { ... });`` ``_converse.api.listen.on('statusMessageChanged', function (message) { ... });``
serviceDiscovered streamFeaturesAdded
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
When converse.js has learned of a service provided by the XMPP server. See XEP-0030.
``_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 windowStateChanged
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
......
...@@ -19,7 +19,7 @@ and a private chat with a URL fragment such as ...@@ -19,7 +19,7 @@ and a private chat with a URL fragment such as
Off-the-record encryption 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. encrypted messaging.
The OTR protocol not only **encrypts your messages**, it provides ways to The OTR protocol not only **encrypts your messages**, it provides ways to
...@@ -38,17 +38,17 @@ secure crypto. ...@@ -38,17 +38,17 @@ secure crypto.
For harsh but fairly valid criticism of JavaScript cryptography, read: For harsh but fairly valid criticism of JavaScript cryptography, read:
`JavaScript Cryptography Considered Harmful <http://www.matasano.com/articles/javascript-cryptography/>`_. `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>`_. `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 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. communications privacy, then you're much better off using native software.
Notifications 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. message.
For more info, refer to the :ref:`play-sounds` configuration setting. 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 ...@@ -61,13 +61,10 @@ For more info, refer to the :ref:`show-desktop-notifications` configuration sett
Multilingual Support Multilingual Support
==================== ====================
Converse.js is translated into multiple languages. The default build, Converse is translated into multiple languages. Translations are supplied in
``converse.min.js``, includes all languages. 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
Languages increase the size of the Converse.js significantly. :ref:`locales-url` configuration setting.
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.
Moderating chatrooms Moderating chatrooms
==================== ====================
...@@ -103,7 +100,7 @@ Here are the different commands that may be used to moderate a chatroom: ...@@ -103,7 +100,7 @@ Here are the different commands that may be used to moderate a chatroom:
Passwordless login with client certificates 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 used together with x509 client certificates to enable passwordless login or
even 2-factor authentication. even 2-factor authentication.
......
This diff is collapsed.
#conversejs { #conversejs {
.chatbox-navback {
display: none;
}
.flyout { .flyout {
border-radius: $chatbox-border-radius; border-radius: $chatbox-border-radius;
position: absolute; position: absolute;
...@@ -51,10 +54,13 @@ ...@@ -51,10 +54,13 @@
margin-right: 0.5em; margin-right: 0.5em;
} }
.chatbox-title {
.chatroom-description {
font-size: 80%;
}
}
.chatbox-buttons { .chatbox-buttons {
flex-direction: row-reverse; flex-direction: row-reverse;
@include make-col-ready();
@include make-col(4);
padding: 0; padding: 0;
} }
...@@ -443,7 +449,14 @@ ...@@ -443,7 +449,14 @@
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
.chatbox-title {
@include make-col(8);
}
.chatbox-buttons {
@include make-col(4);
}
} }
.chatbox { .chatbox {
min-width: $overlayed-chat-width!important; min-width: $overlayed-chat-width!important;
width: $overlayed-chat-width; width: $overlayed-chat-width;
...@@ -536,9 +549,6 @@ ...@@ -536,9 +549,6 @@
} }
.chat-head { .chat-head {
height: $fullpage-chat-head-height; height: $fullpage-chat-head-height;
.chatbox-buttons {
@include make-col(3);
}
font-size: $font-size-huge; font-size: $font-size-huge;
padding: 0; padding: 0;
.user-custom-message { .user-custom-message {
...@@ -546,6 +556,12 @@ ...@@ -546,6 +556,12 @@
height: auto; height: auto;
line-height: $line-height; line-height: $line-height;
} }
.chatbox-title {
@include make-col(10);
}
.chatbox-buttons {
@include make-col(2);
}
} }
.chat-textarea { .chat-textarea {
max-height: $fullpage-max-chat-textarea-height; max-height: $fullpage-max-chat-textarea-height;
...@@ -623,22 +639,16 @@ ...@@ -623,22 +639,16 @@
} }
} }
@media screen and (max-width: 767px) { @include media-breakpoint-down(sm) {
#conversejs:not(.converse-embedded) { #conversejs:not(.converse-embedded) {
> .row { > .row {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
#converse-login-panel { #converse-login-panel {
.converse-form { .converse-form {
padding: 3em 2em 3em; padding: 3em 2em 3em;
} }
} }
.sidebar {
display: block;
}
.chatbox { .chatbox {
width: calc(100% - 50px); width: calc(100% - 50px);
.row { .row {
...@@ -651,12 +661,29 @@ ...@@ -651,12 +661,29 @@
} }
} }
} }
}
@media screen and (max-width: 767px) { #conversejs.converse-mobile,
#conversejs:not(.converse-embedded).converse-fullscreen { #conversejs.converse-overlayed,
#conversejs.converse-embedded,
#conversejs.converse-fullscreen {
.chatbox { .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);
}
}
} }
} }
} }
...@@ -120,9 +120,17 @@ ...@@ -120,9 +120,17 @@
border-bottom-right-radius: $chatbox-border-radius; border-bottom-right-radius: $chatbox-border-radius;
padding: 0.5em; padding: 0.5em;
.occupants-heading { .occupants-header {
font-family: $heading-font; display: flex;
padding: 0.3em 0; flex-direction: column;
.hide-occupants {
align-self: flex-end;
cursor: pointer;
}
.occupants-heading {
font-family: $heading-font;
padding: 0.3em 0;
}
} }
.chatroom-features { .chatroom-features {
...@@ -292,6 +300,9 @@ ...@@ -292,6 +300,9 @@
font-size: 80%; font-size: 80%;
} }
} }
.chatbox-buttons {
@include make-col(4);
}
.chatroom-body { .chatroom-body {
.occupants { .occupants {
.chatroom-features { .chatroom-features {
...@@ -308,7 +319,39 @@ ...@@ -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-fullscreen,
#conversejs.converse-mobile { #conversejs.converse-mobile {
...@@ -321,7 +364,6 @@ ...@@ -321,7 +364,6 @@
width: 100%; width: 100%;
.chatbox-title { .chatbox-title {
@include make-col(9);
.chatroom-description { .chatroom-description {
font-size: 70%; font-size: 70%;
} }
......
...@@ -357,7 +357,6 @@ ...@@ -357,7 +357,6 @@
.chatbox { .chatbox {
.box-flyout { .box-flyout {
top: -100vh;
margin-left: 15px; // Counteracts Bootstrap margins, but margin-left: 15px; // Counteracts Bootstrap margins, but
// not clear why needed... // not clear why needed...
left: 0; left: 0;
...@@ -393,7 +392,7 @@ ...@@ -393,7 +392,7 @@
} }
} }
#conversejs:not(.converse-fullscreen) { #conversejs.converse-overlayed {
#controlbox { #controlbox {
order: -1; order: -1;
min-width: $controlbox-width !important; min-width: $controlbox-width !important;
...@@ -445,7 +444,8 @@ ...@@ -445,7 +444,8 @@
} }
} }
#conversejs.converse-fullscreen { #conversejs.converse-fullscreen,
#conversejs.converse-mobile {
#controlbox { #controlbox {
@include make-col-ready(); @include make-col-ready();
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
......
...@@ -67,12 +67,16 @@ body.reset { ...@@ -67,12 +67,16 @@ body.reset {
} }
} }
&.converse-fullscreen { &.converse-fullscreen,
&.converse-mobile {
.converse-chatboxes { .converse-chatboxes {
width: 100vw; width: 100vw;
right: 15px; // Hack due to padding added by bootstrap right: 15px; // Hack due to padding added by bootstrap
} }
} }
&.converse-overlayed {
height: 3em;
}
.brand-heading { .brand-heading {
font-family: $heading-font; font-family: $heading-font;
...@@ -89,7 +93,6 @@ body.reset { ...@@ -89,7 +93,6 @@ body.reset {
z-index: 1031; // One more than bootstrap navbar z-index: 1031; // One more than bootstrap navbar
position: fixed; position: fixed;
bottom: 0; bottom: 0;
height: 3em;
right: 0; right: 0;
} }
......
...@@ -3170,7 +3170,7 @@ ...@@ -3170,7 +3170,7 @@
test_utils.openControlBox(); test_utils.openControlBox();
var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; 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); test_utils.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = roomspanel.add_room_modal;
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
...@@ -3205,7 +3205,7 @@ ...@@ -3205,7 +3205,7 @@
test_utils.openControlBox(); test_utils.openControlBox();
var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; 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); test_utils.closeControlBox(_converse);
const modal = roomspanel.list_rooms_modal; const modal = roomspanel.list_rooms_modal;
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
......
...@@ -209,7 +209,7 @@ ...@@ -209,7 +209,7 @@
* </iq> * </iq>
*/ */
spyOn(_converse.roster, "updateContact").and.callThrough(); 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('query', {'xmlns': 'jabber:iq:roster'})
.c('item', { .c('item', {
'jid': 'contact@example.org', 'jid': 'contact@example.org',
......
...@@ -35,6 +35,68 @@ ...@@ -35,6 +35,68 @@
describe("The Contacts Roster", function () { 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 () { describe("The live filter", function () {
it("will only appear when roster contacts flow over the visible area", it("will only appear when roster contacts flow over the visible area",
......
...@@ -62,7 +62,9 @@ ...@@ -62,7 +62,9 @@
// Refer to docs/source/configuration.rst for explanations of these // Refer to docs/source/configuration.rst for explanations of these
// configuration settings. // configuration settings.
_converse.api.settings.update({ _converse.api.settings.update({
auto_join_private_chats: [], 'filter_by_resource': false,
'auto_join_private_chats': [],
'forward_messages': false,
}); });
_converse.api.promises.add([ _converse.api.promises.add([
'chatBoxesFetched', 'chatBoxesFetched',
...@@ -720,9 +722,6 @@ ...@@ -720,9 +722,6 @@
_converse.root.appendChild(el); _converse.root.appendChild(el);
} }
} }
if (_.includes(['mobile', 'fullscreen'], _converse.view_mode)) {
el.classList.add('fullscreen');
}
el.innerHTML = ''; el.innerHTML = '';
this.setElement(el, false); this.setElement(el, false);
} else { } else {
......
...@@ -100,10 +100,11 @@ ...@@ -100,10 +100,11 @@
{ __ } = _converse; { __ } = _converse;
_converse.api.settings.update({ _converse.api.settings.update({
'use_emojione': false,
'emojione_image_path': emojione.imagePathPNG, 'emojione_image_path': emojione.imagePathPNG,
'show_send_button': false,
'show_toolbar': true, 'show_toolbar': true,
'time_format': 'HH:mm', 'time_format': 'HH:mm',
'use_emojione': false,
'visible_toolbar_buttons': { 'visible_toolbar_buttons': {
'call': false, 'call': false,
'clear': true, 'clear': true,
...@@ -320,16 +321,17 @@ ...@@ -320,16 +321,17 @@
events: { events: {
'change input.fileupload': 'onFileSelection', 'change input.fileupload': 'onFileSelection',
'click .chatbox-navback': 'showControlBox',
'click .close-chatbox-button': 'close', 'click .close-chatbox-button': 'close',
'click .show-user-details-modal': 'showUserDetailsModal',
'click .new-msgs-indicator': 'viewUnreadMessages', 'click .new-msgs-indicator': 'viewUnreadMessages',
'click .send-button': 'onFormSubmitted', 'click .send-button': 'onFormSubmitted',
'click .show-user-details-modal': 'showUserDetailsModal',
'click .spoiler-toggle': 'toggleSpoilerMessage',
'click .toggle-call': 'toggleCall', 'click .toggle-call': 'toggleCall',
'click .toggle-clear': 'clearMessages', 'click .toggle-clear': 'clearMessages',
'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .spoiler-toggle': 'toggleSpoilerMessage',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed', 'keypress .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
...@@ -412,6 +414,13 @@ ...@@ -412,6 +414,13 @@
this.renderToolbar(); 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) { showUserDetailsModal (ev) {
if (_.isUndefined(this.user_details_modal)) { if (_.isUndefined(this.user_details_modal)) {
this.user_details_modal = new _converse.UserDetailsModal({model: this.model}); this.user_details_modal = new _converse.UserDetailsModal({model: this.model});
......
// Converse.js // Converse.js
// https://conversejs.org // https://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) // Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) { (function (root, factory) {
...@@ -151,6 +151,64 @@ ...@@ -151,6 +151,64 @@
_converse.DEFAULT_IMAGE_TYPE = 'image/png'; _converse.DEFAULT_IMAGE_TYPE = 'image/png';
_converse.DEFAULT_IMAGE = "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"; _converse.DEFAULT_IMAGE = "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";
_converse.TIMEOUTS = { // Set as module attr so that we can override in tests.
'PAUSED': 10000,
'INACTIVE': 90000
};
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
_converse.INACTIVE = 'inactive';
_converse.ACTIVE = 'active';
_converse.COMPOSING = 'composing';
_converse.PAUSED = 'paused';
_converse.GONE = 'gone';
// Default configuration values
// ----------------------------
_converse.default_settings = {
allow_non_roster_messaging: false,
animate: true,
authentication: 'login', // Available values are "login", "prebind", "anonymous" and "external".
auto_away: 0, // Seconds after which user status is set to 'away'
auto_login: false, // Currently only used in connection with anonymous login
auto_reconnect: true,
auto_xa: 0, // Seconds after which user status is set to 'xa'
blacklisted_plugins: [],
bosh_service_url: undefined,
connection_options: {},
credentials_url: null, // URL from where login credentials can be fetched
csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
debug: false,
default_state: 'online',
expose_rid_and_sid: false,
geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
jid: undefined,
keepalive: true,
locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json',
locales: [
'af', 'ar', 'bg', 'ca', 'de', 'es', 'eu', 'en', 'fr', 'he',
'hu', 'id', 'it', 'ja', 'nb', 'nl',
'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'
],
message_carbons: true,
nickname: undefined,
password: undefined,
prebind_url: null,
priority: 0,
rid: undefined,
root: window.document,
sid: undefined,
storage: 'session',
strict_plugin_dependencies: false,
trusted: true,
view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile'
websocket_url: undefined,
whitelisted_plugins: []
};
_converse.log = function (message, level, style='') { _converse.log = function (message, level, style='') {
/* Logs messages to the browser's developer console. /* Logs messages to the browser's developer console.
* *
...@@ -276,73 +334,6 @@ ...@@ -276,73 +334,6 @@
unloadevent = 'unload'; unloadevent = 'unload';
} }
// Instance level constants
this.TIMEOUTS = { // Set as module attr so that we can override in tests.
'PAUSED': 10000,
'INACTIVE': 90000
};
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
this.INACTIVE = 'inactive';
this.ACTIVE = 'active';
this.COMPOSING = 'composing';
this.PAUSED = 'paused';
this.GONE = 'gone';
// Default configuration values
// ----------------------------
this.default_settings = {
allow_contact_requests: true,
allow_non_roster_messaging: false,
animate: true,
authentication: 'login', // Available values are "login", "prebind", "anonymous" and "external".
auto_away: 0, // Seconds after which user status is set to 'away'
auto_login: false, // Currently only used in connection with anonymous login
auto_reconnect: true,
auto_subscribe: false,
auto_xa: 0, // Seconds after which user status is set to 'xa'
blacklisted_plugins: [],
bosh_service_url: undefined,
connection_options: {},
credentials_url: null, // URL from where login credentials can be fetched
csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
debug: false,
default_state: 'online',
expose_rid_and_sid: false,
filter_by_resource: false,
forward_messages: false,
geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
hide_offline_users: false,
include_offline_state: false,
jid: undefined,
keepalive: true,
locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json',
locales: [
'af', 'ar', 'bg', 'ca', 'de', 'es', 'eu', 'en', 'fr', 'he',
'hu', 'id', 'it', 'ja', 'nb', 'nl',
'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'
],
message_carbons: true,
nickname: undefined,
password: undefined,
prebind_url: null,
priority: 0,
registration_domain: '',
rid: undefined,
root: window.document,
show_only_online_users: false,
show_send_button: false,
sid: undefined,
storage: 'session',
strict_plugin_dependencies: false,
synchronize_availability: true,
trusted: true,
view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile'
websocket_url: undefined,
whitelisted_plugins: []
};
_.assignIn(this, this.default_settings); _.assignIn(this, this.default_settings);
// Allow only whitelisted configuration attributes to be overwritten // Allow only whitelisted configuration attributes to be overwritten
_.assignIn(this, _.pick(settings, _.keys(this.default_settings))); _.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
...@@ -649,6 +640,7 @@ ...@@ -649,6 +640,7 @@
_converse.session.id = id; // Appears to be necessary for backbone.browserStorage _converse.session.id = id; // Appears to be necessary for backbone.browserStorage
_converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id); _converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_converse.session.fetch(); _converse.session.fetch();
_converse.emit('sessionInitialized');
}; };
this.clearSession = function () { this.clearSession = function () {
...@@ -725,6 +717,7 @@ ...@@ -725,6 +717,7 @@
if( document[hidden] !== undefined ) { if( document[hidden] !== undefined ) {
_.partial(_converse.saveWindowState, _, hidden)({type: document[hidden] ? "blur" : "focus"}); _.partial(_converse.saveWindowState, _, hidden)({type: document[hidden] ? "blur" : "focus"});
} }
_converse.emit('registeredGlobalEventHandlers');
}; };
this.enableCarbons = function () { this.enableCarbons = function () {
......
...@@ -37,24 +37,24 @@ ...@@ -37,24 +37,24 @@
this.waitUntilFeaturesDiscovered = utils.getResolveablePromise(); this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
this.dataforms = new Backbone.Collection(); 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')}`) b64_sha1(`converse.dataforms-{this.get('jid')}`)
); );
this.features = new Backbone.Collection(); 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')}`) b64_sha1(`converse.features-${this.get('jid')}`)
); );
this.features.on('add', this.onFeatureAdded, this); this.features.on('add', this.onFeatureAdded, this);
this.identities = new Backbone.Collection(); 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')}`) b64_sha1(`converse.identities-${this.get('jid')}`)
); );
this.fetchFeatures(); this.fetchFeatures();
this.items = new _converse.DiscoEntities(); 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')}`) b64_sha1(`converse.disco-items-${this.get('jid')}`)
); );
this.items.fetch(); this.items.fetch();
...@@ -217,12 +217,34 @@ ...@@ -217,12 +217,34 @@
return this; 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 () { function initializeDisco () {
addClientFeatures(); addClientFeatures();
_converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
_converse.disco_entities = new _converse.DiscoEntities(); _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}`) b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)
); );
...@@ -236,6 +258,7 @@ ...@@ -236,6 +258,7 @@
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
} }
_converse.api.listen.on('sessionInitialized', initStreamFeatures);
_converse.api.listen.on('reconnected', initializeDisco); _converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco); _converse.api.listen.on('connected', initializeDisco);
...@@ -291,6 +314,15 @@ ...@@ -291,6 +314,15 @@
* @namespace * @namespace
*/ */
'disco': { '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 * The "own" grouping
* @namespace * @namespace
......
...@@ -52,16 +52,6 @@ ...@@ -52,16 +52,6 @@
// //
// New functions which don't exist yet can also be added. // 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: { ChatBox: {
initialize () { initialize () {
this.__super__.initialize.apply(this, arguments); this.__super__.initialize.apply(this, arguments);
...@@ -541,6 +531,15 @@ ...@@ -541,6 +531,15 @@
_converse.emit('minimizedChatsInitialized'); _converse.emit('minimizedChatsInitialized');
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }).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) { _converse.on('controlBoxOpened', function (chatbox) {
// Wrapped in anon method because at scan time, chatboxviews // Wrapped in anon method because at scan time, chatboxviews
// attr not set yet. // attr not set yet.
......
...@@ -492,8 +492,10 @@ ...@@ -492,8 +492,10 @@
is_chatroom: true, is_chatroom: true,
events: { events: {
'change input.fileupload': 'onFileSelection', 'change input.fileupload': 'onFileSelection',
'click .chatbox-navback': 'showControlBox',
'click .close-chatbox-button': 'close', 'click .close-chatbox-button': 'close',
'click .configure-chatroom-button': 'getAndRenderConfigurationForm', 'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
'click .hide-occupants': 'hideOccupants',
'click .new-msgs-indicator': 'viewUnreadMessages', 'click .new-msgs-indicator': 'viewUnreadMessages',
'click .occupant-nick': 'onOccupantClicked', 'click .occupant-nick': 'onOccupantClicked',
'click .send-button': 'onFormSubmitted', 'click .send-button': 'onFormSubmitted',
...@@ -697,15 +699,32 @@ ...@@ -697,15 +699,32 @@
setOccupantsVisibility () { setOccupantsVisibility () {
const icon_el = this.el.querySelector('.toggle-occupants'); const icon_el = this.el.querySelector('.toggle-occupants');
if (this.model.get('hidden_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')); u.hideElement(this.el.querySelector('.occupants'));
} else { } else {
this.el.querySelector('.chat-area').classList.remove('full'); u.addClass('fa-angle-double-right', icon_el);
this.el.querySelector('.occupants').classList.remove('hidden'); 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(); 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) { toggleOccupants (ev, preserve_state) {
/* Show or hide the right sidebar containing the chat /* Show or hide the right sidebar containing the chat
* occupants (and the invite widget). * occupants (and the invite widget).
...@@ -1535,8 +1554,8 @@ ...@@ -1535,8 +1554,8 @@
className: 'controlbox-section', className: 'controlbox-section',
id: 'chatrooms', id: 'chatrooms',
events: { events: {
'click a.chatbox-btn.fa-users': 'showAddRoomModal', 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal',
'click a.chatbox-btn.fa-list-ul': 'showListRoomsModal', 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal',
'click a.room-info': 'toggleRoomInfo' 'click a.room-info': 'toggleRoomInfo'
}, },
......
...@@ -985,13 +985,18 @@ ...@@ -985,13 +985,18 @@
}, },
onAvatarChanged () { onAvatarChanged () {
const vcard = _converse.vcards.findWhere({'jid': this.get('from')});
if (!vcard) { return; }
const hash = this.get('image_hash'); const hash = this.get('image_hash');
if (hash && vcard.get('image_hash') !== hash) { const vcards = [];
_converse.api.vcard.update(vcard); 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 () { getDisplayName () {
...@@ -1032,6 +1037,7 @@ ...@@ -1032,6 +1037,7 @@
// Remove absent occupants who've been removed from // Remove absent occupants who've been removed from
// the members lists. // the members lists.
const occupant = this.findOccupant({'jid': removed_jid}); const occupant = this.findOccupant({'jid': removed_jid});
if (!occupant) { return; }
if (occupant.get('show') === 'offline') { if (occupant.get('show') === 'offline') {
occupant.destroy(); occupant.destroy();
} }
......
...@@ -62,16 +62,21 @@ ...@@ -62,16 +62,21 @@
LoginPanel: { LoginPanel: {
render: function (cfg) { insertRegisterLink () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
this.__super__.render.apply(this, arguments); if (_.isUndefined(this.registerlinkview)) {
if (_converse.allow_registration) { this.registerlinkview = new _converse.RegisterLinkView({'model': this.model});
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(); 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; return this;
} }
...@@ -139,9 +144,10 @@ ...@@ -139,9 +144,10 @@
_converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({ _converse.api.settings.update({
allow_registration: true, 'allow_registration': true,
domain_placeholder: __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form '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 'providers_link': 'https://xmpp.net/directory.php', // Link to XMPP providers shown on registration page
'registration_domain': ''
}); });
......
...@@ -22,6 +22,12 @@ ...@@ -22,6 +22,12 @@
const { _converse } = this, const { _converse } = this,
{ __ } = _converse; { __ } = _converse;
_converse.api.settings.update({
'allow_contact_requests': true,
'auto_subscribe': false,
'synchronize_availability': true,
});
_converse.api.promises.add([ _converse.api.promises.add([
'cachedRoster', 'cachedRoster',
'roster', 'roster',
...@@ -48,6 +54,13 @@ ...@@ -48,6 +54,13 @@
_converse.roster = new _converse.RosterContacts(); _converse.roster = new _converse.RosterContacts();
_converse.roster.browserStorage = new Backbone.BrowserStorage[_converse.storage]( _converse.roster.browserStorage = new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.contacts-${_converse.bare_jid}`)); 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 = new _converse.RosterGroups();
_converse.rostergroups.browserStorage = new Backbone.BrowserStorage[_converse.storage]( _converse.rostergroups.browserStorage = new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.roster.groups${_converse.bare_jid}`)); b64_sha1(`converse.roster.groups${_converse.bare_jid}`));
...@@ -330,28 +343,28 @@ ...@@ -330,28 +343,28 @@
onConnected () { onConnected () {
/* Called as soon as the connection has been established /* Called as soon as the connection has been established
* (either after initial login, or after reconnection). * (either after initial login, or after reconnection).
* *
* Use the opportunity to register stanza handlers. * Use the opportunity to register stanza handlers.
*/ */
this.registerRosterHandler(); this.registerRosterHandler();
this.registerRosterXHandler(); this.registerRosterXHandler();
}, },
registerRosterHandler () { registerRosterHandler () {
/* Register a handler for roster IQ "set" stanzas, which update /* Register a handler for roster IQ "set" stanzas, which update
* roster contacts. * roster contacts.
*/ */
_converse.connection.addHandler( _converse.connection.addHandler((iq) => {
_converse.roster.onRosterPush.bind(_converse.roster), _converse.roster.onRosterPush(iq);
Strophe.NS.ROSTER, 'iq', "set" return true;
); }, Strophe.NS.ROSTER, 'iq', "set");
}, },
registerRosterXHandler () { registerRosterXHandler () {
/* Register a handler for RosterX message stanzas, which are /* 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; let t = 0;
_converse.connection.addHandler( _converse.connection.addHandler(
function (msg) { function (msg) {
...@@ -375,12 +388,14 @@ ...@@ -375,12 +388,14 @@
* Returns a promise which resolves once the contacts have been * Returns a promise which resolves once the contacts have been
* fetched. * fetched.
*/ */
const that = this;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.fetch({ this.fetch({
'add': true, 'add': true,
'silent': true, 'silent': true,
success (collection) { success (collection) {
if (collection.length === 0) { if (collection.length === 0 ||
(that.rosterVersioningSupported() && !_converse.session.get('roster_fetched'))) {
_converse.send_initial_presence = true; _converse.send_initial_presence = true;
_converse.roster.fetchFromServer().then(resolve).catch(reject); _converse.roster.fetchFromServer().then(resolve).catch(reject);
} else { } else {
...@@ -506,30 +521,44 @@ ...@@ -506,30 +521,44 @@
onRosterPush (iq) { onRosterPush (iq) {
/* Handle roster updates from the XMPP server. /* Handle roster updates from the XMPP server.
* See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
* *
* Parameters: * Parameters:
* (XMLElement) IQ - The IQ stanza received from the XMPP server. * (XMLElement) IQ - The IQ stanza received from the XMPP server.
*/ */
const id = iq.getAttribute('id'); const id = iq.getAttribute('id');
const from = iq.getAttribute('from'); const from = iq.getAttribute('from');
if (from && from !== "" && Strophe.getBareJidFromJid(from) !== _converse.bare_jid) { if (from && from !== _converse.connection.jid) {
// Receiving client MUST ignore stanza unless it has no from or from = user's bare JID. // https://tools.ietf.org/html/rfc6121#page-15
// XXX: Some naughty servers apparently send from a full //
// JID so we need to explicitly compare bare jids here. // A receiving client MUST ignore the stanza unless it has no 'from'
// https://github.com/jcbrand/converse.js/issues/493 // attribute (i.e., implicitly from the bare JID of the user's
_converse.connection.send( // account) or it has a 'from' attribute whose value matches the
$iq({type: 'error', id, from: _converse.connection.jid}) // user's bare JID <user@domainpart>.
.c('error', {'type': 'cancel'}) return;
.c('service-unavailable', {'xmlns': Strophe.NS.ROSTER })
);
return true;
} }
_converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid})); _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); _converse.emit('rosterPush', iq);
return true; return;
},
rosterVersioningSupported () {
return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version');
}, },
fetchFromServer () { fetchFromServer () {
...@@ -539,7 +568,9 @@ ...@@ -539,7 +568,9 @@
'type': 'get', 'type': 'get',
'id': _converse.connection.getUniqueId('roster') 'id': _converse.connection.getUniqueId('roster')
}).c('query', {xmlns: Strophe.NS.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 callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
const errback = function (iq) { const errback = function (iq) {
const errmsg = "Error while trying to fetch roster from the server"; const errmsg = "Error while trying to fetch roster from the server";
...@@ -552,17 +583,22 @@ ...@@ -552,17 +583,22 @@
onReceivedFromServer (iq) { onReceivedFromServer (iq) {
/* An IQ stanza containing the roster has been received from /* An IQ stanza containing the roster has been received from
* the XMPP server. * the XMPP server.
*/ */
const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq); const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
_.each(items, this.updateContact.bind(this)); 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); _converse.emit('roster', iq);
}, },
updateContact (item) { updateContact (item) {
/* Update or create RosterContact models based on items /* 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'); const jid = item.getAttribute('jid');
if (this.isSelf(jid)) { return; } if (this.isSelf(jid)) { return; }
......
...@@ -80,7 +80,9 @@ ...@@ -80,7 +80,9 @@
_converse.api.settings.update({ _converse.api.settings.update({
'allow_chat_pending_contacts': true, 'allow_chat_pending_contacts': true,
'allow_contact_removal': true, 'allow_contact_removal': true,
'hide_offline_users': false,
'roster_groups': true, 'roster_groups': true,
'show_only_online_users': false,
'show_toolbar': true, 'show_toolbar': true,
'xhr_user_search_url': null 'xhr_user_search_url': null
}); });
......
// Converse.js (A browser based XMPP chat client) // Converse.js
// http://conversejs.org // 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) // Licensed under the Mozilla Public License (MPLv2)
//
/*global Backbone, define, window, JSON */
/* converse-singleton /* converse-singleton
* ****************** * ******************
...@@ -39,10 +37,6 @@ ...@@ -39,10 +37,6 @@
// NB: These plugins need to have already been loaded via require.js. // NB: These plugins need to have already been loaded via require.js.
dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'], dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'],
enabled (_converse) {
return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
},
overrides: { overrides: {
// overrides mentioned here will be picked up by converse.js's // overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the // plugin architecture they will replace existing methods on the
...@@ -50,21 +44,32 @@ ...@@ -50,21 +44,32 @@
// //
// new functions which don't exist yet can also be added. // new functions which don't exist yet can also be added.
ChatBoxes: { ChatBoxes: {
chatBoxMayBeShown (chatbox) { 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) { createChatBox (jid, attrs) {
/* Make sure new chat boxes are hidden by default. */ /* Make sure new chat boxes are hidden by default. */
attrs = attrs || {}; if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
attrs.hidden = true; attrs = attrs || {};
attrs.hidden = true;
}
return this.__super__.createChatBox.call(this, jid, attrs); return this.__super__.createChatBox.call(this, jid, attrs);
} }
}, },
ChatBoxView: { ChatBoxView: {
shouldShowOnTextMessage () { 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) { _show (focus) {
...@@ -72,16 +77,20 @@ ...@@ -72,16 +77,20 @@
* time. So before opening a chat, we make sure all other * time. So before opening a chat, we make sure all other
* chats are hidden. * chats are hidden.
*/ */
_.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
this.model.set('hidden', false); _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
this.model.set('hidden', false);
}
return this.__super__._show.apply(this, arguments); return this.__super__._show.apply(this, arguments);
} }
}, },
ChatRoomView: { ChatRoomView: {
show (focus) { show (focus) {
_.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); if (_.includes(['mobile', 'fullscreen', 'embedded'], this.__super__._converse.view_mode)) {
this.model.set('hidden', false); _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
this.model.set('hidden', false);
}
return this.__super__.show.apply(this, arguments); return this.__super__.show.apply(this, arguments);
} }
} }
......
// Converse.js // Converse.js
// http://conversejs.org // 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) // Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) { (function (root, factory) {
...@@ -68,7 +68,9 @@ ...@@ -68,7 +68,9 @@
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'), 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
'url': _.get(vcard.querySelector('URL'), 'textContent'), 'url': _.get(vcard.querySelector('URL'), 'textContent'),
'role': _.get(vcard.querySelector('ROLE'), '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) { if (result.image) {
...@@ -82,7 +84,11 @@ ...@@ -82,7 +84,11 @@
function onVCardError (_converse, jid, iq, errback) { function onVCardError (_converse, jid, iq, errback) {
if (errback) { if (errback) {
errback({'stanza': iq, 'jid': jid}); errback({
'stanza': iq,
'jid': jid,
'vcard_error': moment().format()
});
} }
} }
...@@ -141,8 +147,11 @@ ...@@ -141,8 +147,11 @@
'get' (model, force) { 'get' (model, force) {
if (_.isString(model)) { if (_.isString(model)) {
return getVCard(_converse, model); return getVCard(_converse, model);
} else if (!model.get('vcard_updated') || force) { } else if (force ||
const jid = model.get('jid') || model.get('muc_jid'); !model.get('vcard_updated') ||
!moment(model.get('vcard_error')).isSame(new Date(), "day")) {
const jid = model.get('jid');
if (!jid) { if (!jid) {
throw new Error("No JID to get vcard for!"); throw new Error("No JID to get vcard for!");
} }
...@@ -155,10 +164,8 @@ ...@@ -155,10 +164,8 @@
'update' (model, force) { 'update' (model, force) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.get(model, force).then((vcard) => { this.get(model, force).then((vcard) => {
model.save(_.extend( delete vcard['stanza']
_.pick(vcard, ['fullname', 'nickname', 'email', 'url', 'role', 'image_type', 'image', 'image_hash']), model.save(vcard);
{'vcard_updated': moment().format()}
));
resolve(); resolve();
}); });
}); });
......
<div class="chat-head chat-head-chatbox row no-gutters"> <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"> <div class="row no-gutters">
<canvas class="avatar" height="36" width="36"></canvas> <canvas class="avatar" height="36" width="36"></canvas>
<div class="col chat-title" title="{{{o.jid}}}"> <div class="col chat-title" title="{{{o.jid}}}">
......
<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>
<div class="chatbox-title"> <div class="chatbox-title">
<div class="chat-title" title="{{{o.jid}}}"> <div class="chat-title" title="{{{o.jid}}}">
{[ if (o.name && o.name !== o.Strophe.getNodeFromJid(o.jid)) { ]} {[ if (o.name && o.name !== o.Strophe.getNodeFromJid(o.jid)) { ]}
......
<!-- <div class="occupants"> --> <!-- <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> <ul class="occupant-list"></ul>
<div class="chatroom-features"></div> <div class="chatroom-features"></div>
<!-- </div> --> <!-- </div> -->
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
<li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li> <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
{[ } ]} {[ } ]}
{[ if (o.show_occupants_toggle) { ]} {[ 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>
{[ } ]} {[ } ]}
<!-- <div id="chatrooms"> --> <!-- <div id="chatrooms"> -->
<div class="d-flex controlbox-padded"> <div class="d-flex controlbox-padded">
<span class="w-100 controlbox-heading">{{{o.heading_chatrooms}}}</span> <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 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 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-add-muc-modal fa fa-plus" title="{{{o.title_new_room}}}" data-toggle="modal" data-target="#add-chatrooms-modal"></a>
</div> </div>
<div class="list-container open-rooms-list rooms-list-container"></div> <div class="list-container open-rooms-list rooms-list-container"></div>
<div class="list-container bookmarks-list rooms-list-container"></div> <div class="list-container bookmarks-list rooms-list-container"></div>
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
var _ = converse.env._; var _ = converse.env._;
var Promise = converse.env.Promise; var Promise = converse.env.Promise;
var Strophe = converse.env.Strophe; var Strophe = converse.env.Strophe;
var moment = converse.env.moment;
var $iq = converse.env.$iq; var $iq = converse.env.$iq;
var mock = {}; var mock = {};
...@@ -62,6 +63,18 @@ ...@@ -62,6 +63,18 @@
this.IQ_ids.push(id); this.IQ_ids.push(id);
return 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._proto._connect = function () {
c.authenticated = true; c.authenticated = true;
...@@ -99,6 +112,7 @@ ...@@ -99,6 +112,7 @@
}, settings || {})); }, settings || {}));
_converse.ChatBoxViews.prototype.trimChat = function () {}; _converse.ChatBoxViews.prototype.trimChat = function () {};
_converse.api.vcard.get = function (model, force) { _converse.api.vcard.get = function (model, force) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let jid; let jid;
...@@ -120,11 +134,13 @@ ...@@ -120,11 +134,13 @@
} }
var vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree; var vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
var result = { var result = {
'stanza': vcard, 'vcard': vcard,
'fullname': _.get(vcard.querySelector('FN'), 'textContent'), 'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'), 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), '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); resolve(result);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
utils.openControlBox(_converse); utils.openControlBox(_converse);
var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; 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); utils.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = roomspanel.add_room_modal;
utils.waitUntil(function () { utils.waitUntil(function () {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment