vinesmod 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
@@ -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,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta name="apple-mobile-web-app-capable" content="yes"/>
6
+ <title>Vines</title>
7
+ <link rel="shortcut icon" type="image/png" href="/favicon.png"/>
8
+ <link rel="stylesheet" href="/lib/stylesheets/base.css"/>
9
+ <link rel="stylesheet" href="/lib/stylesheets/login.css"/>
10
+ <link rel="stylesheet" href="stylesheets/chat.css"/>
11
+ <script type="text/javascript" src="/lib/javascripts/base.js"></script>
12
+ <script type="text/javascript" src="javascripts/app.js"></script>
13
+ </head>
14
+ <body>
15
+ </body>
16
+ </html>
@@ -0,0 +1 @@
1
+ (function(){this.ChatPage=function(){function e(e){var t=this;this.session=e,this.session.onRoster(function(){return t.roster()}),this.session.onCard(function(e){return t.card(e)}),this.session.onMessage(function(e){return t.message(e)}),this.session.onPresence(function(e){return t.presence(e)}),this.chats={},this.currentContact=null,this.layout=null}return e.prototype.datef=function(e){var t,n,r,i;return t=new Date(e),r=t.getHours()>=12?" pm":" am",n=t.getHours()>12?t.getHours()-12:t.getHours(),n===0&&(n=12),i=t.getMinutes()+"",i.length===1&&(i="0"+i),n+":"+i+r},e.prototype.card=function(e){var t=this;return this.eachContact(e.jid,function(n){return $(".vcard-img",n).attr("src",t.session.avatar(e.jid))})},e.prototype.roster=function(){var e,t,n,r,i,s,o,u,a=this;i=$("#roster"),$("li",i).each(function(e,t){var n;n=$(t).attr("data-jid");if(!a.session.roster[n])return $(t).remove()}),s=function(e,t){return $(".text",e).text(t.name||t.jid),e.attr("data-name",t.name||"")},o=this.session.roster,u=[];for(n in o)e=o[n],t=$("#roster li[data-jid='"+n+"']"),s(t,e),t.length===0?(r=$('<li data-jid="'+n+'" 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="'+n+'" src="'+this.session.avatar(n)+'"/>\n</li>').appendTo(i),s(r,e),u.push(r.click(function(e){return a.selectContact(e)}))):u.push(void 0);return u},e.prototype.message=function(e){var t,n,r,i;if(e.type!=="chat"||!e.text)return;this.queueMessage(e),i=e.from===this.session.jid(),r=e.from.split("/")[0];if(!i&&r!==this.currentContact)return n=this.chat(e.from),n.unread++,this.eachContact(r,function(e){return $(".unread",e).text(n.unread).show()});t=this.atBottom(),this.appendMessage(e);if(t)return this.scroll()},e.prototype.eachContact=function(e,t){var n,r,i,s,o;s=$("#roster li[data-jid='"+e+"']").get(),o=[];for(r=0,i=s.length;r<i;r++)n=s[r],o.push(t($(n)));return o},e.prototype.appendMessage=function(e){var t,n,r,i;return n=e.from.split("/")[0],t=this.session.roster[n],r=t?t.name||n:n,e.from===this.session.jid()&&(r="Me"),i=$('<li data-jid="'+n+'" style="display:none;">\n <p></p>\n <img alt="'+n+'" src="'+this.session.avatar(n)+'"/>\n <footer>\n <span class="author"></span>\n <span class="time">'+this.datef(e.received)+"</span>\n </footer>\n</li>").appendTo("#messages"),$("p",i).text(e.text),$(".author",i).text(r),i.fadeIn(200)},e.prototype.queueMessage=function(e){var t,n,r;return r=e.from===this.session.jid(),n=e[r?"to":"from"],t=this.chat(n),t.jid=n,t.messages.push(e)},e.prototype.chat=function(e){var t,n;return t=e.split("/")[0],n=this.chats[t],n||(n={jid:e,messages:[],unread:0},this.chats[t]=n),n},e.prototype.presence=function(e){var t,n,r,i=this;n=e.from.split("/")[0];if(n===this.session.bareJid())return;if(!e.type||e.offline)t=this.session.roster[n],this.eachContact(n,function(e){return $(".status-msg",e).text(t.status()),t.offline()?e.addClass("offline"):e.removeClass("offline")});e.offline&&(this.chat(n).jid=n);if(e.type==="subscribe")return r=$('<li data-jid="'+e.from+'" style="display:none;">\n <form class="inset">\n <h2>Buddy Approval</h2>\n <p>'+e.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"),r.fadeIn(200),$("form",r).submit(function(){return i.acceptContact(r,e.from)}),$('input[type="button"]',r).click(function(){return i.rejectContact(r,e.from)})},e.prototype.acceptContact=function(e,t){return e.fadeOut(200,function(){return e.remove()}),this.session.sendSubscribed(t),this.session.sendSubscribe(t),!1},e.prototype.rejectContact=function(e,t){return e.fadeOut(200,function(){return e.remove()}),this.session.sendUnsubscribed(t)},e.prototype.selectContact=function(e){var t,n,r,i,s,o,u;r=$(e.currentTarget).attr("data-jid"),n=this.session.roster[r];if(this.currentContact===r)return;this.currentContact=r,$("#roster li").removeClass("selected"),$(e.currentTarget).addClass("selected"),$("#chat-title").text("Chat with "+(n.name||n.jid)),$("#messages").empty(),t=this.chats[r],i=[],t&&(i=t.messages,t.unread=0,this.eachContact(r,function(e){return $(".unread",e).text("").hide()}));for(o=0,u=i.length;o<u;o++)s=i[o],this.appendMessage(s);return 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),$("#edit-contact-form .buttons").fadeIn(200)},e.prototype.scroll=function(){var e;return e=$("#messages"),e.animate({scrollTop:e.prop("scrollHeight")},400)},e.prototype.atBottom=function(){var e,t;return t=$("#messages"),e=t.prop("scrollHeight")-t.outerHeight(),t.scrollTop()>=e},e.prototype.send=function(){var e,t,n,r;return this.currentContact?(t=$("#message"),r=t.val().trim(),r&&(e=this.chats[this.currentContact],n=e?e.jid:this.currentContact,this.message({from:this.session.jid(),text:r,to:n,type:"chat",received:new Date}),this.session.sendMessage(n,r)),t.val(""),!1):!1},e.prototype.addContact=function(){var e;return this.toggleForm("#add-contact-form"),e={jid:$("#add-contact-jid").val(),name:$("#add-contact-name").val(),groups:["Buddies"]},e.jid&&this.session.updateContact(e,!0),!1},e.prototype.removeContact=function(){return 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(),!1},e.prototype.updateContact=function(){var e;return this.toggleForm("#edit-contact-form"),e={jid:this.currentContact,name:$("#edit-contact-name").val(),groups:this.session.roster[this.currentContact].groups},this.session.updateContact(e),!1},e.prototype.toggleForm=function(e,t){var n=this;return e=$(e),$("form.overlay").each(function(){if(this.id!==e.attr("id"))return $(this).hide()}),e.is(":hidden")?(t&&t(),e.fadeIn(100)):e.fadeOut(100,function(){e[0].reset(),n.layout.resize();if(t)return t()})},e.prototype.draw=function(){var e,t=this;if(!this.session.connected()){window.location.hash="";return}return $("body").attr("id","chat-page"),$("#container").hide().empty(),$('<div id="alpha" class="sidebar column y-fill">\n <h2>Buddies <div id="search-roster-icon"></div></h2>\n <div id="search-roster-form"></div>\n <ul id="roster" class="selectable scroll y-fill"></ul>\n <div id="alpha-controls" class="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="overlay" 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="overlay" 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="overlay" 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="primary column x-fill y-fill">\n <h2 id="chat-title">Select a buddy to chat</h2>\n <ul id="messages" class="scroll 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="sidebar column y-fill">\n <h2>Notifications</h2>\n <ul id="notifications" class="scroll y-fill"></ul>\n <div id="charlie-controls" class="controls">\n <div id="clear-notices"></div>\n </div>\n</div>').appendTo("#container"),this.roster(),new Button("#clear-notices",ICONS.no),new Button("#add-contact",ICONS.plus),new Button("#remove-contact",ICONS.minus),new Button("#edit-contact",ICONS.user),$("#message").focus(function(){return $("form.overlay").fadeOut()}),$("#message-form").submit(function(){return t.send()}),$("#clear-notices").click(function(){return $("#notifications li").fadeOut(200)}),$("#add-contact").click(function(){return t.toggleForm("#add-contact-form")}),$("#remove-contact").click(function(){return t.toggleForm("#remove-contact-form")}),$("#edit-contact").click(function(){return t.toggleForm("#edit-contact-form",function(){if(t.currentContact)return $("#edit-contact-jid").text(t.currentContact),$("#edit-contact-name").val(t.session.roster[t.currentContact].name)})}),$("#add-contact-cancel").click(function(){return t.toggleForm("#add-contact-form")}),$("#remove-contact-cancel").click(function(){return t.toggleForm("#remove-contact-form")}),$("#edit-contact-cancel").click(function(){return t.toggleForm("#edit-contact-form")}),$("#add-contact-form").submit(function(){return t.addContact()}),$("#remove-contact-form").submit(function(){return t.removeContact()}),$("#edit-contact-form").submit(function(){return t.updateContact()}),$("#container").fadeIn(200),this.layout=this.resize(),e=function(){return t.layout.resize(),t.layout.resize()},new Filter({list:"#roster",icon:"#search-roster-icon",form:"#search-roster-form",attrs:["data-jid","data-name"],open:e,close:e})},e.prototype.resize=function(){var e,t,n,r,i;return e=$("#alpha"),t=$("#beta"),n=$("#charlie"),i=$("#message"),r=$("#message-form"),new Layout(function(){return n.css("left",e.width()+t.width()),i.width(r.width()-32)})},e}(),$(function(){var e,t,n,r,i,s;s=new Session,r=new NavBar(s),r.draw(),e={Messages:ICONS.chat,Logout:ICONS.power};for(n in e)t=e[n],r.addButton(n,t);return i={"/messages":new ChatPage(s),"/logout":new LogoutPage(s),"default":new LoginPage(s,"/messages/")},(new Router(i)).draw(),r.select($("#nav-link-messages").parent())})}).call(this);
@@ -0,0 +1,144 @@
1
+ #chat-page #container {
2
+ height: 100%;
3
+ }
4
+ #chat-page #chat-title {
5
+ background: #f8f8f8;
6
+ }
7
+ #chat-page #messages {
8
+ background: #fff;
9
+ height: 100%;
10
+ list-style: none;
11
+ text-shadow: 0 1px 1px #ddd;
12
+ width: 100%;
13
+ }
14
+ #chat-page #messages li {
15
+ border-bottom: 1px solid #f0f0f0;
16
+ min-height: 40px;
17
+ padding: 10px;
18
+ position: relative;
19
+ }
20
+ #chat-page #messages li:hover > span .time {
21
+ opacity: 0.3;
22
+ }
23
+ #chat-page #messages li img {
24
+ border: 3px solid #fff;
25
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 0 40px rgba(0, 0, 0, 0.06) inset;
26
+ position: absolute;
27
+ top: 7px;
28
+ right: 7px;
29
+ height: 40px;
30
+ width: 40px;
31
+ }
32
+ #chat-page #messages li p {
33
+ line-height: 1.5;
34
+ width: 90%;
35
+ }
36
+ #chat-page #messages li footer {
37
+ font-size: 9pt;
38
+ padding-right: 50px;
39
+ text-align: right;
40
+ }
41
+ #chat-page #messages li footer span {
42
+ color: #d8d8d8;
43
+ margin-right: 0.5em;
44
+ text-shadow: none;
45
+ }
46
+ #chat-page #messages li footer .author::before {
47
+ content: '\2014 ';
48
+ }
49
+ #chat-page #message-form {
50
+ background: #f8f8f8 -moz-linear-gradient(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.05));
51
+ background: #f8f8f8 -ms-linear-gradient(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.05));
52
+ background: #f8f8f8 -o-linear-gradient(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.05));
53
+ background: #f8f8f8 -webkit-linear-gradient(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.05));
54
+ border-top: 1px solid #dfdfdf;
55
+ height: 50px;
56
+ position: absolute;
57
+ bottom: 0;
58
+ width: 100%;
59
+ }
60
+ #chat-page #message {
61
+ display: block;
62
+ position: relative;
63
+ left: 10px;
64
+ top: 10px;
65
+ width: 428px;
66
+ }
67
+ #chat-page #roster,
68
+ #chat-page #notifications {
69
+ height: 100%;
70
+ list-style: none;
71
+ text-shadow: 0 1px 1px #fff;
72
+ width: 260px;
73
+ }
74
+ #chat-page #roster li,
75
+ #chat-page #notifications li {
76
+ cursor: pointer;
77
+ border-bottom: 1px solid #ddd;
78
+ font-weight: bold;
79
+ min-height: 42px;
80
+ padding: 0 10px;
81
+ position: relative;
82
+ -moz-transition: background 0.3s;
83
+ -o-transition: background 0.3s;
84
+ -webkit-transition: background 0.3s;
85
+ transition: background 0.3s;
86
+ }
87
+ #chat-page #notifications li {
88
+ font-weight: normal;
89
+ padding: 10px 0 0 0;
90
+ }
91
+ #chat-page #roster li:hover:not(.selected),
92
+ #chat-page #notifications li:hover {
93
+ background: rgba(255, 255, 255, 1.0);
94
+ }
95
+ #chat-page #roster li.offline > * {
96
+ opacity: 0.4;
97
+ }
98
+ #chat-page #roster li.selected > * {
99
+ opacity: 1.0;
100
+ }
101
+ #chat-page #roster li.selected .status-msg {
102
+ color: rgba(255, 255, 255, 0.85);
103
+ }
104
+ #chat-page #roster .status-msg {
105
+ display: block;
106
+ font-size: 11px;
107
+ font-weight: normal;
108
+ line-height: 11px;
109
+ }
110
+ #chat-page #roster .unread {
111
+ background: #319be7;
112
+ background: -moz-linear-gradient(#319be7, #1b78d9);
113
+ background: -ms-linear-gradient(#319be7, #1b78d9);
114
+ background: -o-linear-gradient(#319be7, #1b78d9);
115
+ background: -webkit-linear-gradient(#319be7, #1b78d9);
116
+ border-radius: 30px;
117
+ color: #fff;
118
+ display: inline-block;
119
+ font-size: 11px;
120
+ font-weight: normal;
121
+ line-height: 15px;
122
+ padding: 0px 6px;
123
+ position: absolute;
124
+ right: 50px;
125
+ top: 14px;
126
+ text-shadow: none;
127
+ }
128
+ #chat-page #roster .vcard-img {
129
+ background: #fff;
130
+ border: 1px solid #fff;
131
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 0 40px rgba(0, 0, 0, 0.06) inset;
132
+ height: 32px;
133
+ width: 32px;
134
+ position: absolute;
135
+ top: 4px;
136
+ right: 10px;
137
+ }
138
+ #chat-page #charlie-controls {
139
+ text-align: right;
140
+ }
141
+ #chat-page #edit-contact-jid {
142
+ color: #444;
143
+ margin-top: -5px;
144
+ }
data/web/favicon.png ADDED
Binary file
@@ -0,0 +1,25 @@
1
+ class @Button
2
+ constructor: (node, path, options) ->
3
+ @node = $ node
4
+ @path = path
5
+ @options = options || {}
6
+ @options.animate = true unless @options.animate?
7
+ this.draw()
8
+
9
+ draw: ->
10
+ paper = Raphael @node.get(0)
11
+
12
+ transform = "s#{@options.scale || 0.85}"
13
+ transform += ",t#{@options.translation}" if @options.translation
14
+
15
+ icon = paper.path(@path).attr
16
+ fill: @options.fill || '#000'
17
+ stroke: @options.stroke || '#fff'
18
+ 'stroke-width': @options['stroke-width'] || 0.3
19
+ opacity: @options.opacity || 0.6
20
+ transform: transform
21
+
22
+ if @options.animate
23
+ @node.hover(
24
+ -> icon.animate(opacity: 1.0, 200),
25
+ -> icon.animate(opacity: 0.6, 200))
@@ -0,0 +1,32 @@
1
+ class @Contact
2
+ constructor: (node) ->
3
+ node = $(node)
4
+ @jid = node.attr 'jid'
5
+ @name = node.attr 'name'
6
+ @ask = node.attr 'ask'
7
+ @subscription = node.attr 'subscription'
8
+ @groups = $('group', node).map(-> $(this).text()).get()
9
+ @presence = []
10
+
11
+ online: ->
12
+ @presence.length > 0
13
+
14
+ offline: ->
15
+ @presence.length == 0
16
+
17
+ available: ->
18
+ this.online() && (p for p in @presence when !p.away).length > 0
19
+
20
+ away: -> !this.available()
21
+
22
+ status: ->
23
+ available = (p.status for p in @presence when p.status && !p.away)[0] || 'Available'
24
+ away = (p.status for p in @presence when p.status && p.away)[0] || 'Away'
25
+ if this.offline() then 'Offline'
26
+ else if this.away() then away
27
+ else available
28
+
29
+ update: (presence) ->
30
+ @presence = (p for p in @presence when p.from != presence.from)
31
+ @presence.push presence unless presence.type
32
+ @presence = [] if presence.type == 'unsubscribed'
@@ -0,0 +1,49 @@
1
+ class @Filter
2
+ constructor: (options) ->
3
+ @list = options.list
4
+ @icon = options.icon
5
+ @form = options.form
6
+ @attrs = options.attrs
7
+ @open = options.open
8
+ @close = options.close
9
+ this.draw()
10
+
11
+ draw: ->
12
+ $(@icon).addClass 'filter-button'
13
+ form = $('<form class="filter-form" style="display:none;"></form>').appendTo @form
14
+ text = $('<input class="filter-text" type="search" placeholder="Filter" results="5"/>').appendTo form
15
+
16
+ if @icon
17
+ new Button @icon, ICONS.search,
18
+ scale: 0.5
19
+ translation: '-16,-16'
20
+
21
+ form.submit -> false
22
+ text.keyup => this.filter(text)
23
+ text.change => this.filter(text)
24
+ text.click => this.filter(text)
25
+ $(@icon).click =>
26
+ if form.is ':hidden'
27
+ this.filter(text)
28
+ form.show()
29
+ this.open() if this.open
30
+ else
31
+ form.hide()
32
+ form[0].reset()
33
+ this.filter(text)
34
+ this.close() if this.close
35
+
36
+ filter: (input) ->
37
+ text = input.val().toLowerCase()
38
+ if text == ''
39
+ $('li', @list).show()
40
+ return
41
+
42
+ test = (node, attr) ->
43
+ val = (node.attr(attr) || '').toLowerCase()
44
+ val.indexOf(text) != -1
45
+
46
+ $('> li', @list).each (ix, node) =>
47
+ node = $ node
48
+ matches = (true for attr in @attrs when test node, attr)
49
+ if matches.length > 0 then node.show() else node.hide()
@@ -0,0 +1,30 @@
1
+ class @Layout
2
+ constructor: (@fn) ->
3
+ this.resize()
4
+ this.listen()
5
+ setTimeout (=> this.resize()), 250
6
+
7
+ resize: ->
8
+ this.fill '.x-fill', 'outerWidth', 'width'
9
+ this.fill '.y-fill', 'outerHeight', 'height'
10
+ this.fn()
11
+
12
+ fill: (selector, get, set) ->
13
+ $(selector).filter(':visible').each (ix, node) =>
14
+ node = $(node)
15
+ getter = node[get]
16
+ parent = getter.call node.parent(), true
17
+ fixed = this.fixed node, selector, (n) -> getter.call(n, true)
18
+ node[set].call node, parent - fixed
19
+
20
+ fixed: (node, selector, fn) ->
21
+ node.siblings().not(selector).not('.float').filter(':visible')
22
+ .map(-> fn $ this).get()
23
+ .reduce ((sum, num) -> sum + num), 0
24
+
25
+ listen: ->
26
+ id = null
27
+ $(window).resize =>
28
+ clearTimeout id
29
+ id = setTimeout (=> this.resize()), 10
30
+ this.resize()
@@ -0,0 +1,68 @@
1
+ class @LoginPage
2
+ constructor: (@session, @startPage) ->
3
+
4
+ start: ->
5
+ $('#error').hide()
6
+
7
+ [jid, password] = ($(id).val().trim() for id in ['#jid', '#password'])
8
+ if jid.length == 0 || password.length == 0 || jid.indexOf('@') == -1
9
+ $('#error').show()
10
+ return
11
+
12
+ @session.connect jid, password, (success) =>
13
+ unless success
14
+ @session.disconnect()
15
+ $('#error').show()
16
+ $('#password').val('').focus()
17
+ return
18
+
19
+ localStorage['jid'] = jid
20
+ $('#current-user-name').text @session.bareJid()
21
+ $('#current-user-avatar').attr 'src', @session.avatar @session.jid()
22
+ $('#current-user-avatar').attr 'alt', @session.bareJid()
23
+ $('#container').fadeOut 200, =>
24
+ $('#navbar').show()
25
+ window.location.hash = @startPage
26
+
27
+ draw: ->
28
+ @session.disconnect()
29
+ jid = localStorage['jid'] || ''
30
+ $('#navbar').hide()
31
+ $('body').attr 'id', 'login-page'
32
+ $('#container').hide().empty()
33
+ $("""
34
+ <form id="login-form">
35
+ <div id="icon"></div>
36
+ <h1>vines</h1>
37
+ <fieldset id="login-form-controls">
38
+ <input id="jid" name="jid" type="email" maxlength="1024" value="#{jid}" placeholder="Your user name"/>
39
+ <input id="password" name="password" type="password" maxlength="1024" placeholder="Your password"/>
40
+ <input id="start" type="submit" value="Sign in"/>
41
+ </fieldset>
42
+ <p id="error" style="display:none;">User name and password not found.</p>
43
+ </form>
44
+ """).appendTo '#container'
45
+ $('#container').fadeIn 1000
46
+ $('#login-form').submit => this.start(); false
47
+ $('#jid').keydown -> $('#error').fadeOut()
48
+ $('#password').keydown -> $('#error').fadeOut()
49
+ this.resize()
50
+ this.icon()
51
+
52
+ icon: ->
53
+ opts =
54
+ fill: '90-#ccc-#fff'
55
+ stroke: '#fff'
56
+ 'stroke-width': 1.1
57
+ opacity: 0.95
58
+ scale: 3.0
59
+ translation: '10,8'
60
+ animate: false
61
+ new Button('#icon', ICONS.chat, opts)
62
+
63
+ resize: ->
64
+ win = $ window
65
+ form = $ '#login-form'
66
+ sizer = -> form.css 'top', win.height() / 2 - form.height() / 2
67
+ win.resize sizer
68
+ sizer()
@@ -0,0 +1,5 @@
1
+ class @LogoutPage
2
+ constructor: (@session) ->
3
+ draw: ->
4
+ window.location.hash = ''
5
+ window.location.reload()
@@ -0,0 +1,84 @@
1
+ class @NavBar
2
+ constructor: (@session) ->
3
+ @session.onCard (card) =>
4
+ if card.jid == @session.bareJid()
5
+ $('#current-user-avatar').attr 'src', @session.avatar card.jid
6
+
7
+ draw: ->
8
+ $("""
9
+ <header id="navbar" class="x-fill">
10
+ <h1 id="logo">vines&gt;</h1>
11
+ <div id="current-user">
12
+ <img id="current-user-avatar" alt="#{@session.bareJid()}" src="#{@session.avatar(@session.jid())}"/>
13
+ <div id="current-user-info">
14
+ <h1 id="current-user-name">#{@session.bareJid()}</h1>
15
+ <form id="current-user-presence-form">
16
+ <span class="select">
17
+ <span class="text">Available</span>
18
+ <select id="current-user-presence">
19
+ <optgroup label="Available">
20
+ <option>Available</option>
21
+ <option>Surfing the web</option>
22
+ <option>Reading email</option>
23
+ </optgroup>
24
+ <optgroup label="Away">
25
+ <option value="xa">Away</option>
26
+ <option value="xa">Out to lunch</option>
27
+ <option value="xa">On the phone</option>
28
+ <option value="xa">In a meeting</option>
29
+ </optgroup>
30
+ </select>
31
+ </span>
32
+ </form>
33
+ </div>
34
+ </div>
35
+ <nav id="app-nav" class="x-fill">
36
+ <ul id="nav-links"></ul>
37
+ </nav>
38
+ </header>
39
+ """).appendTo 'body'
40
+ $('<div id="container" class="x-fill y-fill"></div>').appendTo 'body'
41
+
42
+ $('#current-user-presence').change (event) =>
43
+ selected = $ 'option:selected', event.currentTarget
44
+ $('#current-user-presence-form .text').text selected.text()
45
+ @session.sendPresence selected.val() == 'xa', selected.text()
46
+
47
+ addButton: (label, icon) ->
48
+ id = "nav-link-#{label.toLowerCase()}"
49
+ node = $("""
50
+ <li>
51
+ <a id="#{id}" href="#/#{label.toLowerCase()}">
52
+ <span>#{label}</span>
53
+ </a>
54
+ </li>
55
+ """).appendTo '#nav-links'
56
+ this.button(id, icon)
57
+ node.click (event) => this.select(event.currentTarget)
58
+
59
+ select: (button) ->
60
+ button = $(button)
61
+ $('#nav-links li').removeClass('selected')
62
+ $('#nav-links li a').removeClass('selected')
63
+ button.addClass('selected')
64
+ $('a', button).addClass('selected')
65
+ dark = $('#nav-links svg path')
66
+ dark.attr 'opacity', '0.6'
67
+ dark.css 'opacity', '0.6'
68
+ light = $('svg path', button)
69
+ light.attr 'opacity', '1.0'
70
+ light.css 'opacity', '1.0'
71
+
72
+ button: (id, path) ->
73
+ paper = Raphael(id)
74
+ icon = paper.path(path).attr
75
+ fill: '#fff'
76
+ stroke: '#000'
77
+ 'stroke-width': 0.3
78
+ opacity: 0.6
79
+
80
+ node = $('#' + id)
81
+ node.hover(
82
+ -> icon.animate(opacity: 1.0, 200),
83
+ -> icon.animate(opacity: 0.6, 200) unless node.hasClass('selected'))
84
+ node.get 0