diff --git a/client/js/render.js b/client/js/render.js index 57f4da61..b076e230 100644 --- a/client/js/render.js +++ b/client/js/render.js @@ -81,7 +81,7 @@ function buildChatMessage(data) { renderPreview(preview, msg); }); - if ((type === "message" || type === "action") && chan.hasClass("channel")) { + if ((type === "message" || type === "action" || type === "notice") && chan.hasClass("channel")) { const nicks = chan.find(".users").data("nicks"); if (nicks) { const find = nicks.indexOf(data.msg.from); @@ -143,22 +143,10 @@ function renderChannelMessages(data) { function renderChannelUsers(data) { const users = chat.find("#chan-" + data.id).find(".users"); - let nicks = users.data("nicks") || []; - const oldSortOrder = {}; - - for (const i in nicks) { - oldSortOrder[nicks[i]] = i; - } - - nicks = []; - - for (const i in data.users) { - nicks.push(data.users[i].nick); - } - - nicks = nicks.sort(function(a, b) { - return (oldSortOrder[a] || Number.MAX_VALUE) - (oldSortOrder[b] || Number.MAX_VALUE); - }); + const nicks = data.users + .concat() // Make a copy of the user list, sort is applied in-place + .sort((a, b) => b.lastMessage - a.lastMessage) + .map((a) => a.nick); const search = users .find(".search") @@ -191,7 +179,13 @@ function renderNetworks(data) { channels: channels }) ); - channels.forEach(renderChannel); + channels.forEach((channel) => { + renderChannel(channel); + + if (channel.type === "channel") { + chat.find("#chan-" + channel.id).data("needsNamesRefresh", true); + } + }); utils.confirmExit(); sorting(); diff --git a/src/models/chan.js b/src/models/chan.js index 66418195..af4607a8 100644 --- a/src/models/chan.js +++ b/src/models/chan.js @@ -93,8 +93,12 @@ Chan.prototype.sortUsers = function(irc) { }); }; +Chan.prototype.findUser = function(nick) { + return _.find(this.users, {nick: nick}); +}; + Chan.prototype.getMode = function(name) { - var user = _.find(this.users, {nick: name}); + var user = this.findUser(name); if (user) { return user.mode; } @@ -104,6 +108,7 @@ Chan.prototype.getMode = function(name) { Chan.prototype.toJSON = function() { var clone = _.clone(this); + clone.users = []; // Do not send user list, the client will explicitly request it when needed clone.messages = clone.messages.slice(-100); return clone; }; diff --git a/src/models/user.js b/src/models/user.js index f1dca1e4..2f8340f0 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -8,20 +8,24 @@ function User(attr, prefixLookup) { _.defaults(this, attr, { modes: [], mode: "", - nick: "" + nick: "", + lastMessage: 0, }); - // irc-framework sets character mode, but lounge works with symbols - this.modes = this.modes.map((mode) => prefixLookup[mode]); - - if (this.modes[0]) { - this.mode = this.modes[0]; - } + this.setModes(this.modes, prefixLookup); } +User.prototype.setModes = function(modes, prefixLookup) { + // irc-framework sets character mode, but lounge works with symbols + this.modes = modes.map((mode) => prefixLookup[mode]); + + this.mode = this.modes[0] || ""; +}; + User.prototype.toJSON = function() { return { nick: this.nick, mode: this.mode, + lastMessage: this.lastMessage, }; }; diff --git a/src/plugins/irc-events/kick.js b/src/plugins/irc-events/kick.js index cbe525a3..6b3174e6 100644 --- a/src/plugins/irc-events/kick.js +++ b/src/plugins/irc-events/kick.js @@ -11,10 +11,12 @@ module.exports = function(irc, network) { return; } + const user = chan.findUser(data.kicked); + if (data.kicked === irc.user.nick) { chan.users = []; } else { - chan.users = _.without(chan.users, _.find(chan.users, {nick: data.kicked})); + chan.users = _.without(chan.users, user); } client.emit("users", { @@ -24,7 +26,7 @@ module.exports = function(irc, network) { var msg = new Msg({ type: Msg.Type.KICK, time: data.time, - mode: chan.getMode(data.nick), + mode: user.mode, from: data.nick, target: data.kicked, text: data.message || "", diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index 039f5d62..a358a74a 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -71,6 +71,12 @@ module.exports = function(irc, network) { // Query messages (unless self) always highlight if (chan.type === Chan.Type.QUERY) { highlight = !self; + } else if (chan.type === Chan.Type.CHANNEL) { + const user = chan.findUser(data.nick); + + if (user) { + user.lastMessage = data.time || Date.now(); + } } } diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.js index 28f94052..266515bb 100644 --- a/src/plugins/irc-events/mode.js +++ b/src/plugins/irc-events/mode.js @@ -81,7 +81,7 @@ module.exports = function(irc, network) { return; } - const user = _.find(targetChan.users, {nick: mode.param}); + const user = targetChan.findUser(mode.param); if (!user) { return; } diff --git a/src/plugins/irc-events/names.js b/src/plugins/irc-events/names.js index 16ade0f3..c75f860f 100644 --- a/src/plugins/irc-events/names.js +++ b/src/plugins/irc-events/names.js @@ -1,19 +1,39 @@ "use strict"; -var User = require("../../models/user"); +const User = require("../../models/user"); module.exports = function(irc, network) { - var client = this; + const client = this; + irc.on("userlist", function(data) { - var chan = network.getChannel(data.channel); + const chan = network.getChannel(data.channel); if (typeof chan === "undefined") { return; } - chan.users = data.users.map((user) => new User({ - nick: user.nick, - modes: user.modes, - }, network.prefixLookup)); + // Create lookup map of current users, + // as we need to keep certain properties + // and we can recycle existing User objects + const oldUsers = new Map(); + + chan.users.forEach((user) => { + oldUsers.set(user.nick, user); + }); + + chan.users = data.users.map((user) => { + const oldUser = oldUsers.get(user.nick); + + // For existing users, we only need to update mode + if (oldUser) { + oldUser.setModes(user.modes, network.prefixLookup); + return oldUser; + } + + return new User({ + nick: user.nick, + modes: user.modes, + }, network.prefixLookup); + }); chan.sortUsers(irc); diff --git a/src/plugins/irc-events/nick.js b/src/plugins/irc-events/nick.js index 7c8b61ad..af10f233 100644 --- a/src/plugins/irc-events/nick.js +++ b/src/plugins/irc-events/nick.js @@ -1,6 +1,5 @@ "use strict"; -var _ = require("lodash"); var Msg = require("../../models/msg"); module.exports = function(irc, network) { @@ -25,7 +24,7 @@ module.exports = function(irc, network) { } network.channels.forEach((chan) => { - var user = _.find(chan.users, {nick: data.nick}); + const user = chan.findUser(data.nick); if (typeof user === "undefined") { return; } diff --git a/src/plugins/irc-events/part.js b/src/plugins/irc-events/part.js index 001bbc71..70376cdf 100644 --- a/src/plugins/irc-events/part.js +++ b/src/plugins/irc-events/part.js @@ -18,7 +18,7 @@ module.exports = function(irc, network) { chan: chan.id }); } else { - var user = _.find(chan.users, {nick: from}); + const user = chan.findUser(from); chan.users = _.without(chan.users, user); client.emit("users", { chan: chan.id diff --git a/src/plugins/irc-events/quit.js b/src/plugins/irc-events/quit.js index 3882eadc..fd0c3caa 100644 --- a/src/plugins/irc-events/quit.js +++ b/src/plugins/irc-events/quit.js @@ -7,8 +7,7 @@ module.exports = function(irc, network) { var client = this; irc.on("quit", function(data) { network.channels.forEach((chan) => { - var from = data.nick; - var user = _.find(chan.users, {nick: from}); + const user = chan.findUser(data.nick); if (typeof user === "undefined") { return; } @@ -22,7 +21,7 @@ module.exports = function(irc, network) { mode: user.mode || "", text: data.message || "", hostmask: data.ident + "@" + data.hostname, - from: from + from: data.nick }); chan.pushMessage(client, msg); });