hardlounge/client/js/libs/handlebars/parse.js

202 lines
5.0 KiB
JavaScript
Raw Normal View History

"use strict";
const parseStyle = require("./ircmessageparser/parseStyle");
const findChannels = require("./ircmessageparser/findChannels");
const findLinks = require("./ircmessageparser/findLinks");
2017-08-23 10:19:04 -04:00
const findEmoji = require("./ircmessageparser/findEmoji");
2017-11-14 17:36:45 -05:00
const findNames = require("./ircmessageparser/findNames");
const merge = require("./ircmessageparser/merge");
2017-11-14 17:36:45 -05:00
const colorClass = require("./colorClass");
2018-03-09 17:00:16 -05:00
const emojiMap = require("../fullnamemap.json");
2018-07-11 14:00:12 -04:00
const LinkPreviewToggle = require("../../../components/LinkPreviewToggle.vue").default;
2019-08-09 16:20:08 -04:00
const LinkPreviewFileSize = require("../../../components/LinkPreviewFileSize.vue").default;
2019-07-17 05:33:59 -04:00
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/gu;
// Create an HTML `span` with styling information for a given fragment
2018-07-12 04:41:40 -04:00
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");
}
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}`;
}
}
return hasData ? createElement("span", data, fragment.text) : fragment.text;
}
2017-11-14 17:36:45 -05: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.
2018-07-19 13:44:24 -04:00
module.exports = function parse(createElement, text, message = undefined, network = undefined) {
// 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).
2018-07-19 13:44:24 -04:00
const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
const userModes = network ? network.serverOptions.PREFIX : ["!", "@", "%", "+"];
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
const linkParts = findLinks(cleanText);
2017-08-23 10:19:04 -04:00
const emojiParts = findEmoji(cleanText);
2019-07-17 05:33:59 -04:00
const nameParts = findNames(cleanText, message ? message.users || [] : []);
const parts = channelParts
.concat(linkParts)
2017-08-23 10:19:04 -04:00
.concat(emojiParts)
2018-04-19 12:00:46 -04:00
.concat(nameParts);
2017-11-14 17:36:45 -05:00
// Merge the styling information with the channels / URLs / nicks / text objects and
// generate HTML strings with the resulting fragments
2018-04-19 12:01:20 -04:00
return merge(parts, styleFragments, cleanText).map((textPart) => {
2019-07-17 05:33:59 -04:00
const fragments = textPart.fragments.map((fragment) =>
createFragment(fragment, createElement)
);
// Wrap these potentially styled fragments with links and channel buttons
if (textPart.link) {
2019-08-09 16:20:08 -04:00
const preview =
message &&
message.previews &&
message.previews.find((p) => p.link === textPart.link);
2019-07-17 05:33:59 -04:00
const link = createElement(
"a",
{
attrs: {
href: textPart.link,
2019-08-09 16:20:08 -04:00
dir: preview ? null : "auto",
2019-07-17 05:33:59 -04:00
target: "_blank",
rel: "noopener",
},
2018-07-12 04:41:40 -04:00
},
2019-07-17 05:33:59 -04:00
fragments
);
2018-07-12 04:41:40 -04:00
if (!preview) {
return link;
2018-03-09 17:00:16 -05:00
}
2019-08-09 16:20:08 -04: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
return createElement(
"span",
{
attrs: {
dir: "auto",
2019-07-17 05:33:59 -04:00
},
},
2019-08-09 16:20:08 -04:00
linkEls
);
2018-07-12 04:41:40 -04:00
} else if (textPart.channel) {
2019-07-17 05:33:59 -04:00
return createElement(
"span",
{
class: ["inline-channel"],
attrs: {
role: "button",
dir: "auto",
2019-07-17 05:33:59 -04:00
tabindex: 0,
"data-chan": textPart.channel,
},
2018-07-12 04:41:40 -04:00
},
2019-07-17 05:33:59 -04:00
fragments
);
2018-07-12 04:41:40 -04:00
} else if (textPart.emoji) {
2019-06-10 15:14:11 -04:00
const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, "");
2019-07-17 05:33:59 -04: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 04:41:40 -04:00
},
2019-07-17 05:33:59 -04:00
fragments
);
2017-11-14 17:36:45 -05:00
} else if (textPart.nick) {
2019-07-17 05:33:59 -04:00
return createElement(
"span",
{
class: ["user", colorClass(textPart.nick)],
attrs: {
role: "button",
dir: "auto",
2019-07-17 05:33:59 -04:00
"data-name": textPart.nick,
},
2018-07-12 04:41:40 -04:00
},
2019-07-17 05:33:59 -04:00
fragments
);
}
return fragments;
2018-07-12 04:41:40 -04:00
});
};