mirror of
https://github.com/thelounge/thelounge
synced 2024-11-22 12:03:11 +00:00
Link nicks mentioned in messages
This commit is contained in:
parent
ba002cca64
commit
3d31fa4686
6 changed files with 113 additions and 5 deletions
17
client/js/libs/handlebars/ircmessageparser/findNames.js
Normal file
17
client/js/libs/handlebars/ircmessageparser/findNames.js
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
26
test/client/js/libs/handlebars/ircmessageparser/findNames.js
Normal file
26
test/client/js/libs/handlebars/ircmessageparser/findNames.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue