Moved all the registration code into a plugin
This commit is contained in:
parent
147a3c9e3e
commit
cb241dd594
@ -6,10 +6,11 @@
|
||||
define("converse", [
|
||||
/* Removable components
|
||||
* --------------------
|
||||
* Any of the following components can be removed if they're not needed.
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
"converse-muc", // XEP-0045 Multi-user chat
|
||||
"converse-otr", // Off-the-record encryption for one-on-one messages
|
||||
"converse-register", // XEP-0077 In-band registration
|
||||
/* End: Removable components */
|
||||
|
||||
"converse-core"
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
## 0.11.0 (Unreleased)
|
||||
|
||||
- Split converse.js into different modules. The code for the OTR and MUC
|
||||
features are now in separate modules and these can be removed completely from
|
||||
the build. [jcbrand]
|
||||
- Split converse.js into different modules.
|
||||
The code for the OTR, MUC and registration features are now in separate
|
||||
modules and these can be removed completely from the build. [jcbrand]
|
||||
- Don't play sound notifications for OTR messages which are setting up an
|
||||
encrypted session. [jcbrand]
|
||||
- Removed the `account.logout` API, instead use `user.logout`. [jcbrand]
|
||||
|
1
main.js
1
main.js
@ -47,6 +47,7 @@ require.config({
|
||||
"converse-core": "src/converse-core",
|
||||
"converse-muc": "src/converse-muc",
|
||||
"converse-otr": "src/converse-otr",
|
||||
"converse-register": "src/converse-register",
|
||||
"converse-dependencies": "src/deps-full",
|
||||
"converse-templates": "src/templates",
|
||||
|
||||
|
@ -131,21 +131,10 @@
|
||||
Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
|
||||
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
|
||||
Strophe.addNamespace('MAM', 'urn:xmpp:mam:0');
|
||||
Strophe.addNamespace('REGISTER', 'jabber:iq:register');
|
||||
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
|
||||
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
|
||||
Strophe.addNamespace('XFORM', 'jabber:x:data');
|
||||
|
||||
// Add Strophe Statuses
|
||||
var i = 0;
|
||||
Object.keys(Strophe.Status).forEach(function (key) {
|
||||
i = Math.max(i, Strophe.Status[key]);
|
||||
});
|
||||
Strophe.Status.REGIFAIL = i + 1;
|
||||
Strophe.Status.REGISTERED = i + 2;
|
||||
Strophe.Status.CONFLICT = i + 3;
|
||||
Strophe.Status.NOTACCEPTABLE = i + 5;
|
||||
|
||||
// Constants
|
||||
// ---------
|
||||
var LOGIN = "login";
|
||||
@ -258,7 +247,6 @@
|
||||
moment.locale = moment.lang;
|
||||
}
|
||||
moment.locale(this.detectLocale(this.isMomentLocale));
|
||||
this.i18n = settings.i18n ? settings.i18n : locales[this.detectLocale(this.isConverseLocale)];
|
||||
|
||||
// Translation machinery
|
||||
// ---------------------
|
||||
@ -272,7 +260,6 @@
|
||||
allow_contact_requests: true,
|
||||
allow_dragresize: true,
|
||||
allow_logout: true,
|
||||
allow_registration: true,
|
||||
animate: true,
|
||||
archived_messages_page_size: '20',
|
||||
authentication: 'login', // Available values are "login", "prebind", "anonymous".
|
||||
@ -286,7 +273,6 @@
|
||||
csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
|
||||
debug: false,
|
||||
default_domain: undefined,
|
||||
domain_placeholder: __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form
|
||||
expose_rid_and_sid: false,
|
||||
forward_messages: false,
|
||||
hide_offline_users: false,
|
||||
@ -302,7 +288,6 @@
|
||||
play_sounds: false,
|
||||
prebind: false, // XXX: Deprecated, use "authentication" instead.
|
||||
prebind_url: null,
|
||||
providers_link: 'https://xmpp.net/directory.php', // Link to XMPP providers shown on registration page
|
||||
rid: undefined,
|
||||
roster_groups: false,
|
||||
show_controlbox_by_default: false,
|
||||
@ -2032,22 +2017,16 @@
|
||||
renderLoginPanel: function () {
|
||||
var $feedback = this.$('.conn-feedback'); // we want to still show any existing feedback.
|
||||
this.$el.html(converse.templates.controlbox(this.model.toJSON()));
|
||||
var cfg = {'$parent': this.$el.find('.controlbox-panes'), 'model': this};
|
||||
var cfg = {
|
||||
'$parent': this.$el.find('.controlbox-panes'),
|
||||
'model': this
|
||||
};
|
||||
if (!this.loginpanel) {
|
||||
this.loginpanel = new converse.LoginPanel(cfg);
|
||||
if (converse.allow_registration) {
|
||||
this.registerpanel = new converse.RegisterPanel(cfg);
|
||||
}
|
||||
} else {
|
||||
this.loginpanel.delegateEvents().initialize(cfg);
|
||||
if (converse.allow_registration) {
|
||||
this.registerpanel.delegateEvents().initialize(cfg);
|
||||
}
|
||||
}
|
||||
this.loginpanel.render();
|
||||
if (converse.allow_registration) {
|
||||
this.registerpanel.render().$el.hide();
|
||||
}
|
||||
this.initDragResize().setDimensions();
|
||||
if ($feedback.length && $feedback.text() !== __('Connecting')) {
|
||||
this.$('.conn-feedback').replaceWith($feedback);
|
||||
@ -4059,427 +4038,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
this.RegisterPanel = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
id: "register",
|
||||
className: 'controlbox-pane',
|
||||
events: {
|
||||
'submit form#converse-register': 'onProviderChosen'
|
||||
},
|
||||
|
||||
initialize: function (cfg) {
|
||||
this.reset();
|
||||
this.$parent = cfg.$parent;
|
||||
this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
|
||||
this.registerHooks();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$parent.append(this.$el.html(
|
||||
converse.templates.register_panel({
|
||||
'label_domain': __("Your XMPP provider's domain name:"),
|
||||
'label_register': __('Fetch registration form'),
|
||||
'help_providers': __('Tip: A list of public XMPP providers is available'),
|
||||
'help_providers_link': __('here'),
|
||||
'href_providers': converse.providers_link,
|
||||
'domain_placeholder': converse.domain_placeholder
|
||||
})
|
||||
));
|
||||
this.$tabs.append(converse.templates.register_tab({label_register: __('Register')}));
|
||||
return this;
|
||||
},
|
||||
|
||||
registerHooks: function () {
|
||||
/* Hook into Strophe's _connect_cb, so that we can send an IQ
|
||||
* requesting the registration fields.
|
||||
*/
|
||||
var conn = converse.connection;
|
||||
var connect_cb = conn._connect_cb.bind(conn);
|
||||
conn._connect_cb = function (req, callback, raw) {
|
||||
if (!this._registering) {
|
||||
connect_cb(req, callback, raw);
|
||||
} else {
|
||||
if (this.getRegistrationFields(req, callback, raw)) {
|
||||
this._registering = false;
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
getRegistrationFields: function (req, _callback, raw) {
|
||||
/* Send an IQ stanza to the XMPP server asking for the
|
||||
* registration fields.
|
||||
* Parameters:
|
||||
* (Strophe.Request) req - The current request
|
||||
* (Function) callback
|
||||
*/
|
||||
converse.log("sendQueryStanza was called");
|
||||
var conn = converse.connection;
|
||||
conn.connected = true;
|
||||
|
||||
var body = conn._proto._reqToData(req);
|
||||
if (!body) { return; }
|
||||
if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) {
|
||||
return false;
|
||||
}
|
||||
var register = body.getElementsByTagName("register");
|
||||
var mechanisms = body.getElementsByTagName("mechanism");
|
||||
if (register.length === 0 && mechanisms.length === 0) {
|
||||
conn._proto._no_auth_received(_callback);
|
||||
return false;
|
||||
}
|
||||
if (register.length === 0) {
|
||||
conn._changeConnectStatus(
|
||||
Strophe.Status.REGIFAIL,
|
||||
__('Sorry, the given provider does not support in band account registration. Please try with a different provider.')
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// Send an IQ stanza to get all required data fields
|
||||
conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null);
|
||||
conn.send($iq({type: "get"}).c("query", {xmlns: Strophe.NS.REGISTER}).tree());
|
||||
return true;
|
||||
},
|
||||
|
||||
onRegistrationFields: function (stanza) {
|
||||
/* Handler for Registration Fields Request.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) elem - The query stanza.
|
||||
*/
|
||||
if (stanza.getElementsByTagName("query").length !== 1) {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
|
||||
return false;
|
||||
}
|
||||
this.setFields(stanza);
|
||||
this.renderRegistrationForm(stanza);
|
||||
return false;
|
||||
},
|
||||
|
||||
reset: function (settings) {
|
||||
var defaults = {
|
||||
fields: {},
|
||||
urls: [],
|
||||
title: "",
|
||||
instructions: "",
|
||||
registered: false,
|
||||
_registering: false,
|
||||
domain: null,
|
||||
form_type: null
|
||||
};
|
||||
_.extend(this, defaults);
|
||||
if (settings) {
|
||||
_.extend(this, _.pick(settings, Object.keys(defaults)));
|
||||
}
|
||||
},
|
||||
|
||||
onProviderChosen: function (ev) {
|
||||
/* Callback method that gets called when the user has chosen an
|
||||
* XMPP provider.
|
||||
*
|
||||
* Parameters:
|
||||
* (Submit Event) ev - Form submission event.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var $form = $(ev.target),
|
||||
$domain_input = $form.find('input[name=domain]'),
|
||||
domain = $domain_input.val();
|
||||
if (!domain) {
|
||||
$domain_input.addClass('error');
|
||||
return;
|
||||
}
|
||||
$form.find('input[type=submit]').hide()
|
||||
.after(converse.templates.registration_request({
|
||||
cancel: __('Cancel'),
|
||||
info_message: __('Requesting a registration form from the XMPP server')
|
||||
}));
|
||||
$form.find('button.cancel').on('click', this.cancelRegistration.bind(this));
|
||||
this.reset({
|
||||
domain: Strophe.getDomainFromJid(domain),
|
||||
_registering: true
|
||||
});
|
||||
converse.connection.connect(this.domain, "", this.onRegistering.bind(this));
|
||||
return false;
|
||||
},
|
||||
|
||||
giveFeedback: function (message, klass) {
|
||||
this.$('.reg-feedback').attr('class', 'reg-feedback').text(message);
|
||||
if (klass) {
|
||||
$('.reg-feedback').addClass(klass);
|
||||
}
|
||||
},
|
||||
|
||||
onRegistering: function (status, error) {
|
||||
var that;
|
||||
converse.log('onRegistering');
|
||||
if (_.contains([
|
||||
Strophe.Status.DISCONNECTED,
|
||||
Strophe.Status.CONNFAIL,
|
||||
Strophe.Status.REGIFAIL,
|
||||
Strophe.Status.NOTACCEPTABLE,
|
||||
Strophe.Status.CONFLICT
|
||||
], status)) {
|
||||
|
||||
converse.log('Problem during registration: Strophe.Status is: '+status);
|
||||
this.cancelRegistration();
|
||||
if (error) {
|
||||
this.giveFeedback(error, 'error');
|
||||
} else {
|
||||
this.giveFeedback(__(
|
||||
'Something went wrong while establishing a connection with "%1$s". Are you sure it exists?',
|
||||
this.domain
|
||||
), 'error');
|
||||
}
|
||||
} else if (status === Strophe.Status.REGISTERED) {
|
||||
converse.log("Registered successfully.");
|
||||
converse.connection.reset();
|
||||
that = this;
|
||||
this.$('form').hide(function () {
|
||||
$(this).replaceWith('<span class="spinner centered"/>');
|
||||
if (that.fields.password && that.fields.username) {
|
||||
// automatically log the user in
|
||||
converse.connection.connect(
|
||||
that.fields.username.toLowerCase()+'@'+that.domain.toLowerCase(),
|
||||
that.fields.password,
|
||||
converse.onConnectStatusChanged
|
||||
);
|
||||
converse.chatboxviews.get('controlbox')
|
||||
.switchTab({target: that.$tabs.find('.current')})
|
||||
.giveFeedback(__('Now logging you in'));
|
||||
} else {
|
||||
converse.chatboxviews.get('controlbox')
|
||||
.renderLoginPanel()
|
||||
.giveFeedback(__('Registered successfully'));
|
||||
}
|
||||
that.reset();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
renderRegistrationForm: function (stanza) {
|
||||
/* Renders the registration form based on the XForm fields
|
||||
* received from the XMPP server.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza received from the XMPP server.
|
||||
*/
|
||||
var $form= this.$('form'),
|
||||
$stanza = $(stanza),
|
||||
$fields, $input;
|
||||
$form.empty().append(converse.templates.registration_form({
|
||||
'domain': this.domain,
|
||||
'title': this.title,
|
||||
'instructions': this.instructions
|
||||
}));
|
||||
if (this.form_type === 'xform') {
|
||||
$fields = $stanza.find('field');
|
||||
_.each($fields, function (field) {
|
||||
$form.append(utils.xForm2webForm.bind(this, $(field), $stanza));
|
||||
}.bind(this));
|
||||
} else {
|
||||
// Show fields
|
||||
_.each(Object.keys(this.fields), function (key) {
|
||||
if (key === "username") {
|
||||
$input = templates.form_username({
|
||||
domain: ' @'+this.domain,
|
||||
name: key,
|
||||
type: "text",
|
||||
label: key,
|
||||
value: '',
|
||||
required: 1
|
||||
});
|
||||
} else {
|
||||
$form.append('<label>'+key+'</label>');
|
||||
$input = $('<input placeholder="'+key+'" name="'+key+'"></input>');
|
||||
if (key === 'password' || key === 'email') {
|
||||
$input.attr('type', key);
|
||||
}
|
||||
}
|
||||
$form.append($input);
|
||||
}.bind(this));
|
||||
// Show urls
|
||||
_.each(this.urls, function (url) {
|
||||
$form.append($('<a target="blank"></a>').attr('href', url).text(url));
|
||||
}.bind(this));
|
||||
}
|
||||
if (this.fields) {
|
||||
$form.append('<input type="submit" class="pure-button button-primary" value="'+__('Register')+'"/>');
|
||||
$form.on('submit', this.submitRegistrationForm.bind(this));
|
||||
$form.append('<input type="button" class="pure-button button-cancel" value="'+__('Cancel')+'"/>');
|
||||
$form.find('input[type=button]').on('click', this.cancelRegistration.bind(this));
|
||||
} else {
|
||||
$form.append('<input type="button" class="submit" value="'+__('Return')+'"/>');
|
||||
$form.find('input[type=button]').on('click', this.cancelRegistration.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
reportErrors: function (stanza) {
|
||||
/* Report back to the user any error messages received from the
|
||||
* XMPP server after attempted registration.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza received from the
|
||||
* XMPP server.
|
||||
*/
|
||||
var $form= this.$('form'), flash;
|
||||
var $errmsgs = $(stanza).find('error text');
|
||||
var $flash = $form.find('.form-errors');
|
||||
if (!$flash.length) {
|
||||
flash = '<legend class="form-errors"></legend>';
|
||||
if ($form.find('p.instructions').length) {
|
||||
$form.find('p.instructions').append(flash);
|
||||
} else {
|
||||
$form.prepend(flash);
|
||||
}
|
||||
$flash = $form.find('.form-errors');
|
||||
} else {
|
||||
$flash.empty();
|
||||
}
|
||||
$errmsgs.each(function (idx, txt) {
|
||||
$flash.append($('<p>').text($(txt).text()));
|
||||
});
|
||||
if (!$errmsgs.length) {
|
||||
$flash.append($('<p>').text(
|
||||
__('The provider rejected your registration attempt. '+
|
||||
'Please check the values you entered for correctness.')));
|
||||
}
|
||||
$flash.show();
|
||||
},
|
||||
|
||||
cancelRegistration: function (ev) {
|
||||
/* Handler, when the user cancels the registration form.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
converse.connection.reset();
|
||||
this.render();
|
||||
},
|
||||
|
||||
submitRegistrationForm : function (ev) {
|
||||
/* Handler, when the user submits the registration form.
|
||||
* Provides form error feedback or starts the registration
|
||||
* process.
|
||||
*
|
||||
* Parameters:
|
||||
* (Event) ev - the submit event.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var $empty_inputs = this.$('input.required:emptyVal');
|
||||
if ($empty_inputs.length) {
|
||||
$empty_inputs.addClass('error');
|
||||
return;
|
||||
}
|
||||
var $inputs = $(ev.target).find(':input:not([type=button]):not([type=submit])'),
|
||||
iq = $iq({type: "set"}).c("query", {xmlns:Strophe.NS.REGISTER});
|
||||
|
||||
if (this.form_type === 'xform') {
|
||||
iq.c("x", {xmlns: Strophe.NS.XFORM, type: 'submit'});
|
||||
$inputs.each(function () {
|
||||
iq.cnode(utils.webForm2xForm(this)).up();
|
||||
});
|
||||
} else {
|
||||
$inputs.each(function () {
|
||||
var $input = $(this);
|
||||
iq.c($input.attr('name'), {}, $input.val());
|
||||
});
|
||||
}
|
||||
converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null);
|
||||
converse.connection.send(iq);
|
||||
this.setFields(iq.tree());
|
||||
},
|
||||
|
||||
setFields: function (stanza) {
|
||||
/* Stores the values that will be sent to the XMPP server
|
||||
* during attempted registration.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server.
|
||||
*/
|
||||
var $query = $(stanza).find('query'), $xform;
|
||||
if ($query.length > 0) {
|
||||
$xform = $query.find('x[xmlns="'+Strophe.NS.XFORM+'"]');
|
||||
if ($xform.length > 0) {
|
||||
this._setFieldsFromXForm($xform);
|
||||
} else {
|
||||
this._setFieldsFromLegacy($query);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setFieldsFromLegacy: function ($query) {
|
||||
$query.children().each(function (idx, field) {
|
||||
var $field = $(field);
|
||||
if (field.tagName.toLowerCase() === 'instructions') {
|
||||
this.instructions = Strophe.getText(field);
|
||||
return;
|
||||
} else if (field.tagName.toLowerCase() === 'x') {
|
||||
if ($field.attr('xmlns') === 'jabber:x:oob') {
|
||||
$field.find('url').each(function (idx, url) {
|
||||
this.urls.push($(url).text());
|
||||
}.bind(this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.fields[field.tagName.toLowerCase()] = Strophe.getText(field);
|
||||
}.bind(this));
|
||||
this.form_type = 'legacy';
|
||||
},
|
||||
|
||||
_setFieldsFromXForm: function ($xform) {
|
||||
this.title = $xform.find('title').text();
|
||||
this.instructions = $xform.find('instructions').text();
|
||||
$xform.find('field').each(function (idx, field) {
|
||||
var _var = field.getAttribute('var');
|
||||
if (_var) {
|
||||
this.fields[_var.toLowerCase()] = $(field).children('value').text();
|
||||
} else {
|
||||
// TODO: other option seems to be type="fixed"
|
||||
converse.log("WARNING: Found field we couldn't parse");
|
||||
}
|
||||
}.bind(this));
|
||||
this.form_type = 'xform';
|
||||
},
|
||||
|
||||
_onRegisterIQ: function (stanza) {
|
||||
/* Callback method that gets called when a return IQ stanza
|
||||
* is received from the XMPP server, after attempting to
|
||||
* register a new user.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza.
|
||||
*/
|
||||
var error = null,
|
||||
query = stanza.getElementsByTagName("query");
|
||||
if (query.length > 0) {
|
||||
query = query[0];
|
||||
}
|
||||
if (stanza.getAttribute("type") === "error") {
|
||||
converse.log("Registration failed.");
|
||||
error = stanza.getElementsByTagName("error");
|
||||
if (error.length !== 1) {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
|
||||
return false;
|
||||
}
|
||||
error = error[0].firstChild.tagName.toLowerCase();
|
||||
if (error === 'conflict') {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error);
|
||||
} else if (error === 'not-acceptable') {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error);
|
||||
} else {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);
|
||||
}
|
||||
this.reportErrors(stanza);
|
||||
} else {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.$tabs.empty();
|
||||
this.$el.parent().empty();
|
||||
}
|
||||
});
|
||||
|
||||
this.LoginPanel = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
id: "login-dialog",
|
||||
|
@ -22,6 +22,7 @@
|
||||
factory(converse, utils);
|
||||
}
|
||||
}(this, function (converse_api, utils) {
|
||||
"use strict";
|
||||
// Strophe methods for building stanzas
|
||||
var Strophe = converse_api.env.Strophe,
|
||||
$iq = converse_api.env.$iq,
|
||||
@ -36,7 +37,10 @@
|
||||
|
||||
// Translation machinery
|
||||
// ---------------------
|
||||
var __ = utils.__.bind(this);
|
||||
// __ is just a placeholder for now, we need to bind the utils.__ method
|
||||
// to the inner converse object, which we can't here, so we do it in the
|
||||
// initialize method.
|
||||
var __ = function () {};
|
||||
var ___ = utils.___;
|
||||
|
||||
// Add Strophe Namespaces
|
||||
@ -194,8 +198,9 @@
|
||||
/* The initialize function gets called as soon as the plugin is
|
||||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
|
||||
var converse = this.converse;
|
||||
// For translations
|
||||
__ = utils.__.bind(converse);
|
||||
// Configuration values for this plugin
|
||||
var settings = {
|
||||
allow_muc: true,
|
||||
|
@ -23,6 +23,7 @@
|
||||
factory(otr, converse, utils);
|
||||
}
|
||||
}(this, function (otr, converse_api, utils) {
|
||||
"use strict";
|
||||
// Strophe methods for building stanzas
|
||||
var Strophe = converse_api.env.Strophe,
|
||||
b64_sha1 = converse_api.env.b64_sha1;
|
||||
@ -30,10 +31,6 @@
|
||||
var $ = converse_api.env.jQuery,
|
||||
_ = converse_api.env._;
|
||||
|
||||
// Translation machinery
|
||||
// ---------------------
|
||||
var __ = utils.__.bind(this);
|
||||
|
||||
var HAS_CSPRNG = ((typeof crypto !== 'undefined') &&
|
||||
((typeof crypto.randomBytes === 'function') ||
|
||||
(typeof crypto.getRandomValues === 'function')
|
||||
@ -49,20 +46,28 @@
|
||||
var VERIFIED= 2;
|
||||
var FINISHED = 3;
|
||||
|
||||
// Translation aware constants
|
||||
// ---------------------------
|
||||
|
||||
var OTR_CLASS_MAPPING = {};
|
||||
OTR_CLASS_MAPPING[UNENCRYPTED] = 'unencrypted';
|
||||
OTR_CLASS_MAPPING[UNVERIFIED] = 'unverified';
|
||||
OTR_CLASS_MAPPING[VERIFIED] = 'verified';
|
||||
OTR_CLASS_MAPPING[FINISHED] = 'finished';
|
||||
|
||||
// Translation aware constants
|
||||
// ---------------------------
|
||||
|
||||
// Just a placeholder for now, we need to bind the utils.__ method to the
|
||||
// inner converse object, which we can't here, so we do it in the
|
||||
// initialize method.
|
||||
var __ = function () {};
|
||||
|
||||
var OTR_TRANSLATED_MAPPING = {};
|
||||
OTR_TRANSLATED_MAPPING[UNENCRYPTED] = __('unencrypted');
|
||||
OTR_TRANSLATED_MAPPING[UNVERIFIED] = __('unverified');
|
||||
OTR_TRANSLATED_MAPPING[VERIFIED] = __('verified');
|
||||
OTR_TRANSLATED_MAPPING[FINISHED] = __('finished');
|
||||
|
||||
|
||||
converse_api.plugins.add('otr', {
|
||||
|
||||
overrides: {
|
||||
@ -486,6 +491,8 @@
|
||||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
var converse = this.converse;
|
||||
// For translations
|
||||
__ = utils.__.bind(converse);
|
||||
// Configuration values for this plugin
|
||||
var settings = {
|
||||
allow_otr: true,
|
||||
|
527
src/converse-register.js
Normal file
527
src/converse-register.js
Normal file
@ -0,0 +1,527 @@
|
||||
// Converse.js (A browser based XMPP chat client)
|
||||
// http://conversejs.org
|
||||
//
|
||||
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
//
|
||||
/*global converse, utils, Backbone, define */
|
||||
|
||||
/* This is a Converse.js plugin which add support for in-band registration
|
||||
* as specified in XEP-0077.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD module loading
|
||||
define("converse-register", ["converse-core", "utils"], factory);
|
||||
} else {
|
||||
// When not using a module loader
|
||||
// -------------------------------
|
||||
// In this case, the dependencies need to be available already as
|
||||
// global variables, and should be loaded separately via *script* tags.
|
||||
// See the file **non_amd.html** for an example of this usecase.
|
||||
factory(converse, utils);
|
||||
}
|
||||
}(this, function (converse_api, utils) {
|
||||
"use strict";
|
||||
// Strophe methods for building stanzas
|
||||
var Strophe = converse_api.env.Strophe,
|
||||
$iq = converse_api.env.$iq;
|
||||
// Other necessary globals
|
||||
var $ = converse_api.env.jQuery,
|
||||
_ = converse_api.env._;
|
||||
|
||||
// Translation machinery
|
||||
// ---------------------
|
||||
// Just a placeholder for now, we need to bind the utils.__ method to the
|
||||
// inner converse object, which we can't here, so we do it in the
|
||||
// initialize method.
|
||||
var __ = function () {};
|
||||
|
||||
// Add Strophe Namespaces
|
||||
Strophe.addNamespace('REGISTER', 'jabber:iq:register');
|
||||
|
||||
// Add Strophe Statuses
|
||||
var i = 0;
|
||||
Object.keys(Strophe.Status).forEach(function (key) {
|
||||
i = Math.max(i, Strophe.Status[key]);
|
||||
});
|
||||
Strophe.Status.REGIFAIL = i + 1;
|
||||
Strophe.Status.REGISTERED = i + 2;
|
||||
Strophe.Status.CONFLICT = i + 3;
|
||||
Strophe.Status.NOTACCEPTABLE = i + 5;
|
||||
|
||||
converse_api.plugins.add('register', {
|
||||
|
||||
overrides: {
|
||||
// Overrides mentioned here will be picked up by converse.js's
|
||||
// plugin architecture they will replace existing methods on the
|
||||
// relevant objects or classes.
|
||||
//
|
||||
// New functions which don't exist yet can also be added.
|
||||
|
||||
ControlBoxView: {
|
||||
|
||||
renderLoginPanel: function () {
|
||||
/* Also render a registration panel, when rendering the
|
||||
* login panel.
|
||||
*/
|
||||
this._super.renderLoginPanel.apply(this, arguments);
|
||||
var converse = this._super.converse,
|
||||
cfg;
|
||||
if (converse.allow_registration) {
|
||||
cfg = {
|
||||
'$parent': this.$el.find('.controlbox-panes'),
|
||||
'model': this
|
||||
};
|
||||
if (typeof this.registerpanel === 'undefined') {
|
||||
this.registerpanel = new converse.RegisterPanel(cfg);
|
||||
} else {
|
||||
this.registerpanel.delegateEvents().initialize(cfg);
|
||||
}
|
||||
this.registerpanel.render().$el.hide();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
/* The initialize function gets called as soon as the plugin is
|
||||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
var converse = this.converse;
|
||||
// For translations
|
||||
__ = utils.__.bind(converse);
|
||||
// Configuration values for this plugin
|
||||
var settings = {
|
||||
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
|
||||
};
|
||||
_.extend(converse, settings);
|
||||
_.extend(converse, _.pick(converse.user_settings, Object.keys(settings)));
|
||||
|
||||
|
||||
converse.RegisterPanel = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
id: "register",
|
||||
className: 'controlbox-pane',
|
||||
events: {
|
||||
'submit form#converse-register': 'onProviderChosen'
|
||||
},
|
||||
|
||||
initialize: function (cfg) {
|
||||
this.reset();
|
||||
this.$parent = cfg.$parent;
|
||||
this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
|
||||
this.registerHooks();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$parent.append(this.$el.html(
|
||||
converse.templates.register_panel({
|
||||
'label_domain': __("Your XMPP provider's domain name:"),
|
||||
'label_register': __('Fetch registration form'),
|
||||
'help_providers': __('Tip: A list of public XMPP providers is available'),
|
||||
'help_providers_link': __('here'),
|
||||
'href_providers': converse.providers_link,
|
||||
'domain_placeholder': converse.domain_placeholder
|
||||
})
|
||||
));
|
||||
this.$tabs.append(converse.templates.register_tab({label_register: __('Register')}));
|
||||
return this;
|
||||
},
|
||||
|
||||
registerHooks: function () {
|
||||
/* Hook into Strophe's _connect_cb, so that we can send an IQ
|
||||
* requesting the registration fields.
|
||||
*/
|
||||
var conn = converse.connection;
|
||||
var connect_cb = conn._connect_cb.bind(conn);
|
||||
conn._connect_cb = function (req, callback, raw) {
|
||||
if (!this._registering) {
|
||||
connect_cb(req, callback, raw);
|
||||
} else {
|
||||
if (this.getRegistrationFields(req, callback, raw)) {
|
||||
this._registering = false;
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
getRegistrationFields: function (req, _callback, raw) {
|
||||
/* Send an IQ stanza to the XMPP server asking for the
|
||||
* registration fields.
|
||||
* Parameters:
|
||||
* (Strophe.Request) req - The current request
|
||||
* (Function) callback
|
||||
*/
|
||||
converse.log("sendQueryStanza was called");
|
||||
var conn = converse.connection;
|
||||
conn.connected = true;
|
||||
|
||||
var body = conn._proto._reqToData(req);
|
||||
if (!body) { return; }
|
||||
if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) {
|
||||
return false;
|
||||
}
|
||||
var register = body.getElementsByTagName("register");
|
||||
var mechanisms = body.getElementsByTagName("mechanism");
|
||||
if (register.length === 0 && mechanisms.length === 0) {
|
||||
conn._proto._no_auth_received(_callback);
|
||||
return false;
|
||||
}
|
||||
if (register.length === 0) {
|
||||
conn._changeConnectStatus(
|
||||
Strophe.Status.REGIFAIL,
|
||||
__('Sorry, the given provider does not support in band account registration. Please try with a different provider.')
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// Send an IQ stanza to get all required data fields
|
||||
conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null);
|
||||
conn.send($iq({type: "get"}).c("query", {xmlns: Strophe.NS.REGISTER}).tree());
|
||||
return true;
|
||||
},
|
||||
|
||||
onRegistrationFields: function (stanza) {
|
||||
/* Handler for Registration Fields Request.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) elem - The query stanza.
|
||||
*/
|
||||
if (stanza.getElementsByTagName("query").length !== 1) {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
|
||||
return false;
|
||||
}
|
||||
this.setFields(stanza);
|
||||
this.renderRegistrationForm(stanza);
|
||||
return false;
|
||||
},
|
||||
|
||||
reset: function (settings) {
|
||||
var defaults = {
|
||||
fields: {},
|
||||
urls: [],
|
||||
title: "",
|
||||
instructions: "",
|
||||
registered: false,
|
||||
_registering: false,
|
||||
domain: null,
|
||||
form_type: null
|
||||
};
|
||||
_.extend(this, defaults);
|
||||
if (settings) {
|
||||
_.extend(this, _.pick(settings, Object.keys(defaults)));
|
||||
}
|
||||
},
|
||||
|
||||
onProviderChosen: function (ev) {
|
||||
/* Callback method that gets called when the user has chosen an
|
||||
* XMPP provider.
|
||||
*
|
||||
* Parameters:
|
||||
* (Submit Event) ev - Form submission event.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var $form = $(ev.target),
|
||||
$domain_input = $form.find('input[name=domain]'),
|
||||
domain = $domain_input.val();
|
||||
if (!domain) {
|
||||
$domain_input.addClass('error');
|
||||
return;
|
||||
}
|
||||
$form.find('input[type=submit]').hide()
|
||||
.after(converse.templates.registration_request({
|
||||
cancel: __('Cancel'),
|
||||
info_message: __('Requesting a registration form from the XMPP server')
|
||||
}));
|
||||
$form.find('button.cancel').on('click', this.cancelRegistration.bind(this));
|
||||
this.reset({
|
||||
domain: Strophe.getDomainFromJid(domain),
|
||||
_registering: true
|
||||
});
|
||||
converse.connection.connect(this.domain, "", this.onRegistering.bind(this));
|
||||
return false;
|
||||
},
|
||||
|
||||
giveFeedback: function (message, klass) {
|
||||
this.$('.reg-feedback').attr('class', 'reg-feedback').text(message);
|
||||
if (klass) {
|
||||
$('.reg-feedback').addClass(klass);
|
||||
}
|
||||
},
|
||||
|
||||
onRegistering: function (status, error) {
|
||||
var that;
|
||||
converse.log('onRegistering');
|
||||
if (_.contains([
|
||||
Strophe.Status.DISCONNECTED,
|
||||
Strophe.Status.CONNFAIL,
|
||||
Strophe.Status.REGIFAIL,
|
||||
Strophe.Status.NOTACCEPTABLE,
|
||||
Strophe.Status.CONFLICT
|
||||
], status)) {
|
||||
|
||||
converse.log('Problem during registration: Strophe.Status is: '+status);
|
||||
this.cancelRegistration();
|
||||
if (error) {
|
||||
this.giveFeedback(error, 'error');
|
||||
} else {
|
||||
this.giveFeedback(__(
|
||||
'Something went wrong while establishing a connection with "%1$s". Are you sure it exists?',
|
||||
this.domain
|
||||
), 'error');
|
||||
}
|
||||
} else if (status === Strophe.Status.REGISTERED) {
|
||||
converse.log("Registered successfully.");
|
||||
converse.connection.reset();
|
||||
that = this;
|
||||
this.$('form').hide(function () {
|
||||
$(this).replaceWith('<span class="spinner centered"/>');
|
||||
if (that.fields.password && that.fields.username) {
|
||||
// automatically log the user in
|
||||
converse.connection.connect(
|
||||
that.fields.username.toLowerCase()+'@'+that.domain.toLowerCase(),
|
||||
that.fields.password,
|
||||
converse.onConnectStatusChanged
|
||||
);
|
||||
converse.chatboxviews.get('controlbox')
|
||||
.switchTab({target: that.$tabs.find('.current')})
|
||||
.giveFeedback(__('Now logging you in'));
|
||||
} else {
|
||||
converse.chatboxviews.get('controlbox')
|
||||
.renderLoginPanel()
|
||||
.giveFeedback(__('Registered successfully'));
|
||||
}
|
||||
that.reset();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
renderRegistrationForm: function (stanza) {
|
||||
/* Renders the registration form based on the XForm fields
|
||||
* received from the XMPP server.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza received from the XMPP server.
|
||||
*/
|
||||
var $form= this.$('form'),
|
||||
$stanza = $(stanza),
|
||||
$fields, $input;
|
||||
$form.empty().append(converse.templates.registration_form({
|
||||
'domain': this.domain,
|
||||
'title': this.title,
|
||||
'instructions': this.instructions
|
||||
}));
|
||||
if (this.form_type === 'xform') {
|
||||
$fields = $stanza.find('field');
|
||||
_.each($fields, function (field) {
|
||||
$form.append(utils.xForm2webForm.bind(this, $(field), $stanza));
|
||||
}.bind(this));
|
||||
} else {
|
||||
// Show fields
|
||||
_.each(Object.keys(this.fields), function (key) {
|
||||
if (key === "username") {
|
||||
$input = converse.templates.form_username({
|
||||
domain: ' @'+this.domain,
|
||||
name: key,
|
||||
type: "text",
|
||||
label: key,
|
||||
value: '',
|
||||
required: 1
|
||||
});
|
||||
} else {
|
||||
$form.append('<label>'+key+'</label>');
|
||||
$input = $('<input placeholder="'+key+'" name="'+key+'"></input>');
|
||||
if (key === 'password' || key === 'email') {
|
||||
$input.attr('type', key);
|
||||
}
|
||||
}
|
||||
$form.append($input);
|
||||
}.bind(this));
|
||||
// Show urls
|
||||
_.each(this.urls, function (url) {
|
||||
$form.append($('<a target="blank"></a>').attr('href', url).text(url));
|
||||
}.bind(this));
|
||||
}
|
||||
if (this.fields) {
|
||||
$form.append('<input type="submit" class="pure-button button-primary" value="'+__('Register')+'"/>');
|
||||
$form.on('submit', this.submitRegistrationForm.bind(this));
|
||||
$form.append('<input type="button" class="pure-button button-cancel" value="'+__('Cancel')+'"/>');
|
||||
$form.find('input[type=button]').on('click', this.cancelRegistration.bind(this));
|
||||
} else {
|
||||
$form.append('<input type="button" class="submit" value="'+__('Return')+'"/>');
|
||||
$form.find('input[type=button]').on('click', this.cancelRegistration.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
reportErrors: function (stanza) {
|
||||
/* Report back to the user any error messages received from the
|
||||
* XMPP server after attempted registration.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza received from the
|
||||
* XMPP server.
|
||||
*/
|
||||
var $form= this.$('form'), flash;
|
||||
var $errmsgs = $(stanza).find('error text');
|
||||
var $flash = $form.find('.form-errors');
|
||||
if (!$flash.length) {
|
||||
flash = '<legend class="form-errors"></legend>';
|
||||
if ($form.find('p.instructions').length) {
|
||||
$form.find('p.instructions').append(flash);
|
||||
} else {
|
||||
$form.prepend(flash);
|
||||
}
|
||||
$flash = $form.find('.form-errors');
|
||||
} else {
|
||||
$flash.empty();
|
||||
}
|
||||
$errmsgs.each(function (idx, txt) {
|
||||
$flash.append($('<p>').text($(txt).text()));
|
||||
});
|
||||
if (!$errmsgs.length) {
|
||||
$flash.append($('<p>').text(
|
||||
__('The provider rejected your registration attempt. '+
|
||||
'Please check the values you entered for correctness.')));
|
||||
}
|
||||
$flash.show();
|
||||
},
|
||||
|
||||
cancelRegistration: function (ev) {
|
||||
/* Handler, when the user cancels the registration form.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
converse.connection.reset();
|
||||
this.render();
|
||||
},
|
||||
|
||||
submitRegistrationForm : function (ev) {
|
||||
/* Handler, when the user submits the registration form.
|
||||
* Provides form error feedback or starts the registration
|
||||
* process.
|
||||
*
|
||||
* Parameters:
|
||||
* (Event) ev - the submit event.
|
||||
*/
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var $empty_inputs = this.$('input.required:emptyVal');
|
||||
if ($empty_inputs.length) {
|
||||
$empty_inputs.addClass('error');
|
||||
return;
|
||||
}
|
||||
var $inputs = $(ev.target).find(':input:not([type=button]):not([type=submit])'),
|
||||
iq = $iq({type: "set"}).c("query", {xmlns:Strophe.NS.REGISTER});
|
||||
|
||||
if (this.form_type === 'xform') {
|
||||
iq.c("x", {xmlns: Strophe.NS.XFORM, type: 'submit'});
|
||||
$inputs.each(function () {
|
||||
iq.cnode(utils.webForm2xForm(this)).up();
|
||||
});
|
||||
} else {
|
||||
$inputs.each(function () {
|
||||
var $input = $(this);
|
||||
iq.c($input.attr('name'), {}, $input.val());
|
||||
});
|
||||
}
|
||||
converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null);
|
||||
converse.connection.send(iq);
|
||||
this.setFields(iq.tree());
|
||||
},
|
||||
|
||||
setFields: function (stanza) {
|
||||
/* Stores the values that will be sent to the XMPP server
|
||||
* during attempted registration.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server.
|
||||
*/
|
||||
var $query = $(stanza).find('query'), $xform;
|
||||
if ($query.length > 0) {
|
||||
$xform = $query.find('x[xmlns="'+Strophe.NS.XFORM+'"]');
|
||||
if ($xform.length > 0) {
|
||||
this._setFieldsFromXForm($xform);
|
||||
} else {
|
||||
this._setFieldsFromLegacy($query);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setFieldsFromLegacy: function ($query) {
|
||||
$query.children().each(function (idx, field) {
|
||||
var $field = $(field);
|
||||
if (field.tagName.toLowerCase() === 'instructions') {
|
||||
this.instructions = Strophe.getText(field);
|
||||
return;
|
||||
} else if (field.tagName.toLowerCase() === 'x') {
|
||||
if ($field.attr('xmlns') === 'jabber:x:oob') {
|
||||
$field.find('url').each(function (idx, url) {
|
||||
this.urls.push($(url).text());
|
||||
}.bind(this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.fields[field.tagName.toLowerCase()] = Strophe.getText(field);
|
||||
}.bind(this));
|
||||
this.form_type = 'legacy';
|
||||
},
|
||||
|
||||
_setFieldsFromXForm: function ($xform) {
|
||||
this.title = $xform.find('title').text();
|
||||
this.instructions = $xform.find('instructions').text();
|
||||
$xform.find('field').each(function (idx, field) {
|
||||
var _var = field.getAttribute('var');
|
||||
if (_var) {
|
||||
this.fields[_var.toLowerCase()] = $(field).children('value').text();
|
||||
} else {
|
||||
// TODO: other option seems to be type="fixed"
|
||||
converse.log("WARNING: Found field we couldn't parse");
|
||||
}
|
||||
}.bind(this));
|
||||
this.form_type = 'xform';
|
||||
},
|
||||
|
||||
_onRegisterIQ: function (stanza) {
|
||||
/* Callback method that gets called when a return IQ stanza
|
||||
* is received from the XMPP server, after attempting to
|
||||
* register a new user.
|
||||
*
|
||||
* Parameters:
|
||||
* (XMLElement) stanza - The IQ stanza.
|
||||
*/
|
||||
var error = null,
|
||||
query = stanza.getElementsByTagName("query");
|
||||
if (query.length > 0) {
|
||||
query = query[0];
|
||||
}
|
||||
if (stanza.getAttribute("type") === "error") {
|
||||
converse.log("Registration failed.");
|
||||
error = stanza.getElementsByTagName("error");
|
||||
if (error.length !== 1) {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
|
||||
return false;
|
||||
}
|
||||
error = error[0].firstChild.tagName.toLowerCase();
|
||||
if (error === 'conflict') {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error);
|
||||
} else if (error === 'not-acceptable') {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error);
|
||||
} else {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);
|
||||
}
|
||||
this.reportErrors(stanza);
|
||||
} else {
|
||||
converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.$tabs.empty();
|
||||
this.$el.parent().empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
18
src/utils.js
18
src/utils.js
@ -1,11 +1,11 @@
|
||||
/*global jQuery, templates, escape, Jed, _, locales */
|
||||
/*global jQuery, templates, escape, _, locales */
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(["jquery", "underscore", "converse-templates", "locales"], factory);
|
||||
define(["jquery", "underscore", "jed", "converse-templates", "locales"], factory);
|
||||
} else {
|
||||
root.utils = factory(jQuery, _, templates, locales);
|
||||
}
|
||||
}(this, function ($, _, templates, locales) {
|
||||
}(this, function ($, _, Jed, templates, locales) {
|
||||
"use strict";
|
||||
|
||||
var XFORM_TYPE_MAP = {
|
||||
@ -110,12 +110,12 @@
|
||||
|
||||
___: function (str) {
|
||||
/* XXX: This is part of a hack to get gettext to scan strings to be
|
||||
* translated. Strings we cannot send to the function above because
|
||||
* they require variable interpolation and we don't yet have the
|
||||
* variables at scan time.
|
||||
*
|
||||
* See actionInfoMessages
|
||||
*/
|
||||
* translated. Strings we cannot send to the function above because
|
||||
* they require variable interpolation and we don't yet have the
|
||||
* variables at scan time.
|
||||
*
|
||||
* See actionInfoMessages in src/converse-muc.js
|
||||
*/
|
||||
return str;
|
||||
},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user