vines 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/README +2 -2
  2. data/Rakefile +63 -8
  3. data/bin/vines +0 -1
  4. data/conf/config.rb +16 -7
  5. data/lib/vines.rb +21 -16
  6. data/lib/vines/command/init.rb +5 -3
  7. data/lib/vines/config.rb +34 -0
  8. data/lib/vines/contact.rb +14 -0
  9. data/lib/vines/stanza.rb +26 -0
  10. data/lib/vines/stanza/iq.rb +1 -1
  11. data/lib/vines/stanza/iq/disco_info.rb +3 -0
  12. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  13. data/lib/vines/stanza/iq/roster.rb +26 -30
  14. data/lib/vines/stanza/presence.rb +0 -12
  15. data/lib/vines/stanza/presence/subscribe.rb +3 -20
  16. data/lib/vines/stanza/presence/subscribed.rb +9 -10
  17. data/lib/vines/stanza/presence/unsubscribe.rb +8 -15
  18. data/lib/vines/stanza/presence/unsubscribed.rb +8 -8
  19. data/lib/vines/storage.rb +28 -0
  20. data/lib/vines/storage/couchdb.rb +29 -0
  21. data/lib/vines/storage/local.rb +22 -0
  22. data/lib/vines/storage/redis.rb +26 -0
  23. data/lib/vines/storage/sql.rb +48 -5
  24. data/lib/vines/stream/client.rb +6 -8
  25. data/lib/vines/stream/http.rb +23 -21
  26. data/lib/vines/stream/http/auth.rb +1 -1
  27. data/lib/vines/stream/http/bind.rb +1 -1
  28. data/lib/vines/stream/http/bind_restart.rb +4 -3
  29. data/lib/vines/stream/http/ready.rb +1 -1
  30. data/lib/vines/stream/http/request.rb +94 -5
  31. data/lib/vines/stream/http/session.rb +8 -6
  32. data/lib/vines/version.rb +1 -1
  33. data/test/config_test.rb +12 -0
  34. data/test/contact_test.rb +40 -0
  35. data/test/rake_test_loader.rb +11 -3
  36. data/test/stanza/iq/private_storage_test.rb +177 -0
  37. data/test/stanza/iq/roster_test.rb +1 -1
  38. data/test/stanza/iq_test.rb +63 -0
  39. data/test/storage/couchdb_test.rb +7 -1
  40. data/test/storage/local_test.rb +8 -2
  41. data/test/storage/redis_test.rb +16 -7
  42. data/test/storage/sql_test.rb +8 -1
  43. data/test/storage/storage_tests.rb +50 -0
  44. data/test/stream/http/auth_test.rb +3 -0
  45. data/test/stream/http/ready_test.rb +3 -0
  46. data/test/stream/http/request_test.rb +86 -0
  47. data/test/stream/parser_test.rb +2 -0
  48. data/web/404.html +43 -0
  49. data/web/apple-touch-icon.png +0 -0
  50. data/web/chat/coffeescripts/chat.coffee +385 -0
  51. data/web/chat/coffeescripts/init.coffee +15 -0
  52. data/web/chat/coffeescripts/logout.coffee +5 -0
  53. data/web/chat/index.html +17 -0
  54. data/web/chat/javascripts/app.js +1 -0
  55. data/web/chat/javascripts/chat.js +436 -0
  56. data/web/chat/javascripts/init.js +21 -0
  57. data/web/chat/javascripts/logout.js +11 -0
  58. data/web/chat/stylesheets/chat.css +290 -0
  59. data/web/favicon.png +0 -0
  60. data/web/lib/coffeescripts/contact.coffee +32 -0
  61. data/web/lib/coffeescripts/layout.coffee +30 -0
  62. data/web/lib/coffeescripts/login.coffee +52 -0
  63. data/web/lib/coffeescripts/navbar.coffee +84 -0
  64. data/web/lib/coffeescripts/router.coffee +40 -0
  65. data/web/lib/coffeescripts/session.coffee +211 -0
  66. data/web/lib/images/default-user.png +0 -0
  67. data/web/lib/images/logo-large.png +0 -0
  68. data/web/lib/images/logo-small.png +0 -0
  69. data/web/lib/javascripts/base.js +9 -0
  70. data/web/lib/javascripts/contact.js +94 -0
  71. data/web/lib/javascripts/icons.js +101 -0
  72. data/web/lib/javascripts/jquery.cookie.js +91 -0
  73. data/web/lib/javascripts/jquery.js +18 -0
  74. data/web/lib/javascripts/layout.js +48 -0
  75. data/web/lib/javascripts/login.js +61 -0
  76. data/web/lib/javascripts/navbar.js +69 -0
  77. data/web/lib/javascripts/raphael.js +8 -0
  78. data/web/lib/javascripts/router.js +105 -0
  79. data/web/lib/javascripts/session.js +322 -0
  80. data/web/lib/javascripts/strophe.js +1 -0
  81. data/web/lib/stylesheets/base.css +223 -0
  82. data/web/lib/stylesheets/login.css +63 -0
  83. 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()
@@ -0,0 +1,5 @@
1
+ class LogoutPage
2
+ constructor: (@session) ->
3
+ draw: ->
4
+ window.location.hash = ''
5
+ window.location.reload()
@@ -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
+ })();