diff --git a/client/css/style.css b/client/css/style.css index 5347cde..1acf668 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -36,10 +36,6 @@ h2 { cursor: pointer; padding: 0 12px; } -#sidebar .channel[data-id='0'] { - background: #eaeaea; - border-bottom-color: #e5e5e5; -} #sidebar .channel:first-child { color: #333; overflow: hidden; @@ -47,6 +43,10 @@ h2 { #sidebar .channel:hover { text-decoration: underline; } +#sidebar .network:first-child .channel:first-child { + background: #eaeaea; + border-bottom-color: #e5e5e5; +} #chat { bottom: 0; left: 200px; diff --git a/client/js/chat.js b/client/js/chat.js index 2535931..3e20503 100644 --- a/client/js/chat.js +++ b/client/js/chat.js @@ -1,12 +1,8 @@ $(function() { var socket = io.connect(""); - socket.on( - "event", - function(event) { - console.log(event); - View[event.action](event); - } - ); + socket.on("event", function(event) { + render(event); + }); var chat = $("#chat"); var sidebar = $("#sidebar"); @@ -17,11 +13,77 @@ $(function() { var messages = $("#messages").html(); var users = $("#users").html() + function render(event) { + var type = event.type; + var data = event.data; + var action = event.action; + var target = event.target; + + if (action == "REMOVE") { + remove(target); + return; + } + if (target != "") { + target = $("[data-id='" + target + "']"); + } + + switch (type) { + + case "NETWORK": + case "CHANNEL": + refresh(data); + break; + + case "USER": + target = target.find(".users"); + target.html(Mustache.render(users, {users: event.data})); + break; + + case "MESSAGE": + var keepAtBottom = target.isScrollBottom(); + target = target.find(".messages"); + target.append(Mustache.render(messages, {messages: event.data})); + if (keepAtBottom) { + target.scrollToBottom(); + } + break; + + } + } + + function remove(id) { + $("[data-id='" + id + "']").remove(); + } + + function refresh(data) { + chat.html(""); + var partials = { + users: users, + messages: messages + }; + data.forEach(function(network) { + chat.append(Mustache.render(channels, network, partials)); + }); + sidebar.html( + Mustache.render(networks, { + networks: data + }) + ); + + chat.find(".messages").scrollToBottom(); + chat.find(".window") + // Sort windows by `data-id` value. + .sort(function(a, b) { return ($(a).data("id") - $(b).data("id")); }) + .last() + .bringToTop() + .find(".input") + .focus(); + } + var View = {}; View.refresh = function(event) { var data = event.data; - sidebar.html( Mustache.render(networks, { networks: data @@ -48,32 +110,23 @@ $(function() { }; View.add = function(event) { - var target = ""; - var render = ""; - + var target = $("[data-id='" + event.target + "'] "); switch (event.type) { - case "user": - target = ".users"; - render = Mustache.render( - users, {users: event.data} - ); + + case "users": + target = target.find(".users"); + target.html(Mustache.render(users, {users: event.data})); break; - case "message": - target = ".messages"; - render = Mustache.render( - messages, {messages: event.data} - ); - break; - } - - if (target != "") { - target = $("[data-id='" + event.target + "'] " + target); + case "messages": var keepAtBottom = target.isScrollBottom(); - target.append(render); + target = target.find(".messages"); + target.append(Mustache.render(messages, {messages: event.data})); if (keepAtBottom) { target.scrollToBottom(); } + break; + } }; @@ -81,10 +134,6 @@ $(function() { $("[data-id='" + event.target + "']").remove(); }; - View.change = function(event) { - // .. - }; - chat.on("submit", "form", function() { var input = $(this).find(".input"); var text = input.val(); @@ -107,7 +156,10 @@ $(function() { (function() { var highest = 1; $.fn.bringToTop = function() { - return this.css('z-index', highest++); + return this + .css('z-index', highest++) + .find("input") + .focus(); }; $.fn.scrollToBottom = function() { diff --git a/lib/models.js b/lib/models.js index 6d6c001..a41641d 100644 --- a/lib/models.js +++ b/lib/models.js @@ -2,7 +2,7 @@ var _ = require("lodash"); var moment = require("moment"); var models = exports; -var id = 0; +var id = 1; models.Network = function(attr) { attr = attr || {}; @@ -56,3 +56,11 @@ models.Event = function(attr) { type: "" })); }; + +models.Target = function(attr) { + attr = attr || {}; + _.extend(this, _.defaults(attr, { + network: "", + channel: "" + })); +}; diff --git a/lib/server.js b/lib/server.js index a660abd..f264f93 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,149 +1,130 @@ -var _ = require("lodash"); var connect = require("connect"); +var models = require("./models.js"); +var _ = require("lodash"); var irc = require("irc"); -var io = require("socket.io"); -var models = require("./models.js"); +var io = require("socket.io"); exports.listen = listen; -var sockets; +var sockets = false;; var networks = []; -addNetwork("Lobby", false); +addToServer( + "NETWORK", + new models.Network({address: "Start"}) +); function listen(port) { var http = connect() .use(connect.static("client")) .listen(port); - sockets = io.listen(http).sockets; - sockets.on("connection", function(socket) { - init(socket); - }); + sockets = io + .listen(http) + .on("connection", initSocket) + .sockets; } -function init(socket) { +function initSocket(socket) { + socket.on("input", handleUserInput); refresh(); - socket.on( - "input", - function(input) { - handleUserInput(input) - } - ); +} + +function sendEvent(params) { + if (sockets) { + sockets.emit("event", new models.Event(params)); + } } function refresh() { - if (typeof sockets === "undefined") { - return; - } - sockets.emit("event", new models.Event({ - action: "refresh", - data: networks - })); + sendEvent({action: "RENDER", type: "NETWORK", data: networks}); } -function handleUserInput(input) { - var text = input.text; - var target = getChannel(input.id); +function addToServer(type, model, target) { + switch (type) { - if (text.charAt(0) != "/") { - return addMessage(target, text); - } + case "NETWORK": + var channel = new models.Channel({ + name: model.address, + type: "network" + }); - var args = text.substr(1).split(" "); - var cmd = args[0].toUpperCase(); + model.channels.push(channel); + networks.push(model); - switch (cmd) { - - case "SERVER": - case "CONNECT": - if (args[1]) { - addNetwork(args[1], true); - } - break; - - case "JOIN": - if (args[1]) { - target.network.channels.push( - new models.Channel({ - name: args[1] - }) - ); - refresh(); - } + refresh(); break; - case "PART": - target.network.channels = - _.without(target.network.channels, target.channel); + case "CHANNEL": + target.network.channels.push(model); refresh(); break; - default: - addMessage( - target, - "Command '/" + args[0] + "' does not exist." + case "MESSAGE": + target.channel.messages + .push(model); + sendEvent({ + action: "RENDER", + type: "MESSAGE", + target: target.channel.id, + data: model + }); + break; + + } +} + +function handleUserInput(input) { + var id = input.id; + var text = input.text; + + var args = text.substr(1).split(' '); + var cmd = text.charAt(0) == "/" ? args[0].toUpperCase() + : "MESSAGE"; + + var target = getTarget(id); + + switch (cmd) { + + case "SERVER": + case "CONNECT": + addToServer( + "NETWORK", + new models.Network({address: args[1]}) ); break; - } -} - -function addNetwork(addr, bool) { - bool = bool || false; - - var chan = new models.Channel({ - name: addr, - type: "network" - }); - var network = new models.Network({ - channels: [chan] - }); - - networks.push(network); - refresh(); - - if (addr == "Lobby") { - return; - } - - network.client = new irc.Client(addr, "default_user"); - network.client.addListener("raw", function() { - handleEvent( - network, arguments + case "JOIN": + addToServer( + "CHANNEL", + new models.Channel({name: args[1]}), + target ); - }); -} + break; + + case "PART": + target.network.channels = _.reject(target.network.channels, {id: id}); + refresh(); + break; -function handleEvent(network) { - var args = arguments; - var target = { - network: network, - channel: network.channels[0] - }; + case "MESSAGE": + addToServer( + "MESSAGE", + new models.Message({text: input.text}), + getTarget(id) + ); + break; - console.log(args[1]); - addMessage(target, args[1][0].args); -} - -function addMessage(target, text) { - var message = _.extend(new models.Message, {text: text}); - target.channel.messages.push(message); - sockets.emit("event", new models.Event({ - action: "add", - type: "message", - target: target.channel.id, - data: message - })); -} - -function getChannel(id) { - for (var i = 0; i < networks.length; i++) { - var find = { - network: networks[i], - channel: _.findWhere(networks[i].channels, {id: id}) - }; - if (typeof find.channel !== "undefined") { - return find; - } + } +} + +function getTarget(id) { + var find; + _.each(networks, function(n) { + find = {network: n, channel: _.findWhere(n.channels, {id: id})}; + if (find.channel) + return; + }); + if (find.channel) { + return new models.Target(find); } }