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

View File

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

View File

@ -38,69 +38,58 @@ $(function() {
});
var tpl = [];
function render(id, data) {
tpl[id] = tpl[id] || Handlebars.compile($(id).remove().html());
return tpl[id](data);
function render(name, data) {
tpl[name] = tpl[name] || Handlebars.compile($("#templates ." + name).html());
return tpl[name](data);
}
function event(e, data) {
switch (e) {
case "join":
chat.append(render("#windows", {windows: [data.chan]}))
.find(".window")
chat.append(render("windows", {windows: [data.chan]}))
.find(".messages")
.last()
.scrollGlue({speed: 200})
.end()
.find(".input")
.tabComplete({list: commands})
.inputHistory({submit: true})
.end()
.bringToTop()
.find(".messages")
.scrollGlue({speed: 400})
.end();
$("#network-" + data.id)
.append(render("#channels", {channels: [data.chan]}))
.append(render("channels", {channels: [data.chan]}))
.find(".channel")
.last()
.uniqueClass("active")
.trigger("click")
.end();
break;
case "msg":
$("#window-" + data.id)
.find(".messages")
.append(render("#messages", {messages: [data.msg]}));
.append(render("messages", {messages: [data.msg]}));
break;
case "networks":
var channels = $.map(data.networks, function(n) { return n.channels; });
chat.html(render("#windows", {windows: channels}))
.find(".window")
.last()
.bringToTop()
.end()
chat.html(render("windows", {windows: channels}))
.find(".input")
.tabComplete({list: commands})
.inputHistory({submit: true})
.end()
.find(".hidden")
.prev(".show-more")
.show();
chat.find(".messages")
.scrollGlue({speed: 400})
.scrollGlue({speed: 200})
.end();
sidebar.html(render("#networks", {networks: data.networks}))
$("#networks")
.html(render("networks", {networks: data.networks}))
.find(".channel")
.last()
.addClass("active")
.end();
break;
case "nick":
// ..
break;
case "part":
$("#channel-" + data.id)
.add("#window-" + data.id)
@ -110,21 +99,55 @@ $(function() {
case "users":
$("#window-" + data.id)
.find(".users")
.html(render("#users", {users: data.users}))
.html(render("users", {users: data.users}))
.end();
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() {
var input = $(this).find(".input");
var form = $(this);
var input = form.find(".input");
var text = input.val();
if (text == "") {
return false;
return;
}
input.val("");
socket.emit("input", {
id: input.data("target"),
id: form.data("target"),
text: text,
});
});
@ -133,74 +156,9 @@ $(function() {
var input = $(this).parents().eq(1).find(".messages").scrollToBottom();
});
chat.on("click", ".user", function() {
var user = $(this);
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) {
Handlebars.registerHelper(
"partial", function(id) {
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>
* Licensed under the MIT License.
*
* Version 0.2.3
* Version 0.2.4
*/
(function($) {
$.fn.tabComplete = function(options) {
@ -211,16 +211,25 @@
var last = text.splice(-1)[0];
if (!match.length) {
match = $.grep(self.data('list'), function(w) {
match = [];
$.each(self.data('list'), function(i, w) {
var l = last;
if (l == '') {
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 = {
port: 9000,
theme: "",
defaults: {
nick: "shout_user",
realname: "http://github.com/erming/shout",

View File

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

View File

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