vines 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +2 -2
- data/Rakefile +63 -8
- data/bin/vines +0 -1
- data/conf/config.rb +16 -7
- data/lib/vines.rb +21 -16
- data/lib/vines/command/init.rb +5 -3
- data/lib/vines/config.rb +34 -0
- data/lib/vines/contact.rb +14 -0
- data/lib/vines/stanza.rb +26 -0
- data/lib/vines/stanza/iq.rb +1 -1
- data/lib/vines/stanza/iq/disco_info.rb +3 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/roster.rb +26 -30
- data/lib/vines/stanza/presence.rb +0 -12
- data/lib/vines/stanza/presence/subscribe.rb +3 -20
- data/lib/vines/stanza/presence/subscribed.rb +9 -10
- data/lib/vines/stanza/presence/unsubscribe.rb +8 -15
- data/lib/vines/stanza/presence/unsubscribed.rb +8 -8
- data/lib/vines/storage.rb +28 -0
- data/lib/vines/storage/couchdb.rb +29 -0
- data/lib/vines/storage/local.rb +22 -0
- data/lib/vines/storage/redis.rb +26 -0
- data/lib/vines/storage/sql.rb +48 -5
- data/lib/vines/stream/client.rb +6 -8
- data/lib/vines/stream/http.rb +23 -21
- data/lib/vines/stream/http/auth.rb +1 -1
- data/lib/vines/stream/http/bind.rb +1 -1
- data/lib/vines/stream/http/bind_restart.rb +4 -3
- data/lib/vines/stream/http/ready.rb +1 -1
- data/lib/vines/stream/http/request.rb +94 -5
- data/lib/vines/stream/http/session.rb +8 -6
- data/lib/vines/version.rb +1 -1
- data/test/config_test.rb +12 -0
- data/test/contact_test.rb +40 -0
- data/test/rake_test_loader.rb +11 -3
- data/test/stanza/iq/private_storage_test.rb +177 -0
- data/test/stanza/iq/roster_test.rb +1 -1
- data/test/stanza/iq_test.rb +63 -0
- data/test/storage/couchdb_test.rb +7 -1
- data/test/storage/local_test.rb +8 -2
- data/test/storage/redis_test.rb +16 -7
- data/test/storage/sql_test.rb +8 -1
- data/test/storage/storage_tests.rb +50 -0
- data/test/stream/http/auth_test.rb +3 -0
- data/test/stream/http/ready_test.rb +3 -0
- data/test/stream/http/request_test.rb +86 -0
- data/test/stream/parser_test.rb +2 -0
- data/web/404.html +43 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +385 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/coffeescripts/logout.coffee +5 -0
- data/web/chat/index.html +17 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/javascripts/chat.js +436 -0
- data/web/chat/javascripts/init.js +21 -0
- data/web/chat/javascripts/logout.js +11 -0
- data/web/chat/stylesheets/chat.css +290 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +52 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +211 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/javascripts/base.js +9 -0
- data/web/lib/javascripts/contact.js +94 -0
- data/web/lib/javascripts/icons.js +101 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +18 -0
- data/web/lib/javascripts/layout.js +48 -0
- data/web/lib/javascripts/login.js +61 -0
- data/web/lib/javascripts/navbar.js +69 -0
- data/web/lib/javascripts/raphael.js +8 -0
- data/web/lib/javascripts/router.js +105 -0
- data/web/lib/javascripts/session.js +322 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +223 -0
- data/web/lib/stylesheets/login.css +63 -0
- metadata +51 -9
@@ -0,0 +1,15 @@
|
|
1
|
+
$ ->
|
2
|
+
session = new Session()
|
3
|
+
nav = new NavBar(session)
|
4
|
+
nav.draw()
|
5
|
+
buttons =
|
6
|
+
Messages: ICONS.chat
|
7
|
+
Logout: ICONS.power
|
8
|
+
nav.addButton(label, icon) for label, icon of buttons
|
9
|
+
|
10
|
+
pages =
|
11
|
+
'/messages': new ChatPage(session)
|
12
|
+
'/logout': new LogoutPage(session)
|
13
|
+
'default': new LoginPage(session, '/messages/')
|
14
|
+
new Router(pages).draw()
|
15
|
+
nav.select $('#nav-link-messages').parent()
|
data/web/chat/index.html
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
7
|
+
<title>Vines</title>
|
8
|
+
<link rel="shortcut icon" type="image/png" href="/favicon.png">
|
9
|
+
<link rel="stylesheet" href="/lib/stylesheets/base.css">
|
10
|
+
<link rel="stylesheet" href="/lib/stylesheets/login.css">
|
11
|
+
<link rel="stylesheet" href="/chat/stylesheets/chat.css">
|
12
|
+
<script type="text/javascript" src="/lib/javascripts/base.js"></script>
|
13
|
+
<script type="text/javascript" src="/chat/javascripts/app.js"></script>
|
14
|
+
</head>
|
15
|
+
<body>
|
16
|
+
</body>
|
17
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
var ChatPage,__bind=function(a,b){return function(){return a.apply(b,arguments)}};ChatPage=function(){function a(a){this.session=a,this.session.onRoster(__bind(function(){return this.roster()},this)),this.session.onCard(__bind(function(a){return this.card(a)},this)),this.session.onMessage(__bind(function(a){return this.message(a)},this)),this.session.onPresence(__bind(function(a){return this.presence(a)},this)),this.chats={},this.currentContact=null}a.prototype.datef=function(a){var b,c,d,e;b=new Date(a),d=b.getHours()>=12?" pm":" am",c=b.getHours()>12?b.getHours()-12:b.getHours(),c===0&&(c=12),e=b.getMinutes()+"",e.length===1&&(e="0"+e);return c+":"+e+d},a.prototype.card=function(a){return this.eachContact(a.jid,__bind(function(b){return $(".vcard-img",b).attr("src",this.session.avatar(a.jid))},this))},a.prototype.roster=function(){var a,b,c,d,e,f,g,h;e=$("#roster"),$("li",e).each(__bind(function(a,b){var c;c=$(b).attr("data-jid");if(!this.session.roster[c])return $(b).remove()},this)),f=function(a,b){$(".text",a).text(b.name||b.jid);return a.attr("data-name",b.name||"")},g=this.session.roster,h=[];for(c in g)a=g[c],b=$("#roster li[data-jid='"+c+"']"),f(b,a),h.push(b.length===0?(d=$('<li data-jid="'+c+'" data-name="" class="offline">\n <span class="text"></span>\n <span class="status-msg">Offline</span>\n <span class="unread" style="display:none;"></span>\n <img class="vcard-img" alt="'+c+'" src="'+this.session.avatar(c)+'"/>\n</li>').appendTo(e),f(d,a),d.click(__bind(function(a){return this.selectContact(a)},this))):void 0);return h},a.prototype.message=function(a){var b,c,d,e;this.queueMessage(a),e=a.from===this.session.jid(),d=a.from.split("/")[0];if(!e&&d!==this.currentContact){c=this.chat(a.from),c.unread++;return this.eachContact(d,function(a){return $(".unread",a).text(c.unread).show()})}b=this.atBottom(),this.appendMessage(a);if(b)return this.scroll()},a.prototype.eachContact=function(a,b){var c,d,e,f,g;f=$("#roster li[data-jid='"+a+"']").get(),g=[];for(d=0,e=f.length;d<e;d++)c=f[d],g.push(b($(c)));return g},a.prototype.appendMessage=function(a){var b,c,d,e;c=a.from.split("/")[0],b=this.session.roster[c],d=b?b.name||c:c,a.from===this.session.jid()&&(d="Me"),e=$('<li data-jid="'+c+'" style="display:none;">\n <p></p>\n <img alt="'+c+'" src="'+this.session.avatar(c)+'"/>\n <footer>\n <span class="author"></span>\n <span class="time">'+this.datef(a.received)+"</span>\n </footer>\n</li>").appendTo("#messages"),$("p",e).text(a.text),$(".author",e).text(d);return e.fadeIn(200)},a.prototype.queueMessage=function(a){var b,c,d;d=a.from===this.session.jid(),c=a[d?"to":"from"],b=this.chat(c),b.jid=c;return b.messages.push(a)},a.prototype.chat=function(a){var b,c;b=a.split("/")[0],c=this.chats[b],c||(c={jid:a,messages:[],unread:0},this.chats[b]=c);return c},a.prototype.presence=function(a){var b,c,d;c=a.from.split("/")[0];if(c!==this.session.bareJid()){if(!a.type||a.offline)b=this.session.roster[c],this.eachContact(c,function(a){$(".status-msg",a).text(b.status());return b.offline()?a.addClass("offline"):a.removeClass("offline")});a.offline&&(this.chat(c).jid=c);if(a.type==="subscribe"){d=$('<li data-jid="'+a.from+'" style="display:none;">\n <form class="notify-form">\n <h2>Buddy Approval</h2>\n <p>'+a.from+' wants to add you as a buddy.</p>\n <fieldset class="buttons">\n <input type="button" value="Decline"/>\n <input type="submit" value="Accept"/>\n </fieldset>\n </form>\n</li>').appendTo("#notifications"),d.fadeIn(200),$("form",d).submit(__bind(function(){return this.acceptContact(d,a.from)},this));return $('input[type="button"]',d).click(__bind(function(){return this.rejectContact(d,a.from)},this))}}},a.prototype.acceptContact=function(a,b){a.fadeOut(200,function(){return a.remove()}),this.session.sendSubscribed(b),this.session.sendSubscribe(b);return!1},a.prototype.rejectContact=function(a,b){a.fadeOut(200,function(){return a.remove()});return this.session.sendUnsubscribed(b)},a.prototype.selectContact=function(a){var b,c,d,e,f,g,h;d=$(a.currentTarget).attr("data-jid"),c=this.session.roster[d];if(this.currentContact!==d){this.currentContact=d,$("#roster li").removeClass("selected"),$(a.currentTarget).addClass("selected"),$("#chat-title").text("Chat with "+(c.name||c.jid)),$("#messages").empty(),b=this.chats[d],e=[],b&&(e=b.messages,b.unread=0,this.eachContact(d,function(a){return $(".unread",a).text("").hide()}));for(g=0,h=e.length;g<h;g++)f=e[g],this.appendMessage(f);this.scroll(),$("#remove-contact-msg").html("Are you sure you want to remove "+("<strong>"+this.currentContact+"</strong> from your buddy list?")),$("#remove-contact-form .buttons").fadeIn(200),$("#edit-contact-jid").text(this.currentContact),$("#edit-contact-name").val(this.session.roster[this.currentContact].name),$("#edit-contact-form input").fadeIn(200);return $("#edit-contact-form .buttons").fadeIn(200)}},a.prototype.scroll=function(){var a;a=$("#messages");return a.animate({scrollTop:a.prop("scrollHeight")},400)},a.prototype.atBottom=function(){var a,b;b=$("#messages"),a=b.prop("scrollHeight")-b.height();return b.scrollTop()===a},a.prototype.send=function(){var a,b,c,d;if(!this.currentContact)return!1;b=$("#message"),d=b.val().trim(),d&&(a=this.chats[this.currentContact],c=a?a.jid:this.currentContact,this.message({from:this.session.jid(),text:d,to:c,received:new Date}),this.session.sendMessage(c,d)),b.val("");return!1},a.prototype.addContact=function(){var a;this.toggleForm("#add-contact-form"),a={jid:$("#add-contact-jid").val(),name:$("#add-contact-name").val(),groups:["Buddies"]},a.jid&&this.session.updateContact(a,!0);return!1},a.prototype.removeContact=function(){this.toggleForm("#remove-contact-form"),this.session.removeContact(this.currentContact),this.currentContact=null,$("#chat-title").text("Select a buddy to chat"),$("#messages").empty(),$("#remove-contact-msg").html("Select a buddy in the list above to remove."),$("#remove-contact-form .buttons").hide(),$("#edit-contact-jid").text("Select a buddy in the list above to update."),$("#edit-contact-name").val(""),$("#edit-contact-form input").hide(),$("#edit-contact-form .buttons").hide();return!1},a.prototype.updateContact=function(){var a;this.toggleForm("#edit-contact-form"),a={jid:this.currentContact,name:$("#edit-contact-name").val(),groups:this.session.roster[this.currentContact].groups},this.session.updateContact(a);return!1},a.prototype.toggleForm=function(a,b){a=$(a),$(".contact-form").each(function(){if(this.id!==a.attr("id"))return $(this).hide()});if(a.is(":hidden")){b&&b();return a.fadeIn(100)}return a.fadeOut(100,function(){a[0].reset();if(b)return b()})},a.prototype.filterRoster=function(){var a;a=$("#search-roster-text").val().toLowerCase();if(a==="")$("#roster li").show();else return $("#roster li").each(function(){var b,c,d,e;e=$(this),b=(e.attr("data-jid")||"").toLowerCase(),d=(e.attr("data-name")||"").toLowerCase(),c=b.indexOf(a)!==-1||d.indexOf(a)!==-1;return c?e.show():e.hide()})},a.prototype.draw=function(){if(!this.session.connected())window.location.hash="";else{$("body").attr("id","chat-page"),$("#container").hide().empty(),$('<div id="alpha" class="y-fill">\n <h2>Buddies <div id="search-roster"></div></h2>\n <form id="search-roster-form" style="display:none;">\n <input id="search-roster-text" type="search" placeholder="Filter" results="5"/>\n </form>\n <ul id="roster" class="y-fill"></ul>\n <div id="roster-controls">\n <div id="add-contact"></div>\n <div id="remove-contact"></div>\n <div id="edit-contact"></div>\n </div>\n <form id="add-contact-form" class="contact-form" style="display:none;">\n <h2>Add Buddy</h2>\n <input id="add-contact-jid" type="email" maxlength="1024" placeholder="Account name"/>\n <input id="add-contact-name" type="text" maxlength="1024" placeholder="Real name"/>\n <fieldset class="buttons">\n <input id="add-contact-cancel" type="button" value="Cancel"/>\n <input id="add-contact-ok" type="submit" value="Add"/>\n </fieldset>\n </form>\n <form id="remove-contact-form" class="contact-form" style="display:none;">\n <h2>Remove Buddy</h2>\n <p id="remove-contact-msg">Select a buddy in the list above to remove.</p>\n <fieldset class="buttons" style="display:none;">\n <input id="remove-contact-cancel" type="button" value="Cancel"/>\n <input id="remove-contact-ok" type="submit" value="Remove"/>\n </fieldset>\n </form>\n <form id="edit-contact-form" class="contact-form" style="display:none;">\n <h2>Update Profile</h2>\n <p id="edit-contact-jid">Select a buddy in the list above to update.</p>\n <input id="edit-contact-name" type="text" maxlength="1024" placeholder="Real name" style="display:none;"/>\n <fieldset class="buttons" style="display:none;">\n <input id="edit-contact-cancel" type="button" value="Cancel"/>\n <input id="edit-contact-ok" type="submit" value="Save"/>\n </fieldset>\n </form>\n</div>\n<div id="beta" class="x-fill y-fill">\n <h2 id="chat-title">Select a buddy to chat</h2>\n <ul id="messages" class="y-fill"></ul>\n <form id="message-form">\n <input id="message" name="message" type="text" maxlength="1024" placeholder="Type a message and press enter to send"/>\n </form>\n</div>\n<div id="charlie" class="y-fill">\n <h2>Notifications</h2>\n <ul id="notifications" class="y-fill"></ul>\n <div id="notification-controls">\n <div id="clear-notices"></div>\n </div>\n</div>').appendTo("#container"),this.roster(),this.button("clear-notices",ICONS.no),this.button("add-contact",ICONS.plus),this.button("remove-contact",ICONS.minus),this.button("edit-contact",ICONS.user),this.button("search-roster",ICONS.search,{scale:.5,translation:"-8 -8"}),$("#message").focus(function(){return $(".contact-form").fadeOut()}),$("#message-form").submit(__bind(function(){return this.send()},this)),$("#clear-notices").click(function(){return $("#notifications li").fadeOut(200)}),$("#add-contact").click(__bind(function(){return this.toggleForm("#add-contact-form")},this)),$("#remove-contact").click(__bind(function(){return this.toggleForm("#remove-contact-form")},this)),$("#edit-contact").click(__bind(function(){return this.toggleForm("#edit-contact-form",__bind(function(){if(this.currentContact){$("#edit-contact-jid").text(this.currentContact);return $("#edit-contact-name").val(this.session.roster[this.currentContact].name)}},this))},this)),$("#add-contact-cancel").click(__bind(function(){return this.toggleForm("#add-contact-form")},this)),$("#remove-contact-cancel").click(__bind(function(){return this.toggleForm("#remove-contact-form")},this)),$("#edit-contact-cancel").click(__bind(function(){return this.toggleForm("#edit-contact-form")},this)),$("#add-contact-form").submit(__bind(function(){return this.addContact()},this)),$("#remove-contact-form").submit(__bind(function(){return this.removeContact()},this)),$("#edit-contact-form").submit(__bind(function(){return this.updateContact()},this)),$("#search-roster-form").submit(function(){return!1}),$("#search-roster-text").keyup(__bind(function(){return this.filterRoster()},this)),$("#search-roster-text").change(__bind(function(){return this.filterRoster()},this)),$("#search-roster-text").click(__bind(function(){return this.filterRoster()},this)),$("#search-roster").click(__bind(function(){return this.toggleForm("#search-roster-form",__bind(function(){return this.filterRoster()},this))},this)),$("#container").fadeIn(200);return this.resize()}},a.prototype.resize=function(){var a,b,c,d,e;a=$("#alpha"),b=$("#beta"),c=$("#charlie"),e=$("#message"),d=$("#message-form");return new Layout(function(){c.css("left",a.width()+b.width());return e.width(d.width()-32)})},a.prototype.button=function(a,b,c){var d,e,f;c||(c={}),f=Raphael(a),d=f.path(b).attr({fill:"#000",stroke:"#fff","stroke-width":.3,opacity:.6,scale:c.scale||.85,translation:c.translation||""}),e=$("#"+a),e.hover(function(){return d.animate({opacity:1},200)},function(){return d.animate({opacity:.6},200)});return e.get(0)};return a}();var LogoutPage;LogoutPage=function(){function a(a){this.session=a}a.prototype.draw=function(){window.location.hash="";return window.location.reload()};return a}(),$(function(){var a,b,c,d,e,f;f=new Session,d=new NavBar(f),d.draw(),a={Messages:ICONS.chat,Logout:ICONS.power};for(c in a)b=a[c],d.addButton(c,b);e={"/messages":new ChatPage(f),"/logout":new LogoutPage(f),"default":new LoginPage(f,"/messages/")},(new Router(e)).draw();return d.select($("#nav-link-messages").parent())})
|
@@ -0,0 +1,436 @@
|
|
1
|
+
var ChatPage;
|
2
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
3
|
+
ChatPage = (function() {
|
4
|
+
function ChatPage(session) {
|
5
|
+
this.session = session;
|
6
|
+
this.session.onRoster(__bind(function() {
|
7
|
+
return this.roster();
|
8
|
+
}, this));
|
9
|
+
this.session.onCard(__bind(function(c) {
|
10
|
+
return this.card(c);
|
11
|
+
}, this));
|
12
|
+
this.session.onMessage(__bind(function(m) {
|
13
|
+
return this.message(m);
|
14
|
+
}, this));
|
15
|
+
this.session.onPresence(__bind(function(p) {
|
16
|
+
return this.presence(p);
|
17
|
+
}, this));
|
18
|
+
this.chats = {};
|
19
|
+
this.currentContact = null;
|
20
|
+
}
|
21
|
+
ChatPage.prototype.datef = function(millis) {
|
22
|
+
var d, hour, meridian, minutes;
|
23
|
+
d = new Date(millis);
|
24
|
+
meridian = d.getHours() >= 12 ? ' pm' : ' am';
|
25
|
+
hour = d.getHours() > 12 ? d.getHours() - 12 : d.getHours();
|
26
|
+
if (hour === 0) {
|
27
|
+
hour = 12;
|
28
|
+
}
|
29
|
+
minutes = d.getMinutes() + '';
|
30
|
+
if (minutes.length === 1) {
|
31
|
+
minutes = '0' + minutes;
|
32
|
+
}
|
33
|
+
return hour + ':' + minutes + meridian;
|
34
|
+
};
|
35
|
+
ChatPage.prototype.card = function(card) {
|
36
|
+
return this.eachContact(card.jid, __bind(function(node) {
|
37
|
+
return $('.vcard-img', node).attr('src', this.session.avatar(card.jid));
|
38
|
+
}, this));
|
39
|
+
};
|
40
|
+
ChatPage.prototype.roster = function() {
|
41
|
+
var contact, found, jid, node, roster, setName, _ref, _results;
|
42
|
+
roster = $('#roster');
|
43
|
+
$('li', roster).each(__bind(function(ix, node) {
|
44
|
+
var jid;
|
45
|
+
jid = $(node).attr('data-jid');
|
46
|
+
if (!this.session.roster[jid]) {
|
47
|
+
return $(node).remove();
|
48
|
+
}
|
49
|
+
}, this));
|
50
|
+
setName = function(node, contact) {
|
51
|
+
$('.text', node).text(contact.name || contact.jid);
|
52
|
+
return node.attr('data-name', contact.name || '');
|
53
|
+
};
|
54
|
+
_ref = this.session.roster;
|
55
|
+
_results = [];
|
56
|
+
for (jid in _ref) {
|
57
|
+
contact = _ref[jid];
|
58
|
+
found = $("#roster li[data-jid='" + jid + "']");
|
59
|
+
setName(found, contact);
|
60
|
+
_results.push(found.length === 0 ? (node = $("<li data-jid=\"" + jid + "\" data-name=\"\" class=\"offline\">\n <span class=\"text\"></span>\n <span class=\"status-msg\">Offline</span>\n <span class=\"unread\" style=\"display:none;\"></span>\n <img class=\"vcard-img\" alt=\"" + jid + "\" src=\"" + (this.session.avatar(jid)) + "\"/>\n</li>").appendTo(roster), setName(node, contact), node.click(__bind(function(event) {
|
61
|
+
return this.selectContact(event);
|
62
|
+
}, this))) : void 0);
|
63
|
+
}
|
64
|
+
return _results;
|
65
|
+
};
|
66
|
+
ChatPage.prototype.message = function(message) {
|
67
|
+
var bottom, chat, from, me;
|
68
|
+
this.queueMessage(message);
|
69
|
+
me = message.from === this.session.jid();
|
70
|
+
from = message.from.split('/')[0];
|
71
|
+
if (me || from === this.currentContact) {
|
72
|
+
bottom = this.atBottom();
|
73
|
+
this.appendMessage(message);
|
74
|
+
if (bottom) {
|
75
|
+
return this.scroll();
|
76
|
+
}
|
77
|
+
} else {
|
78
|
+
chat = this.chat(message.from);
|
79
|
+
chat.unread++;
|
80
|
+
return this.eachContact(from, function(node) {
|
81
|
+
return $('.unread', node).text(chat.unread).show();
|
82
|
+
});
|
83
|
+
}
|
84
|
+
};
|
85
|
+
ChatPage.prototype.eachContact = function(jid, callback) {
|
86
|
+
var node, _i, _len, _ref, _results;
|
87
|
+
_ref = $("#roster li[data-jid='" + jid + "']").get();
|
88
|
+
_results = [];
|
89
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
90
|
+
node = _ref[_i];
|
91
|
+
_results.push(callback($(node)));
|
92
|
+
}
|
93
|
+
return _results;
|
94
|
+
};
|
95
|
+
ChatPage.prototype.appendMessage = function(message) {
|
96
|
+
var contact, from, name, node;
|
97
|
+
from = message.from.split('/')[0];
|
98
|
+
contact = this.session.roster[from];
|
99
|
+
name = contact ? contact.name || from : from;
|
100
|
+
if (message.from === this.session.jid()) {
|
101
|
+
name = 'Me';
|
102
|
+
}
|
103
|
+
node = $("<li data-jid=\"" + from + "\" style=\"display:none;\">\n <p></p>\n <img alt=\"" + from + "\" src=\"" + (this.session.avatar(from)) + "\"/>\n <footer>\n <span class=\"author\"></span>\n <span class=\"time\">" + (this.datef(message.received)) + "</span>\n </footer>\n</li>").appendTo('#messages');
|
104
|
+
$('p', node).text(message.text);
|
105
|
+
$('.author', node).text(name);
|
106
|
+
return node.fadeIn(200);
|
107
|
+
};
|
108
|
+
ChatPage.prototype.queueMessage = function(message) {
|
109
|
+
var chat, full, me;
|
110
|
+
me = message.from === this.session.jid();
|
111
|
+
full = message[me ? 'to' : 'from'];
|
112
|
+
chat = this.chat(full);
|
113
|
+
chat.jid = full;
|
114
|
+
return chat.messages.push(message);
|
115
|
+
};
|
116
|
+
ChatPage.prototype.chat = function(jid) {
|
117
|
+
var bare, chat;
|
118
|
+
bare = jid.split('/')[0];
|
119
|
+
chat = this.chats[bare];
|
120
|
+
if (!chat) {
|
121
|
+
chat = {
|
122
|
+
jid: jid,
|
123
|
+
messages: [],
|
124
|
+
unread: 0
|
125
|
+
};
|
126
|
+
this.chats[bare] = chat;
|
127
|
+
}
|
128
|
+
return chat;
|
129
|
+
};
|
130
|
+
ChatPage.prototype.presence = function(presence) {
|
131
|
+
var contact, from, node;
|
132
|
+
from = presence.from.split('/')[0];
|
133
|
+
if (from === this.session.bareJid()) {
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
if (!presence.type || presence.offline) {
|
137
|
+
contact = this.session.roster[from];
|
138
|
+
this.eachContact(from, function(node) {
|
139
|
+
$('.status-msg', node).text(contact.status());
|
140
|
+
if (contact.offline()) {
|
141
|
+
return node.addClass('offline');
|
142
|
+
} else {
|
143
|
+
return node.removeClass('offline');
|
144
|
+
}
|
145
|
+
});
|
146
|
+
}
|
147
|
+
if (presence.offline) {
|
148
|
+
this.chat(from).jid = from;
|
149
|
+
}
|
150
|
+
if (presence.type === 'subscribe') {
|
151
|
+
node = $("<li data-jid=\"" + presence.from + "\" style=\"display:none;\">\n <form class=\"notify-form\">\n <h2>Buddy Approval</h2>\n <p>" + presence.from + " wants to add you as a buddy.</p>\n <fieldset class=\"buttons\">\n <input type=\"button\" value=\"Decline\"/>\n <input type=\"submit\" value=\"Accept\"/>\n </fieldset>\n </form>\n</li>").appendTo('#notifications');
|
152
|
+
node.fadeIn(200);
|
153
|
+
$('form', node).submit(__bind(function() {
|
154
|
+
return this.acceptContact(node, presence.from);
|
155
|
+
}, this));
|
156
|
+
return $('input[type="button"]', node).click(__bind(function() {
|
157
|
+
return this.rejectContact(node, presence.from);
|
158
|
+
}, this));
|
159
|
+
}
|
160
|
+
};
|
161
|
+
ChatPage.prototype.acceptContact = function(node, jid) {
|
162
|
+
node.fadeOut(200, function() {
|
163
|
+
return node.remove();
|
164
|
+
});
|
165
|
+
this.session.sendSubscribed(jid);
|
166
|
+
this.session.sendSubscribe(jid);
|
167
|
+
return false;
|
168
|
+
};
|
169
|
+
ChatPage.prototype.rejectContact = function(node, jid) {
|
170
|
+
node.fadeOut(200, function() {
|
171
|
+
return node.remove();
|
172
|
+
});
|
173
|
+
return this.session.sendUnsubscribed(jid);
|
174
|
+
};
|
175
|
+
ChatPage.prototype.selectContact = function(event) {
|
176
|
+
var chat, contact, jid, messages, msg, _i, _len;
|
177
|
+
jid = $(event.currentTarget).attr('data-jid');
|
178
|
+
contact = this.session.roster[jid];
|
179
|
+
if (this.currentContact === jid) {
|
180
|
+
return;
|
181
|
+
}
|
182
|
+
this.currentContact = jid;
|
183
|
+
$('#roster li').removeClass('selected');
|
184
|
+
$(event.currentTarget).addClass('selected');
|
185
|
+
$('#chat-title').text('Chat with ' + (contact.name || contact.jid));
|
186
|
+
$('#messages').empty();
|
187
|
+
chat = this.chats[jid];
|
188
|
+
messages = [];
|
189
|
+
if (chat) {
|
190
|
+
messages = chat.messages;
|
191
|
+
chat.unread = 0;
|
192
|
+
this.eachContact(jid, function(node) {
|
193
|
+
return $('.unread', node).text('').hide();
|
194
|
+
});
|
195
|
+
}
|
196
|
+
for (_i = 0, _len = messages.length; _i < _len; _i++) {
|
197
|
+
msg = messages[_i];
|
198
|
+
this.appendMessage(msg);
|
199
|
+
}
|
200
|
+
this.scroll();
|
201
|
+
$('#remove-contact-msg').html("Are you sure you want to remove " + ("<strong>" + this.currentContact + "</strong> from your buddy list?"));
|
202
|
+
$('#remove-contact-form .buttons').fadeIn(200);
|
203
|
+
$('#edit-contact-jid').text(this.currentContact);
|
204
|
+
$('#edit-contact-name').val(this.session.roster[this.currentContact].name);
|
205
|
+
$('#edit-contact-form input').fadeIn(200);
|
206
|
+
return $('#edit-contact-form .buttons').fadeIn(200);
|
207
|
+
};
|
208
|
+
ChatPage.prototype.scroll = function() {
|
209
|
+
var msgs;
|
210
|
+
msgs = $('#messages');
|
211
|
+
return msgs.animate({
|
212
|
+
scrollTop: msgs.prop('scrollHeight')
|
213
|
+
}, 400);
|
214
|
+
};
|
215
|
+
ChatPage.prototype.atBottom = function() {
|
216
|
+
var bottom, msgs;
|
217
|
+
msgs = $('#messages');
|
218
|
+
bottom = msgs.prop('scrollHeight') - msgs.height();
|
219
|
+
return msgs.scrollTop() === bottom;
|
220
|
+
};
|
221
|
+
ChatPage.prototype.send = function() {
|
222
|
+
var chat, input, jid, text;
|
223
|
+
if (!this.currentContact) {
|
224
|
+
return false;
|
225
|
+
}
|
226
|
+
input = $('#message');
|
227
|
+
text = input.val().trim();
|
228
|
+
if (text) {
|
229
|
+
chat = this.chats[this.currentContact];
|
230
|
+
jid = chat ? chat.jid : this.currentContact;
|
231
|
+
this.message({
|
232
|
+
from: this.session.jid(),
|
233
|
+
text: text,
|
234
|
+
to: jid,
|
235
|
+
received: new Date()
|
236
|
+
});
|
237
|
+
this.session.sendMessage(jid, text);
|
238
|
+
}
|
239
|
+
input.val('');
|
240
|
+
return false;
|
241
|
+
};
|
242
|
+
ChatPage.prototype.addContact = function() {
|
243
|
+
var contact;
|
244
|
+
this.toggleForm('#add-contact-form');
|
245
|
+
contact = {
|
246
|
+
jid: $('#add-contact-jid').val(),
|
247
|
+
name: $('#add-contact-name').val(),
|
248
|
+
groups: ['Buddies']
|
249
|
+
};
|
250
|
+
if (contact.jid) {
|
251
|
+
this.session.updateContact(contact, true);
|
252
|
+
}
|
253
|
+
return false;
|
254
|
+
};
|
255
|
+
ChatPage.prototype.removeContact = function() {
|
256
|
+
this.toggleForm('#remove-contact-form');
|
257
|
+
this.session.removeContact(this.currentContact);
|
258
|
+
this.currentContact = null;
|
259
|
+
$('#chat-title').text('Select a buddy to chat');
|
260
|
+
$('#messages').empty();
|
261
|
+
$('#remove-contact-msg').html("Select a buddy in the list above to remove.");
|
262
|
+
$('#remove-contact-form .buttons').hide();
|
263
|
+
$('#edit-contact-jid').text("Select a buddy in the list above to update.");
|
264
|
+
$('#edit-contact-name').val('');
|
265
|
+
$('#edit-contact-form input').hide();
|
266
|
+
$('#edit-contact-form .buttons').hide();
|
267
|
+
return false;
|
268
|
+
};
|
269
|
+
ChatPage.prototype.updateContact = function() {
|
270
|
+
var contact;
|
271
|
+
this.toggleForm('#edit-contact-form');
|
272
|
+
contact = {
|
273
|
+
jid: this.currentContact,
|
274
|
+
name: $('#edit-contact-name').val(),
|
275
|
+
groups: this.session.roster[this.currentContact].groups
|
276
|
+
};
|
277
|
+
this.session.updateContact(contact);
|
278
|
+
return false;
|
279
|
+
};
|
280
|
+
ChatPage.prototype.toggleForm = function(form, fn) {
|
281
|
+
form = $(form);
|
282
|
+
$('.contact-form').each(function() {
|
283
|
+
if (this.id !== form.attr('id')) {
|
284
|
+
return $(this).hide();
|
285
|
+
}
|
286
|
+
});
|
287
|
+
if (form.is(':hidden')) {
|
288
|
+
if (fn) {
|
289
|
+
fn();
|
290
|
+
}
|
291
|
+
return form.fadeIn(100);
|
292
|
+
} else {
|
293
|
+
return form.fadeOut(100, function() {
|
294
|
+
form[0].reset();
|
295
|
+
if (fn) {
|
296
|
+
return fn();
|
297
|
+
}
|
298
|
+
});
|
299
|
+
}
|
300
|
+
};
|
301
|
+
ChatPage.prototype.filterRoster = function() {
|
302
|
+
var text;
|
303
|
+
text = $('#search-roster-text').val().toLowerCase();
|
304
|
+
if (text === '') {
|
305
|
+
$('#roster li').show();
|
306
|
+
return;
|
307
|
+
}
|
308
|
+
return $('#roster li').each(function() {
|
309
|
+
var jid, match, name, node;
|
310
|
+
node = $(this);
|
311
|
+
jid = (node.attr('data-jid') || '').toLowerCase();
|
312
|
+
name = (node.attr('data-name') || '').toLowerCase();
|
313
|
+
match = jid.indexOf(text) !== -1 || name.indexOf(text) !== -1;
|
314
|
+
if (match) {
|
315
|
+
return node.show();
|
316
|
+
} else {
|
317
|
+
return node.hide();
|
318
|
+
}
|
319
|
+
});
|
320
|
+
};
|
321
|
+
ChatPage.prototype.draw = function() {
|
322
|
+
if (!this.session.connected()) {
|
323
|
+
window.location.hash = '';
|
324
|
+
return;
|
325
|
+
}
|
326
|
+
$('body').attr('id', 'chat-page');
|
327
|
+
$('#container').hide().empty();
|
328
|
+
$("<div id=\"alpha\" class=\"y-fill\">\n <h2>Buddies <div id=\"search-roster\"></div></h2>\n <form id=\"search-roster-form\" style=\"display:none;\">\n <input id=\"search-roster-text\" type=\"search\" placeholder=\"Filter\" results=\"5\"/>\n </form>\n <ul id=\"roster\" class=\"y-fill\"></ul>\n <div id=\"roster-controls\">\n <div id=\"add-contact\"></div>\n <div id=\"remove-contact\"></div>\n <div id=\"edit-contact\"></div>\n </div>\n <form id=\"add-contact-form\" class=\"contact-form\" style=\"display:none;\">\n <h2>Add Buddy</h2>\n <input id=\"add-contact-jid\" type=\"email\" maxlength=\"1024\" placeholder=\"Account name\"/>\n <input id=\"add-contact-name\" type=\"text\" maxlength=\"1024\" placeholder=\"Real name\"/>\n <fieldset class=\"buttons\">\n <input id=\"add-contact-cancel\" type=\"button\" value=\"Cancel\"/>\n <input id=\"add-contact-ok\" type=\"submit\" value=\"Add\"/>\n </fieldset>\n </form>\n <form id=\"remove-contact-form\" class=\"contact-form\" style=\"display:none;\">\n <h2>Remove Buddy</h2>\n <p id=\"remove-contact-msg\">Select a buddy in the list above to remove.</p>\n <fieldset class=\"buttons\" style=\"display:none;\">\n <input id=\"remove-contact-cancel\" type=\"button\" value=\"Cancel\"/>\n <input id=\"remove-contact-ok\" type=\"submit\" value=\"Remove\"/>\n </fieldset>\n </form>\n <form id=\"edit-contact-form\" class=\"contact-form\" style=\"display:none;\">\n <h2>Update Profile</h2>\n <p id=\"edit-contact-jid\">Select a buddy in the list above to update.</p>\n <input id=\"edit-contact-name\" type=\"text\" maxlength=\"1024\" placeholder=\"Real name\" style=\"display:none;\"/>\n <fieldset class=\"buttons\" style=\"display:none;\">\n <input id=\"edit-contact-cancel\" type=\"button\" value=\"Cancel\"/>\n <input id=\"edit-contact-ok\" type=\"submit\" value=\"Save\"/>\n </fieldset>\n </form>\n</div>\n<div id=\"beta\" class=\"x-fill y-fill\">\n <h2 id=\"chat-title\">Select a buddy to chat</h2>\n <ul id=\"messages\" class=\"y-fill\"></ul>\n <form id=\"message-form\">\n <input id=\"message\" name=\"message\" type=\"text\" maxlength=\"1024\" placeholder=\"Type a message and press enter to send\"/>\n </form>\n</div>\n<div id=\"charlie\" class=\"y-fill\">\n <h2>Notifications</h2>\n <ul id=\"notifications\" class=\"y-fill\"></ul>\n <div id=\"notification-controls\">\n <div id=\"clear-notices\"></div>\n </div>\n</div>").appendTo('#container');
|
329
|
+
this.roster();
|
330
|
+
this.button('clear-notices', ICONS.no);
|
331
|
+
this.button('add-contact', ICONS.plus);
|
332
|
+
this.button('remove-contact', ICONS.minus);
|
333
|
+
this.button('edit-contact', ICONS.user);
|
334
|
+
this.button('search-roster', ICONS.search, {
|
335
|
+
scale: 0.5,
|
336
|
+
translation: '-8 -8'
|
337
|
+
});
|
338
|
+
$('#message').focus(function() {
|
339
|
+
return $('.contact-form').fadeOut();
|
340
|
+
});
|
341
|
+
$('#message-form').submit(__bind(function() {
|
342
|
+
return this.send();
|
343
|
+
}, this));
|
344
|
+
$('#clear-notices').click(function() {
|
345
|
+
return $('#notifications li').fadeOut(200);
|
346
|
+
});
|
347
|
+
$('#add-contact').click(__bind(function() {
|
348
|
+
return this.toggleForm('#add-contact-form');
|
349
|
+
}, this));
|
350
|
+
$('#remove-contact').click(__bind(function() {
|
351
|
+
return this.toggleForm('#remove-contact-form');
|
352
|
+
}, this));
|
353
|
+
$('#edit-contact').click(__bind(function() {
|
354
|
+
return this.toggleForm('#edit-contact-form', __bind(function() {
|
355
|
+
if (this.currentContact) {
|
356
|
+
$('#edit-contact-jid').text(this.currentContact);
|
357
|
+
return $('#edit-contact-name').val(this.session.roster[this.currentContact].name);
|
358
|
+
}
|
359
|
+
}, this));
|
360
|
+
}, this));
|
361
|
+
$('#add-contact-cancel').click(__bind(function() {
|
362
|
+
return this.toggleForm('#add-contact-form');
|
363
|
+
}, this));
|
364
|
+
$('#remove-contact-cancel').click(__bind(function() {
|
365
|
+
return this.toggleForm('#remove-contact-form');
|
366
|
+
}, this));
|
367
|
+
$('#edit-contact-cancel').click(__bind(function() {
|
368
|
+
return this.toggleForm('#edit-contact-form');
|
369
|
+
}, this));
|
370
|
+
$('#add-contact-form').submit(__bind(function() {
|
371
|
+
return this.addContact();
|
372
|
+
}, this));
|
373
|
+
$('#remove-contact-form').submit(__bind(function() {
|
374
|
+
return this.removeContact();
|
375
|
+
}, this));
|
376
|
+
$('#edit-contact-form').submit(__bind(function() {
|
377
|
+
return this.updateContact();
|
378
|
+
}, this));
|
379
|
+
$('#search-roster-form').submit(function() {
|
380
|
+
return false;
|
381
|
+
});
|
382
|
+
$('#search-roster-text').keyup(__bind(function() {
|
383
|
+
return this.filterRoster();
|
384
|
+
}, this));
|
385
|
+
$('#search-roster-text').change(__bind(function() {
|
386
|
+
return this.filterRoster();
|
387
|
+
}, this));
|
388
|
+
$('#search-roster-text').click(__bind(function() {
|
389
|
+
return this.filterRoster();
|
390
|
+
}, this));
|
391
|
+
$('#search-roster').click(__bind(function() {
|
392
|
+
return this.toggleForm('#search-roster-form', __bind(function() {
|
393
|
+
return this.filterRoster();
|
394
|
+
}, this));
|
395
|
+
}, this));
|
396
|
+
$('#container').fadeIn(200);
|
397
|
+
return this.resize();
|
398
|
+
};
|
399
|
+
ChatPage.prototype.resize = function() {
|
400
|
+
var a, b, c, form, msg;
|
401
|
+
a = $('#alpha');
|
402
|
+
b = $('#beta');
|
403
|
+
c = $('#charlie');
|
404
|
+
msg = $('#message');
|
405
|
+
form = $('#message-form');
|
406
|
+
return new Layout(function() {
|
407
|
+
c.css('left', a.width() + b.width());
|
408
|
+
return msg.width(form.width() - 32);
|
409
|
+
});
|
410
|
+
};
|
411
|
+
ChatPage.prototype.button = function(id, path, options) {
|
412
|
+
var icon, node, paper;
|
413
|
+
options || (options = {});
|
414
|
+
paper = Raphael(id);
|
415
|
+
icon = paper.path(path).attr({
|
416
|
+
fill: '#000',
|
417
|
+
stroke: '#fff',
|
418
|
+
'stroke-width': 0.3,
|
419
|
+
opacity: 0.6,
|
420
|
+
scale: options.scale || 0.85,
|
421
|
+
translation: options.translation || ''
|
422
|
+
});
|
423
|
+
node = $('#' + id);
|
424
|
+
node.hover(function() {
|
425
|
+
return icon.animate({
|
426
|
+
opacity: 1.0
|
427
|
+
}, 200);
|
428
|
+
}, function() {
|
429
|
+
return icon.animate({
|
430
|
+
opacity: 0.6
|
431
|
+
}, 200);
|
432
|
+
});
|
433
|
+
return node.get(0);
|
434
|
+
};
|
435
|
+
return ChatPage;
|
436
|
+
})();
|