Commit 36db4c8b authored by JC Brand's avatar JC Brand

Merge branch 'master' into plugin-api

parents 0963f5c4 e1efb604
......@@ -99,10 +99,17 @@ module.exports = function(grunt) {
done();
};
exec('./node_modules/requirejs/bin/r.js -o src/build.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build.js optimize=none out=builds/converse.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build-no-jquery.js &&' +
'./node_modules/requirejs/bin/r.js -o src/build-no-jquery.js optimize=none out=builds/converse.nojquery.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build-no-locales-no-otr.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build-no-locales-no-otr.js optimize=none out=builds/converse-no-locales-no-otr.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build-no-otr.js &&' +
'./node_modules/requirejs/bin/r.js -o src/build-no-otr.js optimize=none out=builds/converse-no-otr.js && ' +
'./node_modules/requirejs/bin/r.js -o src/build-website-no-otr.js &&' +
'./node_modules/requirejs/bin/r.js -o src/build-website.js', callback);
// XXX: It might be possible to not have separate build config files. For example:
// 'r.js -o src/build.js paths.converse-dependencies=src/deps-no-otr paths.locales=locale/nolocales out=builds/converse-no-locales-no-otr.min.js'
});
grunt.registerTask('minify', 'Create a new release', ['cssmin', 'jsmin']);
......
......@@ -16,7 +16,6 @@
"backbone.browserStorage": "*",
"backbone.overview": "*",
"strophe": "~1.1.3",
"strophe.roster": "https://raw.github.com/strophe/strophejs-plugins/b1f364eb6e854ffe844c57add38e885cfeb9b498/roster/strophe.roster.js",
"strophe.muc": "https://raw.githubusercontent.com/strophe/strophejs-plugins/master/muc/strophe.muc.js",
"otr": "0.2.12",
"crypto-js-evanvosberg": "~3.1.2",
......@@ -30,7 +29,7 @@
"bootstrapJS": "https://raw.githubusercontent.com/jcbrand/bootstrap/7d96a5f60d26c67b5348b270a775518b96a702c8/dist/js/bootstrap.js",
"fontawesome": "~4.1.0",
"typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js",
"strophejs-plugins": "~0.0.4"
"strophejs-plugins": "https://github.com/strophe/strophejs-plugins.git#a56421ff4ecf0807113ab48c46728715597df599"
},
"exportsOverride": {}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -42,7 +42,7 @@ __p += '<li>\n <form class="add-xmpp-contact">\n <input type="text"\n
((__t = (label_contact_username)) == null ? '' : __t) +
'"/>\n <button type="submit">' +
((__t = (label_add)) == null ? '' : __t) +
'</button>\n </form>\n<li>\n';
'</button>\n </form>\n</li>\n';
}
return __p
......@@ -380,7 +380,7 @@ __p += '<form id="converse-login" method="post">\n <label>' +
((__t = (label_password)) == null ? '' : __t) +
'</label>\n <input type="password" name="password" placeholder="Password">\n <input class="login-submit" type="submit" value="' +
((__t = (label_login)) == null ? '' : __t) +
'">\n <span class="conn-feedback"></span>\n</form">\n';
'">\n <span class="conn-feedback"></span>\n</form>\n';
}
return __p
......@@ -660,13 +660,13 @@ this["JST"]["roster"] = function(obj) {
obj || (obj = {});
var __t, __p = '', __e = _.escape;
with (obj) {
__p += '<input class="roster-filter" placeholder="' +
__p += '<input style="display: none;" class="roster-filter" placeholder="' +
((__t = (placeholder)) == null ? '' : __t) +
'">\n<select class="filter-type">\n <option value="contacts">' +
'">\n<select style="display: none;" class="filter-type">\n <option value="contacts">' +
((__t = (label_contacts)) == null ? '' : __t) +
'</option>\n <option value="groups">' +
((__t = (label_groups)) == null ? '' : __t) +
'</option>\n</select>\n<dl class="roster-contacts"></dl>\n';
'</option>\n</select>\n';
}
return __p
......@@ -700,7 +700,7 @@ __p += '<li>\n <form class="search-xmpp-contact">\n <input type="text"
((__t = (label_contact_name)) == null ? '' : __t) +
'"/>\n <button type="submit">' +
((__t = (label_search)) == null ? '' : __t) +
'</button>\n </form>\n<li>\n';
'</button>\n </form>\n</li>\n';
}
return __p
......
......@@ -300,7 +300,7 @@
// Translation machinery
// ---------------------
var __ = utils.__;
var __ = $.proxy(utils.__, this);
var ___ = utils.___;
// Translation aware constants
// ---------------------------
......@@ -541,6 +541,7 @@
};
this.clearSession = function () {
this.roster.browserStorage._clear();
this.session.browserStorage._clear();
// XXX: this should perhaps go into the beforeunload handler
converse.chatboxes.get('controlbox').save({'connected': false});
......@@ -638,7 +639,9 @@
if (this.debug) {
this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); };
Strophe.log = function (level, msg) { console.log(level+' '+msg); };
Strophe.log = function (level, msg) {
console.log(level+' '+msg);
};
Strophe.error = function (msg) {
console.log('ERROR: '+msg);
};
......@@ -906,6 +909,9 @@
if (!body) {
if (composing.length || paused.length) {
// FIXME: use one attribute for chat states (e.g.
// chatstate) instead of saving 'paused' and
// 'composing' separately.
this.messages.add({
fullname: fullname,
sender: 'them',
......@@ -1597,7 +1603,7 @@
label_away: __('Away'),
label_offline: __('Offline'),
label_logout: __('Log out'),
allow_logout: converse.allow_logout,
allow_logout: converse.allow_logout
});
this.$tabs.append(converse.templates.contacts_tab({label_contacts: LABEL_CONTACTS}));
if (converse.xhr_user_search) {
......@@ -1933,8 +1939,16 @@
b64_sha1('converse.roster.groups'+converse.bare_jid));
converse.rosterview = new converse.RosterView({model: rostergroups});
this.contactspanel.$el.append(converse.rosterview.$el);
// TODO:
// See if we shouldn't also fetch the roster here... otherwise
// the roster is always populated by the rosterHandler method,
// which appears to be a less economic way.
// i.e. from what it seems, only groups are fetched from
// browserStorage, and no contacts.
// XXX: Make sure that if fetch is called, we don't sort on
// each item add...
// converse.roster.fetch()
converse.rosterview.render().fetch().update();
converse.connection.roster.get(function () {});
return this;
},
......@@ -2079,7 +2093,7 @@
initialize: function (options) {
this.browserStorage = new Backbone.BrowserStorage[converse.storage](
b64_sha1('converse.occupants'+converse.bare_jid+options.nick));
},
}
});
this.ChatRoomOccupantsView = Backbone.Overview.extend({
......@@ -2169,7 +2183,7 @@
$(ev.target).typeahead('val', '');
}, this));
return this;
},
}
});
......@@ -2210,7 +2224,7 @@
this);
this.occupantsview = new converse.ChatRoomOccupantsView({
model: new converse.ChatRoomOccupants({nick: this.model.get('nick')}),
model: new converse.ChatRoomOccupants({nick: this.model.get('nick')})
});
this.occupantsview.chatroomview = this;
this.render();
......@@ -2243,7 +2257,7 @@
.append(
converse.templates.chatarea({
'show_toolbar': converse.show_toolbar,
'label_message': __('Message'),
'label_message': __('Message')
}))
.append(this.occupantsview.render().$el);
this.renderToolbar();
......@@ -2556,7 +2570,7 @@
172: __('This room is now non-anonymous'),
173: __('This room is now semi-anonymous'),
174: __('This room is now fully-anonymous'),
201: __('A new room has been created'),
201: __('A new room has been created')
},
disconnectMessages: {
......@@ -2710,7 +2724,7 @@
delayed = $message.find('delay').length > 0,
subject = $message.children('subject').text();
if (this.model.messages.findWhere({msgid: msgid})) {
if (msgid && this.model.messages.findWhere({msgid: msgid})) {
return true; // We already have this message stored.
}
this.showStatusMessages($message);
......@@ -2829,6 +2843,8 @@
onMessage: function (message) {
var $message = $(message);
var buddy_jid, $forwarded, $received,
msgid = $message.attr('id'),
chatbox, resource, roster_item,
message_from = $message.attr('from');
if (message_from === converse.connection.jid) {
// FIXME: Forwarded messages should be sent to specific resources,
......@@ -2844,8 +2860,7 @@
message_from = $message.attr('from');
}
var from = Strophe.getBareJidFromJid(message_from),
to = Strophe.getBareJidFromJid($message.attr('to')),
resource, chatbox, roster_item;
to = Strophe.getBareJidFromJid($message.attr('to'));
if (from == converse.bare_jid) {
// I am the sender, so this must be a forwarded message...
buddy_jid = to;
......@@ -2854,15 +2869,15 @@
buddy_jid = from;
resource = Strophe.getResourceFromJid(message_from);
}
chatbox = this.get(buddy_jid);
roster_item = converse.roster.get(buddy_jid);
roster_item = converse.roster.get(buddy_jid);
if (roster_item === undefined) {
// The buddy was likely removed
converse.log('Could not get roster item for JID '+buddy_jid, 'error');
return true;
}
chatbox = this.get(buddy_jid);
if (!chatbox) {
var fullname = roster_item.get('fullname');
fullname = _.isEmpty(fullname)? buddy_jid: fullname;
......@@ -2875,6 +2890,16 @@
'url': roster_item.get('url')
});
}
if (msgid && chatbox.messages.findWhere({msgid: msgid})) {
// FIXME: There's still a bug here..
// If a duplicate message is received just after the chat
// box was closed, then it'll open again (due to it being
// created here above), with now new messages.
// The solution is mostly likely to not let chat boxes show
// automatically when they are created, but to require
// "show" to be called explicitly.
return true; // We already have this message stored.
}
if (!this.isOnlyChatStateNotification($message) && from !== converse.bare_jid) {
playNotification();
}
......@@ -3032,10 +3057,14 @@
},
initialize: function () {
this.model.messages.on('add', this.updateUnreadMessagesCounter, this);
this.model.on('showSentOTRMessage', this.updateUnreadMessagesCounter, this);
this.model.on('showReceivedOTRMessage', this.updateUnreadMessagesCounter, this);
this.model.messages.on('add', function (m) {
if (!(m.get('composing') || m.get('paused'))) {
this.updateUnreadMessagesCounter();
}
}, this);
this.model.on('change:minimized', this.clearUnreadMessagesCounter, this);
this.model.on('showReceivedOTRMessage', this.updateUnreadMessagesCounter, this);
this.model.on('showSentOTRMessage', this.updateUnreadMessagesCounter, this);
},
render: function () {
......@@ -3168,7 +3197,7 @@
this.set({
'collapsed': this.get('collapsed') || false,
'num_minimized': this.get('num_minimized') || 0,
'num_unread': this.get('num_unread') || 0,
'num_unread': this.get('num_unread') || 0
});
}
});
......@@ -3194,7 +3223,7 @@
this.$flyout.show();
}
return this.$el;
},
}
});
this.RosterContact = Backbone.Model.extend({
......@@ -3210,6 +3239,10 @@
'status': ''
}, attributes);
this.set(attrs);
},
showInRoster: function () {
return (!converse.show_only_online_users || this.get('chat_status') === 'online');
}
});
......@@ -3224,24 +3257,12 @@
},
initialize: function () {
this.model.on("change", this.onChange, this);
this.model.on("change", this.render, this);
this.model.on("remove", this.remove, this);
this.model.on("destroy", this.remove, this);
this.model.on("open", this.openChat, this);
},
onChange: function () {
if (converse.show_only_online_users) {
if (this.model.get('chat_status') !== 'online') {
this.$el.hide();
} else {
this.$el.show();
}
} else {
this.render();
}
},
openChat: function (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); }
// XXX: Can this.model.attributes be used here, instead of
......@@ -3291,6 +3312,12 @@
},
render: function () {
if (!this.model.showInRoster()) {
this.$el.hide();
return this;
} else if (this.$el[0].style.display === "none") {
this.$el.show();
}
var item = this.model,
ask = item.get('ask'),
chat_status = item.get('chat_status'),
......@@ -3334,7 +3361,7 @@
this.$el.html(converse.templates.requesting_contact(
_.extend(item.toJSON(), {
'desc_accept': __("Click to accept this contact request"),
'desc_decline': __("Click to decline this contact request"),
'desc_decline': __("Click to decline this contact request")
})
));
converse.controlboxtoggle.showControlBox();
......@@ -3354,13 +3381,13 @@
this.RosterContacts = Backbone.Collection.extend({
model: converse.RosterContact,
comparator: function (contact1, contact2) {
var name1 = contact1.get('fullname').toLowerCase();
var name1, name2;
var status1 = contact1.get('chat_status') || 'offline';
var name2 = contact2.get('fullname').toLowerCase();
var status2 = contact2.get('chat_status') || 'offline';
if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
name1 = contact1.get('fullname').toLowerCase();
name2 = contact2.get('fullname').toLowerCase();
return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else {
return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
......@@ -3368,15 +3395,13 @@
},
subscribeToSuggestedItems: function (msg) {
$(msg).find('item').each(function () {
$(msg).find('item').each(function (i, items) {
var $this = $(this),
jid = $this.attr('jid'),
action = $this.attr('action'),
fullname = $this.attr('name');
if (action === 'add') {
converse.connection.roster.add(jid, fullname, [], function (iq) {
converse.connection.roster.subscribe(jid, null, converse.xmppstatus.get('fullname'));
});
converse.connection.roster.subscribe(jid, null, converse.xmppstatus.get('fullname'));
}
});
return true;
......@@ -3473,17 +3498,19 @@
id = this.models[i].get('id');
if (_.indexOf(_.pluck(items, 'jid'), id) === -1) {
contact = this.get(id);
if (contact) {
if (contact && !contact.get('requesting')) {
contact.destroy();
}
}
}
},
rosterHandler: function (items) {
// TODO: see if we can only use 2nd item par
rosterHandler: function (items, item) {
converse.emit('roster', items);
this.clearCache(items);
_.each(items, function (item, index, items) {
var new_items = item ? [item] : items;
_.each(new_items, function (item, index, items) {
if (this.isSelf(item.jid)) { return; }
var model = this.get(item.jid);
if (!model) {
......@@ -3500,7 +3527,7 @@
groups: item.groups,
jid: item.jid,
subscription: item.subscription
});
}, {sort: false});
} else {
if ((item.subscription === 'none') && (item.ask === null)) {
// This user is no longer in our roster
......@@ -3683,11 +3710,13 @@
var view = new converse.RosterContactView({model: contact});
this.add(contact.get('id'), view);
view = this.positionContact(contact).render();
if (this.model.get('state') === CLOSED) {
view.$el.hide();
this.$el.show();
} else {
this.show();
if (contact.showInRoster()) {
if (this.model.get('state') === CLOSED) {
if (view.$el[0].style.display !== "none") { view.$el.hide(); }
if (this.$el[0].style.display === "none") { this.$el.show(); }
} else {
if (this.$el[0].style.display !== "block") { this.show(); }
}
}
},
......@@ -3709,6 +3738,9 @@
},
show: function () {
// FIXME: There's a bug here, if show_only_online_users is true
// Possible solution, get the group, call _.each and check
// showInRoster
this.$el.nextUntil('dt').addBack().show();
},
......@@ -3726,7 +3758,7 @@
if (q.length === 0) {
if (this.model.get('state') === OPENED) {
this.model.contacts.each($.proxy(function (item) {
if (!(converse.show_only_online_users && item.get('chat_status') === 'online')) {
if (item.showInRoster()) {
this.get(item.get('id')).$el.show();
}
}, this));
......@@ -3846,16 +3878,20 @@
converse.roster.on("remove", this.update, this);
this.model.on("add", this.onGroupAdd, this);
this.model.on("reset", this.reset, this);
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
},
update: function () {
update: _.debounce(function () {
var $count = $('#online-count');
$count.text('('+converse.roster.getNumOnlineContacts()+')');
if (!$count.is(':visible')) {
$count.show();
}
if (this.$roster.parent().length === 0) {
this.$el.append(this.$roster.show());
}
return this.showHideFilter();
},
}, converse.animate ? 100 : 0),
render: function () {
this.$el.html(converse.templates.roster({
......@@ -3868,10 +3904,47 @@
fetch: function () {
this.model.fetch({
silent: true,
success: $.proxy(this.positionFetchedGroups, this)
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: $.proxy(function (collection, resp, options) {
if (collection.length !== 0) {
this.positionFetchedGroups(collection, resp, options);
}
converse.roster.fetch({
add: true,
success: function (collection) {
// XXX: Bit of a hack.
// strophe.roster expects .get to be called for
// every page load so that its "items" attr
// gets populated.
// This is very inefficient for large rosters,
// and we already have the roster cached in
// sessionStorage.
// Therefore we manually populate the "items"
// attr.
// Ideally we should eventually replace
// strophe.roster with something better.
if (collection.length > 0) {
collection.each(function (item) {
converse.connection.roster.items.push({
name : item.get('fullname'),
jid : item.get('jid'),
subscription : item.get('subscription'),
ask : item.get('ask'),
groups : item.get('groups'),
resources : item.get('resources')
});
});
converse.initial_presence_sent = 1;
converse.xmppstatus.sendPresence();
} else {
converse.connection.roster.get();
}
}
});
}, this)
});
converse.roster.fetch({add: true});
return this;
},
......@@ -3939,7 +4012,7 @@
// Don't hide if user is currently filtering.
return;
}
if (this.$('.roster-contacts').hasScrollBar()) {
if (this.$roster.hasScrollBar()) {
if (!visible) {
$filter.show();
$type.show();
......@@ -3954,6 +4027,7 @@
reset: function () {
converse.roster.reset();
this.removeAll();
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
this.render().update();
return this;
},
......@@ -3961,13 +4035,24 @@
registerRosterHandler: function () {
// Register handlers that depend on the roster
converse.connection.roster.registerCallback(
$.proxy(converse.roster.rosterHandler, converse.roster),
null, 'presence', null);
$.proxy(converse.roster.rosterHandler, converse.roster)
);
},
registerRosterXHandler: function () {
var t = 0;
converse.connection.addHandler(
$.proxy(converse.roster.subscribeToSuggestedItems, converse.roster),
function (msg) {
window.setTimeout(
function () {
converse.connection.flush();
$.proxy(converse.roster.subscribeToSuggestedItems, converse.roster)(msg);
},
t
);
t += $(msg).find('item').length*250;
return true;
},
'http://jabber.org/protocol/rosterx', 'message', null);
},
......@@ -4044,7 +4129,7 @@
this.add(group.get('name'), view.render());
}
if (idx === 0) {
this.$('.roster-contacts').append(view.$el);
this.$roster.append(view.$el);
} else {
this.appendGroup(view);
}
......@@ -4055,13 +4140,14 @@
/* Place the group's DOM element in the correct alphabetical
* position amongst the other groups in the roster.
*/
var index = this.model.indexOf(view.model);
var $groups = this.$roster.find('.roster-group'),
index = $groups.length ? this.model.indexOf(view.model) : 0;
if (index === 0) {
this.$('.roster-contacts').prepend(view.$el);
this.$roster.prepend(view.$el);
} else if (index == (this.model.length-1)) {
this.appendGroup(view);
} else {
$(this.$('.roster-group').eq(index)).before(view.$el);
$($groups.eq(index)).before(view.$el);
}
return this;
},
......@@ -4069,7 +4155,7 @@
appendGroup: function (view) {
/* Add the group at the bottom of the roster
*/
var $last = this.$('.roster-group').last();
var $last = this.$roster.find('.roster-group').last();
var $siblings = $last.siblings('dd');
if ($siblings.length > 0) {
$siblings.last().after(view.$el);
......@@ -4351,7 +4437,7 @@
converse.connection.disco.addFeature('http://jabber.org/protocol/rosterx'); // Limited support
converse.connection.disco.addFeature('jabber:x:conference');
converse.connection.disco.addFeature('urn:xmpp:carbons:2');
converse.connection.disco.addFeature('vcard-temp');
converse.connection.disco.addFeature(Strophe.NS.VCARD);
converse.connection.disco.addFeature(Strophe.NS.BOSH);
converse.connection.disco.addFeature(Strophe.NS.DISCO_INFO);
converse.connection.disco.addFeature(Strophe.NS.MUC);
......@@ -4560,8 +4646,6 @@
sid = this.session.get('sid');
jid = this.session.get('jid');
if (rid && jid && sid) {
// We have the necessary tokens for resuming a session
rid += 1;
this.session.save({rid: rid}); // The RID needs to be increased with each request.
this.connection.attach(jid, sid, rid, this.onConnect);
} else if (this.prebind) {
......@@ -4701,6 +4785,6 @@
},
'registerPlugin': function (name, callback) {
converse.plugins[name] = callback;
},
}
};
}));
Changelog
=========
0.8.4 (Unreleased)
------------------
.. note::
The current API methods will be deprecated in future bugfix releases and
a new API will be made available for the 0.9.0 release.
* Bugfix. Error when trying to use prebind and keepalive together. [heban and jcbrand]
* Bugfix. Cannot read property "top" of undefined. [jcbrand]
* Add new event, noResumeableSession, for when keepalive=true and there aren't
any prebind session tokens. [jcbrand]
* Add 2 new API methods, getChatBox and openChatBox. [jcbrand]
* #46 Add 2 new API methods, getChatBox and openChatBox. [jcbrand]
* #151 Browser locks/freezes with many roster users. [jcbrand]
* #251 Non-minified builds for debugging. [jcbrand]
* #264 Remove unnecessary commas for ie8 compatibility. [Deuteu]
* #267 Unread messages counter wrongly gets incremented by chat state notifications. [Deuteu]
0.8.3 (2014-09-22)
------------------
......
......@@ -808,8 +808,20 @@ After adding the string, you'll need to regenerate the POT file, like so:
make pot
To create a new PO file for a language in which converse.js is not yet
translated into, do the following
.. note:: In this example we use Polish (pl), you need to substitute 'pl' to your own language's code.
::
mkdir -p ./locale/pl/LC_MESSAGES
msginit -i ./locale/converse.pot -o ./locale/pl/LC_MESSAGES/converse.po -l pl
You can then create or update the PO file for a specific language by doing the following:
.. note:: In this example we use German (de), you need to substitute 'de' to your own language's code.
::
msgmerge ./locale/de/LC_MESSAGES/converse.po ./locale/converse.pot -U
......
......@@ -229,7 +229,6 @@
<script type="text/javascript">try { var pageTracker = _gat._getTracker("UA-2128260-8"); pageTracker._trackPageview(); } catch(err) {}</script>
<script>
// Configuration loaded, so safe to make other require calls.
require(['converse'], function (converse) {
(function () {
/* XXX: This function initializes jquery.easing for the https://conversejs.org
......@@ -255,14 +254,17 @@
})();
converse.initialize({
allow_otr: true,
auto_list_rooms: false,
auto_subscribe: false,
bosh_service_url: 'https://bind.conversejs.org', // Please use this connection manager only for testing purposes
i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
hide_muc_server: false,
i18n: locales['en'], // Refer to ./locale/locales.js to see which locales are supported
keepalive: true,
play_sounds: true,
prebind: false,
roster_groups: true,
show_controlbox_by_default: true,
debug: true,
roster_groups: true
xhr_user_search: false
});
});
</script>
......
......@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Converse.js 0.7.0\n"
"Project-Id-Version: Converse.js 0.8.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-09-22 18:14+0200\n"
"POT-Creation-Date: 2014-10-21 13:12+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -17,123 +17,123 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: converse.js:338
#: converse.js:314
msgid "unencrypted"
msgstr ""
#: converse.js:339
#: converse.js:315
msgid "unverified"
msgstr ""
#: converse.js:340
#: converse.js:316
msgid "verified"
msgstr ""
#: converse.js:341
#: converse.js:317
msgid "finished"
msgstr ""
#: converse.js:344
#: converse.js:320
msgid "This contact is busy"
msgstr ""
#: converse.js:345
#: converse.js:321
msgid "This contact is online"
msgstr ""
#: converse.js:346
#: converse.js:322
msgid "This contact is offline"
msgstr ""
#: converse.js:347
#: converse.js:323
msgid "This contact is unavailable"
msgstr ""
#: converse.js:348
#: converse.js:324
msgid "This contact is away for an extended period"
msgstr ""
#: converse.js:349
#: converse.js:325
msgid "This contact is away"
msgstr ""
#: converse.js:351
#: converse.js:327
msgid "Click to hide these contacts"
msgstr ""
#: converse.js:353
#: converse.js:329
msgid "My contacts"
msgstr ""
#: converse.js:354
#: converse.js:330
msgid "Pending contacts"
msgstr ""
#: converse.js:355
#: converse.js:331
msgid "Contact requests"
msgstr ""
#: converse.js:356
#: converse.js:332
msgid "Ungrouped"
msgstr ""
#: converse.js:358
#: converse.js:334
msgid "Contacts"
msgstr ""
#: converse.js:359
#: converse.js:335
msgid "Groups"
msgstr ""
#: converse.js:441
#: converse.js:417
msgid "Reconnecting"
msgstr ""
#: converse.js:476
#: converse.js:452
msgid "Disconnected"
msgstr ""
#: converse.js:484
#: converse.js:460
msgid "Error"
msgstr ""
#: converse.js:486
#: converse.js:462
msgid "Connecting"
msgstr ""
#: converse.js:489
#: converse.js:465
msgid "Connection Failed"
msgstr ""
#: converse.js:491
#: converse.js:467
msgid "Authenticating"
msgstr ""
#: converse.js:494
#: converse.js:470
msgid "Authentication Failed"
msgstr ""
#: converse.js:499
#: converse.js:475
msgid "Disconnecting"
msgstr ""
#: converse.js:638 converse.js:684
#: converse.js:614 converse.js:660
msgid "Online Contacts"
msgstr ""
#: converse.js:802
#: converse.js:778
msgid "Re-establishing encrypted session"
msgstr ""
#: converse.js:814
#: converse.js:790
msgid "Generating private key."
msgstr ""
#: converse.js:815
#: converse.js:791
msgid "Your browser might become unresponsive."
msgstr ""
#: converse.js:850
#: converse.js:826
msgid ""
"Authentication request from %1$s\n"
"\n"
......@@ -143,67 +143,67 @@ msgid ""
"%2$s"
msgstr ""
#: converse.js:859
#: converse.js:835
msgid "Could not verify this user's identify."
msgstr ""
#: converse.js:898
#: converse.js:874
msgid "Exchanging private key with buddy."
msgstr ""
#: converse.js:1045
#: converse.js:1023
msgid "Personal message"
msgstr ""
#: converse.js:1077
#: converse.js:1055
msgid "Are you sure you want to clear the messages from this room?"
msgstr ""
#: converse.js:1099
#: converse.js:1077
msgid "me"
msgstr ""
#: converse.js:1154
#: converse.js:1131
msgid "is typing"
msgstr ""
#: converse.js:1157
#: converse.js:1134
msgid "has stopped typing"
msgstr ""
#: converse.js:1199 converse.js:2331
#: converse.js:1176 converse.js:2314
msgid "Show this menu"
msgstr ""
#: converse.js:1200
#: converse.js:1177
msgid "Write in the third person"
msgstr ""
#: converse.js:1201 converse.js:2330
#: converse.js:1178 converse.js:2313
msgid "Remove messages"
msgstr ""
#: converse.js:1285
#: converse.js:1262
msgid "Are you sure you want to clear the messages from this chat box?"
msgstr ""
#: converse.js:1320
#: converse.js:1297
msgid "Your message could not be sent"
msgstr ""
#: converse.js:1323
#: converse.js:1300
msgid "We received an unencrypted message"
msgstr ""
#: converse.js:1326
#: converse.js:1303
msgid "We received an unreadable encrypted message"
msgstr ""
#: converse.js:1335
#: converse.js:1312
msgid "This user has requested an encrypted session."
msgstr ""
#: converse.js:1357
#: converse.js:1334
msgid ""
"Here are the fingerprints, please confirm them with %1$s, outside of this "
"chat.\n"
......@@ -216,7 +216,7 @@ msgid ""
"Cancel."
msgstr ""
#: converse.js:1370
#: converse.js:1347
msgid ""
"You will be prompted to provide a security question and then an answer to "
"that question.\n"
......@@ -225,555 +225,555 @@ msgid ""
"exact same answer (case sensitive), their identity will be verified."
msgstr ""
#: converse.js:1371
#: converse.js:1348
msgid "What is your security question?"
msgstr ""
#: converse.js:1373
#: converse.js:1350
msgid "What is the answer to the security question?"
msgstr ""
#: converse.js:1377
#: converse.js:1354
msgid "Invalid authentication scheme provided"
msgstr ""
#: converse.js:1488
#: converse.js:1465
msgid "Your messages are not encrypted anymore"
msgstr ""
#: converse.js:1490
#: converse.js:1467
msgid ""
"Your messages are now encrypted but your buddy's identity has not been "
"verified."
msgstr ""
#: converse.js:1492
#: converse.js:1469
msgid "Your buddy's identify has been verified."
msgstr ""
#: converse.js:1494
#: converse.js:1471
msgid "Your buddy has ended encryption on their end, you should do the same."
msgstr ""
#: converse.js:1503
#: converse.js:1480
msgid "Your messages are not encrypted. Click here to enable OTR encryption."
msgstr ""
#: converse.js:1505
#: converse.js:1482
msgid "Your messages are encrypted, but your buddy has not been verified."
msgstr ""
#: converse.js:1507
#: converse.js:1484
msgid "Your messages are encrypted and your buddy verified."
msgstr ""
#: converse.js:1509
#: converse.js:1486
msgid ""
"Your buddy has closed their end of the private session, you should do the "
"same"
msgstr ""
#: converse.js:1519
#: converse.js:1496
msgid "Clear all messages"
msgstr ""
#: converse.js:1520
#: converse.js:1497
msgid "End encrypted conversation"
msgstr ""
#: converse.js:1521
#: converse.js:1498
msgid "Hide the list of participants"
msgstr ""
#: converse.js:1522
#: converse.js:1499
msgid "Refresh encrypted conversation"
msgstr ""
#: converse.js:1523
#: converse.js:1500
msgid "Start a call"
msgstr ""
#: converse.js:1524
#: converse.js:1501
msgid "Start encrypted conversation"
msgstr ""
#: converse.js:1525
#: converse.js:1502
msgid "Verify with fingerprints"
msgstr ""
#: converse.js:1526
#: converse.js:1503
msgid "Verify with SMP"
msgstr ""
#: converse.js:1527
#: converse.js:1504
msgid "What's this?"
msgstr ""
#: converse.js:1618
#: converse.js:1595
msgid "Online"
msgstr ""
#: converse.js:1619
#: converse.js:1596
msgid "Busy"
msgstr ""
#: converse.js:1620
#: converse.js:1597
msgid "Away"
msgstr ""
#: converse.js:1621
#: converse.js:1598
msgid "Offline"
msgstr ""
#: converse.js:1622
#: converse.js:1599
msgid "Log out"
msgstr ""
#: converse.js:1628
#: converse.js:1605
msgid "Contact name"
msgstr ""
#: converse.js:1629
#: converse.js:1606
msgid "Search"
msgstr ""
#: converse.js:1633
#: converse.js:1610
msgid "Contact username"
msgstr ""
#: converse.js:1634
#: converse.js:1611
msgid "Add"
msgstr ""
#: converse.js:1639
#: converse.js:1616
msgid "Click to add new chat contacts"
msgstr ""
#: converse.js:1640
#: converse.js:1617
msgid "Add a contact"
msgstr ""
#: converse.js:1664
#: converse.js:1641
msgid "No users found"
msgstr ""
#: converse.js:1670
#: converse.js:1647
msgid "Click to add as a chat contact"
msgstr ""
#: converse.js:1725
#: converse.js:1702
msgid "Room name"
msgstr ""
#: converse.js:1726
#: converse.js:1703
msgid "Nickname"
msgstr ""
#: converse.js:1727
#: converse.js:1704
msgid "Server"
msgstr ""
#: converse.js:1728
#: converse.js:1705
msgid "Join"
msgstr ""
#: converse.js:1729
#: converse.js:1706
msgid "Show rooms"
msgstr ""
#: converse.js:1749
#: converse.js:1726
msgid "Rooms"
msgstr ""
#. For translators: %1$s is a variable and will be replaced with the XMPP server name
#: converse.js:1756
#: converse.js:1733
msgid "No rooms on %1$s"
msgstr ""
#. For translators: %1$s is a variable and will be
#. replaced with the XMPP server name
#: converse.js:1771
#: converse.js:1748
msgid "Rooms on %1$s"
msgstr ""
#: converse.js:1780
#: converse.js:1757
msgid "Click to open this room"
msgstr ""
#: converse.js:1781
#: converse.js:1758
msgid "Show more information on this room"
msgstr ""
#: converse.js:1843
#: converse.js:1820
msgid "Description:"
msgstr ""
#: converse.js:1844
#: converse.js:1821
msgid "Occupants:"
msgstr ""
#: converse.js:1845
#: converse.js:1822
msgid "Features:"
msgstr ""
#: converse.js:1846
#: converse.js:1823
msgid "Requires authentication"
msgstr ""
#: converse.js:1847
#: converse.js:1824
msgid "Hidden"
msgstr ""
#: converse.js:1848
#: converse.js:1825
msgid "Requires an invitation"
msgstr ""
#: converse.js:1849
#: converse.js:1826
msgid "Moderated"
msgstr ""
#: converse.js:1850
#: converse.js:1827
msgid "Non-anonymous"
msgstr ""
#: converse.js:1851
#: converse.js:1828
msgid "Open room"
msgstr ""
#: converse.js:1852
#: converse.js:1829
msgid "Permanent room"
msgstr ""
#: converse.js:1853
#: converse.js:1830
msgid "Public"
msgstr ""
#: converse.js:1854
#: converse.js:1831
msgid "Semi-anonymous"
msgstr ""
#: converse.js:1855
#: converse.js:1832
msgid "Temporary room"
msgstr ""
#: converse.js:1856
#: converse.js:1833
msgid "Unmoderated"
msgstr ""
#: converse.js:2085
#: converse.js:2062
msgid "This user is a moderator"
msgstr ""
#: converse.js:2086
#: converse.js:2063
msgid "This user can send messages in this room"
msgstr ""
#: converse.js:2087
#: converse.js:2064
msgid "This user can NOT send messages in this room"
msgstr ""
#: converse.js:2119
#: converse.js:2096
msgid "Invite..."
msgstr ""
#: converse.js:2120
#: converse.js:2097
msgid "Occupants"
msgstr ""
#: converse.js:2185
#: converse.js:2162
msgid "You are about to invite %1$s to the chat room \"%2$s\". "
msgstr ""
#: converse.js:2186
#: converse.js:2163
msgid ""
"You may optionally include a message, explaining the reason for the "
"invitation."
msgstr ""
#: converse.js:2269
#: converse.js:2246
msgid "Message"
msgstr ""
#: converse.js:2307
#: converse.js:2282
msgid "Error: could not execute the command"
msgstr ""
#: converse.js:2329
#: converse.js:2312
msgid "Ban user from room"
msgstr ""
#: converse.js:2332
#: converse.js:2315
msgid "Kick user from room"
msgstr ""
#: converse.js:2333
#: converse.js:2316
msgid "Write in 3rd person"
msgstr ""
#: converse.js:2334
#: converse.js:2317
msgid "Remove user's ability to post messages"
msgstr ""
#: converse.js:2335
#: converse.js:2318
msgid "Change your nickname"
msgstr ""
#: converse.js:2336
#: converse.js:2319
msgid "Set room topic"
msgstr ""
#: converse.js:2337
#: converse.js:2320
msgid "Allow muted user to post messages"
msgstr ""
#: converse.js:2441 converse.js:4262
#: converse.js:2423 converse.js:4250
msgid "Save"
msgstr ""
#: converse.js:2442
#: converse.js:2424
msgid "Cancel"
msgstr ""
#: converse.js:2487
#: converse.js:2469
msgid "An error occurred while trying to save the form."
msgstr ""
#: converse.js:2531
#: converse.js:2513
msgid "This chatroom requires a password"
msgstr ""
#: converse.js:2532
#: converse.js:2514
msgid "Password: "
msgstr ""
#: converse.js:2533
#: converse.js:2515
msgid "Submit"
msgstr ""
#: converse.js:2568
#: converse.js:2550
msgid "This room is not anonymous"
msgstr ""
#: converse.js:2569
#: converse.js:2551
msgid "This room now shows unavailable members"
msgstr ""
#: converse.js:2570
#: converse.js:2552
msgid "This room does not show unavailable members"
msgstr ""
#: converse.js:2571
#: converse.js:2553
msgid "Non-privacy-related room configuration has changed"
msgstr ""
#: converse.js:2572
#: converse.js:2554
msgid "Room logging is now enabled"
msgstr ""
#: converse.js:2573
#: converse.js:2555
msgid "Room logging is now disabled"
msgstr ""
#: converse.js:2574
#: converse.js:2556
msgid "This room is now non-anonymous"
msgstr ""
#: converse.js:2575
#: converse.js:2557
msgid "This room is now semi-anonymous"
msgstr ""
#: converse.js:2576
#: converse.js:2558
msgid "This room is now fully-anonymous"
msgstr ""
#: converse.js:2577
#: converse.js:2559
msgid "A new room has been created"
msgstr ""
#: converse.js:2581 converse.js:2681
#: converse.js:2563 converse.js:2663
msgid "You have been banned from this room"
msgstr ""
#: converse.js:2582
#: converse.js:2564
msgid "You have been kicked from this room"
msgstr ""
#: converse.js:2583
#: converse.js:2565
msgid "You have been removed from this room because of an affiliation change"
msgstr ""
#: converse.js:2584
#: converse.js:2566
msgid ""
"You have been removed from this room because the room has changed to members-"
"only and you're not a member"
msgstr ""
#: converse.js:2585
#: converse.js:2567
msgid ""
"You have been removed from this room because the MUC (Multi-user chat) "
"service is being shut down."
msgstr ""
#: converse.js:2599
#: converse.js:2581
msgid "<strong>%1$s</strong> has been banned"
msgstr ""
#: converse.js:2600
#: converse.js:2582
msgid "<strong>%1$s</strong>'s nickname has changed"
msgstr ""
#: converse.js:2601
#: converse.js:2583
msgid "<strong>%1$s</strong> has been kicked out"
msgstr ""
#: converse.js:2602
#: converse.js:2584
msgid "<strong>%1$s</strong> has been removed because of an affiliation change"
msgstr ""
#: converse.js:2603
#: converse.js:2585
msgid "<strong>%1$s</strong> has been removed for not being a member"
msgstr ""
#: converse.js:2607
#: converse.js:2589
msgid "Your nickname has been automatically changed to: <strong>%1$s</strong>"
msgstr ""
#: converse.js:2608
#: converse.js:2590
msgid "Your nickname has been changed to: <strong>%1$s</strong>"
msgstr ""
#: converse.js:2656 converse.js:2666
#: converse.js:2638 converse.js:2648
msgid "The reason given is: \""
msgstr ""
#: converse.js:2679
#: converse.js:2661
msgid "You are not on the member list of this room"
msgstr ""
#: converse.js:2685
#: converse.js:2667
msgid "No nickname was specified"
msgstr ""
#: converse.js:2689
#: converse.js:2671
msgid "You are not allowed to create new rooms"
msgstr ""
#: converse.js:2691
#: converse.js:2673
msgid "Your nickname doesn't conform to this room's policies"
msgstr ""
#: converse.js:2695
#: converse.js:2677
msgid "Your nickname is already taken"
msgstr ""
#: converse.js:2697
#: converse.js:2679
msgid "This room does not (yet) exist"
msgstr ""
#: converse.js:2699
#: converse.js:2681
msgid "This room has reached it's maximum number of occupants"
msgstr ""
#: converse.js:2736
#: converse.js:2723
msgid "Topic set by %1$s to: %2$s"
msgstr ""
#: converse.js:2818
#: converse.js:2805
msgid "%1$s has invited you to join a chat room: %2$s"
msgstr ""
#: converse.js:2822
#: converse.js:2809
msgid ""
"%1$s has invited you to join a chat room: %2$s, and left the following "
"reason: \"%3$s\""
msgstr ""
#: converse.js:3058
#: converse.js:3044
msgid "Click to restore this chat"
msgstr ""
#: converse.js:3202
#: converse.js:3188
msgid "Minimized"
msgstr ""
#: converse.js:3274
#: converse.js:3262
msgid "Are you sure you want to remove this contact?"
msgstr ""
#: converse.js:3297
#: converse.js:3285
msgid "Are you sure you want to decline this contact request?"
msgstr ""
#: converse.js:3341 converse.js:3359
#: converse.js:3329 converse.js:3347
msgid "Click to remove this contact"
msgstr ""
#: converse.js:3348
#: converse.js:3336
msgid "Click to accept this contact request"
msgstr ""
#: converse.js:3349
#: converse.js:3337
msgid "Click to decline this contact request"
msgstr ""
#: converse.js:3358
#: converse.js:3346
msgid "Click to chat with this contact"
msgstr ""
#: converse.js:3874
#: converse.js:3862
msgid "Type to filter"
msgstr ""
#. For translators: the %1$s part gets replaced with the status
#. Example, I am online
#: converse.js:4233 converse.js:4310
#: converse.js:4221 converse.js:4298
msgid "I am %1$s"
msgstr ""
#: converse.js:4235 converse.js:4315
#: converse.js:4223 converse.js:4303
msgid "Click here to write a custom status message"
msgstr ""
#: converse.js:4236 converse.js:4316
#: converse.js:4224 converse.js:4304
msgid "Click to change your chat status"
msgstr ""
#: converse.js:4261
#: converse.js:4249
msgid "Custom status"
msgstr ""
#: converse.js:4290 converse.js:4298
#: converse.js:4278 converse.js:4286
msgid "online"
msgstr ""
#: converse.js:4292
#: converse.js:4280
msgid "busy"
msgstr ""
#: converse.js:4294
#: converse.js:4282
msgid "away for long"
msgstr ""
#: converse.js:4296
#: converse.js:4284
msgid "away"
msgstr ""
#: converse.js:4419
#: converse.js:4407
msgid "XMPP/Jabber Username:"
msgstr ""
#: converse.js:4420
#: converse.js:4408
msgid "Password:"
msgstr ""
#: converse.js:4421
#: converse.js:4409
msgid "Log In"
msgstr ""
#: converse.js:4428
#: converse.js:4416
msgid "Sign in"
msgstr ""
#: converse.js:4488
#: converse.js:4476
msgid "Toggle chat"
msgstr ""
# Polish translations for Converse.js package.
# Copyright (C) 2014 Jan-Carel Brand
# This file is distributed under the same license as the Converse.js package.
# Translators:
# Dev Account <info@elkom.com.tw>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: Converse.js 0.8.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-10-21 13:12+0200\n"
"PO-Revision-Date: 2014-10-21 13:13+0200\n"
"Last-Translator: Dev Account <info@elkom.com.tw>\n"
"Language-Team: Polish\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
#: converse.js:314
msgid "unencrypted"
msgstr ""
#: converse.js:315
msgid "unverified"
msgstr ""
#: converse.js:316
msgid "verified"
msgstr ""
#: converse.js:317
msgid "finished"
msgstr ""
#: converse.js:320
msgid "This contact is busy"
msgstr ""
#: converse.js:321
msgid "This contact is online"
msgstr ""
#: converse.js:322
msgid "This contact is offline"
msgstr ""
#: converse.js:323
msgid "This contact is unavailable"
msgstr ""
#: converse.js:324
msgid "This contact is away for an extended period"
msgstr ""
#: converse.js:325
msgid "This contact is away"
msgstr ""
#: converse.js:327
msgid "Click to hide these contacts"
msgstr ""
#: converse.js:329
msgid "My contacts"
msgstr ""
#: converse.js:330
msgid "Pending contacts"
msgstr ""
#: converse.js:331
msgid "Contact requests"
msgstr ""
#: converse.js:332
msgid "Ungrouped"
msgstr ""
#: converse.js:334
msgid "Contacts"
msgstr ""
#: converse.js:335
msgid "Groups"
msgstr ""
#: converse.js:417
msgid "Reconnecting"
msgstr ""
#: converse.js:452
msgid "Disconnected"
msgstr ""
#: converse.js:460
msgid "Error"
msgstr ""
#: converse.js:462
msgid "Connecting"
msgstr ""
#: converse.js:465
msgid "Connection Failed"
msgstr ""
#: converse.js:467
msgid "Authenticating"
msgstr ""
#: converse.js:470
msgid "Authentication Failed"
msgstr ""
#: converse.js:475
msgid "Disconnecting"
msgstr ""
#: converse.js:614 converse.js:660
msgid "Online Contacts"
msgstr ""
#: converse.js:778
msgid "Re-establishing encrypted session"
msgstr ""
#: converse.js:790
msgid "Generating private key."
msgstr ""
#: converse.js:791
msgid "Your browser might become unresponsive."
msgstr ""
#: converse.js:826
msgid ""
"Authentication request from %1$s\n"
"\n"
"Your buddy is attempting to verify your identity, by asking you the question "
"below.\n"
"\n"
"%2$s"
msgstr ""
#: converse.js:835
msgid "Could not verify this user's identify."
msgstr ""
#: converse.js:874
msgid "Exchanging private key with buddy."
msgstr ""
#: converse.js:1023
msgid "Personal message"
msgstr ""
#: converse.js:1055
msgid "Are you sure you want to clear the messages from this room?"
msgstr ""
#: converse.js:1077
msgid "me"
msgstr ""
#: converse.js:1131
msgid "is typing"
msgstr ""
#: converse.js:1134
msgid "has stopped typing"
msgstr ""
#: converse.js:1176 converse.js:2314
msgid "Show this menu"
msgstr ""
#: converse.js:1177
msgid "Write in the third person"
msgstr ""
#: converse.js:1178 converse.js:2313
msgid "Remove messages"
msgstr ""
#: converse.js:1262
msgid "Are you sure you want to clear the messages from this chat box?"
msgstr ""
#: converse.js:1297
msgid "Your message could not be sent"
msgstr ""
#: converse.js:1300
msgid "We received an unencrypted message"
msgstr ""
#: converse.js:1303
msgid "We received an unreadable encrypted message"
msgstr ""
#: converse.js:1312
msgid "This user has requested an encrypted session."
msgstr ""
#: converse.js:1334
msgid ""
"Here are the fingerprints, please confirm them with %1$s, outside of this "
"chat.\n"
"\n"
"Fingerprint for you, %2$s: %3$s\n"
"\n"
"Fingerprint for %1$s: %4$s\n"
"\n"
"If you have confirmed that the fingerprints match, click OK, otherwise click "
"Cancel."
msgstr ""
#: converse.js:1347
msgid ""
"You will be prompted to provide a security question and then an answer to "
"that question.\n"
"\n"
"Your buddy will then be prompted the same question and if they type the "
"exact same answer (case sensitive), their identity will be verified."
msgstr ""
#: converse.js:1348
msgid "What is your security question?"
msgstr ""
#: converse.js:1350
msgid "What is the answer to the security question?"
msgstr ""
#: converse.js:1354
msgid "Invalid authentication scheme provided"
msgstr ""
#: converse.js:1465
msgid "Your messages are not encrypted anymore"
msgstr ""
#: converse.js:1467
msgid ""
"Your messages are now encrypted but your buddy's identity has not been "
"verified."
msgstr ""
#: converse.js:1469
msgid "Your buddy's identify has been verified."
msgstr ""
#: converse.js:1471
msgid "Your buddy has ended encryption on their end, you should do the same."
msgstr ""
#: converse.js:1480
msgid "Your messages are not encrypted. Click here to enable OTR encryption."
msgstr ""
#: converse.js:1482
msgid "Your messages are encrypted, but your buddy has not been verified."
msgstr ""
#: converse.js:1484
msgid "Your messages are encrypted and your buddy verified."
msgstr ""
#: converse.js:1486
msgid ""
"Your buddy has closed their end of the private session, you should do the "
"same"
msgstr ""
#: converse.js:1496
msgid "Clear all messages"
msgstr ""
#: converse.js:1497
msgid "End encrypted conversation"
msgstr ""
#: converse.js:1498
msgid "Hide the list of participants"
msgstr ""
#: converse.js:1499
msgid "Refresh encrypted conversation"
msgstr ""
#: converse.js:1500
msgid "Start a call"
msgstr ""
#: converse.js:1501
msgid "Start encrypted conversation"
msgstr ""
#: converse.js:1502
msgid "Verify with fingerprints"
msgstr ""
#: converse.js:1503
msgid "Verify with SMP"
msgstr ""
#: converse.js:1504
msgid "What's this?"
msgstr ""
#: converse.js:1595
msgid "Online"
msgstr ""
#: converse.js:1596
msgid "Busy"
msgstr ""
#: converse.js:1597
msgid "Away"
msgstr ""
#: converse.js:1598
msgid "Offline"
msgstr ""
#: converse.js:1599
msgid "Log out"
msgstr ""
#: converse.js:1605
msgid "Contact name"
msgstr ""
#: converse.js:1606
msgid "Search"
msgstr ""
#: converse.js:1610
msgid "Contact username"
msgstr ""
#: converse.js:1611
msgid "Add"
msgstr ""
#: converse.js:1616
msgid "Click to add new chat contacts"
msgstr ""
#: converse.js:1617
msgid "Add a contact"
msgstr ""
#: converse.js:1641
msgid "No users found"
msgstr ""
#: converse.js:1647
msgid "Click to add as a chat contact"
msgstr ""
#: converse.js:1702
msgid "Room name"
msgstr ""
#: converse.js:1703
msgid "Nickname"
msgstr ""
#: converse.js:1704
msgid "Server"
msgstr ""
#: converse.js:1705
msgid "Join"
msgstr ""
#: converse.js:1706
msgid "Show rooms"
msgstr ""
#: converse.js:1726
msgid "Rooms"
msgstr ""
#. For translators: %1$s is a variable and will be replaced with the XMPP server name
#: converse.js:1733
msgid "No rooms on %1$s"
msgstr ""
#. For translators: %1$s is a variable and will be
#. replaced with the XMPP server name
#: converse.js:1748
msgid "Rooms on %1$s"
msgstr ""
#: converse.js:1757
msgid "Click to open this room"
msgstr ""
#: converse.js:1758
msgid "Show more information on this room"
msgstr ""
#: converse.js:1820
msgid "Description:"
msgstr ""
#: converse.js:1821
msgid "Occupants:"
msgstr ""
#: converse.js:1822
msgid "Features:"
msgstr ""
#: converse.js:1823
msgid "Requires authentication"
msgstr ""
#: converse.js:1824
msgid "Hidden"
msgstr ""
#: converse.js:1825
msgid "Requires an invitation"
msgstr ""
#: converse.js:1826
msgid "Moderated"
msgstr ""
#: converse.js:1827
msgid "Non-anonymous"
msgstr ""
#: converse.js:1828
msgid "Open room"
msgstr ""
#: converse.js:1829
msgid "Permanent room"
msgstr ""
#: converse.js:1830
msgid "Public"
msgstr ""
#: converse.js:1831
msgid "Semi-anonymous"
msgstr ""
#: converse.js:1832
msgid "Temporary room"
msgstr ""
#: converse.js:1833
msgid "Unmoderated"
msgstr ""
#: converse.js:2062
msgid "This user is a moderator"
msgstr ""
#: converse.js:2063
msgid "This user can send messages in this room"
msgstr ""
#: converse.js:2064
msgid "This user can NOT send messages in this room"
msgstr ""
#: converse.js:2096
msgid "Invite..."
msgstr ""
#: converse.js:2097
msgid "Occupants"
msgstr ""
#: converse.js:2162
msgid "You are about to invite %1$s to the chat room \"%2$s\". "
msgstr ""
#: converse.js:2163
msgid ""
"You may optionally include a message, explaining the reason for the "
"invitation."
msgstr ""
#: converse.js:2246
msgid "Message"
msgstr ""
#: converse.js:2282
msgid "Error: could not execute the command"
msgstr ""
#: converse.js:2312
msgid "Ban user from room"
msgstr ""
#: converse.js:2315
msgid "Kick user from room"
msgstr ""
#: converse.js:2316
msgid "Write in 3rd person"
msgstr ""
#: converse.js:2317
msgid "Remove user's ability to post messages"
msgstr ""
#: converse.js:2318
msgid "Change your nickname"
msgstr ""
#: converse.js:2319
msgid "Set room topic"
msgstr ""
#: converse.js:2320
msgid "Allow muted user to post messages"
msgstr ""
#: converse.js:2423 converse.js:4250
msgid "Save"
msgstr ""
#: converse.js:2424
msgid "Cancel"
msgstr ""
#: converse.js:2469
msgid "An error occurred while trying to save the form."
msgstr ""
#: converse.js:2513
msgid "This chatroom requires a password"
msgstr ""
#: converse.js:2514
msgid "Password: "
msgstr ""
#: converse.js:2515
msgid "Submit"
msgstr ""
#: converse.js:2550
msgid "This room is not anonymous"
msgstr ""
#: converse.js:2551
msgid "This room now shows unavailable members"
msgstr ""
#: converse.js:2552
msgid "This room does not show unavailable members"
msgstr ""
#: converse.js:2553
msgid "Non-privacy-related room configuration has changed"
msgstr ""
#: converse.js:2554
msgid "Room logging is now enabled"
msgstr ""
#: converse.js:2555
msgid "Room logging is now disabled"
msgstr ""
#: converse.js:2556
msgid "This room is now non-anonymous"
msgstr ""
#: converse.js:2557
msgid "This room is now semi-anonymous"
msgstr ""
#: converse.js:2558
msgid "This room is now fully-anonymous"
msgstr ""
#: converse.js:2559
msgid "A new room has been created"
msgstr ""
#: converse.js:2563 converse.js:2663
msgid "You have been banned from this room"
msgstr ""
#: converse.js:2564
msgid "You have been kicked from this room"
msgstr ""
#: converse.js:2565
msgid "You have been removed from this room because of an affiliation change"
msgstr ""
#: converse.js:2566
msgid ""
"You have been removed from this room because the room has changed to members-"
"only and you're not a member"
msgstr ""
#: converse.js:2567
msgid ""
"You have been removed from this room because the MUC (Multi-user chat) "
"service is being shut down."
msgstr ""
#: converse.js:2581
msgid "<strong>%1$s</strong> has been banned"
msgstr ""
#: converse.js:2582
msgid "<strong>%1$s</strong>'s nickname has changed"
msgstr ""
#: converse.js:2583
msgid "<strong>%1$s</strong> has been kicked out"
msgstr ""
#: converse.js:2584
msgid "<strong>%1$s</strong> has been removed because of an affiliation change"
msgstr ""
#: converse.js:2585
msgid "<strong>%1$s</strong> has been removed for not being a member"
msgstr ""
#: converse.js:2589
msgid "Your nickname has been automatically changed to: <strong>%1$s</strong>"
msgstr ""
#: converse.js:2590
msgid "Your nickname has been changed to: <strong>%1$s</strong>"
msgstr ""
#: converse.js:2638 converse.js:2648
msgid "The reason given is: \""
msgstr ""
#: converse.js:2661
msgid "You are not on the member list of this room"
msgstr ""
#: converse.js:2667
msgid "No nickname was specified"
msgstr ""
#: converse.js:2671
msgid "You are not allowed to create new rooms"
msgstr ""
#: converse.js:2673
msgid "Your nickname doesn't conform to this room's policies"
msgstr ""
#: converse.js:2677
msgid "Your nickname is already taken"
msgstr ""
#: converse.js:2679
msgid "This room does not (yet) exist"
msgstr ""
#: converse.js:2681
msgid "This room has reached it's maximum number of occupants"
msgstr ""
#: converse.js:2723
msgid "Topic set by %1$s to: %2$s"
msgstr ""
#: converse.js:2805
msgid "%1$s has invited you to join a chat room: %2$s"
msgstr ""
#: converse.js:2809
msgid ""
"%1$s has invited you to join a chat room: %2$s, and left the following "
"reason: \"%3$s\""
msgstr ""
#: converse.js:3044
msgid "Click to restore this chat"
msgstr ""
#: converse.js:3188
msgid "Minimized"
msgstr ""
#: converse.js:3262
msgid "Are you sure you want to remove this contact?"
msgstr ""
#: converse.js:3285
msgid "Are you sure you want to decline this contact request?"
msgstr ""
#: converse.js:3329 converse.js:3347
msgid "Click to remove this contact"
msgstr ""
#: converse.js:3336
msgid "Click to accept this contact request"
msgstr ""
#: converse.js:3337
msgid "Click to decline this contact request"
msgstr ""
#: converse.js:3346
msgid "Click to chat with this contact"
msgstr ""
#: converse.js:3862
msgid "Type to filter"
msgstr ""
#. For translators: the %1$s part gets replaced with the status
#. Example, I am online
#: converse.js:4221 converse.js:4298
msgid "I am %1$s"
msgstr ""
#: converse.js:4223 converse.js:4303
msgid "Click here to write a custom status message"
msgstr ""
#: converse.js:4224 converse.js:4304
msgid "Click to change your chat status"
msgstr ""
#: converse.js:4249
msgid "Custom status"
msgstr ""
#: converse.js:4278 converse.js:4286
msgid "online"
msgstr ""
#: converse.js:4280
msgid "busy"
msgstr ""
#: converse.js:4282
msgid "away for long"
msgstr ""
#: converse.js:4284
msgid "away"
msgstr ""
#: converse.js:4407
msgid "XMPP/Jabber Username:"
msgstr ""
#: converse.js:4408
msgid "Password:"
msgstr ""
#: converse.js:4409
msgid "Log In"
msgstr ""
#: converse.js:4416
msgid "Sign in"
msgstr ""
#: converse.js:4476
msgid "Toggle chat"
msgstr ""
config = {
require.config({
baseUrl: '.',
paths: {
"backbone": "components/backbone/backbone",
......@@ -17,7 +17,7 @@ config = {
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophejs-plugins/disco/strophe.disco",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.roster": "src/strophe.roster",
"strophe.vcard": "components/strophejs-plugins/vcard/strophe.vcard",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
......@@ -139,16 +139,13 @@ config = {
'crypto.sha1': { deps: ['crypto.core'] },
'crypto.sha256': { deps: ['crypto.core'] },
'bigint': { deps: ['crypto'] },
'strophe': { exports: 'Strophe' },
'strophe.disco': { deps: ['strophe'] },
'strophe.muc': { deps: ['strophe'] },
'strophe.roster': { deps: ['strophe'] },
'strophe.vcard': { deps: ['strophe'] }
}
};
if (typeof(require) !== 'undefined') {
require.config(config);
require(["jquery", "converse"], function($, converse) {
window.converse = converse;
});
}
});
require(["converse"], function(converse) {
window.converse = converse;
});
......@@ -57,23 +57,26 @@
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
// Test that they can be trimmed
var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
expect(this.chatboxviews.trimChats).toHaveBeenCalled();
chatboxview = this.chatboxviews.get(jid);
spyOn(chatboxview, 'hide').andCallThrough();
chatboxview.model.set({'minimized': true});
expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
expect(chatboxview.hide).toHaveBeenCalled();
trimmedview = trimmed_chatboxes.get(jid);
}
// Test that they can be maximized again
runs($.proxy(function () {
converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
}, this));
waits(50);
runs($.proxy(function () {
// Test that they can be maximized again
var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
expect(this.chatboxviews.trimChats).toHaveBeenCalled();
chatboxview = this.chatboxviews.get(jid);
spyOn(chatboxview, 'hide').andCallThrough();
chatboxview.model.set({'minimized': true});
expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
expect(chatboxview.hide).toHaveBeenCalled();
trimmedview = trimmed_chatboxes.get(jid);
}
var key = this.chatboxviews.keys()[1];
trimmedview = trimmed_chatboxes.get(key);
chatbox = trimmedview.model;
......@@ -99,11 +102,19 @@
chatbox = test_utils.openChatBoxFor(contact_jid);
chatboxview = this.chatboxviews.get(contact_jid);
spyOn(chatboxview, 'focus');
$el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
expect(this.chatboxes.length).toEqual(2);
expect(chatboxview.focus).toHaveBeenCalled();
// Test that they can be trimmed
runs($.proxy(function () {
converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
}, this));
waits(50);
runs($.proxy(function () {
$el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
expect(this.chatboxes.length).toEqual(2);
expect(chatboxview.focus).toHaveBeenCalled();
}, this));
}, converse));
it("can be saved to, and retrieved from, browserStorage", $.proxy(function () {
......
......@@ -145,7 +145,7 @@
subscription: 'both'
});
converse.rosterview.update(); // XXX: Will normally called as event handler
if (converse.rosterview.$('.roster-contacts').hasScrollBar()) {
if (converse.rosterview.$roster.hasScrollBar()) {
expect($filter.is(':visible')).toBeTruthy();
} else {
expect($filter.is(':visible')).toBeFalsy();
......@@ -154,11 +154,16 @@
}, converse));
it("can be used to filter the contacts shown", function () {
converse.roster_groups = true;
_clearContacts();
utils.createGroupedContacts();
var $filter = converse.rosterview.$('.roster-filter');
var $roster = converse.rosterview.$('.roster-contacts');
var $filter;
var $roster;
runs(function () {
_clearContacts();
converse.roster_groups = true;
utils.createGroupedContacts();
$filter = converse.rosterview.$('.roster-filter');
$roster = converse.rosterview.$roster;
});
waits(350); // Needed, due to debounce
runs(function () {
expect($roster.find('dd:visible').length).toBe(15);
expect($roster.find('dt:visible').length).toBe(5);
......@@ -199,13 +204,19 @@
});
it("can be used to filter the groups shown", function () {
converse.roster_groups = true;
_clearContacts();
utils.createGroupedContacts();
var $filter = converse.rosterview.$('.roster-filter');
var $roster = converse.rosterview.$('.roster-contacts');
var $type = converse.rosterview.$('.filter-type');
$type.val('groups');
var $filter;
var $roster;
var $type;
runs(function () {
converse.roster_groups = true;
_clearContacts();
utils.createGroupedContacts();
$filter = converse.rosterview.$('.roster-filter');
$roster = converse.rosterview.$roster;
$type = converse.rosterview.$('.filter-type');
$type.val('groups');
});
waits(350); // Needed, due to debounce
runs(function () {
expect($roster.find('dd:visible').length).toBe(15);
expect($roster.find('dt:visible').length).toBe(5);
......@@ -242,7 +253,7 @@
_clearContacts();
utils.createGroupedContacts();
var $filter = converse.rosterview.$('.roster-filter');
var $roster = converse.rosterview.$('.roster-contacts');
var $roster = converse.rosterview.$roster;
runs (function () {
$filter.val("xxx");
$filter.trigger('keydown');
......@@ -267,56 +278,66 @@
});
it("can be used to organize existing contacts", $.proxy(function () {
_clearContacts();
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
utils.createContacts('pending');
utils.createContacts('requesting');
utils.createGroupedContacts();
// Check that the groups appear alphabetically and that
// requesting and pending contacts are last.
var group_titles = $.map(this.rosterview.$el.find('dt'), function (o) { return $(o).text().trim(); });
expect(group_titles).toEqual([
"colleagues",
"Family",
"friends & acquaintences",
"ænemies",
"Ungrouped",
"Contact requests",
"Pending contacts"
]);
// Check that usernames appear alphabetically per group
_.each(_.keys(mock.groups), $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
}, converse));
runs($.proxy(function () {
_clearContacts();
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
utils.createContacts('pending');
utils.createContacts('requesting');
utils.createGroupedContacts();
}, this));
waits(50); // Needed, due to debounce
runs($.proxy(function () {
// Check that the groups appear alphabetically and that
// requesting and pending contacts are last.
var group_titles = $.map(this.rosterview.$el.find('dt'), function (o) { return $(o).text().trim(); });
expect(group_titles).toEqual([
"colleagues",
"Family",
"friends & acquaintences",
"ænemies",
"Ungrouped",
"Contact requests",
"Pending contacts"
]);
// Check that usernames appear alphabetically per group
_.each(_.keys(mock.groups), $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
}, converse));
}, this));
}, converse));
it("can share contacts with other roster groups", $.proxy(function () {
_clearContacts();
var i=0, j=0;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
var groups = ['colleagues', 'friends'];
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
groups: groups,
fullname: mock.cur_names[i]
});
}
// Check that usernames appear alphabetically per group
_.each(groups, $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
expect(names.length).toEqual(mock.cur_names.length);
}, converse));
runs($.proxy(function () {
_clearContacts();
var i=0, j=0;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
groups: groups,
fullname: mock.cur_names[i]
});
}
}, this));
waits(50); // Needed, due to debounce
runs($.proxy(function () {
// Check that usernames appear alphabetically per group
_.each(groups, $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
expect(names.length).toEqual(mock.cur_names.length);
}, this));
}, this));
}, converse));
it("remembers whether it is closed or opened", $.proxy(function () {
......@@ -361,8 +382,13 @@
}
it("can be collapsed under their own header", $.proxy(function () {
_addContacts();
checkHeaderToggling.apply(this, [this.rosterview.get('Pending contacts').$el]);
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
checkHeaderToggling.apply(this, [this.rosterview.get('Pending contacts').$el]);
}, this));
}, converse));
it("can be added to the roster", $.proxy(function () {
......@@ -385,40 +411,62 @@
}, converse));
it("can be removed by the user", $.proxy(function () {
_addContacts();
var name = mock.pend_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
runs($.proxy(function () {
_addContacts();
}, this));
waits(50);
runs($.proxy(function () {
/* FIXME: Monkepatch
* After refactoring the mock connection to use a
* Strophe.Connection object, these tests fail because "remove"
* function in strophe.roster (line 292) gets called and it
* then tries to actually remove the user which is not in the roster...
*/
var old_remove = this.connection.roster.remove;
this.connection.roster.remove = function (jid, callback) { callback(); };
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
var name = mock.pend_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
expect(converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length).toEqual(0);
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
expect(converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length).toEqual(0);
/* XXX Restore Monkeypatch */
this.connection.roster.remove = old_remove;
}, this));
}, converse));
it("do not have a header if there aren't any", $.proxy(function () {
var name = mock.pend_names[0];
_clearContacts();
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: name
});
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(false);
runs($.proxy(function () {
_clearContacts();
}, this));
waits(50);
runs($.proxy(function () {
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: name
});
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(false);
}, this));
}, converse));
......@@ -467,8 +515,13 @@
};
it("can be collapsed under their own header", $.proxy(function () {
_addContacts();
checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt.roster-group')]);
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt.roster-group')]);
}, this));
}, converse));
it("will be hidden when appearing under a collapsed group", $.proxy(function () {
......@@ -488,180 +541,237 @@
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
_clearContacts();
var i, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: mock.cur_names[i]
});
expect(this.rosterview.update).toHaveBeenCalled();
}
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
runs(function () {
_clearContacts();
});
waits(50);
runs($.proxy(function () {
var i, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: mock.cur_names[i]
});
expect(this.rosterview.update).toHaveBeenCalled();
}
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}, this));
}, converse));
it("can be removed by the user", $.proxy(function () {
_addContacts();
var name = mock.cur_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
/* FIXME: Monkepatch
* After refactoring the mock connection to use a
* Strophe.Connection object, these tests fail because "remove"
* function in strophe.roster (line 292) gets called and it
* then tries to actually remove the user which is not in the roster...
*/
var old_remove = this.connection.roster.remove;
this.connection.roster.remove = function (jid, callback) { callback(); };
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
var name = mock.cur_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
expect(converse.rosterview.$el.find(".open-chat:contains('"+name+"')").length).toEqual(0);
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
expect(converse.rosterview.$el.find(".open-chat:contains('"+name+"')").length).toEqual(0);
/* XXX Restore Monkeypatch */
this.connection.roster.remove = old_remove;
}, this));
}, converse));
it("do not have a header if there aren't any", $.proxy(function () {
var name = mock.cur_names[0];
_clearContacts();
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: name
runs(function () {
_clearContacts();
});
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('block');
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
waits(50);
runs($.proxy(function () {
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: name
});
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('block');
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
}, this));
}, converse));
it("can change their status to online and be sorted alphabetically", $.proxy(function () {
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'online');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'online');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, this));
}, converse));
it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'dnd');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'dnd');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, this));
}, converse));
it("can change their status to away and be sorted alphabetically", $.proxy(function () {
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'away');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'away');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, this));
}, converse));
it("can change their status to xa and be sorted alphabetically", $.proxy(function () {
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'xa');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'xa');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, this));
}, converse));
it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'unavailable');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0, i+1).sort().join(''));
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'unavailable');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0, i+1).sort().join(''));
}
}, this));
}, converse));
it("are ordered according to status: online, busy, away, xa, unavailable, offline", $.proxy(function () {
_addContacts();
var i;
for (i=0; i<3; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'online');
}
for (i=3; i<6; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'dnd');
}
for (i=6; i<9; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'away');
}
for (i=9; i<12; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'xa');
}
for (i=12; i<15; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'unavailable');
}
runs(function () {
_addContacts();
});
waits(50);
runs($.proxy(function () {
var i;
for (i=0; i<3; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'online');
}
for (i=3; i<6; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'dnd');
}
for (i=6; i<9; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'away');
}
for (i=9; i<12; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'xa');
}
for (i=12; i<15; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
this.roster.get(jid).set('chat_status', 'unavailable');
}
var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
for (i=0; i<3; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('online');
}
for (i=3; i<6; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('dnd');
}
for (i=6; i<9; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('away');
}
for (i=9; i<12; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('xa');
}
for (i=12; i<15; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('unavailable');
}
for (i=15; i<mock.cur_names.length; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
}
var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
for (i=0; i<3; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('online');
}
for (i=3; i<6; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('dnd');
}
for (i=6; i<9; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('away');
}
for (i=9; i<12; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('xa');
}
for (i=12; i<15; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('unavailable');
}
for (i=15; i<mock.cur_names.length; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
}
}, this));
}, converse));
}, converse));
......@@ -713,20 +823,25 @@
it("do not have a header if there aren't any", $.proxy(function () {
converse.rosterview.model.reset(); // We want to manually create users so that we can spy
var name = mock.req_names[0];
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: null,
requesting: true,
fullname: name
});
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(false);
runs($.proxy(function () {
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: null,
requesting: true,
fullname: name
});
}, this));
waits(50);
runs($.proxy(function () {
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(false);
}, this));
}, converse));
it("can be collapsed under their own header", $.proxy(function () {
......@@ -750,20 +865,70 @@
it("can have their requests denied by the user", $.proxy(function () {
this.rosterview.model.reset();
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'unauthorize');
spyOn(window, 'confirm').andReturn(true);
utils.createContacts('requesting').openControlBox();
var name = mock.req_names.sort()[1];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
// There should now be one less contact
expect(this.roster.length).toEqual(mock.req_names.length-1);
runs($.proxy(function () {
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'unauthorize');
spyOn(window, 'confirm').andReturn(true);
utils.createContacts('requesting').openControlBox();
converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
}, this));
waits(50);
runs($.proxy(function () {
var name = mock.req_names.sort()[1];
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
}, this));
waits(50);
runs($.proxy(function () {
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
// There should now be one less contact
expect(this.roster.length).toEqual(mock.req_names.length-1);
}, this));
}, converse));
it("are persisted even if other contacts' change their presence ", $.proxy(function() {
/* This is a regression test.
* https://github.com/jcbrand/converse.js/issues/262
*/
this.rosterview.model.reset();
spyOn(this.roster, 'clearCache').andCallThrough();
expect(this.roster.pluck('jid').length).toBe(0);
var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
this.connection._dataRecv(test_utils.createRequest(stanza));
expect(this.roster.pluck('jid').length).toBe(1);
expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
// Taken from the spec
// http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
stanza = $iq({
to: this.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster',
}).c('item', {
jid: 'romeo@example.net',
name: 'Romeo',
subscription:'both'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'mercutio@example.org',
name: 'Mercutio',
subscription:'from'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'benvolio@example.org',
name: 'Benvolio',
subscription:'both'
}).c('group').t('Friends');
this.connection.roster._onReceiveRosterSuccess(null, stanza.tree());
expect(this.roster.clearCache).toHaveBeenCalled();
expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
}, converse));
}, converse));
describe("All Contacts", $.proxy(function () {
......@@ -775,7 +940,7 @@
}, converse));
it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () {
var new_attrs, old_attrs, attrs, old_roster;
var new_attrs, old_attrs, attrs;
var num_contacts = this.roster.length;
new_roster = new this.RosterContacts();
// Roster items are yet to be fetched from browserStorage
......
......@@ -84,6 +84,42 @@
expect(this.minimized_chats.toggleview.$('.unread-message-count').is(':visible')).toBeTruthy();
expect(this.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i+1).toString());
}
// Chat state notifications don't increment the unread messages counter
// <composing> state
this.chatboxes.onMessage($msg({
from: contact_jid,
to: this.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(this.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
// <paused> state
this.chatboxes.onMessage($msg({
from: contact_jid,
to: this.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(this.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
// <gone> state
this.chatboxes.onMessage($msg({
from: contact_jid,
to: this.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(this.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
// <inactive> state
this.chatboxes.onMessage($msg({
from: contact_jid,
to: this.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(this.minimized_chats.toggleview.$('.unread-message-count').text()).toBe((i).toString());
}, converse));
}, converse, mock, test_utils));
......
(function (root, factory) {
define([
"jquery",
"mock",
"test_utils"
], function ($, mock, test_utils) {
return factory($, mock, test_utils);
}
);
} (this, function ($, mock, test_utils) {
describe("Profiling", function() {
beforeEach(function() {
converse.connection.roster.items = [];
converse.connection._changeConnectStatus(Strophe.Status.CONNECTED);
});
xit("adds hundreds of contacts to the roster", $.proxy(function() {
converse.roster_groups = false;
spyOn(this.roster, 'clearCache').andCallThrough();
expect(this.roster.pluck('jid').length).toBe(0);
var stanza = $iq({
to: this.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster'
});
_.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) {
var i;
for (i=0; i<100; i++) {
stanza = stanza.c('item', {
jid: Math.random().toString().replace('0.', '')+'@example.net',
subscription:'both'
}).c('group').t(group).up().up();
}
});
this.connection.roster._onReceiveRosterSuccess(null, stanza.tree());
expect(this.roster.clearCache).toHaveBeenCalled();
expect(this.roster.pluck('jid').length).toBe(400);
}, converse));
xit("adds hundreds of contacts to the roster, with roster groups", $.proxy(function() {
// converse.show_only_online_users = true;
converse.roster_groups = true;
spyOn(this.roster, 'clearCache').andCallThrough();
expect(this.roster.pluck('jid').length).toBe(0);
var stanza = $iq({
to: this.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster'
});
_.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) {
var i;
for (i=0; i<100; i++) {
stanza = stanza.c('item', {
jid: Math.random().toString().replace('0.', '')+'@example.net',
subscription:'both'
}).c('group').t(group).up().up();
}
});
this.connection.roster._onReceiveRosterSuccess(null, stanza.tree());
expect(this.roster.clearCache).toHaveBeenCalled();
//expect(this.roster.pluck('jid').length).toBe(400);
}, converse));
it("contacts in a very large roster change their statuses", $.proxy(function() {
}, converse));
});
}));
({
baseUrl: "../",
name: "components/almond/almond.js",
out: "../builds/converse.nojquery.min.js",
include: ['main'],
mainConfigFile: '../main.js',
paths: {
"converse-dependencies": "src/deps-full",
"jquery": "src/jquery-external",
"jquery-private": "src/jquery-private-external",
}
})
......@@ -3,93 +3,9 @@
name: "components/almond/almond.js",
out: "../builds/converse-no-locales-no-otr.min.js",
include: ['main'],
tpl: {
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
mainConfigFile: '../main.js',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"bootstrap": "components/bootstrap/dist/js/bootstrap", // XXX: Only required for https://conversejs.org website
"converse-dependencies": "src/deps-no-otr",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophe.disco/index",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.vcard": "components/strophe.vcard/index",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Locales paths
"locales": "locale/nolocales",
"jed": "components/jed/jed",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
"locales": "locale/nolocales"
}
})
......@@ -3,105 +3,8 @@
name: "components/almond/almond.js",
out: "../builds/converse-no-otr.min.js",
include: ['main'],
tpl: {
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
mainConfigFile: '../main.js',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"converse-dependencies": "src/deps-no-otr",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophe.disco/index",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.vcard": "components/strophe.vcard/index",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Locales paths
"locales": "locale/locales",
"jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"de": "locale/de/LC_MESSAGES/de",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"fr": "locale/fr/LC_MESSAGES/fr",
"he": "locale/he/LC_MESSAGES/he",
"hu": "locale/hu/LC_MESSAGES/hu",
"id": "locale/id/LC_MESSAGES/id",
"it": "locale/it/LC_MESSAGES/it",
"ja": "locale/ja/LC_MESSAGES/ja",
"nl": "locale/nl/LC_MESSAGES/nl",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
"ru": "locale/ru/LC_MESSAGES/ru",
"zh": "locale/zh/LC_MESSAGES/zh",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
"converse-dependencies": "src/deps-no-otr"
}
})
......@@ -3,107 +3,8 @@
name: "components/almond/almond.js",
out: "../builds/converse.website-no-otr.min.js",
include: ['main'],
tpl: {
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
mainConfigFile: '../main.js',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"bootstrapJS": "components/bootstrapJS/index", // XXX: Only required for https://conversejs.org website
"converse-dependencies": "src/deps-website-no-otr",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophe.disco/index",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.vcard": "components/strophe.vcard/index",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Locales paths
"locales": "locale/locales",
"jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"de": "locale/de/LC_MESSAGES/de",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"fr": "locale/fr/LC_MESSAGES/fr",
"he": "locale/he/LC_MESSAGES/he",
"hu": "locale/hu/LC_MESSAGES/hu",
"id": "locale/id/LC_MESSAGES/id",
"it": "locale/it/LC_MESSAGES/it",
"ja": "locale/ja/LC_MESSAGES/ja",
"nl": "locale/nl/LC_MESSAGES/nl",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
"ru": "locale/ru/LC_MESSAGES/ru",
"zh": "locale/zh/LC_MESSAGES/zh",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
"converse-dependencies": "src/deps-website-no-otr"
}
})
......@@ -3,124 +3,8 @@
name: "components/almond/almond.js",
out: "../builds/converse.website.min.js",
include: ['main'],
tpl: {
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
mainConfigFile: '../main.js',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"bootstrapJS": "components/bootstrapJS/index", // XXX: Only required for https://conversejs.org website
"converse-dependencies": "src/deps-website",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophe.disco/index",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.vcard": "components/strophe.vcard/index",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Off-the-record-encryption
"bigint": "src/bigint",
"crypto": "src/crypto",
"crypto.aes": "components/otr/vendor/cryptojs/aes",
"crypto.cipher-core": "components/otr/vendor/cryptojs/cipher-core",
"crypto.core": "components/otr/vendor/cryptojs/core",
"crypto.enc-base64": "components/otr/vendor/cryptojs/enc-base64",
"crypto.evpkdf": "components/crypto-js-evanvosberg/src/evpkdf",
"crypto.hmac": "components/otr/vendor/cryptojs/hmac",
"crypto.md5": "components/crypto-js-evanvosberg/src/md5",
"crypto.mode-ctr": "components/otr/vendor/cryptojs/mode-ctr",
"crypto.pad-nopadding": "components/otr/vendor/cryptojs/pad-nopadding",
"crypto.sha1": "components/otr/vendor/cryptojs/sha1",
"crypto.sha256": "components/otr/vendor/cryptojs/sha256",
"salsa20": "components/otr/build/dep/salsa20",
"otr": "src/otr",
// Locales paths
"locales": "locale/locales",
"jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"de": "locale/de/LC_MESSAGES/de",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"fr": "locale/fr/LC_MESSAGES/fr",
"he": "locale/he/LC_MESSAGES/he",
"hu": "locale/hu/LC_MESSAGES/hu",
"id": "locale/id/LC_MESSAGES/id",
"it": "locale/it/LC_MESSAGES/it",
"ja": "locale/ja/LC_MESSAGES/ja",
"nl": "locale/nl/LC_MESSAGES/nl",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
"ru": "locale/ru/LC_MESSAGES/ru",
"zh": "locale/zh/LC_MESSAGES/zh",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
"converse-dependencies": "src/deps-website"
}
})
......@@ -3,122 +3,8 @@
name: "components/almond/almond.js",
out: "../builds/converse.min.js",
include: ['main'],
tpl: {
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
mainConfigFile: '../main.js',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"converse-dependencies": "src/deps-full",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophe.disco/index",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "components/strophe.roster/index",
"strophe.vcard": "components/strophe.vcard/index",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Off-the-record-encryption
"bigint": "src/bigint",
"crypto": "src/crypto",
"crypto.aes": "components/otr/vendor/cryptojs/aes",
"crypto.cipher-core": "components/otr/vendor/cryptojs/cipher-core",
"crypto.core": "components/otr/vendor/cryptojs/core",
"crypto.enc-base64": "components/otr/vendor/cryptojs/enc-base64",
"crypto.evpkdf": "components/crypto-js-evanvosberg/src/evpkdf",
"crypto.hmac": "components/otr/vendor/cryptojs/hmac",
"crypto.md5": "components/crypto-js-evanvosberg/src/md5",
"crypto.mode-ctr": "components/otr/vendor/cryptojs/mode-ctr",
"crypto.pad-nopadding": "components/otr/vendor/cryptojs/pad-nopadding",
"crypto.sha1": "components/otr/vendor/cryptojs/sha1",
"crypto.sha256": "components/otr/vendor/cryptojs/sha256",
"salsa20": "components/otr/build/dep/salsa20",
"otr": "src/otr",
// Locales paths
"locales": "locale/locales",
"jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"de": "locale/de/LC_MESSAGES/de",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"fr": "locale/fr/LC_MESSAGES/fr",
"he": "locale/he/LC_MESSAGES/he",
"hu": "locale/hu/LC_MESSAGES/hu",
"id": "locale/id/LC_MESSAGES/id",
"it": "locale/it/LC_MESSAGES/it",
"ja": "locale/ja/LC_MESSAGES/ja",
"nl": "locale/nl/LC_MESSAGES/nl",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
"ru": "locale/ru/LC_MESSAGES/ru",
"zh": "locale/zh/LC_MESSAGES/zh",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
"converse-dependencies": "src/deps-full"
}
})
define('jquery', [], function () {
return jQuery;
});
define(['jquery'], function (jq) {
return jq;
});
/*
Copyright 2010, François de Metz <francois@2metz.fr>
*/
/**
* Roster Plugin
* Allow easily roster management
*
* Features
* * Get roster from server
* * handle presence
* * handle roster iq
* * subscribe/unsubscribe
* * authorize/unauthorize
* * roster versioning (xep 237)
*/
Strophe.addConnectionPlugin('roster',
{
/** Function: init
* Plugin init
*
* Parameters:
* (Strophe.Connection) conn - Strophe connection
*/
init: function(conn)
{
this._connection = conn;
this._callbacks = [];
/** Property: items
* Roster items
* [
* {
* name : "",
* jid : "",
* subscription : "",
* ask : "",
* groups : ["", ""],
* resources : {
* myresource : {
* show : "",
* status : "",
* priority : ""
* }
* }
* }
* ]
*/
this.items = [];
/** Property: ver
* current roster revision
* always null if server doesn't support xep 237
*/
this.ver = null;
// Override the connect and attach methods to always add presence and roster handlers.
// They are removed when the connection disconnects, so must be added on connection.
var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach;
var newCallback = function(status)
{
if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED)
{
try
{
// Presence subscription
conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null);
conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null);
}
catch (e)
{
Strophe.error(e);
}
}
if (typeof oldCallback === "function") {
oldCallback.apply(this, arguments);
}
};
conn.connect = function(jid, pass, callback, wait, hold, route)
{
oldCallback = callback;
if (typeof jid == "undefined")
jid = null;
if (typeof pass == "undefined")
pass = null;
callback = newCallback;
_connect.apply(conn, [jid, pass, callback, wait, hold, route]);
};
conn.attach = function(jid, sid, rid, callback, wait, hold, wind)
{
oldCallback = callback;
if (typeof jid == "undefined")
jid = null;
if (typeof sid == "undefined")
sid = null;
if (typeof rid == "undefined")
rid = null;
callback = newCallback;
_attach.apply(conn, [jid, sid, rid, callback, wait, hold, wind]);
};
Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
},
/** Function: supportVersioning
* return true if roster versioning is enabled on server
*/
supportVersioning: function()
{
return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0);
},
/** Function: get
* Get Roster on server
*
* Parameters:
* (Function) userCallback - callback on roster result
* (String) ver - current rev of roster
* (only used if roster versioning is enabled)
* (Array) items - initial items of ver
* (only used if roster versioning is enabled)
* In browser context you can use sessionStorage
* to store your roster in json (JSON.stringify())
*/
get: function(userCallback, ver, items)
{
var attrs = {xmlns: Strophe.NS.ROSTER};
if (this.supportVersioning())
{
// empty rev because i want an rev attribute in the result
attrs.ver = ver || '';
this.items = items || [];
}
var iq = $iq({type: 'get', 'id' : this._connection.getUniqueId('roster')}).c('query', attrs);
return this._connection.sendIQ(iq,
this._onReceiveRosterSuccess.bind(this, userCallback),
this._onReceiveRosterError.bind(this, userCallback));
},
/** Function: registerCallback
* register callback on roster (presence and iq)
*
* Parameters:
* (Function) call_back
*/
registerCallback: function(call_back)
{
this._callbacks.push(call_back);
},
/** Function: findItem
* Find item by JID
*
* Parameters:
* (String) jid
*/
findItem : function(jid)
{
try {
for (var i = 0; i < this.items.length; i++)
{
if (this.items[i] && this.items[i].jid == jid)
{
return this.items[i];
}
}
} catch (e)
{
Strophe.error(e);
}
return false;
},
/** Function: removeItem
* Remove item by JID
*
* Parameters:
* (String) jid
*/
removeItem : function(jid)
{
for (var i = 0; i < this.items.length; i++)
{
if (this.items[i] && this.items[i].jid == jid)
{
this.items.splice(i, 1);
return true;
}
}
return false;
},
/** Function: subscribe
* Subscribe presence
*
* Parameters:
* (String) jid
* (String) message (optional)
* (String) nick (optional)
*/
subscribe: function(jid, message, nick) {
var pres = $pres({to: jid, type: "subscribe"});
if (message && message !== "") {
pres.c("status").t(message).up();
}
if (nick && nick !== "") {
pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
}
this._connection.send(pres);
},
/** Function: unsubscribe
* Unsubscribe presence
*
* Parameters:
* (String) jid
* (String) message
*/
unsubscribe: function(jid, message)
{
var pres = $pres({to: jid, type: "unsubscribe"});
if (message && message !== "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: authorize
* Authorize presence subscription
*
* Parameters:
* (String) jid
* (String) message
*/
authorize: function(jid, message)
{
var pres = $pres({to: jid, type: "subscribed"});
if (message && message !== "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: unauthorize
* Unauthorize presence subscription
*
* Parameters:
* (String) jid
* (String) message
*/
unauthorize: function(jid, message)
{
var pres = $pres({to: jid, type: "unsubscribed"});
if (message && message !== "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: add
* Add roster item
*
* Parameters:
* (String) jid - item jid
* (String) name - name
* (Array) groups
* (Function) call_back
*/
add: function(jid, name, groups, call_back)
{
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid,
name: name});
for (var i = 0; i < groups.length; i++)
{
iq.c('group').t(groups[i]).up();
}
this._connection.sendIQ(iq, call_back, call_back);
},
/** Function: update
* Update roster item
*
* Parameters:
* (String) jid - item jid
* (String) name - name
* (Array) groups
* (Function) call_back
*/
update: function(jid, name, groups, call_back)
{
var item = this.findItem(jid);
if (!item)
{
throw "item not found";
}
var newName = name || item.name;
var newGroups = groups || item.groups;
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
name: newName});
for (var i = 0; i < newGroups.length; i++)
{
iq.c('group').t(newGroups[i]).up();
}
return this._connection.sendIQ(iq, call_back, call_back);
},
/** Function: remove
* Remove roster item
*
* Parameters:
* (String) jid - item jid
* (Function) call_back
*/
remove: function(jid, call_back)
{
var item = this.findItem(jid);
if (!item)
{
throw "item not found";
}
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
subscription: "remove"});
this._connection.sendIQ(iq, call_back, call_back);
},
/** PrivateFunction: _onReceiveRosterSuccess
*
*/
_onReceiveRosterSuccess: function(userCallback, stanza)
{
this._updateItems(stanza);
if (typeof userCallback === "function") {
userCallback(this.items);
}
},
/** PrivateFunction: _onReceiveRosterError
*
*/
_onReceiveRosterError: function(userCallback, stanza)
{
userCallback(this.items);
},
/** PrivateFunction: _onReceivePresence
* Handle presence
*/
_onReceivePresence : function(presence)
{
// TODO: from is optional
var jid = presence.getAttribute('from');
var from = Strophe.getBareJidFromJid(jid);
var item = this.findItem(from);
// not in roster
if (!item)
{
return true;
}
var type = presence.getAttribute('type');
if (type == 'unavailable')
{
delete item.resources[Strophe.getResourceFromJid(jid)];
}
else if (!type)
{
// TODO: add timestamp
item.resources[Strophe.getResourceFromJid(jid)] = {
show : (presence.getElementsByTagName('show').length !== 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "",
status : (presence.getElementsByTagName('status').length !== 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "",
priority : (presence.getElementsByTagName('priority').length !== 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : ""
};
}
else
{
// Stanza is not a presence notification. (It's probably a subscription type stanza.)
return true;
}
this._call_backs(this.items, item);
return true;
},
/** PrivateFunction: _call_backs
*
*/
_call_backs : function(items, item)
{
for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ...
{
this._callbacks[i](items, item);
}
},
/** PrivateFunction: _onReceiveIQ
* Handle roster push.
*/
_onReceiveIQ : function(iq)
{
var id = iq.getAttribute('id');
var from = iq.getAttribute('from');
// Receiving client MUST ignore stanza unless it has no from or from = user's JID.
if (from && from !== "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid))
return true;
var iqresult = $iq({type: 'result', id: id, from: this._connection.jid});
this._connection.send(iqresult);
this._updateItems(iq);
return true;
},
/** PrivateFunction: _updateItems
* Update items from iq
*/
_updateItems : function(iq)
{
var query = iq.getElementsByTagName('query');
if (query.length !== 0)
{
this.ver = query.item(0).getAttribute('ver');
var self = this;
Strophe.forEachChild(query.item(0), 'item',
function (item)
{
self._updateItem(item);
}
);
}
this._call_backs(this.items);
},
/** PrivateFunction: _updateItem
* Update internal representation of roster item
*/
_updateItem : function(item)
{
var jid = item.getAttribute("jid");
var name = item.getAttribute("name");
var subscription = item.getAttribute("subscription");
var ask = item.getAttribute("ask");
var groups = [];
Strophe.forEachChild(item, 'group',
function(group)
{
groups.push(Strophe.getText(group));
}
);
if (subscription == "remove")
{
this.removeItem(jid);
return;
}
item = this.findItem(jid);
if (!item)
{
this.items.push({
name : name,
jid : jid,
subscription : subscription,
ask : ask,
groups : groups,
resources : {}
});
}
else
{
item.name = name;
item.subscription = subscription;
item.ask = ask;
item.groups = groups;
}
}
});
<input class="roster-filter" placeholder="{{placeholder}}">
<select class="filter-type">
<input style="display: none;" class="roster-filter" placeholder="{{placeholder}}">
<select style="display: none;" class="filter-type">
<option value="contacts">{{label_contacts}}</option>
<option value="groups">{{label_groups}}</option>
</select>
<dl class="roster-contacts"></dl>
......@@ -30,7 +30,7 @@ define(["jquery"], function ($) {
var utils = {
// Translation machinery
// ---------------------
__: $.proxy(function (str) {
__: function (str) {
// Translation factory
if (this.i18n === undefined) {
this.i18n = locales.en;
......@@ -41,7 +41,7 @@ define(["jquery"], function ($) {
} else {
return t.fetch();
}
}, this),
},
___: function (str) {
/* XXX: This is part of a hack to get gettext to scan strings to be
......
config = {
baseUrl: '.',
paths: {
"backbone": "components/backbone/backbone",
"backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",
"backbone.overview": "components/backbone.overview/backbone.overview",
"bootstrap": "components/bootstrap/dist/js/bootstrap", // XXX: Only required for https://conversejs.org website
"bootstrapJS": "components/bootstrapJS/index", // XXX: Only required for https://conversejs.org website
"converse-dependencies": "src/deps-website",
"converse-templates": "src/templates",
"eventemitter": "components/otr/build/dep/eventemitter",
"jquery": "components/jquery/dist/jquery",
"jquery-private": "src/jquery-private",
"jquery.browser": "components/jquery.browser/index",
"jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website
"moment": "components/momentjs/moment",
"strophe": "components/strophe/strophe",
"strophe.disco": "components/strophejs-plugins/disco/strophe.disco",
"strophe.muc": "components/strophe.muc/index",
"strophe.roster": "src/strophe.roster",
"strophe.vcard": "components/strophejs-plugins/vcard/strophe.vcard",
"text": 'components/requirejs-text/text',
"tpl": 'components/requirejs-tpl-jcbrand/tpl',
"typeahead": "components/typeahead.js/index",
"underscore": "components/underscore/underscore",
"utils": "src/utils",
// Off-the-record-encryption
"bigint": "src/bigint",
"crypto": "src/crypto",
"crypto.aes": "components/otr/vendor/cryptojs/aes",
"crypto.cipher-core": "components/otr/vendor/cryptojs/cipher-core",
"crypto.core": "components/otr/vendor/cryptojs/core",
"crypto.enc-base64": "components/otr/vendor/cryptojs/enc-base64",
"crypto.evpkdf": "components/crypto-js-evanvosberg/src/evpkdf",
"crypto.hmac": "components/otr/vendor/cryptojs/hmac",
"crypto.md5": "components/crypto-js-evanvosberg/src/md5",
"crypto.mode-ctr": "components/otr/vendor/cryptojs/mode-ctr",
"crypto.pad-nopadding": "components/otr/vendor/cryptojs/pad-nopadding",
"crypto.sha1": "components/otr/vendor/cryptojs/sha1",
"crypto.sha256": "components/otr/vendor/cryptojs/sha256",
"salsa20": "components/otr/build/dep/salsa20",
"otr": "src/otr",
// Locales paths
"locales": "locale/locales",
"jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"de": "locale/de/LC_MESSAGES/de",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"fr": "locale/fr/LC_MESSAGES/fr",
"he": "locale/he/LC_MESSAGES/he",
"hu": "locale/hu/LC_MESSAGES/hu",
"id": "locale/id/LC_MESSAGES/id",
"it": "locale/it/LC_MESSAGES/it",
"ja": "locale/ja/LC_MESSAGES/ja",
"nl": "locale/nl/LC_MESSAGES/nl",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
"ru": "locale/ru/LC_MESSAGES/ru",
"zh": "locale/zh/LC_MESSAGES/zh",
// Templates
"action": "src/templates/action",
"add_contact_dropdown": "src/templates/add_contact_dropdown",
"add_contact_form": "src/templates/add_contact_form",
"change_status_message": "src/templates/change_status_message",
"chat_status": "src/templates/chat_status",
"chatarea": "src/templates/chatarea",
"chatbox": "src/templates/chatbox",
"chatroom": "src/templates/chatroom",
"chatroom_password_form": "src/templates/chatroom_password_form",
"chatroom_sidebar": "src/templates/chatroom_sidebar",
"chatrooms_tab": "src/templates/chatrooms_tab",
"chats_panel": "src/templates/chats_panel",
"choose_status": "src/templates/choose_status",
"contacts_panel": "src/templates/contacts_panel",
"contacts_tab": "src/templates/contacts_tab",
"controlbox": "src/templates/controlbox",
"controlbox_toggle": "src/templates/controlbox_toggle",
"field": "src/templates/field",
"form_checkbox": "src/templates/form_checkbox",
"form_input": "src/templates/form_input",
"form_select": "src/templates/form_select",
"group_header": "src/templates/group_header",
"info": "src/templates/info",
"login_panel": "src/templates/login_panel",
"login_tab": "src/templates/login_tab",
"message": "src/templates/message",
"new_day": "src/templates/new_day",
"occupant": "src/templates/occupant",
"pending_contact": "src/templates/pending_contact",
"pending_contacts": "src/templates/pending_contacts",
"requesting_contact": "src/templates/requesting_contact",
"requesting_contacts": "src/templates/requesting_contacts",
"room_description": "src/templates/room_description",
"room_item": "src/templates/room_item",
"room_panel": "src/templates/room_panel",
"roster": "src/templates/roster",
"roster_item": "src/templates/roster_item",
"search_contact": "src/templates/search_contact",
"select_option": "src/templates/select_option",
"status_option": "src/templates/status_option",
"toggle_chats": "src/templates/toggle_chats",
"toolbar": "src/templates/toolbar",
"trimmed_chat": "src/templates/trimmed_chat"
},
map: {
// '*' means all modules will get 'jquery-private'
// for their 'jquery' dependency.
'*': { 'jquery': 'jquery-private' },
// 'jquery-private' wants the real jQuery module
// though. If this line was not here, there would
// be an unresolvable cyclic dependency.
'jquery-private': { 'jquery': 'jquery' }
},
tpl: {
// Configuration for requirejs-tpl
// Use Mustache style syntax for variable interpolation
templateSettings: {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
},
// define module dependencies for modules not using define
shim: {
'underscore': { exports: '_' },
'crypto.aes': { deps: ['crypto.cipher-core'] },
'crypto.cipher-core': { deps: ['crypto.enc-base64', 'crypto.evpkdf'] },
'crypto.enc-base64': { deps: ['crypto.core'] },
'crypto.evpkdf': { deps: ['crypto.md5'] },
'crypto.hmac': { deps: ['crypto.core'] },
'crypto.md5': { deps: ['crypto.core'] },
'crypto.mode-ctr': { deps: ['crypto.cipher-core'] },
'crypto.pad-nopadding': { deps: ['crypto.cipher-core'] },
'crypto.sha1': { deps: ['crypto.core'] },
'crypto.sha256': { deps: ['crypto.core'] },
'bigint': { deps: ['crypto'] },
'strophe': { exports: 'Strophe' },
'strophe.disco': { deps: ['strophe'] },
'strophe.muc': { deps: ['strophe'] },
'strophe.roster': { deps: ['strophe'] },
'strophe.vcard': { deps: ['strophe'] }
}
};
// Extra test dependencies
config.paths.mock = "tests/mock";
config.paths.test_utils = "tests/utils";
......@@ -40,8 +189,6 @@ require([
window.converse_api = converse;
window.localStorage.clear();
window.sessionStorage.clear();
// XXX: call this to initialize Strophe plugins
new Strophe.Connection('localhost');
converse.initialize({
prebind: false,
......@@ -49,7 +196,8 @@ require([
auto_subscribe: false,
animate: false,
connection: mock.mock_connection,
no_trimming: true
no_trimming: true,
debug: true
}, function (converse) {
window.converse = converse;
window.crypto = {
......@@ -68,7 +216,8 @@ require([
"spec/controlbox",
"spec/chatbox",
"spec/chatroom",
"spec/minchats"
"spec/minchats",
"spec/profiling"
], function () {
// Make sure this callback is only called once.
delete converse.callback;
......
......@@ -36,11 +36,37 @@
'preventDefault': function () {}
};
mock.mock_connection = {
'_proto': {},
'connected': true,
'authenticated': true,
'mock': true,
mock.mock_connection = function () {
Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
var c = new Strophe.Connection('jasmine tests');
c.authenticated = true;
c.connected = true;
c.mock = true;
c.jid = 'dummy@localhost/resource';
c.vcard = {
'get': function (callback, jid) {
var fullname;
if (!jid) {
jid = 'dummy@localhost';
fullname = 'Max Mustermann' ;
} else {
var name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
var last = name.length-1;
name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
fullname = name.join(' ');
}
var vcard = $iq().c('vCard').c('FN').t(fullname);
callback(vcard.tree());
}
};
c._changeConnectStatus(Strophe.Status.CONNECTED);
c.attach(c.jid);
return c;
}();
/*
{
'muc': {
'listRooms': function () {},
'join': function () {},
......@@ -49,7 +75,6 @@
'groupchat': function () {return String((new Date()).getTime()); }
},
'service': 'jasmine tests',
'jid': 'dummy@localhost',
'addHandler': function (handler, ns, name, type, id, from, options) {
return function () {};
},
......@@ -87,5 +112,6 @@
'items': function () {}
}
};
*/
return mock;
}));
......@@ -8,6 +8,17 @@
});
}(this, function ($, mock) {
var utils = {};
utils.createRequest = function (iq) {
iq = typeof iq.tree == "function" ? iq.tree() : iq;
var req = new Strophe.Request(iq, function() {});
req.getResponse = function() {
var env = new Strophe.Builder('env', {type: 'mock'}).tree();
env.appendChild(iq);
return env;
};
return req;
};
utils.closeAllChatBoxes = function () {
var i, chatbox;
......
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