Merge branch 'open-room-via-url'

This commit is contained in:
JC Brand 2017-10-31 23:11:37 +01:00
commit 221798e6e1
13 changed files with 121 additions and 38 deletions

View File

@ -3,6 +3,7 @@
## 3.3.0 (Unreleased) ## 3.3.0 (Unreleased)
### Bugfixes ### Bugfixes
- #800 Could not register successfully in ejabberd 17.01
- Don't require `auto_login` to be `true` when using the API to log in. - Don't require `auto_login` to be `true` when using the API to log in.
- Moment locale wasn't being set to the value passed via the `i18n` option. - Moment locale wasn't being set to the value passed via the `i18n` option.
- Refetch the roster from the server after reconnection. - Refetch the roster from the server after reconnection.
@ -12,7 +13,9 @@
Otherwise connected contacts might not get your presence updates. Otherwise connected contacts might not get your presence updates.
### New Features ### New Features
- #828 Add routing for the `#converse-login` and `#converse-register` URL - #314 Add support for opening chat rooms with a URL fragment such as `#converse/room?jid=room@domain`
and private chats with a URL fragment such as `#converse/chat?jid=user@domain`
- #828 Add routing for the `#converse/login` and `#converse/register` URL
fragments, which will render the registration and login forms respectively. fragments, which will render the registration and login forms respectively.
### UX/UI changes ### UX/UI changes

View File

@ -300,6 +300,22 @@ have to be registered anew.
``_converse.on('reconnected', function () { ... });`` ``_converse.on('reconnected', function () { ... });``
roomsAutoJoined
---------------
Emitted once any rooms that have been configured to be automatically joined,
specified via the _`auto_join_rooms` setting, have been entered.
``_converse.on('roomsAutoJoined', function () { ... });``
Also available as an `ES2015 Promise <http://es6-features.org/#PromiseUsage>`_:
.. code-block:: javascript
_converse.api.waitUntil('roomsAutoJoined').then(function () {
// Your code here...
});
roomInviteSent roomInviteSent
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -6,6 +6,16 @@
Features Features
======== ========
Open chats via URL
==================
From version 3.3.0, converse.js now has the ability to open chats (private or
groupchat) based on the URL fragment.
A room (aka groupchat) can be opened with a URL fragment such as `#converse/room?jid=room@domain`
and a private chat with a URL fragment such as
`#converse/chat?jid=user@domain`.
Off-the-record encryption Off-the-record encryption
========================= =========================

View File

