Commit f7c3351e authored by JC Brand's avatar JC Brand

Merge branch 'master' into webpack

parents 659f70b2 8948be1d
......@@ -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
......
##############################################################################
#
# 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 =
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
......@@ -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;
......
......@@ -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">
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -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
......
......@@ -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
~~~~~~~~~~~~~~~~~~
......
......@@ -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.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
#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);
}
}
}
}
}
......@@ -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%;
}
......
......@@ -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) {
......
......@@ -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;
}
......
......@@ -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 () {
......
......@@ -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',
......
......@@ -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",
......
......@@ -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 {
......
......@@ -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});
......
// Converse.js
// 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)
(function (root, factory) {
......@@ -151,6 +151,64 @@
_converse.DEFAULT_IMAGE_TYPE = 'image/png';
_converse.DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==";
_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='') {
/* Logs messages to the browser's developer console.
*
......@@ -276,73 +334,6 @@
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);
// Allow only whitelisted configuration attributes to be overwritten
_.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
......@@ -649,6 +640,7 @@
_converse.session.id = id; // Appears to be necessary for backbone.browserStorage
_converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_converse.session.fetch();
_converse.emit('sessionInitialized');
};
this.clearSession = function () {
......@@ -725,6 +717,7 @@
if( document[hidden] !== undefined ) {
_.partial(_converse.saveWindowState, _, hidden)({type: document[hidden] ? "blur" : "focus"});
}
_converse.emit('registeredGlobalEventHandlers');
};
this.enableCarbons = function () {
......
......@@ -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
......
......@@ -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.
......
......@@ -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'
},
......
......@@ -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();
}
......
......@@ -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': ''
});
......
......@@ -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; }
......
......@@ -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
});
......
// 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);
}
}
......
// 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();
});
});
......
<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}}}">
......
<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)) { ]}
......
<!-- <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> -->
......@@ -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>
{[ } ]}
<!-- <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>
......
......@@ -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));
......
......@@ -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 () {
......
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