vinesmod 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +57 -0
- data/bin/vines +93 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3366 -0
- data/conf/config.rb +149 -0
- data/lib/vines.rb +197 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/register.rb +27 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/command/unregister.rb +27 -0
- data/lib/vines/config.rb +213 -0
- data/lib/vines/config/host.rb +119 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +35 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/register.rb +42 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/message.rb +43 -0
- data/lib/vines/stanza/presence.rb +156 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/storage.rb +188 -0
- data/lib/vines/storage/local.rb +165 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +260 -0
- data/lib/vines/store.rb +94 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/sasl.rb +92 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +43 -0
- data/vines.gemspec +36 -0
- data/web/404.html +51 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +362 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/index.html +16 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/stylesheets/chat.css +144 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/button.coffee +25 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/filter.coffee +49 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +68 -0
- data/web/lib/coffeescripts/logout.coffee +5 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +229 -0
- data/web/lib/coffeescripts/transfer.coffee +106 -0
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/light-gray.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/images/white.png +0 -0
- data/web/lib/javascripts/base.js +12 -0
- data/web/lib/javascripts/icons.js +110 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +4 -0
- data/web/lib/javascripts/raphael.js +6 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +385 -0
- data/web/lib/stylesheets/login.css +68 -0
- 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()
|
data/web/chat/index.html
ADDED
@@ -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,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></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
|