Commit 0d9fa4a9 authored by JC Brand's avatar JC Brand

Merge branch 'master' into gh-pages

parents 419019c7 93d31f26
......@@ -2,12 +2,12 @@
converse.js
===========
Converse.js_ implements an XMPP_ based instant messaging client in the browser.
Converse.js_ is a web based `XMPP/Jabber`_ instant messaging client.
It is used by collective.xmpp.chat_, which is a Plone_ instant messaging add-on.
The ultimate goal is to enable anyone to add chat functionality to their websites, regardless of the backend.
This is currently possible, except for adding new contacts, which still makes an XHR call to the (Plone) backend to fetch user info.
The ultimate goal is to enable anyone to add chat functionality to their websites, independent of any backend.
You will however need an XMPP server to connect to, either your own, or a public one.
--------
Features
......@@ -54,7 +54,7 @@ Licence
.. _require.js: http:/requirejs.org
.. _collective.xmpp.chat: http://github.com/collective/collective.xmpp.chat
.. _Plone: http://plone.org
.. _XMPP: http://xmpp.org
.. _`XMPP/Jabber`: http://xmpp.org
.. _MIT: http://opensource.org/licenses/mit-license.php
.. _GPL: http://opensource.org/licenses/gpl-license.php
.. _here: http://opkode.com/media/blog/instant-messaging-for-plone-with-javascript-and-xmpp
......
......@@ -182,6 +182,10 @@ div.delayed .chat-message-me {
color: #7EABBB;
}
input.error {
border: 1px solid red;
}
.chat-message-error {
color:#76797C;
font-size:90%;
......@@ -234,9 +238,8 @@ a.subscribe-to-user {
font-weight: bold;
}
dl.add-xmpp-contact {
dl.add-converse-contact {
margin: 0 0 0 0.5em;
padding-top: 3px;
z-index: 21;
background: url('images/add_icon.png') no-repeat 3px;
}
......@@ -245,8 +248,13 @@ dt#xmpp-contact-search {
padding-top: 3px;
}
.fancy-dropdown {
border:1px solid #ddd;
height: 22px;
}
.fancy-dropdown a.choose-xmpp-status,
.fancy-dropdown a.add-xmpp-contact {
.fancy-dropdown a.toggle-xmpp-contact-form {
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
padding-left: 1.5em;
width: 140px;
......@@ -272,7 +280,7 @@ dt#xmpp-contact-search {
ul#found-users {
padding: 10px 0 5px 5px;
border: 0;
border: 0;
}
form.search-xmpp-contact {
......@@ -283,7 +291,7 @@ form.search-xmpp-contact {
}
form.search-xmpp-contact input {
width: 9em;
width: 8em;
}
.oc-chat-head {
......@@ -340,42 +348,42 @@ form.search-xmpp-contact input {
padding-top: 0.5em;
}
#xmppchat-roster dd.odd {
#converse-roster dd.odd {
background-color: #DCEAC5; /* Make this difference */
}
#xmppchat-roster dd.current-xmpp-contact {
#converse-roster dd.current-xmpp-contact {
clear: both;
}
#xmppchat-roster dd.current-xmpp-contact,
#xmppchat-roster dd.current-xmpp-contact:hover {
#converse-roster dd.current-xmpp-contact,
#converse-roster dd.current-xmpp-contact:hover {
background: url(images/user_online_panel.png) no-repeat 5px 2px;
}
#xmppchat-roster dd.current-xmpp-contact.offline:hover,
#xmppchat-roster dd.current-xmpp-contact.unavailable:hover,
#xmppchat-roster dd.current-xmpp-contact.offline,
#xmppchat-roster dd.current-xmpp-contact.unavailable {
#converse-roster dd.current-xmpp-contact.offline:hover,
#converse-roster dd.current-xmpp-contact.unavailable:hover,
#converse-roster dd.current-xmpp-contact.offline,
#converse-roster dd.current-xmpp-contact.unavailable {
background: url(images/user_offline_panel.png) no-repeat 5px 2px;
}
#xmppchat-roster dd.current-xmpp-contact.dnd,
#xmppchat-roster dd.current-xmpp-contact.dnd:hover {
#converse-roster dd.current-xmpp-contact.dnd,
#converse-roster dd.current-xmpp-contact.dnd:hover {
background: url(images/user_busy_panel.png) no-repeat 5px 2px;
}
#xmppchat-roster dd.current-xmpp-contact.away,
#xmppchat-roster dd.current-xmpp-contact.away:hover {
#converse-roster dd.current-xmpp-contact.away,
#converse-roster dd.current-xmpp-contact.away:hover {
background: url(images/user_away_panel.png) no-repeat 5px 2px;
}
#xmppchat-roster dd.requesting-xmpp-contact button{
#converse-roster dd.requesting-xmpp-contact button{
margin-left: 0.5em;
}
#xmppchat-roster dd a,
#xmppchat-roster dd span {
#converse-roster dd a,
#converse-roster dd span {
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
display: inline-block;
overflow: hidden;
......@@ -383,12 +391,12 @@ form.search-xmpp-contact input {
text-overflow: ellipsis;
}
#xmppchat-roster dd a {
#converse-roster dd a {
margin-left: 1.5em;
width: 113px;
}
#xmppchat-roster dd span {
#converse-roster dd span {
width: 125px;
}
......@@ -396,7 +404,7 @@ form.search-xmpp-contact input {
border: none;
}
#xmppchat-roster {
#converse-roster {
height: 200px;
overflow-y: scroll;
width: 100%;
......@@ -408,7 +416,7 @@ form.search-xmpp-contact input {
}
#available-chatrooms dt,
#xmppchat-roster dt {
#converse-roster dt {
font-weight: normal;
display: none;
font-size: 13px;
......@@ -428,7 +436,7 @@ form.search-xmpp-contact input {
dd.available-chatroom,
#xmppchat-roster dd {
#converse-roster dd {
font-weight: bold;
border: none;
display: block;
......@@ -437,7 +445,7 @@ dd.available-chatroom,
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
}
#xmppchat-roster dd a.remove-xmpp-contact {
#converse-roster dd a.remove-xmpp-contact {
background: url('images/delete_icon.png') no-repeat right top;
padding: 0 1em 1em 0;
float: right;
......@@ -494,12 +502,12 @@ div#controlbox-panes {
width: 199px;
}
form#xmppchat-login {
form#converse-login {
background: white;
padding: 2em 0 0.3em 0.5em;
}
form#xmppchat-login input {
form#converse-login input {
display: block;
}
......@@ -599,17 +607,12 @@ form.sendXMPPMessage {
form#set-custom-xmpp-status {
float: left;
padding: 0;
background: none;
}
#set-custom-xmpp-status button {
padding: 1px 2px 1px 1px;
}
input.custom-xmpp-status {
width: 138px;
}
/* status dropdown styles */
dl.dropdown {
margin-right: 0.5em;
......@@ -622,9 +625,17 @@ div.xmpp-status {
padding: 3px;
}
.fancy-dropdown {
border:1px solid #ddd;
height: 22px;
input.custom-xmpp-status {
width: 138px;
}
form.add-xmpp-contact {
background: white;
padding: 5px;
}
form.add-xmpp-contact input {
width: 125px;
}
.dropdown dt a span {
......
......@@ -79,12 +79,12 @@
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
};
root.xmppchat = factory(jQuery, _, console || {log: function(){}});
root.converse = factory(jQuery, _, console || {log: function(){}});
}
}(this, function ($, _, console) {
var xmppchat = {};
xmppchat.msg_counter = 0;
var converse = {};
converse.msg_counter = 0;
var strinclude = function(str, needle){
if (needle === '') { return true; }
......@@ -92,13 +92,13 @@
return String(str).indexOf(needle) !== -1;
};
xmppchat.autoLink = function (text) {
converse.autoLink = function (text) {
// Convert URLs into hyperlinks
var re = /((http|https|ftp):\/\/[\w?=&.\/\-;#~%\-]+(?![\w\s?&.\/;#~%"=\-]*>))/g;
return text.replace(re, '<a target="_blank" href="$1">$1</a>');
};
xmppchat.toISOString = function (date) {
converse.toISOString = function (date) {
var pad;
if (typeof date.toISOString !== 'undefined') {
return date.toISOString();
......@@ -116,7 +116,7 @@
}
};
xmppchat.parseISO8601 = function (datestr) {
converse.parseISO8601 = function (datestr) {
/* Parses string formatted as 2013-02-14T11:27:08.268Z to a Date obj.
*/
    var numericKeys = [1, 4, 5, 6, 7, 10, 11],
......@@ -140,7 +140,7 @@
return new Date(Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]));
};
xmppchat.updateMsgCounter = function () {
converse.updateMsgCounter = function () {
if (this.msg_counter > 0) {
if (document.title.search(/^Messages \(\d+\) /) == -1) {
document.title = "Messages (" + this.msg_counter + ") " + document.title;
......@@ -154,17 +154,17 @@
}
};
xmppchat.incrementMsgCounter = function () {
converse.incrementMsgCounter = function () {
this.msg_counter += 1;
this.updateMsgCounter();
};
xmppchat.clearMsgCounter = function () {
converse.clearMsgCounter = function () {
this.msg_counter = 0;
this.updateMsgCounter();
};
xmppchat.collections = {
converse.collections = {
/* FIXME: XEP-0136 specifies 'urn:xmpp:archive' but the mod_archive_odbc
* add-on for ejabberd wants the URL below. This might break for other
* Jabber servers.
......@@ -172,7 +172,7 @@
'URI': 'http://www.xmpp.org/extensions/xep-0136.html#ns'
};
xmppchat.collections.getLastCollection = function (jid, callback) {
converse.collections.getLastCollection = function (jid, callback) {
var bare_jid = Strophe.getBareJidFromJid(jid),
iq = $iq({'type':'get'})
.c('list', {'xmlns': this.URI,
......@@ -183,14 +183,14 @@
.c('max')
.t('1');
xmppchat.connection.sendIQ(iq,
converse.connection.sendIQ(iq,
callback,
function () {
console.log('Error while retrieving collections');
});
};
xmppchat.collections.getLastMessages = function (jid, callback) {
converse.collections.getLastMessages = function (jid, callback) {
var that = this;
this.getLastCollection(jid, function (result) {
// Retrieve the last page of a collection (max 30 elements).
......@@ -205,20 +205,20 @@
.c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
.c('max')
.t('30');
xmppchat.connection.sendIQ(iq, callback);
converse.connection.sendIQ(iq, callback);
});
};
xmppchat.Message = Backbone.Model.extend();
converse.Message = Backbone.Model.extend();
xmppchat.Messages = Backbone.Collection.extend({
model: xmppchat.Message
converse.Messages = Backbone.Collection.extend({
model: converse.Message
});
xmppchat.ChatBox = Backbone.Model.extend({
converse.ChatBox = Backbone.Model.extend({
initialize: function () {
if (this.get('box_id') !== 'controlbox') {
this.messages = new xmppchat.Messages();
this.messages = new converse.Messages();
this.messages.localStorage = new Backbone.LocalStorage(
hex_sha1('converse.messages'+this.get('jid')));
this.set({
......@@ -234,7 +234,7 @@
messageReceived: function (message) {
var $message = $(message),
body = xmppchat.autoLink($message.children('body').text()),
body = converse.autoLink($message.children('body').text()),
from = Strophe.getBareJidFromJid($message.attr('from')),
composing = $message.find('composing'),
delayed = $message.find('delay').length > 0,
......@@ -247,7 +247,7 @@
fullname: fullname,
sender: 'them',
delayed: delayed,
time: xmppchat.toISOString(new Date()),
time: converse.toISOString(new Date()),
composing: composing.length
});
}
......@@ -256,9 +256,9 @@
stamp = $message.find('delay').attr('stamp');
time = stamp;
} else {
time = xmppchat.toISOString(new Date());
time = converse.toISOString(new Date());
}
if (from == xmppchat.bare_jid) {
if (from == converse.bare_jid) {
fullname = 'me';
sender = 'me';
} else {
......@@ -275,7 +275,7 @@
}
});
xmppchat.ChatBoxView = Backbone.View.extend({
converse.ChatBoxView = Backbone.View.extend({
length: 200,
tagName: 'div',
className: 'chatbox',
......@@ -311,7 +311,7 @@
showMessage: function (message) {
var time = message.get('time'),
times = this.model.messages.pluck('time'),
this_date = xmppchat.parseISO8601(time),
this_date = converse.parseISO8601(time),
$chat_content = this.$el.find('.chat-content'),
previous_message, idx, prev_date, isodate;
......@@ -320,10 +320,10 @@
idx = _.indexOf(times, time)-1;
if (idx >= 0) {
previous_message = this.model.messages.at(idx);
prev_date = xmppchat.parseISO8601(previous_message.get('time'));
prev_date = converse.parseISO8601(previous_message.get('time'));
isodate = new Date(this_date.getTime());
isodate.setUTCHours(0,0,0,0);
isodate = xmppchat.toISOString(isodate);
isodate = converse.toISOString(isodate);
if (this.isDifferentDay(prev_date, this_date)) {
$chat_content.append(this.new_day_template({
isodate: isodate,
......@@ -331,7 +331,7 @@
}));
}
}
if (xmppchat.xmppstatus.get('status') === 'offline') {
if (converse.xmppstatus.get('status') === 'offline') {
// only update the UI if the user is not offline
return;
}
......@@ -350,15 +350,15 @@
}));
}
if (message.get('sender') != 'me') {
xmppchat.incrementMsgCounter();
converse.incrementMsgCounter();
}
this.scrollDown();
},
isDifferentDay: function (prev_date, next_date) {
return (
(next_date.getDate() != prev_date.getDate()) ||
(next_date.getFullYear() != prev_date.getFullYear()) ||
(next_date.getDate() != prev_date.getDate()) ||
(next_date.getFullYear() != prev_date.getFullYear()) ||
(next_date.getMonth() != prev_date.getMonth()));
},
......@@ -396,23 +396,23 @@
}
}
var message = $msg({from: xmppchat.bare_jid, to: bare_jid, type: 'chat', id: timestamp})
var message = $msg({from: converse.bare_jid, to: bare_jid, type: 'chat', id: timestamp})
.c('body').t(text).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'});
// Forward the message, so that other connected resources are also aware of it.
// TODO: Forward the message only to other connected resources (inside the browser)
var forwarded = $msg({to:xmppchat.bare_jid, type:'chat', id:timestamp})
var forwarded = $msg({to:converse.bare_jid, type:'chat', id:timestamp})
.c('forwarded', {xmlns:'urn:xmpp:forward:0'})
.c('delay', {xmns:'urn:xmpp:delay',stamp:timestamp}).up()
.cnode(message.tree());
xmppchat.connection.send(message);
xmppchat.connection.send(forwarded);
converse.connection.send(message);
converse.connection.send(forwarded);
// Add the new message
this.model.messages.create({
fullname: 'me',
sender: 'me',
time: xmppchat.toISOString(new Date()),
message: text
time: converse.toISOString(new Date()),
message: text
});
},
......@@ -444,7 +444,7 @@
// starts with forward-slash.
notify = $msg({'to':this.model.get('jid'), 'type': 'chat'})
.c('composing', {'xmlns':'http://jabber.org/protocol/chatstates'});
xmppchat.connection.send(notify);
converse.connection.send(notify);
}
this.$el.data('composing', true);
}
......@@ -476,7 +476,7 @@
},
closeChat: function () {
if (xmppchat.connection) {
if (converse.connection) {
this.model.destroy();
} else {
this.model.trigger('hide');
......@@ -489,12 +489,12 @@
this.model.on('destroy', this.hide, this);
this.model.on('change', this.onChange, this);
this.$el.appendTo(xmppchat.chatboxesview.$el);
this.$el.appendTo(converse.chatboxesview.$el);
this.render().show().model.messages.fetch({add: true});
if (this.model.get('status')) {
this.showStatusMessage(this.model.get('status'));
}
xmppchat.clearMsgCounter();
converse.clearMsgCounter();
},
template: _.template(
......@@ -537,7 +537,7 @@
},
hide: function () {
if (xmppchat.animate) {
if (converse.animate) {
this.$el.hide('fast');
} else {
this.$el.hide();
......@@ -548,12 +548,12 @@
if (this.$el.is(':visible') && this.$el.css('opacity') == "1") {
return this.focus();
}
if (xmppchat.animate) {
if (converse.animate) {
this.$el.css({'opacity': 0, 'display': 'inline'}).animate({opacity: '1'}, 200);
} else {
this.$el.css({'opacity': 1, 'display': 'inline'});
}
if (xmppchat.connection) {
if (converse.connection) {
// Without a connection, we haven't yet initialized
// localstorage
this.model.save();
......@@ -568,14 +568,15 @@
}
});
xmppchat.ContactsPanel = Backbone.View.extend({
converse.ContactsPanel = Backbone.View.extend({
tagName: 'div',
className: 'oc-chat-content',
id: 'users',
events: {
'click a.add-xmpp-contact': 'toggleContactForm',
'click a.toggle-xmpp-contact-form': 'toggleContactForm',
'submit form.add-xmpp-contact': 'addContactFromForm',
'submit form.search-xmpp-contact': 'searchContacts',
'click a.subscribe-to-user': 'subscribeToContact'
'click a.subscribe-to-user': 'addContactFromList'
},
tab_template: _.template('<li><a class="s current" href="#users">Contacts</a></li>'),
......@@ -590,66 +591,109 @@
'</select>'+
'</span>'+
'</form>'+
'<dl class="add-xmpp-contact dropdown">' +
'<dl class="add-converse-contact dropdown">' +
'<dt id="xmpp-contact-search" class="fancy-dropdown">' +
'<a class="add-xmpp-contact" href="#" title="Click to search for new users to add as chat contacts">Add a contact</a>' +
'<a class="toggle-xmpp-contact-form" href="#" title="Click to add new chat contacts">Add a contact</a>' +
'</dt>' +
'<dd class="search-xmpp" style="display:none"><ul>' +
'<dd class="search-xmpp" style="display:none"><ul></ul></dd>' +
'</dl>'
),
add_contact_template: _.template(
'<form class="add-xmpp-contact">' +
'<input type="text" name="identifier" class="username" placeholder="Contact name"/>' +
'<button type="submit">Add</button>' +
'</form>'),
search_contact_template: _.template(
'<form class="search-xmpp-contact">' +
'<input type="text" name="identifier" class="username" placeholder="Contact name"/>' +
'<button type="submit">Search</button>' +
'<ul id="found-users"></ul>' +
'</form>' +
'</ul></dd>' +
'</dl>'
),
'</form>'),
render: function () {
var markup;
this.$parent.find('#controlbox-tabs').append(this.tab_template());
this.$parent.find('#controlbox-panes').append(this.$el.html(this.template()));
if (converse.xhr_user_search) {
markup = this.search_contact_template();
} else {
markup = this.add_contact_template();
}
this.$el.find('.search-xmpp ul').append(markup);
return this;
},
toggleContactForm: function (ev) {
ev.preventDefault();
this.$el.find('.search-xmpp').toggle().find('input.username').focus();
this.$el.find('.search-xmpp').toggle('fast', function () {
if ($(this).is(':visible')) {
$(this).find('input.username').focus();
}
});
},
searchContacts: function (ev) {
ev.preventDefault();
$.getJSON(portal_url + "/search-users?q=" + $(ev.target).find('input.username').val(), function (data) {
var $results_el = $('#found-users');
$(data).each(function (idx, obj) {
if ($results_el.children().length) {
var $results_el = $('#found-users');
$(data).each(function (idx, obj) {
if ($results_el.children().length) {
$results_el.empty();
}
$results_el.append(
$('<li></li>')
.attr('id', 'found-users-'+obj.id)
.append(
$('<a class="subscribe-to-user" href="#" title="Click to add as a chat contact"></a>')
.attr('data-recipient', Strophe.escapeNode(obj.id)+'@'+xmppchat.domain)
.text(obj.fullname)
)
);
});
});
},
}
$results_el.append(
$('<li></li>')
.attr('id', 'found-users-'+obj.id)
.append(
$('<a class="subscribe-to-user" href="#" title="Click to add as a chat contact"></a>')
.attr('data-recipient', Strophe.escapeNode(obj.id)+'@'+converse.domain)
.text(obj.fullname)
)
);
});
});
},
addContactFromForm: function (ev) {
ev.preventDefault();
var $input = $(ev.target).find('input');
var jid = $input.val();
if (! jid) {
// this is not a valid JID
$input.addClass('error');
return;
}
converse.getVCard(
jid,
$.proxy(function (jid, fullname, image, image_type, url) {
// XXX: Should we perhaps create a roster item here?
this.addContact(jid, fullname);
}, this),
$.proxy(function () {
console.log("An error occured while fetching vcard");
}, this));
$('.search-xmpp').hide();
},
subscribeToContact: function (ev) {
addContactFromList: function (ev) {
ev.preventDefault();
var $target = $(ev.target),
jid = $target.attr('data-recipient'),
name = $target.text();
xmppchat.connection.roster.add(jid, name, [], function (iq) {
xmppchat.connection.roster.subscribe(jid, null, xmppchat.fullname);
});
this.addContact(jid, name);
$target.parent().remove();
$('.search-xmpp').hide();
},
addContact: function (jid, name) {
converse.connection.roster.add(jid, name, [], function (iq) {
converse.connection.roster.subscribe(jid, null, converse.fullname);
});
}
});
xmppchat.RoomsPanel = Backbone.View.extend({
converse.RoomsPanel = Backbone.View.extend({
tagName: 'div',
id: 'chatrooms',
events: {
......@@ -688,7 +732,7 @@
},
updateRoomsList: function () {
xmppchat.connection.muc.listRooms(xmppchat.muc_domain, $.proxy(function (iq) {
converse.connection.muc.listRooms(converse.muc_domain, $.proxy(function (iq) {
var name, jid, i,
rooms = $(iq).find('query').find('item'),
rooms_length = rooms.length,
......@@ -718,23 +762,23 @@
name = input.val().trim().toLowerCase();
input.val(''); // Clear the input
if (name) {
jid = Strophe.escapeNode(name) + '@' + xmppchat.muc_domain;
jid = Strophe.escapeNode(name) + '@' + converse.muc_domain;
} else {
return;
}
}
xmppchat.chatboxes.create({
converse.chatboxes.create({
'id': jid,
'jid': jid,
'name': Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
'nick': xmppchat.xmppstatus.get('fullname')||xmppchat.bare_jid,
'nick': converse.xmppstatus.get('fullname')||converse.bare_jid,
'chatroom': true,
'box_id' : hex_sha1(jid)
});
}
});
xmppchat.ControlBoxView = xmppchat.ChatBoxView.extend({
converse.ControlBoxView = converse.ChatBoxView.extend({
tagName: 'div',
className: 'chatbox',
id: 'controlbox',
......@@ -744,7 +788,7 @@
},
initialize: function () {
this.$el.appendTo(xmppchat.chatboxesview.$el);
this.$el.appendTo(converse.chatboxesview.$el);
this.model.on('change', $.proxy(function (item, changed) {
if (_.has(item.changed, 'connected')) {
this.render();
......@@ -752,7 +796,7 @@
if (_.has(item.changed, 'visible')) {
if (item.changed.visible === true) {
this.show();
}
}
}
}, this));
......@@ -761,7 +805,7 @@
this.model.on('hide', this.hide, this);
if (this.model.get('visible')) {
this.show();
}
}
},
template: _.template(
......@@ -794,17 +838,17 @@
render: function () {
this.$el.html(this.template(this.model.toJSON()));
if ((!xmppchat.prebind) && (!xmppchat.connection)) {
if ((!converse.prebind) && (!converse.connection)) {
// Add login panel if the user still has to authenticate
this.loginpanel = new xmppchat.LoginPanel();
this.loginpanel = new converse.LoginPanel();
this.loginpanel.$parent = this.$el;
this.loginpanel.render();
} else {
this.contactspanel = new xmppchat.ContactsPanel();
this.contactspanel = new converse.ContactsPanel();
this.contactspanel.$parent = this.$el;
this.contactspanel.render();
// TODO: Only add the rooms panel if the server supports MUC
this.roomspanel = new xmppchat.RoomsPanel();
this.roomspanel = new converse.RoomsPanel();
this.roomspanel.$parent = this.$el;
this.roomspanel.render();
}
......@@ -812,7 +856,7 @@
}
});
xmppchat.ChatRoomView = xmppchat.ChatBoxView.extend({
converse.ChatRoomView = converse.ChatBoxView.extend({
length: 300,
tagName: 'div',
className: 'chatroom',
......@@ -829,19 +873,19 @@
// TODO: Private messages
break;
case 'topic':
xmppchat.connection.muc.setTopic(this.model.get('jid'), match[2]);
converse.connection.muc.setTopic(this.model.get('jid'), match[2]);
break;
case 'kick':
xmppchat.connection.muc.kick(this.model.get('jid'), match[2]);
converse.connection.muc.kick(this.model.get('jid'), match[2]);
break;
case 'ban':
xmppchat.connection.muc.ban(this.model.get('jid'), match[2]);
converse.connection.muc.ban(this.model.get('jid'), match[2]);
break;
case 'op':
xmppchat.connection.muc.op(this.model.get('jid'), match[2]);
converse.connection.muc.op(this.model.get('jid'), match[2]);
break;
case 'deop':
xmppchat.connection.muc.deop(this.model.get('jid'), match[2]);
converse.connection.muc.deop(this.model.get('jid'), match[2]);
break;
case 'help':
$chat_content = this.$el.find('.chat-content');
......@@ -856,7 +900,7 @@
this.scrollDown();
break;
default:
this.last_msgid = xmppchat.connection.muc.groupchat(this.model.get('jid'), body);
this.last_msgid = converse.connection.muc.groupchat(this.model.get('jid'), body);
break;
}
},
......@@ -883,7 +927,7 @@
'</div>'),
initialize: function () {
xmppchat.connection.muc.join(
converse.connection.muc.join(
this.model.get('jid'),
this.model.get('nick'),
$.proxy(this.onChatRoomMessage, this),
......@@ -892,22 +936,22 @@
this.model.messages.on('add', this.showMessage, this);
this.model.on('destroy', function (model, response, options) {
this.$el.hide('fast');
xmppchat.connection.muc.leave(
this.model.on('destroy', function (model, response, options) {
this.$el.hide('fast');
converse.connection.muc.leave(
this.model.get('jid'),
this.model.get('nick'),
this.onLeave,
undefined);
},
},
this);
this.$el.appendTo(xmppchat.chatboxesview.$el);
this.$el.appendTo(converse.chatboxesview.$el);
this.render().show().model.messages.fetch({add: true});
xmppchat.clearMsgCounter();
converse.clearMsgCounter();
},
onLeave: function () {
var controlboxview = xmppchat.chatboxesview.views.controlbox;
var controlboxview = converse.chatboxesview.views.controlbox;
if (controlboxview) {
controlboxview.roomspanel.trigger('update-rooms-list');
}
......@@ -944,7 +988,7 @@
}
if (delayed) {
stamp = $message.find('delay').attr('stamp');
message_datetime = xmppchat.parseISO8601(stamp);
message_datetime = converse.parseISO8601(stamp);
} else {
message_datetime = new Date();
}
......@@ -953,7 +997,7 @@
dates = $chat_content.find("time").map(function(){return $(this).attr("datetime");}).get();
message_date = message_datetime;
message_date.setUTCHours(0,0,0,0);
isodate = xmppchat.toISOString(message_date);
isodate = converse.toISOString(message_date);
if (_.indexOf(dates, isodate) == -1) {
$chat_content.append(this.new_day_template({
isodate: isodate,
......@@ -986,7 +1030,7 @@
onChatRoomRoster: function (roster, room) {
// underscore size is needed because roster is an object
var controlboxview = xmppchat.chatboxesview.views.controlbox,
var controlboxview = converse.chatboxesview.views.controlbox,
roster_size = _.size(roster),
$participant_list = this.$el.find('.participant-list'),
participants = [],
......@@ -1010,12 +1054,12 @@
}
});
xmppchat.ChatBoxes = Backbone.Collection.extend({
model: xmppchat.ChatBox,
converse.ChatBoxes = Backbone.Collection.extend({
model: converse.ChatBox,
onConnected: function () {
this.localStorage = new Backbone.LocalStorage(
hex_sha1('converse.chatboxes-'+xmppchat.bare_jid));
hex_sha1('converse.chatboxes-'+converse.bare_jid));
if (!this.get('controlbox')) {
this.add({
id: 'controlbox',
......@@ -1035,14 +1079,14 @@
this.get('controlbox').set({visible:true}).save();
}
}, this)
});
});
},
messageReceived: function (message) {
var partner_jid, $message = $(message),
message_from = $message.attr('from');
if (message_from == xmppchat.connection.jid) {
// FIXME: Forwarded messages should be sent to specific resources,
if (message_from == converse.connection.jid) {
// FIXME: Forwarded messages should be sent to specific resources,
// not broadcasted
return true;
}
......@@ -1053,7 +1097,7 @@
var from = Strophe.getBareJidFromJid(message_from),
to = Strophe.getBareJidFromJid($message.attr('to')),
resource, chatbox;
if (from == xmppchat.bare_jid) {
if (from == converse.bare_jid) {
// I am the sender, so this must be a forwarded message...
partner_jid = to;
resource = Strophe.getResourceFromJid($message.attr('to'));
......@@ -1063,8 +1107,8 @@
}
chatbox = this.get(partner_jid);
if (!chatbox) {
xmppchat.getVCard(
partner_jid,
converse.getVCard(
partner_jid,
$.proxy(function (jid, fullname, image, image_type, url) {
var chatbox = this.create({
'id': jid,
......@@ -1075,7 +1119,7 @@
'url': url
});
chatbox.messageReceived(message);
xmppchat.roster.addResource(partner_jid, resource);
converse.roster.addResource(partner_jid, resource);
}, this),
$.proxy(function () {
// # FIXME
......@@ -1084,12 +1128,12 @@
return true;
}
chatbox.messageReceived(message);
xmppchat.roster.addResource(partner_jid, resource);
converse.roster.addResource(partner_jid, resource);
return true;
}
});
xmppchat.ChatBoxesView = Backbone.View.extend({
converse.ChatBoxesView = Backbone.View.extend({
el: '#collective-xmpp-chat-data',
initialize: function () {
......@@ -1099,12 +1143,12 @@
var view = this.views[item.get('id')];
if (!view) {
if (item.get('chatroom')) {
view = new xmppchat.ChatRoomView({'model': item});
view = new converse.ChatRoomView({'model': item});
} else if (item.get('box_id') === 'controlbox') {
view = new xmppchat.ControlBoxView({model: item});
view = new converse.ControlBoxView({model: item});
view.render();
} else {
view = new xmppchat.ChatBoxView({model: item});
view = new converse.ChatBoxView({model: item});
}
this.views[item.get('id')] = view;
} else {
......@@ -1119,7 +1163,7 @@
}
});
xmppchat.RosterItem = Backbone.Model.extend({
converse.RosterItem = Backbone.Model.extend({
initialize: function (attributes, options) {
var jid = attributes.jid;
if (!attributes.fullname) {
......@@ -1137,7 +1181,7 @@
}
});
xmppchat.RosterItemView = Backbone.View.extend({
converse.RosterItemView = Backbone.View.extend({
tagName: 'dd',
events: {
......@@ -1150,11 +1194,11 @@
openChat: function (ev) {
ev.preventDefault();
var jid = Strophe.getBareJidFromJid(this.model.get('jid')),
chatbox = xmppchat.chatboxes.get(jid);
chatbox = converse.chatboxes.get(jid);
if (chatbox) {
chatbox.trigger('show');
} else {
xmppchat.chatboxes.create({
converse.chatboxes.create({
'id': this.model.get('jid'),
'jid': this.model.get('jid'),
'fullname': this.model.get('fullname'),
......@@ -1170,25 +1214,25 @@
var result = confirm("Are you sure you want to remove this contact?");
if (result === true) {
var bare_jid = this.model.get('jid');
xmppchat.connection.roster.remove(bare_jid, function (iq) {
xmppchat.connection.roster.unauthorize(bare_jid);
xmppchat.rosterview.model.remove(bare_jid);
converse.connection.roster.remove(bare_jid, function (iq) {
converse.connection.roster.unauthorize(bare_jid);
converse.rosterview.model.remove(bare_jid);
});
}
},
acceptRequest: function (ev) {
var jid = this.model.get('jid');
xmppchat.connection.roster.authorize(jid);
xmppchat.connection.roster.add(jid, this.model.get('fullname'), [], function (iq) {
xmppchat.connection.roster.subscribe(jid, null, xmppchat.fullname);
converse.connection.roster.authorize(jid);
converse.connection.roster.add(jid, this.model.get('fullname'), [], function (iq) {
converse.connection.roster.subscribe(jid, null, converse.fullname);
});
ev.preventDefault();
},
declineRequest: function (ev) {
ev.preventDefault();
xmppchat.connection.roster.unauthorize(this.model.get('jid'));
converse.connection.roster.unauthorize(this.model.get('jid'));
this.model.destroy();
},
......@@ -1219,7 +1263,7 @@
} else if (ask === 'request') {
this.$el.addClass('requesting-xmpp-contact');
this.$el.html(this.request_template(item.toJSON()));
xmppchat.showControlBox();
converse.showControlBox();
} else if (subscription === 'both' || subscription === 'to') {
this.$el.addClass('current-xmpp-contact');
this.$el.html(this.template(item.toJSON()));
......@@ -1237,8 +1281,8 @@
}
});
xmppchat.getVCard = function (jid, callback, errback) {
xmppchat.connection.vcard.get($.proxy(function (iq) {
converse.getVCard = function (jid, callback, errback) {
converse.connection.vcard.get($.proxy(function (iq) {
$vcard = $(iq).find('vCard');
var fullname = $vcard.find('FN').text(),
img = $vcard.find('BINVAL').text(),
......@@ -1248,8 +1292,8 @@
}, this), jid, errback);
}
xmppchat.RosterItems = Backbone.Collection.extend({
model: xmppchat.RosterItem,
converse.RosterItems = Backbone.Collection.extend({
model: converse.RosterItem,
comparator : function (rosteritem) {
var chat_status = rosteritem.get('chat_status'),
rank = 4;
......@@ -1283,8 +1327,8 @@
action = $this.attr('action'),
fullname = $this.attr('name');
if (action === 'add') {
xmppchat.connection.roster.add(jid, fullname, [], function (iq) {
xmppchat.connection.roster.subscribe(jid, null, xmppchat.fullname);
converse.connection.roster.add(jid, fullname, [], function (iq) {
converse.connection.roster.subscribe(jid, null, converse.fullname);
});
}
});
......@@ -1292,7 +1336,7 @@
},
isSelf: function (jid) {
return (Strophe.getBareJidFromJid(jid) === Strophe.getBareJidFromJid(xmppchat.connection.jid));
return (Strophe.getBareJidFromJid(jid) === Strophe.getBareJidFromJid(converse.connection.jid));
},
getItem: function (id) {
......@@ -1333,13 +1377,13 @@
subscribeBack: function (jid) {
var bare_jid = Strophe.getBareJidFromJid(jid);
if (xmppchat.connection.roster.findItem(bare_jid)) {
xmppchat.connection.roster.authorize(bare_jid);
xmppchat.connection.roster.subscribe(jid, null, xmppchat.fullname);
if (converse.connection.roster.findItem(bare_jid)) {
converse.connection.roster.authorize(bare_jid);
converse.connection.roster.subscribe(jid, null, converse.fullname);
} else {
xmppchat.connection.roster.add(jid, '', [], function (iq) {
xmppchat.connection.roster.authorize(bare_jid);
xmppchat.connection.roster.subscribe(jid, null, xmppchat.fullname);
converse.connection.roster.add(jid, '', [], function (iq) {
converse.connection.roster.authorize(bare_jid);
converse.connection.roster.subscribe(jid, null, converse.fullname);
});
}
},
......@@ -1351,10 +1395,10 @@
* this step lets the user's server know that it MUST no longer
* send notification of the subscription state change to the user.
*/
xmppchat.xmppstatus.sendPresence('unsubscribe');
if (xmppchat.connection.roster.findItem(jid)) {
xmppchat.connection.roster.remove(jid, function (iq) {
xmppchat.rosterview.model.remove(jid);
converse.xmppstatus.sendPresence('unsubscribe');
if (converse.connection.roster.findItem(jid)) {
converse.connection.roster.remove(jid, function (iq) {
converse.rosterview.model.remove(jid);
});
}
},
......@@ -1399,10 +1443,10 @@
is_last = false;
if (index === (items.length-1)) { is_last = true; }
this.create({
jid: item.jid,
jid: item.jid,
subscription: item.subscription,
ask: item.ask,
fullname: item.name,
fullname: item.name || item.jid,
is_last: is_last
});
} else {
......@@ -1431,11 +1475,11 @@
item;
if (this.isSelf(bare_jid)) {
if ((xmppchat.connection.jid !== jid)&&(presence_type !== 'unavailabe')) {
if ((converse.connection.jid !== jid)&&(presence_type !== 'unavailabe')) {
// Another resource has changed it's status, we'll update ours as well.
// FIXME: We should ideally differentiate between converse.js using
// resources and other resources (i.e Pidgin etc.)
xmppchat.xmppstatus.set({'status': chat_status});
converse.xmppstatus.set({'status': chat_status});
}
return true;
} else if (($presence.find('x').attr('xmlns') || '').indexOf(Strophe.NS.MUC) === 0) {
......@@ -1450,21 +1494,21 @@
if ((presence_type === 'error') || (presence_type === 'subscribed') || (presence_type === 'unsubscribe')) {
return true;
} else if (presence_type === 'subscribe') {
if (xmppchat.auto_subscribe) {
if (converse.auto_subscribe) {
if ((!item) || (item.get('subscription') != 'to')) {
this.subscribeBack(jid);
} else {
xmppchat.connection.roster.authorize(bare_jid);
converse.connection.roster.authorize(bare_jid);
}
} else {
if ((item) && (item.get('subscription') != 'none')) {
xmppchat.connection.roster.authorize(bare_jid);
converse.connection.roster.authorize(bare_jid);
} else {
xmppchat.getVCard(
bare_jid,
converse.getVCard(
bare_jid,
$.proxy(function (jid, fullname, img, img_type, url) {
this.add({
jid: bare_jid,
jid: bare_jid,
subscription: 'none',
ask: 'request',
fullname: fullname,
......@@ -1498,9 +1542,9 @@
}
});
xmppchat.RosterView = Backbone.View.extend({
converse.RosterView = Backbone.View.extend({
tagName: 'dl',
id: 'xmppchat-roster',
id: 'converse-roster',
rosteritemviews: {},
removeRosterItem: function (item) {
......@@ -1514,7 +1558,7 @@
initialize: function () {
this.model.on("add", function (item) {
var view = new xmppchat.RosterItemView({model: item});
var view = new converse.RosterItemView({model: item});
this.rosteritemviews[item.id] = view;
this.render(item);
}, this);
......@@ -1534,11 +1578,11 @@
this.$el.hide().html(this.template());
this.model.fetch({add: true}); // Get the cached roster items from localstorage
this.initialSort();
this.$el.appendTo(xmppchat.chatboxesview.views.controlbox.contactspanel.$el);
this.$el.appendTo(converse.chatboxesview.views.controlbox.contactspanel.$el);
},
updateChatBox: function (item, changed) {
var chatbox = xmppchat.chatboxes.get(item.get('jid')),
var chatbox = converse.chatboxes.get(item.get('jid')),
changes = {};
if (!chatbox) { return; }
if (_.has(item.changed, 'chat_status')) {
......@@ -1626,11 +1670,10 @@
crit = {order:'asc'};
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.offline').tsort('a', crit));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.unavailable').tsort('a', crit));
},
}
});
xmppchat.XMPPStatus = Backbone.Model.extend({
converse.XMPPStatus = Backbone.Model.extend({
initialize: function () {
this.set({
'status' : this.get('status'),
......@@ -1648,7 +1691,7 @@
},
sendPresence: function (type) {
var status_message = this.get('status_message'),
var status_message = this.get('status_message'),
presence;
// Most of these presence types are actually not explicitly sent,
// but I add all of them here fore reference and future proofing.
......@@ -1670,7 +1713,7 @@
presence.c('status').t(status_message);
}
}
xmppchat.connection.send(presence);
converse.connection.send(presence);
},
setStatus: function (value) {
......@@ -1679,13 +1722,13 @@
},
setStatusMessage: function (status_message) {
xmppchat.connection.send($pres().c('show').t(this.get('status')).up().c('status').t(status_message));
converse.connection.send($pres().c('show').t(this.get('status')).up().c('status').t(status_message));
this.save({'status_message': status_message});
}
});
xmppchat.XMPPStatusView = Backbone.View.extend({
converse.XMPPStatusView = Backbone.View.extend({
el: "span#xmpp-status-holder",
events: {
......@@ -1756,7 +1799,7 @@
if (!(_.has(model.changed, 'status')) && !(_.has(model.changed, 'status_message'))) {
return;
}
var stat = model.get('status'),
var stat = model.get('status'),
status_message = model.get('status_message') || "I am " + this.getPrettyStatus(stat);
this.$el.find('#fancy-xmpp-status-select').html(
this.status_template({
......@@ -1811,16 +1854,16 @@
}
});
xmppchat.LoginPanel = Backbone.View.extend({
converse.LoginPanel = Backbone.View.extend({
tagName: 'div',
id: "login-dialog",
events: {
'submit form#xmppchat-login': 'authenticate'
'submit form#converse-login': 'authenticate'
},
tab_template: _.template(
'<li><a class="current" href="#login">Sign in</a></li>'),
template: _.template(
'<form id="xmppchat-login">' +
'<form id="converse-login">' +
'<label>XMPP ID:</label>' +
'<input type="text" id="jid">' +
'<label>Password:</label>' +
......@@ -1833,30 +1876,53 @@
authenticate: function (ev) {
ev.preventDefault();
var $form = $(ev.target),
bosh_service_url = $form.find('input#bosh_service_url').val(),
jid = $form.find('input#jid').val(),
password = $form.find('input#password').val(),
$bsu_input = $form.find('input#bosh_service_url'),
bosh_service_url = $bsu_input.val(),
$jid_input = $form.find('input#jid'),
jid = $jid_input.val(),
$pw_input = $form.find('input#password'),
password = $pw_input.val(),
connection = new Strophe.Connection(bosh_service_url);
var errors = false;
if (! jid) {
errors = true;
$jid_input.addClass('error');
}
if (! password) {
errors = true;
$pw_input.addClass('error');
}
if (! bosh_service_url) {
errors = true;
$bsu_input.addClass('error');
}
if (errors) { return; }
// Clear the form's fields, so that it can't be submitted twice
$bsu_input.val('');
$jid_input.val('');
$pw_input.val('');
connection.connect(jid, password, $.proxy(function (status) {
if (status === Strophe.Status.CONNECTED) {
$(document).trigger('jarnxmpp.connected', connection);
console.log('Connected');
converse.onConnected(connection);
} else if (status === Strophe.Status.DISCONNECTED) {
console.log('Disconnected');
$(document).trigger('jarnxmpp.disconnected');
this.$feedback.text('Unable to communicate with chat server').css('background-image', "url(images/error_icon.png)");
} else if (status === Strophe.Status.Error) {
console.log('Error');
} else if (status === Strophe.Status.CONNECTING) {
console.log('Connecting');
$(document).trigger('jarnxmpp.connecting');
this.$feedback.text('Connecting to chat...');
} else if (status === Strophe.Status.CONNFAIL) {
console.log('Connection Failed');
} else if (status === Strophe.Status.AUTHENTICATING) {
console.log('Authenticating');
xmppchat.giveFeedback('Authenticating');
converse.giveFeedback('Authenticating');
} else if (status === Strophe.Status.AUTHFAIL) {
console.log('Authenticating Failed');
xmppchat.giveFeedback('Authentication failed');
converse.giveFeedback('Authentication failed');
} else if (status === Strophe.Status.DISCONNECTING) {
console.log('Disconnecting');
} else if (status === Strophe.Status.ATTACHED) {
......@@ -1877,7 +1943,7 @@
}
});
xmppchat.showControlBox = function () {
converse.showControlBox = function () {
var controlbox = this.chatboxes.get('controlbox');
if (!controlbox) {
this.chatboxes.add({
......@@ -1893,7 +1959,7 @@
}
}
xmppchat.toggleControlBox = function () {
converse.toggleControlBox = function () {
if ($("div#controlbox").is(':visible')) {
var controlbox = this.chatboxes.get('controlbox');
if (this.connection) {
......@@ -1906,28 +1972,14 @@
}
};
xmppchat.giveFeedback = function (message) {
converse.giveFeedback = function (message) {
$('.conn-feedback').text(message);
}
xmppchat.initialize = function () {
var chatdata = $('div#collective-xmpp-chat-data');
this.fullname = chatdata.attr('fullname');
this.prebind = chatdata.attr('prebind');
this.auto_subscribe = chatdata.attr('auto_subscribe') === "True" || false;
this.chatboxes = new this.ChatBoxes();
this.chatboxesview = new this.ChatBoxesView({model: this.chatboxes});
$('a.toggle-online-users').bind(
'click',
$.proxy(function (e) {
e.preventDefault(); this.toggleControlBox();
}, this)
);
},
converse.onConnected = function (connection) {
this.connection = connection;
xmppchat.onConnected = function (connection) {
this.animate = true; // Use animations
this.connection = connection;
this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); };
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
......@@ -1975,22 +2027,22 @@
this.giveFeedback('Online Contacts');
};
// Event handlers
// --------------
$(document).ready($.proxy(function () {
this.initialize();
$(document).bind('jarnxmpp.connecting', $.proxy(function (ev, conn) {
this.giveFeedback('Connecting to chat...');
}, this));
$(document).bind('jarnxmpp.disconnected', $.proxy(function (ev, conn) {
this.giveFeedback('Unable to communicate with chat server').css('background-image', "url(images/error_icon.png)");
console.log("Connection Failed :(");
}, this));
$(document).unbind('jarnxmpp.connected');
$(document).bind('jarnxmpp.connected', $.proxy(function (ev, connection) {
this.onConnected(connection);
}, this));
}, xmppchat));
converse.initialize = function (settings) {
this.prebind = settings.prebind;
this.fullname = settings.fullname;
this.xhr_user_search = settings.xhr_user_search;
this.auto_subscribe = settings.auto_subscribe;
this.animate = settings.animate;
this.chatboxes = new this.ChatBoxes();
this.chatboxesview = new this.ChatBoxesView({model: this.chatboxes});
$('a.toggle-online-users').bind(
'click',
$.proxy(function (e) {
e.preventDefault(); this.toggleControlBox();
}, this)
);
};
return xmppchat;
return converse;
}));
=========================
Converse.js Documentation
=========================
------------------------------------
Converse.js configuration variables:
------------------------------------
Prebind
========
Use this option if you don't want to render the login form on the chat control
box.
When set to true, the onConnected method needs to be called manually, together
with a Strophe connection object.
The most likely usecase is if you want to already authenticate on the backend
and merely attach to that connection in the browser.
Besides requiring the back-end to authenticate you, you'll also
have to write a Javascript snippet to attach to the set up connection::
$.JSON({
'url': 'mysite.com/xmpp-authenticate',
'success': function (data) {
connection = new Strophe.Connection(data.BOSH_SERVICE_URL);
connection.attach(data.jid, data.sid, data.rid, converse.onConnected);
}
fullname
========
If you are using prebinding, you need to specify the fullname of the currently
logged in user.
xhr_user_search
===============
There are two ways to add users.
* The user inputs a valid JID (Jabber ID), and the user is added as a pending
contact.
* The user inputs some text (for example part of a firstname or lastname), an XHR will be made to a backend, and a list of matches are returned. The user can then choose one of the matches to add as a contact.
This setting enables the second mechanism, otherwise by default the first will
be used.
auto_subscribe
==============
If true, the user will automatically subscribe back to any contact requests.
animate
=======
Show animations, for example when opening and closing chat boxes.
......@@ -29,16 +29,16 @@
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<p><strong>Converse.js</strong> implements an <a href="http://xmpp.org" target="_blank">XMPP</a> based instant messaging client in the browser.</p>
<p><strong>Converse.js</strong> is a web based <a href="http://xmpp.org" target="_blank">XMPP/Jabber</a> instant messaging client.</p>
<p>It is used by <a href="http://github.com/collective/collective.xmpp.chat" target="_blank">collective.xmpp.chat</a>, which is a <a href="http://plone.org" target="_blank">Plone</a> instant messaging add-on.</p>
<p>The ultimate goal is to enable anyone to add chat functionality to their websites, regardless of the backend.</p>
<p>This is currently possible, except for adding new contacts, which still makes an XHR call to the (Plone) backend to fetch user info.</p>
<p>The ultimate goal is to enable anyone to add chat functionality to their websites, independent of any backend.
You will however need an XMPP server to connect to, either your own, or a public one.</p>
<h2>Features</h2>
<ul>
<li>Manually or automically subscribe to other users.</li>
<li>Accept or decline contact requests</li>
<li>Chat status (online, busy, away, offline)</li>
<li>Chat statuses (online, busy, away, offline)</li>
<li>Custom status messages</li>
<li>Typing notifications</li>
<li>Third person messages (/me )</li>
......@@ -131,10 +131,10 @@
</div>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">try { var pageTracker = _gat._getTracker("UA-2128260-8"); pageTracker._trackPageview(); } catch(err) {}</script>
</body>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">try { var pageTracker = _gat._getTracker("UA-2128260-8"); pageTracker._trackPageview(); } catch(err) {}</script>
</body>
</html>
require(["jquery", "converse"], function($) {});
require(["jquery", "converse"], function($, converse) {
converse.initialize({
prebind: false,
xhr_user_search: false,
auto_subscribe: false
});
});
(function (root, factory) {
define([
"converse"
], function (xmppchat) {
return factory(xmppchat);
], function (converse) {
return factory(converse);
}
);
} (this, function (xmppchat) {
} (this, function (converse) {
return describe("Converse.js", $.proxy(function() {
// Names from http://www.fakenamegenerator.com/
......@@ -21,13 +21,12 @@
'Lena Grunewald', 'Laura Grunewald', 'Mandy Seiler', 'Sven Bosch', 'Nuriye Cuypers'
];
var num_contacts = req_names.length + pend_names.length + cur_names.length;
this.bare_jid = 'dummy@localhost';
mock_connection = {
'muc': {
'listRooms': function () {},
'join': function () {}
},
'jid': this.bare_jid,
'jid': 'dummy@localhost',
'addHandler': function (handler, ns, name, type, id, from, options) {
return function () {};
},
......@@ -54,9 +53,13 @@
// Clear localStorage
window.localStorage.clear();
this.prebind = true;
this.initialize({
prebind: false,
xhr_user_search: false,
auto_subscribe: false,
animate: false
});
this.onConnected(mock_connection);
this.animate = false; // don't use animations
// Variable declarations for specs
var open_controlbox;
......@@ -64,7 +67,7 @@
describe("The Control Box", $.proxy(function () {
it("is not shown by default", $.proxy(function () {
expect(this.rosterview.$el.is(':visible')).toEqual(false);
}, xmppchat));
}, converse));
open_controlbox = $.proxy(function () {
// This spec will only pass if the controlbox is not currently
......@@ -74,7 +77,7 @@
$('.toggle-online-users').click();
expect(this.toggleControlBox).toHaveBeenCalled();
expect($("div#controlbox").is(':visible')).toBe(true);
}, xmppchat);
}, converse);
it("can be opened by clicking a DOM element with class 'toggle-online-users'", open_controlbox);
describe("The Status Widget", $.proxy(function () {
......@@ -100,8 +103,8 @@
expect(view.updateStatusUI).toHaveBeenCalled();
expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
expect(view.$el.find('a.choose-xmpp-status span.value').text()).toBe('I am online');
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
it("can be used to set a custom status message", $.proxy(function () {
var view = this.xmppstatusview;
......@@ -123,17 +126,17 @@
expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
expect(view.$el.find('a.choose-xmpp-status span.value').text()).toBe(msg);
});
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
}, xmppchat));
}, converse));
describe("The Contacts Roster", $.proxy(function () {
describe("Pending Contacts", $.proxy(function () {
it("do not have a heading if there aren't any", $.proxy(function () {
expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
}, xmppchat));
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
var i, t, is_last;
......@@ -159,17 +162,17 @@
t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').text();
expect(t).toEqual(pend_names.slice(0,i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('block');
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
describe("Existing Contacts", $.proxy(function () {
it("do not have a heading if there aren't any", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('none');
}, xmppchat));
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
var i, t;
......@@ -187,11 +190,11 @@
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('block');
}, xmppchat));
}, converse));
it("can change their status to online and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
......@@ -209,7 +212,7 @@
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
......@@ -226,7 +229,7 @@
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
expect(t).toEqual(cur_names.slice(3,i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("can change their status to away and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
......@@ -244,7 +247,7 @@
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
expect(t).toEqual(cur_names.slice(6,i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
......@@ -262,7 +265,7 @@
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
expect(t).toEqual(cur_names.slice(9, i+1).sort().join(''));
}
}, xmppchat));
}, converse));
it("are ordered according to status: online, busy, away, unavailable, offline", $.proxy(function () {
var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
......@@ -287,17 +290,17 @@
for (i=12; i<cur_names.length; i++) {
expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
}
}, xmppchat));
}, converse));
}, xmppchat));
}, converse));
describe("Requesting Contacts", $.proxy(function () {
// by default the dts are hidden from css class and only later they will be hidden
// by jQuery therefore for the first check we will see if visible instead of none
it("do not have a heading if there aren't any", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
}, xmppchat));
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
var i, t;
......@@ -319,11 +322,11 @@
// be opened.
expect(this.showControlBox).toHaveBeenCalled();
}
}, xmppchat));
}, converse));
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contact-requests').css('display')).toEqual('block');
}, xmppchat));
}, converse));
it("can have their requests accepted by the user", $.proxy(function () {
// TODO: Testing can be more thorough here, the user is
......@@ -338,7 +341,7 @@
accept_button.click();
expect(view.acceptRequest).toHaveBeenCalled();
expect(this.connection.roster.authorize).toHaveBeenCalled();
}, xmppchat));
}, converse));
it("can have their requests denied by the user", $.proxy(function () {
var jid = req_names.sort()[1].replace(' ','.').toLowerCase() + '@localhost';
......@@ -354,8 +357,8 @@
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
// There should now be one less contact
expect(this.roster.length).toEqual(num_contacts-1);
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
describe("All Contacts", $.proxy(function () {
......@@ -384,7 +387,7 @@
expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
}
this.rosterview.render();
}, xmppchat));
}, converse));
afterEach($.proxy(function () {
// Contacts retrieved from localStorage have chat_status of
......@@ -396,12 +399,24 @@
view = this.rosterview.rosteritemviews[jid];
view.model.set('chat_status', 'online');
}
}, xmppchat));
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
}, converse));
describe("The 'Add Contact' widget", $.proxy(function () {
it("opens up an add form when you click on it", $.proxy(function () {
var panel = this.chatboxesview.views.controlbox.contactspanel;
spyOn(panel, 'toggleContactForm').andCallThrough();
panel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
panel.$el.find('a.toggle-xmpp-contact-form').click();
expect(panel.toggleContactForm).toHaveBeenCalled();
// XXX: Awaiting more tests, close it again for now...
panel.$el.find('a.toggle-xmpp-contact-form').click();
}, converse));
}, converse));
describe("A Chatbox", $.proxy(function () {
it("is created when you click on a roster item", $.proxy(function () {
var i, $el, click, jid, view;
// showControlBox was called earlier, so the controlbox is
......@@ -419,7 +434,7 @@
expect(view.openChat).toHaveBeenCalled();
expect(this.chatboxes.length).toEqual(i+2);
}
}, xmppchat));
}, converse));
it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
// We instantiate a new ChatBoxes collection, which by default
......@@ -439,7 +454,7 @@
expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
}
this.rosterview.render();
}, xmppchat));
}, converse));
it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
var chatbox, view, $el,
......@@ -452,7 +467,7 @@
view.$el.find('.close-chatbox-button').click();
expect(view.closeChat).toHaveBeenCalled();
}
}, xmppchat));
}, converse));
it("will be removed from localStorage when closed", $.proxy(function () {
var newchatboxes = new this.ChatBoxes();
......@@ -465,7 +480,7 @@
// Lets open the controlbox again, purely for visual feedback
open_controlbox();
}, xmppchat));
}, converse));
describe("A Chat Message", $.proxy(function () {
it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
......@@ -473,7 +488,7 @@
var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
msg = $msg({
from: sender_jid,
to: this.bare_jid,
to: this.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('body').t(message).up()
......@@ -488,7 +503,7 @@
// messageReceived is a handler for received XMPP
// messages
this.chatboxes.messageReceived(msg);
}, xmppchat));
}, converse));
waits(500);
runs($.proxy(function () {
// Since we didn't already have an open chatbox, one
......@@ -514,8 +529,8 @@
// chatbox in the DOM
var txt = chatboxview.$el.find('.chat-content').find('.chat-message').find('.chat-message-content').text();
expect(txt).toEqual(message);
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
var contact_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
......@@ -528,9 +543,9 @@
expect(view.model.messages.length, 2);
var txt = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content').text();
expect(txt).toEqual(message);
}, xmppchat));
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
}, converse));
describe("The Controlbox Tabs", $.proxy(function () {
it("consist of two tabs, 'Contacts' and 'ChatRooms', of which 'Contacts' is by default visible", $.proxy(function () {
......@@ -541,7 +556,7 @@
expect($panels.children().first().is(':visible')).toBe(true);
expect($panels.children().last().attr('id')).toBe('chatrooms');
expect($panels.children().last().is(':visible')).toBe(false);
}, xmppchat));
}, converse));
describe("The Chatrooms Panel", $.proxy(function () {
......@@ -562,7 +577,7 @@
expect($chatrooms.is(':visible')).toBe(true);
expect(cbview.switchTab).toHaveBeenCalled();
});
}, xmppchat));
}, converse));
it("contains a form through which a new chatroom can be created", $.proxy(function () {
var roomspanel = this.chatboxesview.views.controlbox.roomspanel;
......@@ -582,13 +597,9 @@
waits('250');
runs($.proxy(function () {
expect($('.chatroom').length).toBe(1); // There should now be an open chatroom
}, xmppchat));
}, xmppchat));
}, xmppchat));
}, xmppchat));
}, xmppchat));
}, converse));
}, converse));
}, converse));
}, converse));
}, converse));
}));
......@@ -29,5 +29,11 @@
</a>
</div>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">try { var pageTracker = _gat._getTracker("UA-2128260-8"); pageTracker._trackPageview(); } catch(err) {}</script>
</body>
</html>
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