diff --git a/client/normalize.css b/client/css/normalize.css similarity index 100% rename from client/normalize.css rename to client/css/normalize.css diff --git a/client/style.css b/client/css/style.css similarity index 61% rename from client/style.css rename to client/css/style.css index 954abdb8..06af5276 100644 --- a/client/style.css +++ b/client/css/style.css @@ -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%; } diff --git a/client/index.html b/client/index.html index 73b11d26..80759a15 100644 --- a/client/index.html +++ b/client/index.html @@ -7,102 +7,114 @@ - - - + + -
-
- - -
- - - -
-
- - - - - - - - - + + + + + + + + - {{/slice}} - + - + diff --git a/client/js/chat.js b/client/js/chat.js index b4bea020..46c0937f 100644 --- a/client/js/chat.js +++ b/client/js/chat.js @@ -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 = { - "<": "<", - ">": ">" - }; - 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 = ""; - } - html += rows.slice(-limit).join(""); - return html; - }, - "uri": function(text) { - text = escape(text); - return URI.withinString(text, function(url) { - return "" + url + ""; - }); - }, - }); + } + ); }); diff --git a/client/js/handlebars.helpers.js b/client/js/handlebars.helpers.js new file mode 100644 index 00000000..2fe41b88 --- /dev/null +++ b/client/js/handlebars.helpers.js @@ -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 = ""; + } + html += rows.slice(-limit).join(""); + return html; + } +); + +function escape(text) { + var e = { + "<": "<", + ">": ">" + }; + return text.replace(/[<>]/g, function (c) { + return e[c]; + }); +} + +Handlebars.registerHelper( + "uri", function(text) { + text = escape(text); + return URI.withinString(text, function(url) { + return "" + url + ""; + }); + } +); diff --git a/client/js/jquery.plugins.js b/client/js/jquery.plugins.js index 7b7df248..1bb48737 100644 --- a/client/js/jquery.plugins.js +++ b/client/js/jquery.plugins.js @@ -179,7 +179,7 @@ * Copyright (c) 2014 Mattias Erming * 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; }); } diff --git a/client/themes/.gitkeep b/client/themes/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config.js b/config.js index 818386b9..b428c3cb 100644 --- a/config.js +++ b/config.js @@ -1,6 +1,5 @@ module.exports = { port: 9000, - theme: "", defaults: { nick: "shout_user", realname: "http://github.com/erming/shout", diff --git a/lib/models/network.js b/lib/models/network.js index 37ecf6fa..538fcecc 100644 --- a/lib/models/network.js +++ b/lib/models/network.js @@ -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; }; diff --git a/lib/server.js b/lib/server.js index 2b46c7b3..6b798bee 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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,