Refactoring

This commit is contained in:
Mattias Erming 2014-05-02 19:46:18 +02:00
parent c0f0edf633
commit cfb7edd659
10 changed files with 302 additions and 313 deletions

View File

@ -3,16 +3,20 @@
} }
html, html,
body { body {
background: #fff;
color: #34495e;
font: 13px Helvetica, Arial, sans-serif;
height: 100%; height: 100%;
font: 14px sans-serif;
margin: 0; margin: 0;
} }
h1,
h2 {
margin: 0;
}
a {
text-decoration: none;
}
a, a,
.user { .user {
color: #16a085; color: #16a085;
text-decoration: none;
transition: all .25s; transition: all .25s;
} }
a:hover, a:hover,
@ -20,14 +24,10 @@ a:hover,
color: #1abc9c; color: #1abc9c;
} }
a:focus, a:focus,
button:focus { button:focus,
input {
outline: 0; outline: 0;
} }
h1,
h2 {
font: inherit;
margin: 0;
}
button { button {
background: none; background: none;
border: 0; border: 0;
@ -47,7 +47,6 @@ button::-moz-focus-inner {
border: 2px solid #bdc3c7; border: 2px solid #bdc3c7;
border-radius: 3px; border-radius: 3px;
color: #aeb6bf; color: #aeb6bf;
font: 14px Helvetica, Arial, sans-serif;
padding: 8px 12px; padding: 8px 12px;
text-decoration: none; text-decoration: none;
transition: all .25s; transition: all .25s;
@ -56,175 +55,158 @@ button::-moz-focus-inner {
border-color: #7f8c8d; border-color: #7f8c8d;
color: #7f8c8d; color: #7f8c8d;
} }
.badge { #wrap {
background-color: #d8dce0;
border-radius: 4px;
color: #ffffff;
font-size: 13px;
line-height: 1.615;
padding: 0 8px;
}
#wrap,
#viewport {
height: 100%; height: 100%;
min-width: 720px; min-width: 640px;
position: relative;
width: 100%; width: 100%;
} }
#sidebar { #sidebar {
border-right: 4px solid #bdc3c7; border-right: 4px solid #bdc3c7;
float: left; position: absolute;
height: 100%; height: 100%;
padding: 20px; width: 240px;
width: 200px;
} }
#sidebar .network + .network { #sidebar h2 {
border-top: 2px solid #ebedef;
margin-top: 14px;
padding-top: 14px;
}
#sidebar .header {
color: #aeb6bf; color: #aeb6bf;
font-size: 13px; font: bold 13px sans-serif;
font-weight: bold;
padding: 6px 12px; padding: 6px 12px;
text-transform: uppercase; text-transform: uppercase;
} }
#sidebar .channel { #sidebar button {
border-radius: 2px; border-radius: 2px;
color: #16a085; color: #16a085;
display: block; display: block;
font-size: 15px; font-size: 15px;
font-weight: bold; font-weight: bold;
line-height: 1.2; line-height: 21px;
padding: 6px 12px; margin-bottom: 3px;
padding: 6px 13px;
text-align: left; text-align: left;
transition: all .25s; transition: all .25s;
white-space: nowrap;
width: 100%; width: 100%;
} }
#sidebar .channel + .channel { #sidebar button:hover {
margin-top: 3px;
}
#sidebar .channel:hover {
background-color: #f1f2f3; background-color: #f1f2f3;
color: #1abc9c; color: #1abc9c;
} }
#sidebar .channel.active { #sidebar button.active {
background-color: #ebedef; background-color: #ebedef;
color: #526476; color: #526476;
} }
#chat { #sidebar .badge {
background: #fff; color: #bdc3c7;
font: 12px sans-serif;
line-height: 21px;
}
#menu,
#networks {
margin: 20px;
}
#networks .network + .network {
border-top: 2px solid #ebedef;
margin-top: 14px;
padding-top: 14px;
}
#networks .badge {
float: right;
}
#footer {
bottom: 0; bottom: 0;
font: 12px "Consolas", monospace;
left: 200px;
line-height: 16px;
position: absolute; position: absolute;
right: 0;
top: 0;
}
#chat form {
border-top: 1px solid #bdc3c7;
bottom: 1px;
height: 30px;
left: 0;
position: absolute;
right: 0;
}
#chat form input {
border: 0;
font: inherit;
height: 30px;
margin: 0;
outline: none;
padding: 0 10px;
width: 100%; width: 100%;
} }
#chat .query .users, #footer .btn {
#chat .lobby .users, background: #fff;
#chat .lobby .close { display: block;
display: none; text-align: center;
margin: 20px;
} }
#chat .query .messages, #main {
#chat .lobby .messages { position: absolute;
height: 100%;
left: 240px;
right: 0; right: 0;
} }
#chat .window { #main .window {
background: #fff; background: #fff;
height: 100%; height: 100%;
position: absolute; position: absolute;
width: 100%; width: 100%;
} }
#chat .messages { #chat {
bottom: 30px; font: 13px "Consolas", monospace;
left: 0; height: 100%;
}
#chat .lobby .messages,
#chat .query .messages {
right: 0;
}
#chat .lobby .users,
#chat .query .users {
display: none;
}
#chat .messages,
#chat .users {
bottom: 35px;
overflow: hidden;
overflow-y: auto; overflow-y: auto;
padding: 0 8px 4px;
position: absolute; position: absolute;
right: 160px;
top: 0; top: 0;
word-wrap: break-word; }
z-index: 0; #chat .messages {
left: 0px;
padding: 2px 0;
right: 160px;
} }
#chat .show-more { #chat .show-more {
display: none; display: none;
margin-top: 4px; margin: 6px 8px 4px;
padding: 2px 0;
} }
#chat .show-more .btn { #chat .show-more .btn {
width: 100%; width: 100%;
} }
#chat .msg { #chat .msg {
margin: 4px 0; line-height: 1.4;
margin: 2px 8px;
} }
#chat .time { #chat .time,
#chat .type {
color: #bdc3c7; color: #bdc3c7;
} }
#chat .error,
#chat .join,
#chat .kick,
#chat .mode,
#chat .motd,
#chat .nick,
#chat .notice,
#chat .part,
#chat .quit,
#chat .topic,
#chat .whois {
color: #95a5a6;
}
#chat .motd .type, #chat .motd .type,
#chat .notice .type, #chat .notice .type,
#chat .whois .type { #chat .whois .type {
display: none; display: none;
} }
#chat .users { #chat .users {
background: #fff;
border-left: 4px solid #bdc3c7; border-left: 4px solid #bdc3c7;
bottom: 30px;
overflow-y: auto;
padding-bottom: 6px; padding-bottom: 6px;
position: absolute;
right: 0; right: 0;
top: 0;
width: 160px; width: 160px;
} }
#chat .users .count {
background: #ecf0f1;
color: #aeb6bf;
margin-bottom: 4px;
padding: 8px 12px;
}
#chat .users .user { #chat .users .user {
display: block; display: block;
padding: 4px 12px; padding: 4px 12px;
} }
#footer { #chat .count {
background: #ecf0f1;
color: #aeb6bf;
margin-bottom: 4px;
padding: 10px 12px;
}
#chat .form {
bottom: 0; bottom: 0;
height: 35px;
position: absolute; position: absolute;
text-align: center; right: 0;
width: 195px; left: 0;
} }
#footer .btn { #chat .input {
display: block; border: 0;
margin: 20px; border-top: 1px solid #bdc3c7;
height: 35px;
padding: 0 10px;
width: 100%;
} }

