diff --git a/lib/channel.js b/lib/channel.js index 43f396ac..9e8a760c 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -2865,6 +2865,57 @@ Channel.prototype.handleChat = function (user, data) { } }; +Channel.prototype.handlePm = function (user, data) { + if (typeof data.meta !== "object") { + data.meta = {}; + } + + if (!user.name) { + return; + } + + if (typeof data.msg !== "string" || typeof data.to !== "string") { + return; + } + + var msg = data.msg.substring(0, 240); + var to = null; + for (var i = 0; i < this.users.length; i++) { + if (this.users[i].name.toLowerCase() === data.to) { + to = this.users[i]; + break; + } + } + + if (!to) { + return; + } + + var meta = {}; + if (user.rank >= 2) { + if ("modflair" in data.meta && data.meta.modflair === user.rank) { + meta.modflair = data.meta.modflair; + } + } + + if (msg.indexOf(">") === 0) { + meta.addClass = "greentext"; + } + + msg = XSS.sanitizeText(msg); + msg = this.filterMessage(msg); + var msgobj = { + username: user.name, + to: data.to, + msg: msg, + meta: meta, + time: Date.now() + }; + + to.socket.emit("pm", msgobj); + user.socket.emit("pm", msgobj); +}; + /** * Filters a chat message */ diff --git a/lib/user.js b/lib/user.js index e6709259..8f7ffc8a 100644 --- a/lib/user.js +++ b/lib/user.js @@ -256,6 +256,14 @@ User.prototype.initChannelCallbacks = function () { self.channel.handleChat(self, data); }); + + wrapTypecheck("pm", function (data) { + if (typeof data.msg !== "string" || typeof data.to !== "string") { + return; + } + + self.channel.handlePm(self, data); + }); wrapTypecheck("newPoll", function (data) { self.channel.handleOpenPoll(self, data); diff --git a/templates/channel.jade b/templates/channel.jade index d890fe3a..bf579b4a 100644 --- a/templates/channel.jade +++ b/templates/channel.jade @@ -202,6 +202,7 @@ html(lang="en") mixin permeditor() .modal-footer button.btn.btn-default(type="button", data-dismiss="modal") Close + #pmbar include footer mixin footer() script(src=sioSource) diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index c1ce3ad0..01a00dbb 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -461,6 +461,25 @@ Callbacks = { addChatMessage(data); }, + pm: function (data) { + var name = data.username; + if (IGNORED.indexOf(name) !== -1) { + return; + } + + if (data.username === CLIENT.name) { + name = data.to; + } + var pm = initPm(name); + var msg = formatChatMessage(data, pm.data("last")); + var buffer = pm.find(".pm-buffer"); + msg.appendTo(buffer); + buffer.scrollTop(buffer.prop("scrollHeight")); + if (pm.find(".panel-body").is(":hidden")) { + pm.removeClass("panel-default").addClass("panel-primary"); + } + }, + joinMessage: function(data) { if(USEROPTS.joinmessage) addChatMessage(data); diff --git a/www/assets/js/data.js b/www/assets/js/data.js index 331e0d40..5e747dd9 100644 --- a/www/assets/js/data.js +++ b/www/assets/js/data.js @@ -56,8 +56,9 @@ var CHATHIST = []; var CHATHISTIDX = 0; var CHATTHROTTLE = false; var SCROLLCHAT = true; -var LASTCHATNAME = ""; -var LASTCHATTIME = 0; +var LASTCHAT = { + name: "" +}; var FOCUSED = true; var PAGETITLE = "CyTube"; var TITLE_BLINK; diff --git a/www/assets/js/util.js b/www/assets/js/util.js index 4b592c18..1d1b67c4 100644 --- a/www/assets/js/util.js +++ b/www/assets/js/util.js @@ -199,6 +199,15 @@ function addUserDropdown(entry) { ignore.text("Unignore User"); } + /* pm button */ + var pm = $("").addClass("btn btn-xs btn-default") + .text("Private Message") + .appendTo(btngroup) + .click(function () { + initPm(name).find(".panel-heading").click(); + menu.hide(); + }); + /* give/remove leader (moderator+ only) */ if (hasPermission("leaderctl")) { var ldr = $("").addClass("btn btn-xs btn-default") @@ -1251,7 +1260,7 @@ function sendVideoUpdate() { /* chat */ -function formatChatMessage(data) { +function formatChatMessage(data, last) { // Backwards compat if (!data.meta || data.msgclass) { data.meta = { @@ -1261,7 +1270,7 @@ function formatChatMessage(data) { }; } // Phase 1: Determine whether to show the username or not - var skip = data.username === LASTCHATNAME; + var skip = data.username === last.name; if(data.meta.addClass === "server-whisper") skip = true; // Prevent impersonation by abuse of the bold filter @@ -1272,8 +1281,7 @@ function formatChatMessage(data) { data.msg = execEmotes(data.msg); - LASTCHATNAME = data.username; - LASTCHATTIME = data.time; + last.name = data.username; var div = $("
"); /* drink is a special case because the entire container gets the class, not just the message */ @@ -1332,7 +1340,7 @@ function addChatMessage(data) { if(IGNORED.indexOf(data.username) !== -1) { return; } - var div = formatChatMessage(data); + var div = formatChatMessage(data, LASTCHAT); // Incoming: a bunch of crap for the feature where if you hover over // a message, it highlights messages from that user div.data("sender", data.username); @@ -2265,3 +2273,72 @@ function execEmotes(msg) { return msg; } + +function initPm(user) { + if ($("#pm-" + user).length > 0) { + return $("#pm-" + user); + } + + var pm = $("").addClass("panel panel-default pm-panel") + .appendTo($("#pmbar")) + .data("last", { name: "" }) + .attr("id", "pm-" + user); + + var title = $("").addClass("panel-heading").text(user).appendTo(pm); + var close = $("").addClass("close pull-right") + .html("×") + .appendTo(title).click(function () { + pm.remove(); + $("#pm-placeholder-" + user).remove(); + }); + + var body = $("").addClass("panel-body").appendTo(pm).hide(); + var placeholder; + title.click(function () { + body.toggle(); + pm.removeClass("panel-primary").addClass("panel-default"); + if (!body.is(":hidden")) { + placeholder = $("").addClass("pm-panel-placeholder") + .attr("id", "pm-placeholder-" + user) + .insertAfter(pm); + var left = pm.position().left; + pm.css("position", "absolute") + .css("bottom", "0px") + .css("left", left); + } else { + pm.css("position", ""); + $("#pm-placeholder-" + user).remove(); + } + }); + var buffer = $("").addClass("pm-buffer linewrap").appendTo(body); + $("