2017-03-18 09:21:18 +00:00
|
|
|
"use strict";
|
|
|
|
|
2017-03-18 08:35:17 +00:00
|
|
|
const parseStyle = require("./ircmessageparser/parseStyle");
|
|
|
|
const findChannels = require("./ircmessageparser/findChannels");
|
2017-04-18 03:28:35 +00:00
|
|
|
const findLinks = require("./ircmessageparser/findLinks");
|
2017-08-23 14:19:04 +00:00
|
|
|
const findEmoji = require("./ircmessageparser/findEmoji");
|
2017-11-14 22:36:45 +00:00
|
|
|
const findNames = require("./ircmessageparser/findNames");
|
2017-03-18 08:35:17 +00:00
|
|
|
const merge = require("./ircmessageparser/merge");
|
2017-11-14 22:36:45 +00:00
|
|
|
const colorClass = require("./colorClass");
|
2019-11-05 10:36:44 +00:00
|
|
|
const emojiMap = require("./fullnamemap.json");
|
|
|
|
const LinkPreviewToggle = require("../../components/LinkPreviewToggle.vue").default;
|
|
|
|
const LinkPreviewFileSize = require("../../components/LinkPreviewFileSize.vue").default;
|
|
|
|
const InlineChannel = require("../../components/InlineChannel.vue").default;
|
2019-07-17 09:33:59 +00:00
|
|
|
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/gu;
|
2019-11-09 22:21:34 +00:00
|
|
|
const store = require("../store").default;
|
|
|
|
const {generateUserContextMenu} = require("./contextMenu");
|
2016-10-09 19:14:02 +00:00
|
|
|
|
2017-04-04 04:36:03 +00:00
|
|
|
// Create an HTML `span` with styling information for a given fragment
|
2018-07-12 08:41:40 +00:00
|
|
|
function createFragment(fragment, createElement) {
|
2018-07-11 17:22:44 +00:00
|
|
|
const classes = [];
|
|
|
|
|
|
|
|
if (fragment.bold) {
|
|
|
|
classes.push("irc-bold");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.textColor !== undefined) {
|
|
|
|
classes.push("irc-fg" + fragment.textColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.bgColor !== undefined) {
|
|
|
|
classes.push("irc-bg" + fragment.bgColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.italic) {
|
|
|
|
classes.push("irc-italic");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.underline) {
|
|
|
|
classes.push("irc-underline");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.strikethrough) {
|
|
|
|
classes.push("irc-strikethrough");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.monospace) {
|
|
|
|
classes.push("irc-monospace");
|
|
|
|
}
|
|
|
|
|
2018-09-05 18:00:39 +00:00
|
|
|
const data = {};
|
|
|
|
let hasData = false;
|
|
|
|
|
|
|
|
if (classes.length > 0) {
|
|
|
|
hasData = true;
|
|
|
|
data.class = classes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fragment.hexColor) {
|
|
|
|
hasData = true;
|
|
|
|
data.style = {
|
|
|
|
color: `#${fragment.hexColor}`,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (fragment.hexBgColor) {
|
|
|
|
data.style["background-color"] = `#${fragment.hexBgColor}`;
|
|
|
|
}
|
2018-07-11 17:22:44 +00:00
|
|
|
}
|
|
|
|
|
2018-09-05 18:00:39 +00:00
|
|
|
return hasData ? createElement("span", data, fragment.text) : fragment.text;
|
2018-07-11 17:22:44 +00:00
|
|
|
}
|
|
|
|
|
2017-11-14 22:36:45 +00:00
|
|
|
// 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.
|
2019-11-09 22:21:34 +00:00
|
|
|
module.exports = function parse(
|
|
|
|
createElement,
|
|
|
|
text,
|
|
|
|
message = undefined,
|
|
|
|
network = undefined,
|
|
|
|
$root
|
|
|
|
) {
|
2017-04-04 04:36:03 +00:00
|
|
|
// Extract the styling information and get the plain text version from it
|
2017-03-18 08:35:17 +00:00
|
|
|
const styleFragments = parseStyle(text);
|
2017-04-08 12:34:31 +00:00
|
|
|
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
|
2014-12-10 11:30:45 +00:00
|
|
|
|
2017-04-04 04:36:03 +00:00
|
|
|
// On the plain text, find channels and URLs, returned as "parts". Parts are
|
|
|
|
// arrays of objects containing start and end markers, as well as metadata
|
|
|
|
// depending on what was found (channel or link).
|
2018-07-19 17:44:24 +00:00
|
|
|
const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
|
|
|
|
const userModes = network ? network.serverOptions.PREFIX : ["!", "@", "%", "+"];
|
2017-03-18 08:35:17 +00:00
|
|
|
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
|
|
|
const linkParts = findLinks(cleanText);
|
2017-08-23 14:19:04 +00:00
|
|
|
const emojiParts = findEmoji(cleanText);
|
2019-07-17 09:33:59 +00:00
|
|
|
const nameParts = findNames(cleanText, message ? message.users || [] : []);
|
2014-12-10 11:30:45 +00:00
|
|
|
|
2017-03-18 08:35:17 +00:00
|
|
|
const parts = channelParts
|
|
|
|
.concat(linkParts)
|
2017-08-23 14:19:04 +00:00
|
|
|
.concat(emojiParts)
|
2018-04-19 16:00:46 +00:00
|
|
|
.concat(nameParts);
|
2016-05-12 13:07:15 +00:00
|
|
|
|
2017-11-14 22:36:45 +00:00
|
|
|
// Merge the styling information with the channels / URLs / nicks / text objects and
|
2017-04-04 04:36:03 +00:00
|
|
|
// generate HTML strings with the resulting fragments
|
2018-04-19 16:01:20 +00:00
|
|
|
return merge(parts, styleFragments, cleanText).map((textPart) => {
|
2019-07-17 09:33:59 +00:00
|
|
|
const fragments = textPart.fragments.map((fragment) =>
|
|
|
|
createFragment(fragment, createElement)
|
|
|
|
);
|
2014-12-10 11:30:45 +00:00
|
|
|
|
2017-04-04 04:36:03 +00:00
|
|
|
// Wrap these potentially styled fragments with links and channel buttons
|
2017-03-18 08:35:17 +00:00
|
|
|
if (textPart.link) {
|
2019-08-09 20:20:08 +00:00
|
|
|
const preview =
|
|
|
|
message &&
|
|
|
|
message.previews &&
|
|
|
|
message.previews.find((p) => p.link === textPart.link);
|
2019-07-17 09:33:59 +00:00
|
|
|
const link = createElement(
|
|
|
|
"a",
|
|
|
|
{
|
|
|
|
attrs: {
|
|
|
|
href: textPart.link,
|
2019-08-09 20:20:08 +00:00
|
|
|
dir: preview ? null : "auto",
|
2019-07-17 09:33:59 +00:00
|
|
|
target: "_blank",
|
|
|
|
rel: "noopener",
|
|
|
|
},
|
2018-07-12 08:41:40 +00:00
|
|
|
},
|
2019-07-17 09:33:59 +00:00
|
|
|
fragments
|
|
|
|
);
|
2018-07-12 08:41:40 +00:00
|
|
|
|
|
|
|
if (!preview) {
|
|
|
|
return link;
|
2018-03-09 22:00:16 +00:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:20:08 +00:00
|
|
|
const linkEls = [link];
|
|
|
|
|
|
|
|
if (preview.size > 0) {
|
|
|
|
linkEls.push(
|
|
|
|
createElement(LinkPreviewFileSize, {
|
|
|
|
props: {
|
|
|
|
size: preview.size,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
linkEls.push(
|
|
|
|
createElement(LinkPreviewToggle, {
|
|
|
|
props: {
|
|
|
|
link: preview,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// We wrap the link, size, and the toggle button into <span dir="auto">
|
|
|
|
// to correctly keep the left-to-right order of these elements
|
2019-08-03 12:35:13 +00:00
|
|
|
return createElement(
|
|
|
|
"span",
|
|
|
|
{
|
|
|
|
attrs: {
|
|
|
|
dir: "auto",
|
2019-07-17 09:33:59 +00:00
|
|
|
},
|
2019-08-03 12:35:13 +00:00
|
|
|
},
|
2019-08-09 20:20:08 +00:00
|
|
|
linkEls
|
2019-08-03 12:35:13 +00:00
|
|
|
);
|
2018-07-12 08:41:40 +00:00
|
|
|
} else if (textPart.channel) {
|
2019-07-17 09:33:59 +00:00
|
|
|
return createElement(
|
2019-10-17 14:17:02 +00:00
|
|
|
InlineChannel,
|
2019-07-17 09:33:59 +00:00
|
|
|
{
|
2019-10-17 14:17:02 +00:00
|
|
|
props: {
|
|
|
|
channel: textPart.channel,
|
2019-07-17 09:33:59 +00:00
|
|
|
},
|
2018-07-12 08:41:40 +00:00
|
|
|
},
|
2019-07-17 09:33:59 +00:00
|
|
|
fragments
|
|
|
|
);
|
2018-07-12 08:41:40 +00:00
|
|
|
} else if (textPart.emoji) {
|
2019-06-10 19:14:11 +00:00
|
|
|
const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, "");
|
2019-07-17 09:33:59 +00:00
|
|
|
const title = emojiMap[emojiWithoutModifiers]
|
|
|
|
? `Emoji: ${emojiMap[emojiWithoutModifiers]}`
|
|
|
|
: null;
|
|
|
|
|
|
|
|
return createElement(
|
|
|
|
"span",
|
|
|
|
{
|
|
|
|
class: ["emoji"],
|
|
|
|
attrs: {
|
|
|
|
role: "img",
|
|
|
|
"aria-label": title,
|
|
|
|
title: title,
|
|
|
|
},
|
2018-07-12 08:41:40 +00:00
|
|
|
},
|
2019-07-17 09:33:59 +00:00
|
|
|
fragments
|
|
|
|
);
|
2017-11-14 22:36:45 +00:00
|
|
|
} else if (textPart.nick) {
|
2019-07-17 09:33:59 +00:00
|
|
|
return createElement(
|
|
|
|
"span",
|
|
|
|
{
|
|
|
|
class: ["user", colorClass(textPart.nick)],
|
|
|
|
attrs: {
|
|
|
|
role: "button",
|
2019-08-03 12:35:13 +00:00
|
|
|
dir: "auto",
|
2019-07-17 09:33:59 +00:00
|
|
|
"data-name": textPart.nick,
|
|
|
|
},
|
2019-11-09 22:21:34 +00:00
|
|
|
on: {
|
|
|
|
contextmenu($event) {
|
|
|
|
$event.preventDefault();
|
|
|
|
const channel = store.state.activeChannel.channel;
|
|
|
|
let user = channel.users.find((u) => u.nick === textPart.nick);
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
user = {
|
|
|
|
nick: textPart.nick,
|
|
|
|
mode: "",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const items = generateUserContextMenu($root, channel, network, user);
|
|
|
|
$root.$refs.app.openContextMenu($event, items);
|
|
|
|
},
|
|
|
|
},
|
2018-07-12 08:41:40 +00:00
|
|
|
},
|
2019-07-17 09:33:59 +00:00
|
|
|
fragments
|
|
|
|
);
|
2017-03-18 08:35:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fragments;
|
2018-07-12 08:41:40 +00:00
|
|
|
});
|
2017-03-18 08:35:17 +00:00
|
|
|
};
|