Commit 4caabf50 authored by JC Brand's avatar JC Brand

Merge branch 'master' into gh-pages

Conflicts:
	index.html
parents 7850b3a5 e3899134
/*
Copyright 2010, François de Metz <francois@2metz.fr>
*/
/**
* Disco Strophe Plugin
* Implement http://xmpp.org/extensions/xep-0030.html
* TODO: manage node hierarchies, and node on info request
*/
Strophe.addConnectionPlugin('disco',
{
_connection: null,
_identities : [],
_features : [],
_items : [],
/** Function: init
* Plugin init
*
* Parameters:
* (Strophe.Connection) conn - Strophe connection
*/
init: function(conn)
{
this._connection = conn;
this._identities = [];
this._features = [];
this._items = [];
// disco info
conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
// disco items
conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null);
},
/** Function: addIdentity
* See http://xmpp.org/registrar/disco-categories.html
* Parameters:
* (String) category - category of identity (like client, automation, etc ...)
* (String) type - type of identity (like pc, web, bot , etc ...)
* (String) name - name of identity in natural language
* (String) lang - lang of name parameter
*
* Returns:
* Boolean
*/
addIdentity: function(category, type, name, lang)
{
for (var i=0; i<this._identities.length; i++)
{
if (this._identities[i].category == category &&
this._identities[i].type == type &&
this._identities[i].name == name &&
this._identities[i].lang == lang)
{
return false;
}
}
this._identities.push({category: category, type: type, name: name, lang: lang});
return true;
},
/** Function: addFeature
*
* Parameters:
* (String) var_name - feature name (like jabber:iq:version)
*
* Returns:
* boolean
*/
addFeature: function(var_name)
{
for (var i=0; i<this._features.length; i++)
{
if (this._features[i] == var_name)
return false;
}
this._features.push(var_name);
return true;
},
/** Function: removeFeature
*
* Parameters:
* (String) var_name - feature name (like jabber:iq:version)
*
* Returns:
* boolean
*/
removeFeature: function(var_name)
{
for (var i=0; i<this._features.length; i++)
{
if (this._features[i] === var_name){
this._features.splice(i,1)
return true;
}
}
return false;
},
/** Function: addItem
*
* Parameters:
* (String) jid
* (String) name
* (String) node
* (Function) call_back
*
* Returns:
* boolean
*/
addItem: function(jid, name, node, call_back)
{
if (node && !call_back)
return false;
this._items.push({jid: jid, name: name, node: node, call_back: call_back});
return true;
},
/** Function: info
* Info query
*
* Parameters:
* (Function) call_back
* (String) jid
* (String) node
*/
info: function(jid, node, success, error, timeout)
{
var attrs = {xmlns: Strophe.NS.DISCO_INFO};
if (node)
attrs.node = node;
var info = $iq({from:this._connection.jid,
to:jid, type:'get'}).c('query', attrs);
this._connection.sendIQ(info, success, error, timeout);
},
/** Function: items
* Items query
*
* Parameters:
* (Function) call_back
* (String) jid
* (String) node
*/
items: function(jid, node, success, error, timeout)
{
var attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
if (node)
attrs.node = node;
var items = $iq({from:this._connection.jid,
to:jid, type:'get'}).c('query', attrs);
this._connection.sendIQ(items, success, error, timeout);
},
/** PrivateFunction: _buildIQResult
*/
_buildIQResult: function(stanza, query_attrs)
{
var id = stanza.getAttribute('id');
var from = stanza.getAttribute('from');
var iqresult = $iq({type: 'result', id: id});
if (from !== null) {
iqresult.attrs({to: from});
}
return iqresult.c('query', query_attrs);
},
/** PrivateFunction: _onDiscoInfo
* Called when receive info request
*/
_onDiscoInfo: function(stanza)
{
var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
var attrs = {xmlns: Strophe.NS.DISCO_INFO};
if (node)
{
attrs.node = node;
}
var iqresult = this._buildIQResult(stanza, attrs);
for (var i=0; i<this._identities.length; i++)
{
var attrs = {category: this._identities[i].category,
type : this._identities[i].type};
if (this._identities[i].name)
attrs.name = this._identities[i].name;
if (this._identities[i].lang)
attrs['xml:lang'] = this._identities[i].lang;
iqresult.c('identity', attrs).up();
}
for (var i=0; i<this._features.length; i++)
{
iqresult.c('feature', {'var':this._features[i]}).up();
}
this._connection.send(iqresult.tree());
return true;
},
/** PrivateFunction: _onDiscoItems
* Called when receive items request
*/
_onDiscoItems: function(stanza)
{
var query_attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
if (node)
{
query_attrs.node = node;
var items = [];
for (var i = 0; i < this._items.length; i++)
{
if (this._items[i].node == node)
{
items = this._items[i].call_back(stanza);
break;
}
}
}
else
{
var items = this._items;
}
var iqresult = this._buildIQResult(stanza, query_attrs);
for (var i = 0; i < items.length; i++)
{
var attrs = {jid: items[i].jid};
if (items[i].name)
attrs.name = items[i].name;
if (items[i].node)
attrs.node = items[i].node;
iqresult.c('item', attrs).up();
}
this._connection.send(iqresult.tree());
return true;
}
});
...@@ -1917,7 +1917,6 @@ Strophe.Handler.prototype = { ...@@ -1917,7 +1917,6 @@ Strophe.Handler.prototype = {
try { try {
result = this.handler(elem); result = this.handler(elem);
} catch (e) { } catch (e) {
result = this.handler(elem);
if (e.sourceURL) { if (e.sourceURL) {
Strophe.fatal("error: " + this.handler + Strophe.fatal("error: " + this.handler +
" " + e.sourceURL + ":" + " " + e.sourceURL + ":" +
......
...@@ -415,6 +415,19 @@ form.search-xmpp-contact input { ...@@ -415,6 +415,19 @@ form.search-xmpp-contact input {
margin-top: 0.5em; margin-top: 0.5em;
} }
#available-chatrooms {
height: 225px;
overflow-y: scroll;
}
#available-chatrooms dd {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
width: 160px;
}
#available-chatrooms dt, #available-chatrooms dt,
#converse-roster dt { #converse-roster dt {
font-weight: normal; font-weight: normal;
...@@ -430,11 +443,6 @@ form.search-xmpp-contact input { ...@@ -430,11 +443,6 @@ form.search-xmpp-contact input {
padding-top: 1em; padding-top: 1em;
} }
#available-chatrooms li {
display: block;
}
dd.available-chatroom, dd.available-chatroom,
#converse-roster dd { #converse-roster dd {
font-weight: bold; font-weight: bold;
......
/*! /*!
* Converse.js (XMPP-based instant messaging with Strophe.js and backbone.js) * Converse.js (Web-based XMPP instant messaging client)
* http://conversejs.org * http://conversejs.org
* *
* Copyright (c) 2012 Jan-Carel Brand (jc@opkode.com) * Copyright (c) 2012, Jan-Carel Brand <jc@opkode.com>
* Dual licensed under the MIT and GPL Licenses * Dual licensed under the MIT and GPL Licenses
*/ */
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
"strophe": "Libraries/strophe", "strophe": "Libraries/strophe",
"strophe.muc": "Libraries/strophe.muc", "strophe.muc": "Libraries/strophe.muc",
"strophe.roster": "Libraries/strophe.roster", "strophe.roster": "Libraries/strophe.roster",
"strophe.vcard": "Libraries/strophe.vcard" "strophe.vcard": "Libraries/strophe.vcard",
"strophe.disco": "Libraries/strophe.disco"
}, },
// define module dependencies for modules not using define // define module dependencies for modules not using define
...@@ -38,22 +39,11 @@ ...@@ -38,22 +39,11 @@
//module value. //module value.
exports: 'Backbone' exports: 'Backbone'
}, },
'underscore': { exports: '_' },
'underscore': { 'strophe.muc': { deps: ['strophe', 'jquery'] },
exports: '_' 'strophe.roster': { deps: ['strophe', 'jquery'] },
}, 'strophe.vcard': { deps: ['strophe', 'jquery'] },
'strophe.disco': { deps: ['strophe', 'jquery'] }
'strophe.muc': {
deps: ['strophe', 'jquery']
},
'strophe.roster': {
deps: ['strophe', 'jquery']
},
'strophe.vcard': {
deps: ['strophe', 'jquery']
}
} }
}); });
...@@ -63,7 +53,8 @@ ...@@ -63,7 +53,8 @@
"sjcl", "sjcl",
"strophe.muc", "strophe.muc",
"strophe.roster", "strophe.roster",
"strophe.vcard" "strophe.vcard",
"strophe.disco"
], function() { ], function() {
// Use Mustache style syntax for variable interpolation // Use Mustache style syntax for variable interpolation
_.templateSettings = { _.templateSettings = {
...@@ -82,7 +73,6 @@ ...@@ -82,7 +73,6 @@
root.converse = factory(jQuery, _, console || {log: function(){}}); root.converse = factory(jQuery, _, console || {log: function(){}});
} }
}(this, function ($, _, console) { }(this, function ($, _, console) {
var converse = {}; var converse = {};
converse.msg_counter = 0; converse.msg_counter = 0;
...@@ -728,11 +718,10 @@ ...@@ -728,11 +718,10 @@
this.on('update-rooms-list', function (ev) { this.on('update-rooms-list', function (ev) {
this.updateRoomsList(); this.updateRoomsList();
}); });
this.trigger('update-rooms-list');
}, },
updateRoomsList: function () { updateRoomsList: function () {
converse.connection.muc.listRooms(converse.muc_domain, $.proxy(function (iq) { converse.connection.muc.listRooms(this.muc_domain, $.proxy(function (iq) {
var name, jid, i, var name, jid, i,
rooms = $(iq).find('query').find('item'), rooms = $(iq).find('query').find('item'),
rooms_length = rooms.length, rooms_length = rooms.length,
...@@ -744,7 +733,7 @@ ...@@ -744,7 +733,7 @@
$available_chatrooms.find('dt').hide(); $available_chatrooms.find('dt').hide();
} }
for (i=0; i<rooms_length; i++) { for (i=0; i<rooms_length; i++) {
name = Strophe.unescapeNode($(rooms[i]).attr('name')); name = Strophe.unescapeNode($(rooms[i]).attr('name')||$(rooms[i]).attr('jid'));
jid = $(rooms[i]).attr('jid'); jid = $(rooms[i]).attr('jid');
$available_chatrooms.append(this.room_template({'name':name, 'jid':jid})); $available_chatrooms.append(this.room_template({'name':name, 'jid':jid}));
} }
...@@ -762,7 +751,7 @@ ...@@ -762,7 +751,7 @@
name = input.val().trim().toLowerCase(); name = input.val().trim().toLowerCase();
input.val(''); // Clear the input input.val(''); // Clear the input
if (name) { if (name) {
jid = Strophe.escapeNode(name) + '@' + converse.muc_domain; jid = Strophe.escapeNode(name) + '@' + this.muc_domain;
} else { } else {
return; return;
} }
...@@ -790,8 +779,16 @@ ...@@ -790,8 +779,16 @@
initialize: function () { initialize: function () {
this.$el.appendTo(converse.chatboxesview.$el); this.$el.appendTo(converse.chatboxesview.$el);
this.model.on('change', $.proxy(function (item, changed) { this.model.on('change', $.proxy(function (item, changed) {
var i;
if (_.has(item.changed, 'connected')) { if (_.has(item.changed, 'connected')) {
this.render(); this.render();
converse.features.on('add', $.proxy(this.featureAdded, this));
// Features could have been added before the controlbox was
// initialized. Currently we're only interested in MUC
var feature = converse.features.findWhere({'var': 'http://jabber.org/protocol/muc'});
if (feature) {
this.featureAdded(feature);
}
} }
if (_.has(item.changed, 'visible')) { if (_.has(item.changed, 'visible')) {
if (item.changed.visible === true) { if (item.changed.visible === true) {
...@@ -799,7 +796,6 @@ ...@@ -799,7 +796,6 @@
} }
} }
}, this)); }, this));
this.model.on('show', this.show, this); this.model.on('show', this.show, this);
this.model.on('destroy', this.hide, this); this.model.on('destroy', this.hide, this);
this.model.on('hide', this.hide, this); this.model.on('hide', this.hide, this);
...@@ -808,6 +804,17 @@ ...@@ -808,6 +804,17 @@
} }
}, },
featureAdded: function (feature) {
if (feature.get('var') == 'http://jabber.org/protocol/muc') {
if (!this.roomspanel) {
this.roomspanel = new converse.RoomsPanel();
this.roomspanel.muc_domain = feature.get('from');
this.roomspanel.$parent = this.$el;
this.roomspanel.render().trigger('update-rooms-list');
}
}
},
template: _.template( template: _.template(
'<div class="chat-head oc-chat-head">'+ '<div class="chat-head oc-chat-head">'+
'<ul id="controlbox-tabs"></ul>'+ '<ul id="controlbox-tabs"></ul>'+
...@@ -847,10 +854,6 @@ ...@@ -847,10 +854,6 @@
this.contactspanel = new converse.ContactsPanel(); this.contactspanel = new converse.ContactsPanel();
this.contactspanel.$parent = this.$el; this.contactspanel.$parent = this.$el;
this.contactspanel.render(); this.contactspanel.render();
// TODO: Only add the rooms panel if the server supports MUC
this.roomspanel = new converse.RoomsPanel();
this.roomspanel.$parent = this.$el;
this.roomspanel.render();
} }
return this; return this;
} }
...@@ -1842,8 +1845,7 @@ ...@@ -1842,8 +1845,7 @@
})); }));
// iterate through all the <option> elements and add option values // iterate through all the <option> elements and add option values
options.each(function(){ options.each(function(){
options_list.push(that.option_template({ options_list.push(that.option_template({'value': $(this).val(),
'value': $(this).val(),
'text': $(this).text() 'text': $(this).text()
})); }));
}); });
...@@ -1854,6 +1856,57 @@ ...@@ -1854,6 +1856,57 @@
} }
}); });
converse.Feature = Backbone.Model.extend();
converse.Features = Backbone.Collection.extend({
/* This collection stores Feature Models, representing features
* provided by available XMPP entities (e.g. servers)
*
* See XEP-0030 for more details: http://xmpp.org/extensions/xep-0030.html
*/
model: converse.Feature,
initialize: function () {
this.localStorage = new Backbone.LocalStorage(
hex_sha1('converse.features'+converse.bare_jid));
if (this.localStorage.records.length === 0) {
// localStorage is empty, so we've likely never queried this
// domain for features yet
converse.connection.disco.info(converse.domain, null, this.onInfo, this.onError);
converse.connection.disco.items(converse.domain, null, $.proxy(this.onItems, this), $.proxy(this.onError, this));
} else {
this.fetch({add:true});
}
},
onItems: function (stanza) {
$(stanza).find('query item').each($.proxy(function (idx, item) {
converse.connection.disco.info(
$(item).attr('jid'),
null,
$.proxy(this.onInfo, this),
$.proxy(this.onError, this));
}, this));
},
onInfo: function (stanza) {
var $stanza = $(stanza);
if (($stanza.find('identity[category=server][type=im]').length === 0) &&
($stanza.find('identity[category=conference][type=text]').length === 0)) {
// This isn't an IM server component
return;
}
$stanza.find('feature').each($.proxy(function (idx, feature) {
this.create({
'var': $(feature).attr('var'),
'from': $stanza.attr('from')
});
}, this));
},
onError: function (stanza) {
console.log("Error while doing service discovery");
}
});
converse.LoginPanel = Backbone.View.extend({ converse.LoginPanel = Backbone.View.extend({
tagName: 'div', tagName: 'div',
id: "login-dialog", id: "login-dialog",
...@@ -1868,7 +1921,7 @@ ...@@ -1868,7 +1921,7 @@
'<input type="text" id="jid">' + '<input type="text" id="jid">' +
'<label>Password:</label>' + '<label>Password:</label>' +
'<input type="password" id="password">' + '<input type="password" id="password">' +
'<input type="submit" name="submit"/>' + '<button type="submit">Log In</button>' +
'</form">'), '</form">'),
bosh_url_input: _.template( bosh_url_input: _.template(
...@@ -1946,6 +1999,7 @@ ...@@ -1946,6 +1999,7 @@
template.find('form').append(this.bosh_url_input); template.find('form').append(this.bosh_url_input);
} }
this.$parent.find('#controlbox-panes').append(this.$el.html(template)); this.$parent.find('#controlbox-panes').append(this.$el.html(template));
this.$el.find('input#jid').focus();
return this; return this;
} }
}); });
...@@ -1985,13 +2039,11 @@ ...@@ -1985,13 +2039,11 @@
converse.onConnected = function (connection) { converse.onConnected = function (connection) {
this.connection = connection; this.connection = connection;
this.animate = true; // Use animations
this.connection.xmlInput = function (body) { console.log(body); }; this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); }; this.connection.xmlOutput = function (body) { console.log(body); };
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid); this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
this.domain = Strophe.getDomainFromJid(this.connection.jid); this.domain = Strophe.getDomainFromJid(this.connection.jid);
this.muc_domain = 'conference.' + this.domain; this.features = new this.Features();
// Set up the roster // Set up the roster
this.roster = new this.RosterItems(); this.roster = new this.RosterItems();
......
...@@ -43,18 +43,19 @@ ...@@ -43,18 +43,19 @@
<h2>Features</h2> <h2>Features</h2>
<ul> <ul>
<li>Single and multi-user chat</li> <li>Single-user chat</li>
<li>Multi-user chat in chatrooms (<a href="http://xmpp.org/extensions/xep-0045.html">XEP 45</a>)</li>
<li>vCard support (<a href="http://xmpp.org/extensions/xep-0054.html">XEP 54</a>)</li>
<li>Service discovery (<a href="http://xmpp.org/extensions/xep-0030.html">XEP 30</a>)</li>
<li>Contact rosters</li> <li>Contact rosters</li>
<li>Manually or automically subscribe to other contacts</li> <li>Manually or automically subscribe to other contacts</li>
<li>Roster item exchange (<a href="http://xmpp.org/extensions/tmp/xep-0144-1.1.html">XEP 144</a>)</li>
<li>Accept or decline contact requests</li> <li>Accept or decline contact requests</li>
<li>Roster item exchange (<a href="http://xmpp.org/extensions/tmp/xep-0144-1.1.html">XEP 144</a>)</li>
<li>Chat statuses (online, busy, away, offline)</li> <li>Chat statuses (online, busy, away, offline)</li>
<li>Custom status messages</li> <li>Custom status messages</li>
<li>Typing notifications</li> <li>Typing notifications</li>
<li>Third person messages (/me )</li> <li>Third person messages (/me )</li>
<li>Multi-user chat in chatrooms (<a href="http://xmpp.org/extensions/xep-0045.html">XEP 45</a>)</li>
<li>Chatroom Topics</li> <li>Chatroom Topics</li>
<li>vCard support (<a href="http://xmpp.org/extensions/xep-0054.html">XEP 54</a>)</li>
</ul> </ul>
<h2>CMS Integration</h2> <h2>CMS Integration</h2>
...@@ -76,8 +77,7 @@ ...@@ -76,8 +77,7 @@
<h2>Demo</h2> <h2>Demo</h2>
<p><a href="#" class="chat toggle-online-users">Click this link</a> or click the link on the bottom right corner of this page.</a></p> <p><a href="#" class="chat toggle-online-users">Click this link</a> or click the link on the bottom right corner of this page.</a></p>
<p> <p>You can log in with any existing federated Jabber/XMPP account, or create a new one at any of these providers:
You can log in with any existing federated Jabber/XMPP account, or create a new one at any of these providers:
<ul> <ul>
<li><a href="http://jabber.org" target="_blank">jabber.org</a></li> <li><a href="http://jabber.org" target="_blank">jabber.org</a></li>
<li><a href="https://jappix.com" target="_blank">jappix.com</a></li> <li><a href="https://jappix.com" target="_blank">jappix.com</a></li>
...@@ -98,10 +98,7 @@ ...@@ -98,10 +98,7 @@
establish an authenticated connection on the server side and then attach to establish an authenticated connection on the server side and then attach to
this connection in your browser. this connection in your browser.
</p> </p>
<p><strong>Converse.js</strong> already supports this usecase, but you'll have to <p><strong>Converse.js</strong> already supports this usecase, but you'll have to do more manual work yourself.</p>
do more manual work yourself.
</p>
<h2>Tests</h2> <h2>Tests</h2>
</p> </p>
......
require(["jquery", "converse"], function($, converse) { require(["jquery", "converse"], function($, converse) {
converse.initialize({ converse.initialize({
animate: true,
bosh_service_url: 'https://bind.opkode.im', bosh_service_url: 'https://bind.opkode.im',
prebind: false, prebind: false,
xhr_user_search: false, xhr_user_search: false,
......
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