From 3d31fa46864c759f55a6650a6de29b35fc381c51 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Tue, 14 Nov 2017 14:36:45 -0800 Subject: [PATCH] Link nicks mentioned in messages --- .../handlebars/ircmessageparser/findNames.js | 17 ++++++++ client/js/libs/handlebars/parse.js | 20 +++++++-- client/views/msg.tpl | 2 +- src/plugins/irc-events/message.js | 10 +++++ .../handlebars/ircmessageparser/findNames.js | 26 +++++++++++ test/client/js/libs/handlebars/parse.js | 43 +++++++++++++++++++ 6 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 client/js/libs/handlebars/ircmessageparser/findNames.js create mode 100644 test/client/js/libs/handlebars/ircmessageparser/findNames.js diff --git a/client/js/libs/handlebars/ircmessageparser/findNames.js b/client/js/libs/handlebars/ircmessageparser/findNames.js new file mode 100644 index 00000000..fd06f5d4 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/findNames.js @@ -0,0 +1,17 @@ +"use strict"; + +function findNames(text, users) { + const result = []; + let index = 0; + users.forEach((nick) => { + index = text.indexOf(nick, index); + result.push({ + start: index, + end: index + nick.length, + nick: nick, + }); + }); + return result; +} + +module.exports = findNames; diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 0bccaa1d..3c75219e 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -6,7 +6,9 @@ const anyIntersection = require("./ircmessageparser/anyIntersection"); const findChannels = require("./ircmessageparser/findChannels"); const findLinks = require("./ircmessageparser/findLinks"); const findEmoji = require("./ircmessageparser/findEmoji"); +const findNames = require("./ircmessageparser/findNames"); const merge = require("./ircmessageparser/merge"); +const colorClass = require("./colorClass"); // Create an HTML `span` with styling information for a given fragment function createFragment(fragment) { @@ -47,9 +49,14 @@ function createFragment(fragment) { return escapedText; } -// Transform an IRC message potentially filled with styling control codes, URLs -// and channels into a string of HTML elements to display on the client. -module.exports = function parse(text) { +// Transform an IRC message potentially filled with styling control codes, URLs, +// nicknames, and channels into a string of HTML elements to display on the client. +module.exports = function parse(text, users) { + // if it's not the users we're expecting, but rather is passed from Handlebars (occurs when users passed to template is null or undefined) + if (users && users.hash) { + users = []; + } + // Extract the styling information and get the plain text version from it const styleFragments = parseStyle(text); const cleanText = styleFragments.map((fragment) => fragment.text).join(""); @@ -62,11 +69,13 @@ module.exports = function parse(text) { const channelParts = findChannels(cleanText, channelPrefixes, userModes); const linkParts = findLinks(cleanText); const emojiParts = findEmoji(cleanText); + const nameParts = findNames(cleanText, (users || [])); // Sort all parts identified based on their position in the original text const parts = channelParts .concat(linkParts) .concat(emojiParts) + .concat(nameParts) .sort((a, b) => a.start - b.start || b.end - a.end) .reduce((prev, curr) => { const intersection = prev.some((p) => anyIntersection(p, curr)); @@ -77,7 +86,7 @@ module.exports = function parse(text) { return prev.concat([curr]); }, []); - // Merge the styling information with the channels / URLs / text objects and + // Merge the styling information with the channels / URLs / nicks / text objects and // generate HTML strings with the resulting fragments return merge(parts, styleFragments).map((textPart) => { // Create HTML strings with styling information @@ -92,6 +101,9 @@ module.exports = function parse(text) { return `${fragments}`; } else if (textPart.emoji) { return `${fragments}`; + } else if (textPart.nick) { + const nick = Handlebars.Utils.escapeExpression(textPart.nick); + return `${fragments}`; } return fragments; diff --git a/client/views/msg.tpl b/client/views/msg.tpl index 4c14619b..64ec97e7 100644 --- a/client/views/msg.tpl +++ b/client/views/msg.tpl @@ -8,7 +8,7 @@ {{/if}} - {{{parse text}}} + {{{parse text users}}} {{#each previews}}
diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index 3fed76d0..4b648bc2 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -4,6 +4,7 @@ const Chan = require("../../models/chan"); const Msg = require("../../models/msg"); const LinkPrefetch = require("./link"); const cleanIrcMessage = require("../../../client/js/libs/handlebars/ircmessageparser/cleanIrcMessage"); +const nickRegExp = /[\w[\]\\`^{|}-]{4,}/gi; module.exports = function(irc, network) { const client = this; @@ -88,6 +89,14 @@ module.exports = function(irc, network) { highlight = network.highlightRegex.test(data.message); } + const users = []; + let match; + while ((match = nickRegExp.exec(data.message))) { + if (chan.findUser(match[0])) { + users.push(match[0]); + } + } + const msg = new Msg({ type: data.type, time: data.time, @@ -95,6 +104,7 @@ module.exports = function(irc, network) { text: data.message, self: self, highlight: highlight, + users: users, }); // No prefetch URLs unless are simple MESSAGE or ACTION types diff --git a/test/client/js/libs/handlebars/ircmessageparser/findNames.js b/test/client/js/libs/handlebars/ircmessageparser/findNames.js new file mode 100644 index 00000000..52c937d7 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/findNames.js @@ -0,0 +1,26 @@ +"use strict"; + +const expect = require("chai").expect; +const findNames = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findNames"); + +describe("findNames", () => { + it("should find nicks in text", () => { + const input = ": Hello, xPaw, how's it going?"; + const expected = [ + { + start: 1, + end: 10, + nick: "MaxLeiter", + }, + { + start: 20, + end: 24, + nick: "xPaw", + }, + ]; + const nicks = ["MaxLeiter", "xPaw"]; + const actual = findNames(input, nicks); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js index 2c08f8f2..4921059b 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -243,6 +243,49 @@ describe("parse Handlebars helper", () => { expect(actual).to.deep.equal(expected); }); + it("should find nicks", () => { + const testCases = [{ + users: ["MaxLeiter"], + input: "test, MaxLeiter", + expected: + "test, " + + "" + + "MaxLeiter" + + "", + }]; + + const actual = testCases.map((testCase) => parse(testCase.input, testCase.users)); + const expected = testCases.map((testCase) => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find nicks", () => { + const testCases = [{ + users: ["MaxLeiter, test"], + input: "#test-channelMaxLeiter", + expected: + "" + + "#test-channelMaxLeiter" + + "", + }, + { + users: ["MaxLeiter, test"], + input: "https://www.MaxLeiter.com/test", + expected: + "" + + "https://www.MaxLeiter.com/test" + + "", + }, + + ]; + + const actual = testCases.map((testCase) => parse(testCase.input)); + const expected = testCases.map((testCase) => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + it("should go bonkers like mirc", () => { const testCases = [{ input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge",