Merge pull request #1709 from MaxLeiter/master

Link nicks mentioned in messages
This commit is contained in:
Pavel Djundik 2017-11-27 19:31:36 +02:00 committed by GitHub
commit 4e2eed2023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 5 deletions

View File

@ -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;

View File

@ -6,7 +6,9 @@ const anyIntersection = require("./ircmessageparser/anyIntersection");
const findChannels = require("./ircmessageparser/findChannels"); const findChannels = require("./ircmessageparser/findChannels");
const findLinks = require("./ircmessageparser/findLinks"); const findLinks = require("./ircmessageparser/findLinks");
const findEmoji = require("./ircmessageparser/findEmoji"); const findEmoji = require("./ircmessageparser/findEmoji");
const findNames = require("./ircmessageparser/findNames");
const merge = require("./ircmessageparser/merge"); const merge = require("./ircmessageparser/merge");
const colorClass = require("./colorClass");
// Create an HTML `span` with styling information for a given fragment // Create an HTML `span` with styling information for a given fragment
function createFragment(fragment) { function createFragment(fragment) {
@ -47,9 +49,14 @@ function createFragment(fragment) {
return escapedText; return escapedText;
} }
// Transform an IRC message potentially filled with styling control codes, URLs // Transform an IRC message potentially filled with styling control codes, URLs,
// and channels into a string of HTML elements to display on the client. // nicknames, and channels into a string of HTML elements to display on the client.
module.exports = function parse(text) { 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 // Extract the styling information and get the plain text version from it
const styleFragments = parseStyle(text); const styleFragments = parseStyle(text);
const cleanText = styleFragments.map((fragment) => fragment.text).join(""); const cleanText = styleFragments.map((fragment) => fragment.text).join("");
@ -62,11 +69,13 @@ module.exports = function parse(text) {
const channelParts = findChannels(cleanText, channelPrefixes, userModes); const channelParts = findChannels(cleanText, channelPrefixes, userModes);
const linkParts = findLinks(cleanText); const linkParts = findLinks(cleanText);
const emojiParts = findEmoji(cleanText); const emojiParts = findEmoji(cleanText);
const nameParts = findNames(cleanText, (users || []));
// Sort all parts identified based on their position in the original text // Sort all parts identified based on their position in the original text
const parts = channelParts const parts = channelParts
.concat(linkParts) .concat(linkParts)
.concat(emojiParts) .concat(emojiParts)
.concat(nameParts)
.sort((a, b) => a.start - b.start || b.end - a.end) .sort((a, b) => a.start - b.start || b.end - a.end)
.reduce((prev, curr) => { .reduce((prev, curr) => {
const intersection = prev.some((p) => anyIntersection(p, curr)); const intersection = prev.some((p) => anyIntersection(p, curr));
@ -77,7 +86,7 @@ module.exports = function parse(text) {
return prev.concat([curr]); 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 // generate HTML strings with the resulting fragments
return merge(parts, styleFragments).map((textPart) => { return merge(parts, styleFragments).map((textPart) => {
// Create HTML strings with styling information // Create HTML strings with styling information
@ -92,6 +101,9 @@ module.exports = function parse(text) {
return `<span class="inline-channel" role="button" tabindex="0" data-chan="${escapedChannel}">${fragments}</span>`; return `<span class="inline-channel" role="button" tabindex="0" data-chan="${escapedChannel}">${fragments}</span>`;
} else if (textPart.emoji) { } else if (textPart.emoji) {
return `<span class="emoji">${fragments}</span>`; return `<span class="emoji">${fragments}</span>`;
} else if (textPart.nick) {
const nick = Handlebars.Utils.escapeExpression(textPart.nick);
return `<span role="button" class="user ${colorClass(nick)}" data-name="${nick}">${fragments}</span>`;
} }
return fragments; return fragments;

View File

@ -8,7 +8,7 @@
{{/if}} {{/if}}
</span> </span>
<span class="content"> <span class="content">
<span class="text">{{{parse text}}}</span> <span class="text">{{{parse text users}}}</span>
{{#each previews}} {{#each previews}}
<div class="preview" data-url="{{link}}"></div> <div class="preview" data-url="{{link}}"></div>

View File

@ -4,6 +4,7 @@ const Chan = require("../../models/chan");
const Msg = require("../../models/msg"); const Msg = require("../../models/msg");
const LinkPrefetch = require("./link"); const LinkPrefetch = require("./link");
const cleanIrcMessage = require("../../../client/js/libs/handlebars/ircmessageparser/cleanIrcMessage"); const cleanIrcMessage = require("../../../client/js/libs/handlebars/ircmessageparser/cleanIrcMessage");
const nickRegExp = /[\w[\]\\`^{|}-]{4,}/gi;
module.exports = function(irc, network) { module.exports = function(irc, network) {
const client = this; const client = this;
@ -88,6 +89,14 @@ module.exports = function(irc, network) {
highlight = network.highlightRegex.test(data.message); 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({ const msg = new Msg({
type: data.type, type: data.type,
time: data.time, time: data.time,
@ -95,6 +104,7 @@ module.exports = function(irc, network) {
text: data.message, text: data.message,
self: self, self: self,
highlight: highlight, highlight: highlight,
users: users,
}); });
// No prefetch URLs unless are simple MESSAGE or ACTION types // No prefetch URLs unless are simple MESSAGE or ACTION types

View File

@ -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 = "<MaxLeiter>: 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);
});
});

View File

@ -243,6 +243,49 @@ describe("parse Handlebars helper", () => {
expect(actual).to.deep.equal(expected); expect(actual).to.deep.equal(expected);
}); });
it("should find nicks", () => {
const testCases = [{
users: ["MaxLeiter"],
input: "test, MaxLeiter",
expected:
"test, " +
"<span role=\"button\" class=\"user color-12\" data-name=\"MaxLeiter\">" +
"MaxLeiter" +
"</span>",
}];
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:
"<span class=\"inline-channel\" role=\"button\" tabindex=\"0\" data-chan=\"#test-channelMaxLeiter\">" +
"#test-channelMaxLeiter" +
"</span>",
},
{
users: ["MaxLeiter, test"],
input: "https://www.MaxLeiter.com/test",
expected:
"<a href=\"https://www.MaxLeiter.com/test\" target=\"_blank\" rel=\"noopener\">" +
"https://www.MaxLeiter.com/test" +
"</a>",
},
];
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", () => { it("should go bonkers like mirc", () => {
const testCases = [{ const testCases = [{
input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge", input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge",