vines 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
})();
|