mirror of
https://github.com/thelounge/thelounge
synced 2024-11-23 12:33:07 +00:00
145 lines
4.1 KiB
JavaScript
145 lines
4.1 KiB
JavaScript
"use strict";
|
|
|
|
const parseStyle = require("./ircmessageparser/parseStyle");
|
|
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");
|
|
const emojiMap = require("../fullnamemap.json");
|
|
const LinkPreviewToggle = require("../../../components/LinkPreviewToggle.vue").default;
|
|
|
|
// Create an HTML `span` with styling information for a given fragment
|
|
function createFragment(fragment, createElement) {
|
|
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");
|
|
}
|
|
|
|
if (classes.length === 0 && !fragment.hexColor) {
|
|
return fragment.text;
|
|
}
|
|
|
|
return createElement("span", {
|
|
class: classes,
|
|
style: {
|
|
"color": `#${fragment.hexColor}`,
|
|
"background-color": fragment.hexBgColor ? `#${fragment.hexBgColor}` : null,
|
|
},
|
|
}, fragment.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(createElement, text, message = null) {
|
|
// Extract the styling information and get the plain text version from it
|
|
const styleFragments = parseStyle(text);
|
|
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
|
|
|
|
// 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).
|
|
const channelPrefixes = ["#", "&"]; // TODO Channel prefixes should be RPL_ISUPPORT.CHANTYPES
|
|
const userModes = ["!", "@", "%", "+"]; // TODO User modes should be RPL_ISUPPORT.PREFIX
|
|
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
|
const linkParts = findLinks(cleanText);
|
|
const emojiParts = findEmoji(cleanText);
|
|
const nameParts = findNames(cleanText, message ? (message.users || []) : []);
|
|
|
|
const parts = channelParts
|
|
.concat(linkParts)
|
|
.concat(emojiParts)
|
|
.concat(nameParts);
|
|
|
|
// Merge the styling information with the channels / URLs / nicks / text objects and
|
|
// generate HTML strings with the resulting fragments
|
|
return merge(parts, styleFragments, cleanText).map((textPart) => {
|
|
const fragments = textPart.fragments.map((fragment) => createFragment(fragment, createElement));
|
|
|
|
// Wrap these potentially styled fragments with links and channel buttons
|
|
if (textPart.link) {
|
|
const preview = message && message.previews.find((p) => p.link === textPart.link);
|
|
const link = createElement("a", {
|
|
class: [
|
|
"inline-channel",
|
|
],
|
|
attrs: {
|
|
href: textPart.link,
|
|
target: "_blank",
|
|
rel: "noopener",
|
|
},
|
|
}, fragments);
|
|
|
|
if (!preview) {
|
|
return link;
|
|
}
|
|
|
|
return [link, createElement(LinkPreviewToggle, {
|
|
class: ["toggle-button", "toggle-preview"],
|
|
props: {
|
|
link: preview,
|
|
},
|
|
}, fragments)];
|
|
} else if (textPart.channel) {
|
|
return createElement("span", {
|
|
class: [
|
|
"inline-channel",
|
|
],
|
|
attrs: {
|
|
"role": "button",
|
|
"tabindex": 0,
|
|
"data-chan": textPart.channel,
|
|
},
|
|
}, fragments);
|
|
} else if (textPart.emoji) {
|
|
return createElement("span", {
|
|
class: [
|
|
"emoji",
|
|
],
|
|
attrs: {
|
|
"role": "img",
|
|
"aria-label": emojiMap[textPart.emoji] ? `Emoji: ${emojiMap[textPart.emoji]}` : null,
|
|
},
|
|
}, fragments);
|
|
} else if (textPart.nick) {
|
|
return createElement("span", {
|
|
class: [
|
|
"user",
|
|
colorClass(textPart.nick),
|
|
],
|
|
attrs: {
|
|
"role": "button",
|
|
"data-name": textPart.nick,
|
|
},
|
|
}, fragments);
|
|
}
|
|
|
|
return fragments;
|
|
});
|
|
};
|