@ -10,7 +10,7 @@
define(["converse-core"], factory); define(["converse-core"], factory);
}(this, function (converse) { }(this, function (converse) {
"use strict"; "use strict";
const { Backbone, Strophe, b64_sha1, utils, _ } = converse.env; const { Backbone, Promise, Strophe, b64_sha1, utils, _ } = converse.env;
converse.plugins.add('converse-chatboxes', { converse.plugins.add('converse-chatboxes', {
@ -55,6 +55,23 @@
'chatBoxesInitialized' 'chatBoxesInitialized'
]); ]);
function openChat (jid) {
if (!utils.isValidJID(jid)) {
return converse.log(
`Invalid JID "${jid}" provided in URL fragment`,
Strophe.LogLevel.WARN
);
}
Promise.all([
_converse.api.waitUntil('rosterContactsFetched'),
_converse.api.waitUntil('chatBoxesFetched')
]).then(() => {
_converse.api.chats.open(jid);
});
}
_converse.router.route('converse/chat?jid=:jid', openChat);
_converse.ChatBoxes = Backbone.Collection.extend({ _converse.ChatBoxes = Backbone.Collection.extend({
comparator: 'time_opened', comparator: 'time_opened',
@ -343,9 +360,12 @@
_converse.log("chats.open: You need to provide at least one JID", Strophe.LogLevel.ERROR); _converse.log("chats.open: You need to provide at least one JID", Strophe.LogLevel.ERROR);
return null; return null;
} else if (_.isString(jids)) { } else if (_.isString(jids)) {
return _converse.getViewForChatBox( const chatbox = _converse.chatboxes.getChatBox(jids, true, attrs);
_converse.chatboxes.getChatBox(jids, true, attrs).trigger('show') if (_.isNil(chatbox)) {
); _converse.log("Could not open chatbox for JID: "+jids);
return;
}
return _converse.getViewForChatBox(chatbox.trigger('show'));
} }
return _.map(jids, (jid) => return _.map(jids, (jid) =>
_converse.getViewForChatBox( _converse.getViewForChatBox(

View File

@ -836,6 +836,9 @@
close (ev) { close (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) {
_converse.router.navigate('');
}
if (_converse.connection.connected) { if (_converse.connection.connected) {
// Immediately sending the chat state, because the // Immediately sending the chat state, because the
// model is going to be destroyed afterwards. // model is going to be destroyed afterwards.

View File

@ -511,7 +511,7 @@
if (jid_element.value && if (jid_element.value &&
!_converse.locked_domain && !_converse.locked_domain &&
!_converse.default_domain && !_converse.default_domain &&
_.filter(jid_element.value.split('@')).length < 2) { !utils.isValidJID(jid_element.value)) {
jid_element.setCustomValidity(__('Please enter a valid XMPP address')); jid_element.setCustomValidity(__('Please enter a valid XMPP address'));
return false; return false;
} }
@ -550,6 +550,10 @@
jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource; jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource;
} }
} }
if (_.includes(["converse/login", "converse/register"],
Backbone.history.getFragment())) {
_converse.router.navigate('', {'replace': true});
}
_converse.connection.reset(); _converse.connection.reset();
_converse.connection.connect(jid, password, _converse.onConnectStatusChanged); _converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
} }

View File

@ -230,6 +230,9 @@
} }
}; };
_converse.router = new Backbone.Router();
_converse.initialize = function (settings, callback) { _converse.initialize = function (settings, callback) {
"use strict"; "use strict";
settings = !_.isUndefined(settings) ? settings : {}; settings = !_.isUndefined(settings) ? settings : {};

View File

@ -353,9 +353,28 @@
'toggle_occupants': true 'toggle_occupants': true
}, },
}); });
_converse.api.promises.add('roomsPanelRendered'); _converse.api.promises.add(['roomsPanelRendered', 'roomsAutoJoined']);
_converse.openChatRoom = function (settings, bring_to_foreground) {
function openRoom (jid) {
if (!utils.isValidJID(jid)) {
return converse.log(
`Invalid JID "${jid}" provided in URL fragment`,
Strophe.LogLevel.WARN
);
}
const promises = [_converse.api.waitUntil('roomsAutoJoined')]
if (!_converse.allow_bookmarks) {
promises.push( _converse.api.waitUntil('bookmarksInitialized'));
}
Promise.all(promises).then(() => {
_converse.api.rooms.open(jid);
});
}
_converse.router.route('converse/room?jid=:jid', openRoom);
function openChatRoom (settings, bring_to_foreground) {
/* Opens a chat room, making sure that certain attributes /* Opens a chat room, making sure that certain attributes
* are correct, for example that the "type" is set to * are correct, for example that the "type" is set to
* "chatroom". * "chatroom".
@ -367,7 +386,7 @@
settings.id = settings.jid; settings.id = settings.jid;
settings.box_id = b64_sha1(settings.jid) settings.box_id = b64_sha1(settings.jid)
return _converse.chatboxviews.showChat(settings, bring_to_foreground); return _converse.chatboxviews.showChat(settings, bring_to_foreground);
}; }
_converse.ChatRoom = _converse.ChatBox.extend({ _converse.ChatRoom = _converse.ChatBox.extend({
@ -823,7 +842,11 @@
affiliations = [affiliations]; affiliations = [affiliations];
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const promises = _.map(affiliations, _.partial(this.requestMemberList, this.model.get('jid'))); const promises = _.map(
affiliations,
_.partial(this.requestMemberList, this.model.get('jid'))
);
Promise.all(promises).then( Promise.all(promises).then(
_.flow(this.marshallAffiliationIQs.bind(this), resolve), _.flow(this.marshallAffiliationIQs.bind(this), resolve),
_.flow(this.marshallAffiliationIQs.bind(this), resolve) _.flow(this.marshallAffiliationIQs.bind(this), resolve)
@ -1243,6 +1266,9 @@
* reason for leaving. * reason for leaving.
*/ */
this.hide(); this.hide();
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
_converse.router.navigate('');
}
this.occupantsview.model.reset(); this.occupantsview.model.reset();
this.occupantsview.model.browserStorage._clear(); this.occupantsview.model.browserStorage._clear();
if (_converse.connection.connected) { if (_converse.connection.connected) {
@ -2637,7 +2663,7 @@
ev.preventDefault(); ev.preventDefault();
const data = this.parseRoomDataFromEvent(ev); const data = this.parseRoomDataFromEvent(ev);
if (!_.isUndefined(data)) { if (!_.isUndefined(data)) {
_converse.openChatRoom(data); openChatRoom(data);
} }
}, },
@ -2685,7 +2711,7 @@
} }
} }
if (result === true) { if (result === true) {
const chatroom = _converse.openChatRoom({ const chatroom = openChatRoom({
'jid': room_jid, 'jid': room_jid,
'password': $x.attr('password') 'password': $x.attr('password')
}); });
@ -2724,6 +2750,7 @@
Strophe.LogLevel.ERROR); Strophe.LogLevel.ERROR);
} }
}); });
_converse.emit('roomsAutoJoined');
} }
_converse.on('chatBoxesFetched', autoJoinRooms); _converse.on('chatBoxesFetched', autoJoinRooms);
@ -2775,9 +2802,9 @@
if (_.isUndefined(jids)) { if (_.isUndefined(jids)) {
throw new TypeError('rooms.open: You need to provide at least one JID'); throw new TypeError('rooms.open: You need to provide at least one JID');
} else if (_.isString(jids)) { } else if (_.isString(jids)) {
return _converse.getChatRoom(jids, attrs, _converse.openChatRoom); return _converse.getChatRoom(jids, attrs, openChatRoom);
} }
return _.map(jids, _.partial(_converse.getChatRoom, _, attrs, _converse.openChatRoom)); return _.map(jids, _.partial(_converse.getChatRoom, _, attrs, openChatRoom));
}, },
'get' (jids, attrs, create) { 'get' (jids, attrs, create) {
if (_.isString(attrs)) { if (_.isString(attrs)) {

View File

@ -59,7 +59,7 @@
// relevant objects or classes. // relevant objects or classes.
// //
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
LoginPanel: { LoginPanel: {
render: function (cfg) { render: function (cfg) {
@ -80,9 +80,6 @@
ControlBoxView: { ControlBoxView: {
events: {
},
initialize () { initialize () {
this.__super__.initialize.apply(this, arguments); this.__super__.initialize.apply(this, arguments);
this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this)) this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this))
@ -102,7 +99,6 @@
} }
}, },
renderRegistrationPanel () { renderRegistrationPanel () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (_converse.allow_registration) { if (_converse.allow_registration) {
@ -149,21 +145,15 @@
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
}); });
_converse.RegistrationRouter = Backbone.Router.extend({
initialize () { function setActiveForm (value) {
this.route('converse-login', _.partial(this.setActiveForm, 'login')); _converse.api.waitUntil('controlboxInitialized').then(() => {
this.route('converse-register', _.partial(this.setActiveForm, 'register')); const controlbox = _converse.chatboxes.get('controlbox')
}, controlbox.set({'active-form': value});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
setActiveForm (value) { }
_converse.api.waitUntil('controlboxInitialized').then(() => { _converse.router.route('converse/login', _.partial(setActiveForm, 'login'));
const controlbox = _converse.chatboxes.get('controlbox') _converse.router.route('converse/register', _.partial(setActiveForm, 'register'));
controlbox.set({'active-form': value});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
});
const router = new _converse.RegistrationRouter();
_converse.RegisterPanel = Backbone.View.extend({ _converse.RegisterPanel = Backbone.View.extend({
@ -425,11 +415,14 @@
); );
this.abortRegistration(); this.abortRegistration();
} else if (status_code === Strophe.Status.REGISTERED) { } else if (status_code === Strophe.Status.REGISTERED) {
router.navigate(); // Strip the URL fragment
_converse.log("Registered successfully."); _converse.log("Registered successfully.");
_converse.connection.reset(); _converse.connection.reset();
this.showSpinner(); this.showSpinner();
if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) {
_converse.router.navigate('', {'replace': true});
}
if (this.fields.password && this.fields.username) { if (this.fields.password && this.fields.username) {
// automatically log the user in // automatically log the user in
_converse.connection.connect( _converse.connection.connect(
@ -464,7 +457,7 @@
form.insertAdjacentHTML( form.insertAdjacentHTML(
'beforeend', 'beforeend',
tpl_form_input({ tpl_form_input({
'label': key, 'label': key,
'name': key, 'name': key,
'placeholder': key, 'placeholder': key,
'required': true, 'required': true,

View File

@ -31,7 +31,7 @@
function (iq, jid) { function (iq, jid) {
_converse.log( _converse.log(
`Error while retrieving vcard for ${jid}`, `Error while retrieving vcard for ${jid}`,
Strophe.LogLevel.ERROR Strophe.LogLevel.WARN
); );
_converse.createRequestingContactFromVCard(presence, iq, jid); _converse.createRequestingContactFromVCard(presence, iq, jid);
} }

View File

@ -1,4 +1,4 @@
<div class="switch-form"> <div class="switch-form">
<p>{{{ __("Don't have a chat account?") }}}</p> <p>{{{ __("Don't have a chat account?") }}}</p>
<p><a class="register-account toggle-register-login" href="#converse-register">{{{__("Create an account")}}}</a></p> <p><a class="register-account toggle-register-login" href="#converse/register">{{{__("Create an account")}}}</a></p>
</div> </div>

View File

@ -17,6 +17,6 @@
<div class="switch-form"> <div class="switch-form">
<p>{{{ __("Already have a chat account?") }}}</p> <p>{{{ __("Already have a chat account?") }}}</p>
<p> <p>
<a class="login-here toggle-register-login" href="#converse-login">{{{__("Log in here")}}}</a> <a class="login-here toggle-register-login" href="#converse/login">{{{__("Log in here")}}}</a>
</p> </p>
</div> </div>

View File

@ -266,6 +266,10 @@
} }
}; };
u.isValidJID = function (jid) {
return _.filter(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@');
};
u.isSameBareJID = function (jid1, jid2) { u.isSameBareJID = function (jid1, jid2) {
return Strophe.getBareJidFromJid(jid1).toLowerCase() === return Strophe.getBareJidFromJid(jid1).toLowerCase() ===
Strophe.getBareJidFromJid(jid2).toLowerCase(); Strophe.getBareJidFromJid(jid2).toLowerCase();