View File

@ -7,102 +7,114 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="/normalize.css"> <link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="<%= typeof theme !== 'undefined' ? theme : '' %>">
</head> </head>
<body> <body>
<div id="wrap"> <div id="wrap" class="table">
<div id="viewport"> <aside id="sidebar">
<div id="menu">
<aside id="sidebar"></aside> <h2>Shout Client</h2>
<div id="chat"></div> <button data-target="#settings">Settings</button>
</div>
<footer id="footer"> <div id="networks">
<a href="<%= homepage %>" target="_blank" class="btn"> </div>
<strong><%= name %></strong> <footer id="footer">
<%= version %> <a href="<%= homepage %>" target="_blank" class="btn">
</a> <strong><%= name %></strong>
</footer> <%= version %>
</a>
</div> </footer>
</div> </aside>
<div id="main">
<script type="text/html" id="networks"> <div id="windows">
{{#each networks}} <div id="settings" class="window">
<div id="network-{{id}}" data-nick="{{nick}}" class="network"> </div>
<h2 class="header">{{host}}</h2> </div>
{{partial "#channels"}} <div id="chat">
</div>
{{/each}}
</script>
<script type="text/html" id="channels">
{{#each channels}}
<button id="channel-{{id}}" class="channel">
{{name}}
</button>
{{/each}}
</script>
<script type="text/html" id="windows">
{{#each windows}}
<div id="window-{{id}}" class="window {{type}}">
<div class="users">
{{partial "#users"}}
</div>
<div class="messages">
<div class="show-more">
<button class="btn">Show more</button>
</div> </div>
{{partial "#messages"}}
</div> </div>
<form onSubmit="return false;">
<input type="text" class="input" data-target="{{id}}"/>
<input type="submit" style="display: none;"/>
</form>
</div> </div>
{{/each}}
</script>
<script type="text/html" id="users"> <div id="templates">
<div class="count"> <script type="text/html" class="networks">
Users: {{users.length}} {{#each networks}}
</div> <div id="network-{{id}}" class="network">
{{#each users}} <h2>{{name}}</h2>
<button class="user"> {{partial "channels"}}
{{mode}}{{name}} </div>
</button> {{/each}}
{{/each}} </script>
</script>
<script type="text/html" class="channels">
<script type="text/html" id="messages"> {{#each channels}}
{{#slice messages limit="20"}} <button id="channel-{{id}}" class="channel" data-target="#window-{{id}}">
<div class="msg {{type}}"> <span class="badge"></span>
<span class="time"> {{name}}
{{time}}
</span>
<button class="user">
{{from}}
</button> </button>
<span class="text"> {{/each}}
{{#if type}} </script>
<em class="type">{{type}}</em>
{{/if}} <script type="text/html" class="windows">
{{{uri text}}} {{#each windows}}
</span> <div id="window-{{id}}" class="window {{type}}">
<div class="chat">
<div class="messages">
<div class="show-more">
<button class="btn">Show more</button>
</div>
{{partial "messages"}}
</div>
<div class="users">
{{partial "users"}}
</div>
</div>
<form class="form" onSubmit="return false" data-target="{{id}}">
<input type="text" class="input">
</form>
</div>
{{/each}}
</script>
<script type="text/html" class="users">
<div class="count">
Users: {{users.length}}
</div>
{{#each users}}
<button class="user">
{{mode}}{{name}}
</button>
{{/each}}
</script>
<script type="text/html" class="messages">
{{#slice messages limit=100}}
<div class="msg {{type}}">
<span class="time">
{{time}}
</span>
<button class="user">
{{from}}
</button>
<span class="text">
{{#if type}}
<em class="type">{{type}}</em>
{{/if}}
{{{uri text}}}
</span>
</div>
{{/slice}}
</script>
</div> </div>
{{/slice}}
</script>
<script src="/socket.io/socket.io.js"></script>
<script src="/js/jquery.js"></script> <script src="/js/jquery.js"></script>
<script src="/js/jquery.plugins.js"></script> <script src="/js/jquery.plugins.js"></script>
<script src="/js/uri.js"></script> <script src="/js/uri.js"></script>
<script src="/js/handlebars.js"></script> <script src="/js/handlebars.js"></script>
<script src="/socket.io/socket.io.js"></script> <script src="/js/handlebars.helpers.js"></script>
<script src="/js/chat.js"></script> <script src="/js/chat.js"></script>
</body> </body>

View File

@ -38,69 +38,58 @@ $(function() {
}); });
var tpl = []; var tpl = [];
function render(id, data) { function render(name, data) {
tpl[id] = tpl[id] || Handlebars.compile($(id).remove().html()); tpl[name] = tpl[name] || Handlebars.compile($("#templates ." + name).html());
return tpl[id](data); return tpl[name](data);
} }
function event(e, data) { function event(e, data) {
switch (e) { switch (e) {
case "join": case "join":
chat.append(render("#windows", {windows: [data.chan]})) chat.append(render("windows", {windows: [data.chan]}))
.find(".window") .find(".messages")
.last() .last()
.scrollGlue({speed: 200})
.end()
.find(".input") .find(".input")
.tabComplete({list: commands}) .tabComplete({list: commands})
.inputHistory({submit: true})
.end()
.bringToTop()
.find(".messages")
.scrollGlue({speed: 400})
.end(); .end();
$("#network-" + data.id) $("#network-" + data.id)
.append(render("#channels", {channels: [data.chan]})) .append(render("channels", {channels: [data.chan]}))
.find(".channel") .find(".channel")
.last() .last()
.uniqueClass("active") .trigger("click")
.end(); .end();
break; break;
case "msg": case "msg":
$("#window-" + data.id) $("#window-" + data.id)
.find(".messages") .find(".messages")
.append(render("#messages", {messages: [data.msg]})); .append(render("messages", {messages: [data.msg]}));
break; break;
case "networks": case "networks":
var channels = $.map(data.networks, function(n) { return n.channels; }); var channels = $.map(data.networks, function(n) { return n.channels; });
chat.html(render("#windows", {windows: channels})) chat.html(render("windows", {windows: channels}))
.find(".window")
.last()
.bringToTop()
.end()
.find(".input") .find(".input")
.tabComplete({list: commands}) .tabComplete({list: commands})
.inputHistory({submit: true})
.end() .end()
.find(".hidden") .find(".hidden")
.prev(".show-more") .prev(".show-more")
.show(); .show();
chat.find(".messages") chat.find(".messages")
.scrollGlue({speed: 400}) .scrollGlue({speed: 200})
.end(); .end();
sidebar.html(render("#networks", {networks: data.networks})) $("#networks")
.html(render("networks", {networks: data.networks}))
.find(".channel") .find(".channel")
.last() .last()
.addClass("active") .addClass("active")
.end(); .end();
break; break;
case "nick":
// ..
break;
case "part": case "part":
$("#channel-" + data.id) $("#channel-" + data.id)
.add("#window-" + data.id) .add("#window-" + data.id)
@ -110,21 +99,55 @@ $(function() {
case "users": case "users":
$("#window-" + data.id) $("#window-" + data.id)
.find(".users") .find(".users")
.html(render("#users", {users: data.users})) .html(render("users", {users: data.users}))
.end(); .end();
break; break;
} }
} }
var z = 1;
sidebar.on("click", "button", function() {
var button = $(this);
var target = button.data("target");
sidebar.find(".active").removeClass("active");
button.addClass("active")
$(target)
.css({"z-index": z++})
.find(".input")
.focus()
.end();
});
chat.on("click", ".show-more .btn", function() {
var target = $(this).parent();
var html = $.parseHTML(target.next(".hidden").remove().html());
target.replaceWith(html);
});
chat.on("click", ".user", function() {
var user = $(this);
var id = user.closest(".window").find(".form").data("target");
var name = user.html().replace(/[\s+@]/g, "");
if (name == "-!-" || name.indexOf(".") != -1) {
return;
}
console.log({id: id, text: "/whois " + name});
socket.emit("input", {
id: id,
text: "/whois " + name,
});
});
chat.on("submit", "form", function() { chat.on("submit", "form", function() {
var input = $(this).find(".input"); var form = $(this);
var input = form.find(".input");
var text = input.val(); var text = input.val();
if (text == "") { if (text == "") {
return false; return;
} }
input.val(""); input.val("");
socket.emit("input", { socket.emit("input", {
id: input.data("target"), id: form.data("target"),
text: text, text: text,
}); });
}); });
@ -133,74 +156,9 @@ $(function() {
var input = $(this).parents().eq(1).find(".messages").scrollToBottom(); var input = $(this).parents().eq(1).find(".messages").scrollToBottom();
}); });
chat.on("click", ".user", function() { Handlebars.registerHelper(
var user = $(this); "partial", function(id) {
var id = user.closest(".window").find(".input").data("target");
var name = user.text().trim();
if (name == "-!-" || name.indexOf(".") != -1) {
return;
}
socket.emit("input", {
id: id,
text: "/whois " + name,
});
});
chat.on("click", ".close", function() {
var id = $(this).closest(".window").find(".input").data("target");
socket.emit("input", {
id: id,
text: "/part",
});
});
chat.on("click", ".show-more .btn", function() {
var more = $(this).parent();
var html = $.parseHTML(more.next(".hidden").remove().html());
more.replaceWith(html);
});
sidebar.on("click", ".channel", function(e) {
e.preventDefault();
sidebar.find(".channel").removeClass("active");
$("#window-" + $(this).addClass("active").attr("id").replace("channel-", ""))
.bringToTop();
});
function escape(text) {
var e = {
"<": "&lt;",
">": "&gt;"
};
return text.replace(/[<>]/g, function (c) {
return e[c];
});
}
Handlebars.registerHelper({
"partial": function(id) {
return new Handlebars.SafeString(render(id, this)); return new Handlebars.SafeString(render(id, this));
}, }
"slice": function(items, block) { );
var limit = block.hash.limit;
var rows = $.map(items, function(i) {
return block.fn(i);
});
var html = "";
var hide = rows
.slice(0, Math.max(0, rows.length - limit))
.join("");
if (hide != "") {
html = "<script type='text/html' class='hidden'>" + hide + "</script>";
}
html += rows.slice(-limit).join("");
return html;
},
"uri": function(text) {
text = escape(text);
return URI.withinString(text, function(url) {
return "<a href='" + url.replace(/^www/, "//www") + "' target='_blank'>" + url + "</a>";
});
},
});
}); });

View File

@ -0,0 +1,37 @@
Handlebars.registerHelper(
"slice", function(items, block) {
var limit = block.hash.limit;
var rows = [];
items.forEach(function(i) {
rows.push(block.fn(i));
});
var html = "";
var hide = rows
.slice(0, Math.max(0, rows.length - limit))
.join("");
if (hide != "") {
html = "<script type='text/html' class='hidden'>" + hide + "</script>";
}
html += rows.slice(-limit).join("");
return html;
}
);
function escape(text) {
var e = {
"<": "&lt;",
">": "&gt;"
};
return text.replace(/[<>]/g, function (c) {
return e[c];
});
}
Handlebars.registerHelper(
"uri", function(text) {
text = escape(text);
return URI.withinString(text, function(url) {
return "<a href='" + url.replace(/^www/, "//www") + "' target='_blank'>" + url + "</a>";
});
}
);

View File

@ -179,7 +179,7 @@
* Copyright (c) 2014 Mattias Erming <mattias@mattiaserming.com> * Copyright (c) 2014 Mattias Erming <mattias@mattiaserming.com>
* Licensed under the MIT License. * Licensed under the MIT License.
* *
* Version 0.2.3 * Version 0.2.4
*/ */
(function($) { (function($) {
$.fn.tabComplete = function(options) { $.fn.tabComplete = function(options) {
@ -211,16 +211,25 @@
var last = text.splice(-1)[0]; var last = text.splice(-1)[0];
if (!match.length) { if (!match.length) {
match = $.grep(self.data('list'), function(w) { match = [];
$.each(self.data('list'), function(i, w) {
var l = last; var l = last;
if (l == '') { if (l == '') {
return; return;
} else if (typeof w === "function") {
var words = w(l);
if (words) {
match = match.concat(words);
}
} else if (!settings.caseSensitive) {
if (0 == w.toLowerCase().indexOf(l.toLowerCase())) {
match.push(w);
}
} else {
if (0 == w.indexOf(l)) {
match.push(w);
}
} }
if (!settings.caseSensitive) {
l = l.toLowerCase();
w = w.toLowerCase();
}
return w.indexOf(l) == 0;
}); });
} }

View File

View File

@ -1,6 +1,5 @@
module.exports = { module.exports = {
port: 9000, port: 9000,
theme: "",
defaults: { defaults: {
nick: "shout_user", nick: "shout_user",
realname: "http://github.com/erming/shout", realname: "http://github.com/erming/shout",

View File

@ -19,13 +19,7 @@ function Network(attr) {
}; };
Network.prototype.toJSON = function() { Network.prototype.toJSON = function() {
var copy = _.omit( var clone = _.omit(this, "client");
this, clone.name = clone.host.split(".")[1] || clone.host;
"client" return clone;
);
var name = copy.host.split(".")[1];
if (name) {
copy.host = name;
}
return copy;
}; };

View File

@ -14,9 +14,7 @@ var Network = require("./models/network");
var User = require("./models/user"); var User = require("./models/user");
var sockets = null; var sockets = null;
var networks = [ var networks = [];
new Network({host: "Shout Client"})
];
var events = [ var events = [
"join", "join",
@ -59,7 +57,7 @@ function index(req, res, next) {
fs.readFile("client/index.html", function(err, file) { fs.readFile("client/index.html", function(err, file) {
var data = _.merge( var data = _.merge(
require("../package.json"), require("../package.json"),
config {} // config
); );
res.end(_.template( res.end(_.template(
file, file,