Commit 59d00b8e authored by JC Brand's avatar JC Brand

Move more dependencies from bower to NPM

parent ab76f1da
...@@ -8,10 +8,6 @@ ...@@ -8,10 +8,6 @@
"sinon": "^1.17.3" "sinon": "^1.17.3"
}, },
"dependencies": { "dependencies": {
"requirejs": "~2.2.0",
"crypto-js-evanvosberg": "https://github.com/evanvosberg/crypto-js.git#release-3.1.2-5",
"requirejs-text": "~2.0.14",
"requirejs-tpl-jcbrand": "*",
"bootstrap": "~3.2.0", "bootstrap": "~3.2.0",
"fontawesome": "~4.1.0", "fontawesome": "~4.1.0",
"typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js", "typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js",
......
...@@ -22,7 +22,7 @@ require.config({ ...@@ -22,7 +22,7 @@ require.config({
"jquery": "node_modules/jquery/dist/jquery", "jquery": "node_modules/jquery/dist/jquery",
"jquery-private": "src/jquery-private", "jquery-private": "src/jquery-private",
"jquery.browser": "node_modules/jquery.browser/dist/jquery.browser", "jquery.browser": "node_modules/jquery.browser/dist/jquery.browser",
"jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website "jquery.easing": "node_modules/jquery-easing/jquery.easing.1.3.umd", // XXX: Only required for https://conversejs.org website
"moment": "node_modules/moment/moment", "moment": "node_modules/moment/moment",
"pluggable": "node_modules/pluggable.js/pluggable", "pluggable": "node_modules/pluggable.js/pluggable",
"strophe": "node_modules/strophe.js/src/wrapper", "strophe": "node_modules/strophe.js/src/wrapper",
...@@ -38,8 +38,8 @@ require.config({ ...@@ -38,8 +38,8 @@ require.config({
"strophe.ping": "src/strophe.ping", "strophe.ping": "src/strophe.ping",
"strophe.rsm": "components/strophejs-plugins/rsm/strophe.rsm", "strophe.rsm": "components/strophejs-plugins/rsm/strophe.rsm",
"strophe.vcard": "src/strophe.vcard", "strophe.vcard": "src/strophe.vcard",
"text": 'components/requirejs-text/text', "text": "node_modules/requirejs-text/text",
"tpl": 'components/requirejs-tpl-jcbrand/tpl', "tpl": "node_modules/requirejs-undertemplate/tpl",
"typeahead": "components/typeahead.js/index", "typeahead": "components/typeahead.js/index",
"underscore": "node_modules/underscore/underscore", "underscore": "node_modules/underscore/underscore",
"utils": "src/utils", "utils": "src/utils",
...@@ -70,9 +70,9 @@ require.config({ ...@@ -70,9 +70,9 @@ require.config({
"crypto.cipher-core": "node_modules/otr/vendor/cryptojs/cipher-core", "crypto.cipher-core": "node_modules/otr/vendor/cryptojs/cipher-core",
"crypto.core": "node_modules/otr/vendor/cryptojs/core", "crypto.core": "node_modules/otr/vendor/cryptojs/core",
"crypto.enc-base64": "node_modules/otr/vendor/cryptojs/enc-base64", "crypto.enc-base64": "node_modules/otr/vendor/cryptojs/enc-base64",
"crypto.evpkdf": "components/crypto-js-evanvosberg/src/evpkdf", "crypto.evpkdf": "node_modules/crypto-js/src/evpkdf",
"crypto.hmac": "node_modules/otr/vendor/cryptojs/hmac", "crypto.hmac": "node_modules/otr/vendor/cryptojs/hmac",
"crypto.md5": "components/crypto-js-evanvosberg/src/md5", "crypto.md5": "node_modules/crypto-js/src/md5",
"crypto.mode-ctr": "node_modules/otr/vendor/cryptojs/mode-ctr", "crypto.mode-ctr": "node_modules/otr/vendor/cryptojs/mode-ctr",
"crypto.pad-nopadding": "node_modules/otr/vendor/cryptojs/pad-nopadding", "crypto.pad-nopadding": "node_modules/otr/vendor/cryptojs/pad-nopadding",
"crypto.sha1": "node_modules/otr/vendor/cryptojs/sha1", "crypto.sha1": "node_modules/otr/vendor/cryptojs/sha1",
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<link type="text/css" rel="stylesheet" media="screen" href="components/fontawesome/css/font-awesome.min.css" /> <link type="text/css" rel="stylesheet" media="screen" href="components/fontawesome/css/font-awesome.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="components/requirejs/require.js"></script> <script src="node_modules/requirejs/require.js"></script>
<script src="config.js"></script> <script src="config.js"></script>
<script src="converse.js"></script> <script src="converse.js"></script>
</head> </head>
......
/** /**
* @license almond 0.3.2 Copyright jQuery Foundation and other contributors. * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE * Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/ */
//Going sloppy to avoid 'use strict' string cost, but strict practices should //Going sloppy to avoid 'use strict' string cost, but strict practices should
...@@ -195,32 +195,39 @@ var requirejs, require, define; ...@@ -195,32 +195,39 @@ var requirejs, require, define;
return [prefix, name]; return [prefix, name];
} }
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/** /**
* Makes a name map, normalizing the name, and using a plugin * Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin * for normalization if necessary. Grabs a ref to plugin
* too, as an optimization. * too, as an optimization.
*/ */
makeMap = function (name, relName) { makeMap = function (name, relParts) {
var plugin, var plugin,
parts = splitPrefix(name), parts = splitPrefix(name),
prefix = parts[0]; prefix = parts[0],
relResourceName = relParts[1];
name = parts[1]; name = parts[1];
if (prefix) { if (prefix) {
prefix = normalize(prefix, relName); prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix); plugin = callDep(prefix);
} }
//Normalize according //Normalize according
if (prefix) { if (prefix) {
if (plugin && plugin.normalize) { if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName)); name = plugin.normalize(name, makeNormalize(relResourceName));
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
} }
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
parts = splitPrefix(name); parts = splitPrefix(name);
prefix = parts[0]; prefix = parts[0];
name = parts[1]; name = parts[1];
...@@ -267,13 +274,14 @@ var requirejs, require, define; ...@@ -267,13 +274,14 @@ var requirejs, require, define;
}; };
main = function (name, deps, callback, relName) { main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, var cjsModule, depName, ret, map, i, relParts,
args = [], args = [],
callbackType = typeof callback, callbackType = typeof callback,
usingExports; usingExports;
//Use name if no relName //Use name if no relName
relName = relName || name; relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary. //Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') { if (callbackType === 'undefined' || callbackType === 'function') {
...@@ -282,7 +290,7 @@ var requirejs, require, define; ...@@ -282,7 +290,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps //Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) { for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName); map = makeMap(deps[i], relParts);
depName = map.f; depName = map.f;
//Fast path CommonJS standard dependencies. //Fast path CommonJS standard dependencies.
...@@ -338,7 +346,7 @@ var requirejs, require, define; ...@@ -338,7 +346,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed) //deps arg is the module name, and second arg (if passed)
//is just the relName. //is just the relName.
//Normalize module name, if it contains . or .. //Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f); return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) { } else if (!deps.splice) {
//deps is a config object, not an array. //deps is a config object, not an array.
config = deps; config = deps;
...@@ -23958,8 +23966,9 @@ return Strophe; ...@@ -23958,8 +23966,9 @@ return Strophe;
})); }));
/** /**
* @license text 2.0.15 Copyright jQuery Foundation and other contributors. * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Released under MIT license, http://github.com/requirejs/text/LICENSE * Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/ */
/*jslint regexp: true */ /*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject, /*global require, XMLHttpRequest, ActiveXObject,
...@@ -23980,26 +23989,8 @@ define('text',['module'], function (module) { ...@@ -23980,26 +23989,8 @@ define('text',['module'], function (module) {
buildMap = {}, buildMap = {},
masterConfig = (module.config && module.config()) || {}; masterConfig = (module.config && module.config()) || {};
function useDefault(value, defaultValue) {
return value === undefined || value === '' ? defaultValue : value;
}
//Allow for default ports for http and https.
function isSamePort(protocol1, port1, protocol2, port2) {
if (port1 === port2) {
return true;
} else if (protocol1 === protocol2) {
if (protocol1 === 'http') {
return useDefault(port1, '80') === useDefault(port2, '80');
} else if (protocol1 === 'https') {
return useDefault(port1, '443') === useDefault(port2, '443');
}
}
return false;
}
text = { text = {
version: '2.0.15', version: '2.0.12',
strip: function (content) { strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML //Strips <?xml ...?> declarations so that external SVG and XML
...@@ -24061,13 +24052,13 @@ define('text',['module'], function (module) { ...@@ -24061,13 +24052,13 @@ define('text',['module'], function (module) {
parseName: function (name) { parseName: function (name) {
var modName, ext, temp, var modName, ext, temp,
strip = false, strip = false,
index = name.lastIndexOf("."), index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 || isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0; name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) { if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index); modName = name.substring(0, index);
ext = name.substring(index + 1); ext = name.substring(index + 1, name.length);
} else { } else {
modName = name; modName = name;
} }
...@@ -24117,7 +24108,7 @@ define('text',['module'], function (module) { ...@@ -24117,7 +24108,7 @@ define('text',['module'], function (module) {
return (!uProtocol || uProtocol === protocol) && return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || isSamePort(uProtocol, uPort, protocol, port)); ((!uPort && !uHostName) || uPort === port);
}, },
finishLoad: function (name, strip, content, onLoad) { finishLoad: function (name, strip, content, onLoad) {
...@@ -24220,8 +24211,7 @@ define('text',['module'], function (module) { ...@@ -24220,8 +24211,7 @@ define('text',['module'], function (module) {
typeof process !== "undefined" && typeof process !== "undefined" &&
process.versions && process.versions &&
!!process.versions.node && !!process.versions.node &&
!process.versions['node-webkit'] && !process.versions['node-webkit'])) {
!process.versions['atom-shell'])) {
//Using special require.nodeRequire, something added by r.js. //Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs'); fs = require.nodeRequire('fs');
...@@ -24229,7 +24219,7 @@ define('text',['module'], function (module) { ...@@ -24229,7 +24219,7 @@ define('text',['module'], function (module) {
try { try {
var file = fs.readFileSync(url, 'utf8'); var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there. //Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file[0] === '\uFEFF') { if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1); file = file.substring(1);
} }
callback(file); callback(file);
...@@ -24418,7 +24408,7 @@ define('tpl',['text', 'underscore'], function (text, _) { ...@@ -24418,7 +24408,7 @@ define('tpl',['text', 'underscore'], function (text, _) {
onload(buildMap[moduleName]); onload(buildMap[moduleName]);
} else { } else {
var ext = (config.tpl && config.tpl.extension) || '.html'; var ext = config.tpl && !_.isUndefined(config.tpl.extension) ? config.tpl.extension : '.html';
var path = (config.tpl && config.tpl.path) || ''; var path = (config.tpl && config.tpl.path) || '';
text.load(path + moduleName + ext, parentRequire, function (source) { text.load(path + moduleName + ext, parentRequire, function (source) {
buildMap[moduleName] = _.template(source); buildMap[moduleName] = _.template(source);
...@@ -26191,11 +26181,7 @@ define("polyfill", function(){}); ...@@ -26191,11 +26181,7 @@ define("polyfill", function(){});
function PluginSocket (plugged, name) { function PluginSocket (plugged, name) {
this.name = name; this.name = name;
this.plugged = plugged; this.plugged = plugged;
if (typeof this.plugged.__super__ === 'undefined') { this.plugged.__super__ = {};
this.plugged.__super__ = {};
} else if (typeof this.plugged.__super__ === 'string') {
this.plugged.__super__ = { '__string__': this.plugged.__super__ };
}
this.plugins = {}; this.plugins = {};
this.initialized_plugins = []; this.initialized_plugins = [];
} }
...@@ -28695,14 +28681,20 @@ return Backbone.BrowserStorage; ...@@ -28695,14 +28681,20 @@ return Backbone.BrowserStorage;
$(event_context).trigger(evt, data); $(event_context).trigger(evt, data);
}, },
once: function (evt, handler) { once: function (evt, handler, context) {
if (context) {
handler = handler.bind(context);
}
$(event_context).one(evt, handler); $(event_context).one(evt, handler);
}, },
on: function (evt, handler) { on: function (evt, handler, context) {
if (_.contains(['ready', 'initialized'], evt)) { if (_.contains(['ready', 'initialized'], evt)) {
converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".'); converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".');
} }
if (context) {
handler = handler.bind(context);
}
$(event_context).bind(evt, handler); $(event_context).bind(evt, handler);
}, },
...@@ -29283,33 +29275,54 @@ return Backbone.BrowserStorage; ...@@ -29283,33 +29275,54 @@ return Backbone.BrowserStorage;
}; };
this.initRoster = function () { this.initRoster = function () {
this.roster = new this.RosterContacts(); converse.roster = new converse.RosterContacts();
this.roster.browserStorage = new Backbone.BrowserStorage.session( converse.roster.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.contacts-'+this.bare_jid)); b64_sha1('converse.contacts-'+converse.bare_jid));
this.rostergroups = new converse.RosterGroups(); converse.rostergroups = new converse.RosterGroups();
this.rostergroups.browserStorage = new Backbone.BrowserStorage.session( converse.rostergroups.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.roster.groups'+converse.bare_jid)); b64_sha1('converse.roster.groups'+converse.bare_jid));
}; };
this.populateRoster = function () {
/* Fetch all the roster groups, and then the roster contacts.
* Emit an event after fetching is done in each case.
*/
converse.rostergroups.fetchRosterGroups().then(function () {
converse.emit('rosterGroupsFetched');
converse.roster.fetchRosterContacts().then(function () {
converse.emit('rosterContactsFetched');
converse.sendInitialPresence();
});
});
};
this.unregisterPresenceHandler = function () { this.unregisterPresenceHandler = function () {
if (typeof this.presence_ref !== 'undefined') { if (typeof converse.presence_ref !== 'undefined') {
this.connection.deleteHandler(this.presence_ref); converse.connection.deleteHandler(converse.presence_ref);
delete this.presence_ref; delete converse.presence_ref;
} }
}; };
this.registerPresenceHandler = function () { this.registerPresenceHandler = function () {
this.unregisterPresenceHandler(); converse.unregisterPresenceHandler();
this.presence_ref = converse.connection.addHandler( converse.presence_ref = converse.connection.addHandler(
function (presence) { function (presence) {
converse.roster.presenceHandler(presence); converse.roster.presenceHandler(presence);
return true; return true;
}, null, 'presence', null); }, null, 'presence', null);
}; };
this.sendInitialPresence = function () {
if (converse.send_initial_presence) {
converse.xmppstatus.sendPresence();
}
};
this.onStatusInitialized = function () { this.onStatusInitialized = function () {
this.registerIntervalHandler(); this.registerIntervalHandler();
this.initRoster(); this.initRoster();
this.populateRoster();
this.chatboxes.onConnected(); this.chatboxes.onConnected();
this.registerPresenceHandler(); this.registerPresenceHandler();
this.giveFeedback(__('Contacts')); this.giveFeedback(__('Contacts'));
...@@ -29495,6 +29508,36 @@ return Backbone.BrowserStorage; ...@@ -29495,6 +29508,36 @@ return Backbone.BrowserStorage;
} }
}, },
fetchRosterContacts: function () {
/* Fetches the roster contacts, first by trying the
* sessionStorage cache, and if that's empty, then by querying
* the XMPP server.
*
* Returns a promise which resolves once the contacts have been
* fetched.
*/
var deferred = new $.Deferred();
this.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.send_initial_presence = true;
converse.roster.fetchFromServer(deferred.resolve);
} else {
converse.emit('cachedRoster', collection);
deferred.resolve();
}
}
});
return deferred.promise();
},
subscribeToSuggestedItems: function (msg) { subscribeToSuggestedItems: function (msg) {
$(msg).find('item').each(function (i, items) { $(msg).find('item').each(function (i, items) {
if (this.getAttribute('action') === 'add') { if (this.getAttribute('action') === 'add') {
...@@ -29834,6 +29877,22 @@ return Backbone.BrowserStorage; ...@@ -29834,6 +29877,22 @@ return Backbone.BrowserStorage;
this.RosterGroups = Backbone.Collection.extend({ this.RosterGroups = Backbone.Collection.extend({
model: converse.RosterGroup, model: converse.RosterGroup,
fetchRosterGroups: function () {
/* Fetches all the roster groups from sessionStorage.
*
* Returns a promise which resolves once the groups have been
* returned.
*/
var deferred = new $.Deferred();
this.fetch({
silent: true, // We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
success: deferred.resolve
});
return deferred.promise();
}
}); });
...@@ -30690,11 +30749,11 @@ return Backbone.BrowserStorage; ...@@ -30690,11 +30749,11 @@ return Backbone.BrowserStorage;
} }
}, },
'listen': { 'listen': {
'once': function (evt, handler) { 'once': function (evt, handler, context) {
converse.once(evt, handler); converse.once(evt, handler, context);
}, },
'on': function (evt, handler) { 'on': function (evt, handler, context) {
converse.on(evt, handler); converse.on(evt, handler, context);
}, },
'not': function (evt, handler) { 'not': function (evt, handler) {
converse.off(evt, handler); converse.off(evt, handler);
...@@ -32689,6 +32748,17 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32689,6 +32748,17 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
this.__super__.afterReconnected.apply(this, arguments); this.__super__.afterReconnected.apply(this, arguments);
}, },
initRoster: function () {
/* Create an instance of RosterView once the RosterGroups
* collection has been created (in converse-core.js)
*/
this.__super__.initRoster.apply(this, arguments);
converse.rosterview = new converse.RosterView({
'model': converse.rostergroups
});
converse.rosterview.render();
},
RosterGroups: { RosterGroups: {
comparator: function () { comparator: function () {
// RosterGroupsComparator only gets set later (once i18n is // RosterGroupsComparator only gets set later (once i18n is
...@@ -32892,17 +32962,13 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32892,17 +32962,13 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
converse.roster.on("remove", this.update, this); converse.roster.on("remove", this.update, this);
this.model.on("add", this.onGroupAdd, this); this.model.on("add", this.onGroupAdd, this);
this.model.on("reset", this.reset, this); this.model.on("reset", this.reset, this);
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>'); converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
// Create a model on which we can store filter properties converse.on('rosterContactsFetched', this.update, this);
var model = new converse.RosterFilter(); this.createRosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
}, },
render: function () { render: function () {
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
this.$el.html(this.filter_view.render()); this.$el.html(this.filter_view.render());
if (!converse.allow_contact_requests) { if (!converse.allow_contact_requests) {
// XXX: if we ever support live editing of config then // XXX: if we ever support live editing of config then
...@@ -32912,6 +32978,16 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32912,6 +32978,16 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
createRosterFilter: function () {
// Create a model on which we can store filter properties
var model = new converse.RosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
},
updateFilter: _.debounce(function () { updateFilter: _.debounce(function () {
/* Filter the roster again. /* Filter the roster again.
* Called whenever the filter settings have been changed or * Called whenever the filter settings have been changed or
...@@ -32954,45 +33030,6 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32954,45 +33030,6 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
fetch: function () {
this.model.fetch({
silent: true, // We use the success handler to handle groups that were added,
// we need to first have all groups before positionFetchedGroups
// will work properly.
success: function (collection, resp, options) {
if (collection.length !== 0) {
this.positionFetchedGroups(collection, resp, options);
}
converse.roster.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.roster.fetchFromServer(
converse.xmppstatus.sendPresence.bind(converse.xmppstatus));
} else {
converse.emit('cachedRoster', collection);
if (converse.send_initial_presence) {
/* We're not going to fetch the roster again because we have
* it already cached in sessionStorage, but we still need to
* send out a presence stanza because this is a new session.
* See: https://github.com/jcbrand/converse.js/issues/536
*/
converse.xmppstatus.sendPresence();
}
}
}
});
}.bind(this)
});
return this;
},
filter: function (query, type) { filter: function (query, type) {
// First we make sure the filter is restored to its // First we make sure the filter is restored to its
// original state // original state
...@@ -33106,8 +33143,8 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33106,8 +33143,8 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
* positioned aren't already in inserted into the * positioned aren't already in inserted into the
* roster DOM element. * roster DOM element.
*/ */
model.sort(); this.model.sort();
model.each(function (group, idx) { this.model.each(function (group, idx) {
var view = this.get(group.get('name')); var view = this.get(group.get('name'));
if (!view) { if (!view) {
view = new converse.RosterGroupView({model: group}); view = new converse.RosterGroupView({model: group});
...@@ -33782,7 +33819,7 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33782,7 +33819,7 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
this.model.on('change:closed', this.ensureClosedState, this); this.model.on('change:closed', this.ensureClosedState, this);
this.render(); this.render();
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.initRoster(); this.insertRoster();
} }
if (typeof this.model.get('closed')==='undefined') { if (typeof this.model.get('closed')==='undefined') {
this.model.set('closed', !converse.show_controlbox_by_default); this.model.set('closed', !converse.show_controlbox_by_default);
...@@ -33818,17 +33855,14 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33818,17 +33855,14 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
onConnected: function () { onConnected: function () {
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.render().initRoster(); this.render().insertRoster();
} }
}, },
initRoster: function () { insertRoster: function () {
/* We initialize the roster, which will appear inside the /* Place the rosterview inside the "Contacts" panel.
* Contacts Panel.
*/ */
converse.rosterview = new converse.RosterView({model: converse.rostergroups});
this.contactspanel.$el.append(converse.rosterview.$el); this.contactspanel.$el.append(converse.rosterview.$el);
converse.rosterview.render().fetch().update();
return this; return this;
}, },
/** /**
* @license almond 0.3.2 Copyright jQuery Foundation and other contributors. * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE * Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/ */
//Going sloppy to avoid 'use strict' string cost, but strict practices should //Going sloppy to avoid 'use strict' string cost, but strict practices should
...@@ -195,32 +195,39 @@ var requirejs, require, define; ...@@ -195,32 +195,39 @@ var requirejs, require, define;
return [prefix, name]; return [prefix, name];
} }
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/** /**
* Makes a name map, normalizing the name, and using a plugin * Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin * for normalization if necessary. Grabs a ref to plugin
* too, as an optimization. * too, as an optimization.
*/ */
makeMap = function (name, relName) { makeMap = function (name, relParts) {
var plugin, var plugin,
parts = splitPrefix(name), parts = splitPrefix(name),
prefix = parts[0]; prefix = parts[0],
relResourceName = relParts[1];
name = parts[1]; name = parts[1];
if (prefix) { if (prefix) {
prefix = normalize(prefix, relName); prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix); plugin = callDep(prefix);
} }
//Normalize according //Normalize according
if (prefix) { if (prefix) {
if (plugin && plugin.normalize) { if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName)); name = plugin.normalize(name, makeNormalize(relResourceName));
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
} }
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
parts = splitPrefix(name); parts = splitPrefix(name);
prefix = parts[0]; prefix = parts[0];
name = parts[1]; name = parts[1];
...@@ -267,13 +274,14 @@ var requirejs, require, define; ...@@ -267,13 +274,14 @@ var requirejs, require, define;
}; };
main = function (name, deps, callback, relName) { main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, var cjsModule, depName, ret, map, i, relParts,
args = [], args = [],
callbackType = typeof callback, callbackType = typeof callback,
usingExports; usingExports;
//Use name if no relName //Use name if no relName
relName = relName || name; relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary. //Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') { if (callbackType === 'undefined' || callbackType === 'function') {
...@@ -282,7 +290,7 @@ var requirejs, require, define; ...@@ -282,7 +290,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps //Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) { for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName); map = makeMap(deps[i], relParts);
depName = map.f; depName = map.f;
//Fast path CommonJS standard dependencies. //Fast path CommonJS standard dependencies.
...@@ -338,7 +346,7 @@ var requirejs, require, define; ...@@ -338,7 +346,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed) //deps arg is the module name, and second arg (if passed)
//is just the relName. //is just the relName.
//Normalize module name, if it contains . or .. //Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f); return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) { } else if (!deps.splice) {
//deps is a config object, not an array. //deps is a config object, not an array.
config = deps; config = deps;
...@@ -428,8 +436,9 @@ var requirejs, require, define; ...@@ -428,8 +436,9 @@ var requirejs, require, define;
define("almond", function(){}); define("almond", function(){});
/** /**
* @license text 2.0.15 Copyright jQuery Foundation and other contributors. * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Released under MIT license, http://github.com/requirejs/text/LICENSE * Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/ */
/*jslint regexp: true */ /*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject, /*global require, XMLHttpRequest, ActiveXObject,
...@@ -450,26 +459,8 @@ define('text',['module'], function (module) { ...@@ -450,26 +459,8 @@ define('text',['module'], function (module) {
buildMap = {}, buildMap = {},
masterConfig = (module.config && module.config()) || {}; masterConfig = (module.config && module.config()) || {};
function useDefault(value, defaultValue) {
return value === undefined || value === '' ? defaultValue : value;
}
//Allow for default ports for http and https.
function isSamePort(protocol1, port1, protocol2, port2) {
if (port1 === port2) {
return true;
} else if (protocol1 === protocol2) {
if (protocol1 === 'http') {
return useDefault(port1, '80') === useDefault(port2, '80');
} else if (protocol1 === 'https') {
return useDefault(port1, '443') === useDefault(port2, '443');
}
}
return false;
}
text = { text = {
version: '2.0.15', version: '2.0.12',
strip: function (content) { strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML //Strips <?xml ...?> declarations so that external SVG and XML
...@@ -531,13 +522,13 @@ define('text',['module'], function (module) { ...@@ -531,13 +522,13 @@ define('text',['module'], function (module) {
parseName: function (name) { parseName: function (name) {
var modName, ext, temp, var modName, ext, temp,
strip = false, strip = false,
index = name.lastIndexOf("."), index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 || isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0; name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) { if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index); modName = name.substring(0, index);
ext = name.substring(index + 1); ext = name.substring(index + 1, name.length);
} else { } else {
modName = name; modName = name;
} }
...@@ -587,7 +578,7 @@ define('text',['module'], function (module) { ...@@ -587,7 +578,7 @@ define('text',['module'], function (module) {
return (!uProtocol || uProtocol === protocol) && return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || isSamePort(uProtocol, uPort, protocol, port)); ((!uPort && !uHostName) || uPort === port);
}, },
finishLoad: function (name, strip, content, onLoad) { finishLoad: function (name, strip, content, onLoad) {
...@@ -690,8 +681,7 @@ define('text',['module'], function (module) { ...@@ -690,8 +681,7 @@ define('text',['module'], function (module) {
typeof process !== "undefined" && typeof process !== "undefined" &&
process.versions && process.versions &&
!!process.versions.node && !!process.versions.node &&
!process.versions['node-webkit'] && !process.versions['node-webkit'])) {
!process.versions['atom-shell'])) {
//Using special require.nodeRequire, something added by r.js. //Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs'); fs = require.nodeRequire('fs');
...@@ -699,7 +689,7 @@ define('text',['module'], function (module) { ...@@ -699,7 +689,7 @@ define('text',['module'], function (module) {
try { try {
var file = fs.readFileSync(url, 'utf8'); var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there. //Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file[0] === '\uFEFF') { if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1); file = file.substring(1);
} }
callback(file); callback(file);
...@@ -888,7 +878,7 @@ define('tpl',['text', 'underscore'], function (text, _) { ...@@ -888,7 +878,7 @@ define('tpl',['text', 'underscore'], function (text, _) {
onload(buildMap[moduleName]); onload(buildMap[moduleName]);
} else { } else {
var ext = (config.tpl && config.tpl.extension) || '.html'; var ext = config.tpl && !_.isUndefined(config.tpl.extension) ? config.tpl.extension : '.html';
var path = (config.tpl && config.tpl.path) || ''; var path = (config.tpl && config.tpl.path) || '';
text.load(path + moduleName + ext, parentRequire, function (source) { text.load(path + moduleName + ext, parentRequire, function (source) {
buildMap[moduleName] = _.template(source); buildMap[moduleName] = _.template(source);
...@@ -2661,11 +2651,7 @@ define("polyfill", function(){}); ...@@ -2661,11 +2651,7 @@ define("polyfill", function(){});
function PluginSocket (plugged, name) { function PluginSocket (plugged, name) {
this.name = name; this.name = name;
this.plugged = plugged; this.plugged = plugged;
if (typeof this.plugged.__super__ === 'undefined') { this.plugged.__super__ = {};
this.plugged.__super__ = {};
} else if (typeof this.plugged.__super__ === 'string') {
this.plugged.__super__ = { '__string__': this.plugged.__super__ };
}
this.plugins = {}; this.plugins = {};
this.initialized_plugins = []; this.initialized_plugins = [];
} }
...@@ -2934,14 +2920,20 @@ define("polyfill", function(){}); ...@@ -2934,14 +2920,20 @@ define("polyfill", function(){});
$(event_context).trigger(evt, data); $(event_context).trigger(evt, data);
}, },
once: function (evt, handler) { once: function (evt, handler, context) {
if (context) {
handler = handler.bind(context);
}
$(event_context).one(evt, handler); $(event_context).one(evt, handler);
}, },
on: function (evt, handler) { on: function (evt, handler, context) {
if (_.contains(['ready', 'initialized'], evt)) { if (_.contains(['ready', 'initialized'], evt)) {
converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".'); converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".');
} }
if (context) {
handler = handler.bind(context);
}
$(event_context).bind(evt, handler); $(event_context).bind(evt, handler);
}, },
...@@ -3522,33 +3514,54 @@ define("polyfill", function(){}); ...@@ -3522,33 +3514,54 @@ define("polyfill", function(){});
}; };
this.initRoster = function () { this.initRoster = function () {
this.roster = new this.RosterContacts(); converse.roster = new converse.RosterContacts();
this.roster.browserStorage = new Backbone.BrowserStorage.session( converse.roster.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.contacts-'+this.bare_jid)); b64_sha1('converse.contacts-'+converse.bare_jid));
this.rostergroups = new converse.RosterGroups(); converse.rostergroups = new converse.RosterGroups();
this.rostergroups.browserStorage = new Backbone.BrowserStorage.session( converse.rostergroups.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.roster.groups'+converse.bare_jid)); b64_sha1('converse.roster.groups'+converse.bare_jid));
}; };
this.populateRoster = function () {
/* Fetch all the roster groups, and then the roster contacts.
* Emit an event after fetching is done in each case.
*/
converse.rostergroups.fetchRosterGroups().then(function () {
converse.emit('rosterGroupsFetched');
converse.roster.fetchRosterContacts().then(function () {
converse.emit('rosterContactsFetched');
converse.sendInitialPresence();
});
});
};
this.unregisterPresenceHandler = function () { this.unregisterPresenceHandler = function () {
if (typeof this.presence_ref !== 'undefined') { if (typeof converse.presence_ref !== 'undefined') {
this.connection.deleteHandler(this.presence_ref); converse.connection.deleteHandler(converse.presence_ref);
delete this.presence_ref; delete converse.presence_ref;
} }
}; };
this.registerPresenceHandler = function () { this.registerPresenceHandler = function () {
this.unregisterPresenceHandler(); converse.unregisterPresenceHandler();
this.presence_ref = converse.connection.addHandler( converse.presence_ref = converse.connection.addHandler(
function (presence) { function (presence) {
converse.roster.presenceHandler(presence); converse.roster.presenceHandler(presence);
return true; return true;
}, null, 'presence', null); }, null, 'presence', null);
}; };
this.sendInitialPresence = function () {
if (converse.send_initial_presence) {
converse.xmppstatus.sendPresence();
}
};
this.onStatusInitialized = function () { this.onStatusInitialized = function () {
this.registerIntervalHandler(); this.registerIntervalHandler();
this.initRoster(); this.initRoster();
this.populateRoster();
this.chatboxes.onConnected(); this.chatboxes.onConnected();
this.registerPresenceHandler(); this.registerPresenceHandler();
this.giveFeedback(__('Contacts')); this.giveFeedback(__('Contacts'));
...@@ -3734,6 +3747,36 @@ define("polyfill", function(){}); ...@@ -3734,6 +3747,36 @@ define("polyfill", function(){});
} }
}, },
fetchRosterContacts: function () {
/* Fetches the roster contacts, first by trying the
* sessionStorage cache, and if that's empty, then by querying
* the XMPP server.
*
* Returns a promise which resolves once the contacts have been
* fetched.
*/
var deferred = new $.Deferred();
this.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.send_initial_presence = true;
converse.roster.fetchFromServer(deferred.resolve);
} else {
converse.emit('cachedRoster', collection);
deferred.resolve();
}
}
});
return deferred.promise();
},
subscribeToSuggestedItems: function (msg) { subscribeToSuggestedItems: function (msg) {
$(msg).find('item').each(function (i, items) { $(msg).find('item').each(function (i, items) {
if (this.getAttribute('action') === 'add') { if (this.getAttribute('action') === 'add') {
...@@ -4073,6 +4116,22 @@ define("polyfill", function(){}); ...@@ -4073,6 +4116,22 @@ define("polyfill", function(){});
this.RosterGroups = Backbone.Collection.extend({ this.RosterGroups = Backbone.Collection.extend({
model: converse.RosterGroup, model: converse.RosterGroup,
fetchRosterGroups: function () {
/* Fetches all the roster groups from sessionStorage.
*
* Returns a promise which resolves once the groups have been
* returned.
*/
var deferred = new $.Deferred();
this.fetch({
silent: true, // We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
success: deferred.resolve
});
return deferred.promise();
}
}); });
...@@ -4929,11 +4988,11 @@ define("polyfill", function(){}); ...@@ -4929,11 +4988,11 @@ define("polyfill", function(){});
} }
}, },
'listen': { 'listen': {
'once': function (evt, handler) { 'once': function (evt, handler, context) {
converse.once(evt, handler); converse.once(evt, handler, context);
}, },
'on': function (evt, handler) { 'on': function (evt, handler, context) {
converse.on(evt, handler); converse.on(evt, handler, context);
}, },
'not': function (evt, handler) { 'not': function (evt, handler) {
converse.off(evt, handler); converse.off(evt, handler);
...@@ -6824,6 +6883,17 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -6824,6 +6883,17 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
this.__super__.afterReconnected.apply(this, arguments); this.__super__.afterReconnected.apply(this, arguments);
}, },
initRoster: function () {
/* Create an instance of RosterView once the RosterGroups
* collection has been created (in converse-core.js)
*/
this.__super__.initRoster.apply(this, arguments);
converse.rosterview = new converse.RosterView({
'model': converse.rostergroups
});
converse.rosterview.render();
},
RosterGroups: { RosterGroups: {
comparator: function () { comparator: function () {
// RosterGroupsComparator only gets set later (once i18n is // RosterGroupsComparator only gets set later (once i18n is
...@@ -7027,17 +7097,13 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7027,17 +7097,13 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
converse.roster.on("remove", this.update, this); converse.roster.on("remove", this.update, this);
this.model.on("add", this.onGroupAdd, this); this.model.on("add", this.onGroupAdd, this);
this.model.on("reset", this.reset, this); this.model.on("reset", this.reset, this);
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>'); converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
// Create a model on which we can store filter properties converse.on('rosterContactsFetched', this.update, this);
var model = new converse.RosterFilter(); this.createRosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
}, },
render: function () { render: function () {
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
this.$el.html(this.filter_view.render()); this.$el.html(this.filter_view.render());
if (!converse.allow_contact_requests) { if (!converse.allow_contact_requests) {
// XXX: if we ever support live editing of config then // XXX: if we ever support live editing of config then
...@@ -7047,6 +7113,16 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7047,6 +7113,16 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
createRosterFilter: function () {
// Create a model on which we can store filter properties
var model = new converse.RosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
},
updateFilter: _.debounce(function () { updateFilter: _.debounce(function () {
/* Filter the roster again. /* Filter the roster again.
* Called whenever the filter settings have been changed or * Called whenever the filter settings have been changed or
...@@ -7089,45 +7165,6 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7089,45 +7165,6 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
fetch: function () {
this.model.fetch({
silent: true, // We use the success handler to handle groups that were added,
// we need to first have all groups before positionFetchedGroups
// will work properly.
success: function (collection, resp, options) {
if (collection.length !== 0) {
this.positionFetchedGroups(collection, resp, options);
}
converse.roster.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.roster.fetchFromServer(
converse.xmppstatus.sendPresence.bind(converse.xmppstatus));
} else {
converse.emit('cachedRoster', collection);
if (converse.send_initial_presence) {
/* We're not going to fetch the roster again because we have
* it already cached in sessionStorage, but we still need to
* send out a presence stanza because this is a new session.
* See: https://github.com/jcbrand/converse.js/issues/536
*/
converse.xmppstatus.sendPresence();
}
}
}
});
}.bind(this)
});
return this;
},
filter: function (query, type) { filter: function (query, type) {
// First we make sure the filter is restored to its // First we make sure the filter is restored to its
// original state // original state
...@@ -7241,8 +7278,8 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7241,8 +7278,8 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
* positioned aren't already in inserted into the * positioned aren't already in inserted into the
* roster DOM element. * roster DOM element.
*/ */
model.sort(); this.model.sort();
model.each(function (group, idx) { this.model.each(function (group, idx) {
var view = this.get(group.get('name')); var view = this.get(group.get('name'));
if (!view) { if (!view) {
view = new converse.RosterGroupView({model: group}); view = new converse.RosterGroupView({model: group});
...@@ -7917,7 +7954,7 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7917,7 +7954,7 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
this.model.on('change:closed', this.ensureClosedState, this); this.model.on('change:closed', this.ensureClosedState, this);
this.render(); this.render();
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.initRoster(); this.insertRoster();
} }
if (typeof this.model.get('closed')==='undefined') { if (typeof this.model.get('closed')==='undefined') {
this.model.set('closed', !converse.show_controlbox_by_default); this.model.set('closed', !converse.show_controlbox_by_default);
...@@ -7953,17 +7990,14 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local ...@@ -7953,17 +7990,14 @@ define('text!ca',[],function () { return '{\n "domain": "converse",\n "local
onConnected: function () { onConnected: function () {
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.render().initRoster(); this.render().insertRoster();
} }
}, },
initRoster: function () { insertRoster: function () {
/* We initialize the roster, which will appear inside the /* Place the rosterview inside the "Contacts" panel.
* Contacts Panel.
*/ */
converse.rosterview = new converse.RosterView({model: converse.rostergroups});
this.contactspanel.$el.append(converse.rosterview.$el); this.contactspanel.$el.append(converse.rosterview.$el);
converse.rosterview.render().fetch().update();
return this; return this;
}, },
......
/** /**
* @license almond 0.3.2 Copyright jQuery Foundation and other contributors. * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE * Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/ */
//Going sloppy to avoid 'use strict' string cost, but strict practices should //Going sloppy to avoid 'use strict' string cost, but strict practices should
...@@ -195,32 +195,39 @@ var requirejs, require, define; ...@@ -195,32 +195,39 @@ var requirejs, require, define;
return [prefix, name]; return [prefix, name];
} }
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/** /**
* Makes a name map, normalizing the name, and using a plugin * Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin * for normalization if necessary. Grabs a ref to plugin
* too, as an optimization. * too, as an optimization.
*/ */
makeMap = function (name, relName) { makeMap = function (name, relParts) {
var plugin, var plugin,
parts = splitPrefix(name), parts = splitPrefix(name),
prefix = parts[0]; prefix = parts[0],
relResourceName = relParts[1];
name = parts[1]; name = parts[1];
if (prefix) { if (prefix) {
prefix = normalize(prefix, relName); prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix); plugin = callDep(prefix);
} }
//Normalize according //Normalize according
if (prefix) { if (prefix) {
if (plugin && plugin.normalize) { if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName)); name = plugin.normalize(name, makeNormalize(relResourceName));
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
} }
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
parts = splitPrefix(name); parts = splitPrefix(name);
prefix = parts[0]; prefix = parts[0];
name = parts[1]; name = parts[1];
...@@ -267,13 +274,14 @@ var requirejs, require, define; ...@@ -267,13 +274,14 @@ var requirejs, require, define;
}; };
main = function (name, deps, callback, relName) { main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, var cjsModule, depName, ret, map, i, relParts,
args = [], args = [],
callbackType = typeof callback, callbackType = typeof callback,
usingExports; usingExports;
//Use name if no relName //Use name if no relName
relName = relName || name; relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary. //Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') { if (callbackType === 'undefined' || callbackType === 'function') {
...@@ -282,7 +290,7 @@ var requirejs, require, define; ...@@ -282,7 +290,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps //Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) { for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName); map = makeMap(deps[i], relParts);
depName = map.f; depName = map.f;
//Fast path CommonJS standard dependencies. //Fast path CommonJS standard dependencies.
...@@ -338,7 +346,7 @@ var requirejs, require, define; ...@@ -338,7 +346,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed) //deps arg is the module name, and second arg (if passed)
//is just the relName. //is just the relName.
//Normalize module name, if it contains . or .. //Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f); return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) { } else if (!deps.splice) {
//deps is a config object, not an array. //deps is a config object, not an array.
config = deps; config = deps;
...@@ -23958,8 +23966,9 @@ return Strophe; ...@@ -23958,8 +23966,9 @@ return Strophe;
})); }));
/** /**
* @license text 2.0.15 Copyright jQuery Foundation and other contributors. * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Released under MIT license, http://github.com/requirejs/text/LICENSE * Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/ */
/*jslint regexp: true */ /*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject, /*global require, XMLHttpRequest, ActiveXObject,
...@@ -23980,26 +23989,8 @@ define('text',['module'], function (module) { ...@@ -23980,26 +23989,8 @@ define('text',['module'], function (module) {
buildMap = {}, buildMap = {},
masterConfig = (module.config && module.config()) || {}; masterConfig = (module.config && module.config()) || {};
function useDefault(value, defaultValue) {
return value === undefined || value === '' ? defaultValue : value;
}
//Allow for default ports for http and https.
function isSamePort(protocol1, port1, protocol2, port2) {
if (port1 === port2) {
return true;
} else if (protocol1 === protocol2) {
if (protocol1 === 'http') {
return useDefault(port1, '80') === useDefault(port2, '80');
} else if (protocol1 === 'https') {
return useDefault(port1, '443') === useDefault(port2, '443');
}
}
return false;
}
text = { text = {
version: '2.0.15', version: '2.0.12',
strip: function (content) { strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML //Strips <?xml ...?> declarations so that external SVG and XML
...@@ -24061,13 +24052,13 @@ define('text',['module'], function (module) { ...@@ -24061,13 +24052,13 @@ define('text',['module'], function (module) {
parseName: function (name) { parseName: function (name) {
var modName, ext, temp, var modName, ext, temp,
strip = false, strip = false,
index = name.lastIndexOf("."), index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 || isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0; name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) { if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index); modName = name.substring(0, index);
ext = name.substring(index + 1); ext = name.substring(index + 1, name.length);
} else { } else {
modName = name; modName = name;
} }
...@@ -24117,7 +24108,7 @@ define('text',['module'], function (module) { ...@@ -24117,7 +24108,7 @@ define('text',['module'], function (module) {
return (!uProtocol || uProtocol === protocol) && return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || isSamePort(uProtocol, uPort, protocol, port)); ((!uPort && !uHostName) || uPort === port);
}, },
finishLoad: function (name, strip, content, onLoad) { finishLoad: function (name, strip, content, onLoad) {
...@@ -24220,8 +24211,7 @@ define('text',['module'], function (module) { ...@@ -24220,8 +24211,7 @@ define('text',['module'], function (module) {
typeof process !== "undefined" && typeof process !== "undefined" &&
process.versions && process.versions &&
!!process.versions.node && !!process.versions.node &&
!process.versions['node-webkit'] && !process.versions['node-webkit'])) {
!process.versions['atom-shell'])) {
//Using special require.nodeRequire, something added by r.js. //Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs'); fs = require.nodeRequire('fs');
...@@ -24229,7 +24219,7 @@ define('text',['module'], function (module) { ...@@ -24229,7 +24219,7 @@ define('text',['module'], function (module) {
try { try {
var file = fs.readFileSync(url, 'utf8'); var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there. //Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file[0] === '\uFEFF') { if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1); file = file.substring(1);
} }
callback(file); callback(file);
...@@ -24418,7 +24408,7 @@ define('tpl',['text', 'underscore'], function (text, _) { ...@@ -24418,7 +24408,7 @@ define('tpl',['text', 'underscore'], function (text, _) {
onload(buildMap[moduleName]); onload(buildMap[moduleName]);
} else { } else {
var ext = (config.tpl && config.tpl.extension) || '.html'; var ext = config.tpl && !_.isUndefined(config.tpl.extension) ? config.tpl.extension : '.html';
var path = (config.tpl && config.tpl.path) || ''; var path = (config.tpl && config.tpl.path) || '';
text.load(path + moduleName + ext, parentRequire, function (source) { text.load(path + moduleName + ext, parentRequire, function (source) {
buildMap[moduleName] = _.template(source); buildMap[moduleName] = _.template(source);
...@@ -26191,11 +26181,7 @@ define("polyfill", function(){}); ...@@ -26191,11 +26181,7 @@ define("polyfill", function(){});
function PluginSocket (plugged, name) { function PluginSocket (plugged, name) {
this.name = name; this.name = name;
this.plugged = plugged; this.plugged = plugged;
if (typeof this.plugged.__super__ === 'undefined') { this.plugged.__super__ = {};
this.plugged.__super__ = {};
} else if (typeof this.plugged.__super__ === 'string') {
this.plugged.__super__ = { '__string__': this.plugged.__super__ };
}
this.plugins = {}; this.plugins = {};
this.initialized_plugins = []; this.initialized_plugins = [];
} }
...@@ -28695,14 +28681,20 @@ return Backbone.BrowserStorage; ...@@ -28695,14 +28681,20 @@ return Backbone.BrowserStorage;
$(event_context).trigger(evt, data); $(event_context).trigger(evt, data);
}, },
once: function (evt, handler) { once: function (evt, handler, context) {
if (context) {
handler = handler.bind(context);
}
$(event_context).one(evt, handler); $(event_context).one(evt, handler);
}, },
on: function (evt, handler) { on: function (evt, handler, context) {
if (_.contains(['ready', 'initialized'], evt)) { if (_.contains(['ready', 'initialized'], evt)) {
converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".'); converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".');
} }
if (context) {
handler = handler.bind(context);
}
$(event_context).bind(evt, handler); $(event_context).bind(evt, handler);
}, },
...@@ -29283,33 +29275,54 @@ return Backbone.BrowserStorage; ...@@ -29283,33 +29275,54 @@ return Backbone.BrowserStorage;
}; };
this.initRoster = function () { this.initRoster = function () {
this.roster = new this.RosterContacts(); converse.roster = new converse.RosterContacts();
this.roster.browserStorage = new Backbone.BrowserStorage.session( converse.roster.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.contacts-'+this.bare_jid)); b64_sha1('converse.contacts-'+converse.bare_jid));
this.rostergroups = new converse.RosterGroups(); converse.rostergroups = new converse.RosterGroups();
this.rostergroups.browserStorage = new Backbone.BrowserStorage.session( converse.rostergroups.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.roster.groups'+converse.bare_jid)); b64_sha1('converse.roster.groups'+converse.bare_jid));
}; };
this.populateRoster = function () {
/* Fetch all the roster groups, and then the roster contacts.
* Emit an event after fetching is done in each case.
*/
converse.rostergroups.fetchRosterGroups().then(function () {
converse.emit('rosterGroupsFetched');
converse.roster.fetchRosterContacts().then(function () {
converse.emit('rosterContactsFetched');
converse.sendInitialPresence();
});
});
};
this.unregisterPresenceHandler = function () { this.unregisterPresenceHandler = function () {
if (typeof this.presence_ref !== 'undefined') { if (typeof converse.presence_ref !== 'undefined') {
this.connection.deleteHandler(this.presence_ref); converse.connection.deleteHandler(converse.presence_ref);
delete this.presence_ref; delete converse.presence_ref;
} }
}; };
this.registerPresenceHandler = function () { this.registerPresenceHandler = function () {
this.unregisterPresenceHandler(); converse.unregisterPresenceHandler();
this.presence_ref = converse.connection.addHandler( converse.presence_ref = converse.connection.addHandler(
function (presence) { function (presence) {
converse.roster.presenceHandler(presence); converse.roster.presenceHandler(presence);
return true; return true;
}, null, 'presence', null); }, null, 'presence', null);
}; };
this.sendInitialPresence = function () {
if (converse.send_initial_presence) {
converse.xmppstatus.sendPresence();
}
};
this.onStatusInitialized = function () { this.onStatusInitialized = function () {
this.registerIntervalHandler(); this.registerIntervalHandler();
this.initRoster(); this.initRoster();
this.populateRoster();
this.chatboxes.onConnected(); this.chatboxes.onConnected();
this.registerPresenceHandler(); this.registerPresenceHandler();
this.giveFeedback(__('Contacts')); this.giveFeedback(__('Contacts'));
...@@ -29495,6 +29508,36 @@ return Backbone.BrowserStorage; ...@@ -29495,6 +29508,36 @@ return Backbone.BrowserStorage;
} }
}, },
fetchRosterContacts: function () {
/* Fetches the roster contacts, first by trying the
* sessionStorage cache, and if that's empty, then by querying
* the XMPP server.
*
* Returns a promise which resolves once the contacts have been
* fetched.
*/
var deferred = new $.Deferred();
this.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.send_initial_presence = true;
converse.roster.fetchFromServer(deferred.resolve);
} else {
converse.emit('cachedRoster', collection);
deferred.resolve();
}
}
});
return deferred.promise();
},
subscribeToSuggestedItems: function (msg) { subscribeToSuggestedItems: function (msg) {
$(msg).find('item').each(function (i, items) { $(msg).find('item').each(function (i, items) {
if (this.getAttribute('action') === 'add') { if (this.getAttribute('action') === 'add') {
...@@ -29834,6 +29877,22 @@ return Backbone.BrowserStorage; ...@@ -29834,6 +29877,22 @@ return Backbone.BrowserStorage;
this.RosterGroups = Backbone.Collection.extend({ this.RosterGroups = Backbone.Collection.extend({
model: converse.RosterGroup, model: converse.RosterGroup,
fetchRosterGroups: function () {
/* Fetches all the roster groups from sessionStorage.
*
* Returns a promise which resolves once the groups have been
* returned.
*/
var deferred = new $.Deferred();
this.fetch({
silent: true, // We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
success: deferred.resolve
});
return deferred.promise();
}
}); });
...@@ -30690,11 +30749,11 @@ return Backbone.BrowserStorage; ...@@ -30690,11 +30749,11 @@ return Backbone.BrowserStorage;
} }
}, },
'listen': { 'listen': {
'once': function (evt, handler) { 'once': function (evt, handler, context) {
converse.once(evt, handler); converse.once(evt, handler, context);
}, },
'on': function (evt, handler) { 'on': function (evt, handler, context) {
converse.on(evt, handler); converse.on(evt, handler, context);
}, },
'not': function (evt, handler) { 'not': function (evt, handler) {
converse.off(evt, handler); converse.off(evt, handler);
...@@ -32689,6 +32748,17 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32689,6 +32748,17 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
this.__super__.afterReconnected.apply(this, arguments); this.__super__.afterReconnected.apply(this, arguments);
}, },
initRoster: function () {
/* Create an instance of RosterView once the RosterGroups
* collection has been created (in converse-core.js)
*/
this.__super__.initRoster.apply(this, arguments);
converse.rosterview = new converse.RosterView({
'model': converse.rostergroups
});
converse.rosterview.render();
},
RosterGroups: { RosterGroups: {
comparator: function () { comparator: function () {
// RosterGroupsComparator only gets set later (once i18n is // RosterGroupsComparator only gets set later (once i18n is
...@@ -32892,17 +32962,13 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32892,17 +32962,13 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
converse.roster.on("remove", this.update, this); converse.roster.on("remove", this.update, this);
this.model.on("add", this.onGroupAdd, this); this.model.on("add", this.onGroupAdd, this);
this.model.on("reset", this.reset, this); this.model.on("reset", this.reset, this);
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>'); converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
// Create a model on which we can store filter properties converse.on('rosterContactsFetched', this.update, this);
var model = new converse.RosterFilter(); this.createRosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
}, },
render: function () { render: function () {
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
this.$el.html(this.filter_view.render()); this.$el.html(this.filter_view.render());
if (!converse.allow_contact_requests) { if (!converse.allow_contact_requests) {
// XXX: if we ever support live editing of config then // XXX: if we ever support live editing of config then
...@@ -32912,6 +32978,16 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32912,6 +32978,16 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
createRosterFilter: function () {
// Create a model on which we can store filter properties
var model = new converse.RosterFilter();
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new converse.RosterFilterView({'model': model});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
},
updateFilter: _.debounce(function () { updateFilter: _.debounce(function () {
/* Filter the roster again. /* Filter the roster again.
* Called whenever the filter settings have been changed or * Called whenever the filter settings have been changed or
...@@ -32954,45 +33030,6 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -32954,45 +33030,6 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
return this; return this;
}, },
fetch: function () {
this.model.fetch({
silent: true, // We use the success handler to handle groups that were added,
// we need to first have all groups before positionFetchedGroups
// will work properly.
success: function (collection, resp, options) {
if (collection.length !== 0) {
this.positionFetchedGroups(collection, resp, options);
}
converse.roster.fetch({
add: true,
success: function (collection) {
if (collection.length === 0) {
/* We don't have any roster contacts stored in sessionStorage,
* so lets fetch the roster from the XMPP server. We pass in
* 'sendPresence' as callback method, because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts.
*/
converse.roster.fetchFromServer(
converse.xmppstatus.sendPresence.bind(converse.xmppstatus));
} else {
converse.emit('cachedRoster', collection);
if (converse.send_initial_presence) {
/* We're not going to fetch the roster again because we have
* it already cached in sessionStorage, but we still need to
* send out a presence stanza because this is a new session.
* See: https://github.com/jcbrand/converse.js/issues/536
*/
converse.xmppstatus.sendPresence();
}
}
}
});
}.bind(this)
});
return this;
},
filter: function (query, type) { filter: function (query, type) {
// First we make sure the filter is restored to its // First we make sure the filter is restored to its
// original state // original state
...@@ -33106,8 +33143,8 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33106,8 +33143,8 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
* positioned aren't already in inserted into the * positioned aren't already in inserted into the
* roster DOM element. * roster DOM element.
*/ */
model.sort(); this.model.sort();
model.each(function (group, idx) { this.model.each(function (group, idx) {
var view = this.get(group.get('name')); var view = this.get(group.get('name'));
if (!view) { if (!view) {
view = new converse.RosterGroupView({model: group}); view = new converse.RosterGroupView({model: group});
...@@ -33782,7 +33819,7 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33782,7 +33819,7 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
this.model.on('change:closed', this.ensureClosedState, this); this.model.on('change:closed', this.ensureClosedState, this);
this.render(); this.render();
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.initRoster(); this.insertRoster();
} }
if (typeof this.model.get('closed')==='undefined') { if (typeof this.model.get('closed')==='undefined') {
this.model.set('closed', !converse.show_controlbox_by_default); this.model.set('closed', !converse.show_controlbox_by_default);
...@@ -33818,17 +33855,14 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local ...@@ -33818,17 +33855,14 @@ define('text!zh',[],function () { return '{\n "domain": "converse",\n "local
onConnected: function () { onConnected: function () {
if (this.model.get('connected')) { if (this.model.get('connected')) {
this.render().initRoster(); this.render().insertRoster();
} }
}, },
initRoster: function () { insertRoster: function () {
/* We initialize the roster, which will appear inside the /* Place the rosterview inside the "Contacts" panel.
* Contacts Panel.
*/ */
converse.rosterview = new converse.RosterView({model: converse.rostergroups});
this.contactspanel.$el.append(converse.rosterview.$el); this.contactspanel.$el.append(converse.rosterview.$el);
converse.rosterview.render().fetch().update();
return this; return this;
}, },
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="config.js"></script> <script src="config.js"></script>
<script src="converse.js"></script> <script src="converse.js"></script>
<script data-main="tests/main" src="components/requirejs/require.js"></script> <script data-main="tests/main" src="node_modules/requirejs/require.js"></script>
</head> </head>
<body> <body>
......
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