Files
S.P.L.U.R.T-Station-13/tgui/packages/tgui-panel/chat/replaceInTextNode.js
2020-08-15 16:08:10 +08:00

129 lines
3.2 KiB
JavaScript

/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
/**
* Replaces text matching a regular expression with a custom node.
*/
export const replaceInTextNode = (regex, createNode) => node => {
const text = node.textContent;
const textLength = text.length;
let match;
let lastIndex = 0;
let fragment;
let n = 0;
// eslint-disable-next-line no-cond-assign
while (match = regex.exec(text)) {
n += 1;
// Lazy init fragment
if (!fragment) {
fragment = document.createDocumentFragment();
}
const matchText = match[0];
const matchLength = matchText.length;
const matchIndex = match.index;
// Insert previous unmatched chunk
if (lastIndex < matchIndex) {
fragment.appendChild(document.createTextNode(
text.substring(lastIndex, matchIndex)));
}
lastIndex = matchIndex + matchLength;
// Create a wrapper node
fragment.appendChild(createNode(matchText));
}
if (fragment) {
// Insert the remaining unmatched chunk
if (lastIndex < textLength) {
fragment.appendChild(document.createTextNode(
text.substring(lastIndex, textLength)));
}
// Commit the fragment
node.parentNode.replaceChild(fragment, node);
}
return n;
};
// Highlight
// --------------------------------------------------------
/**
* Default highlight node.
*/
const createHighlightNode = text => {
const node = document.createElement('span');
node.setAttribute('style',
'background-color:#fd4;color:#000');
node.textContent = text;
return node;
};
/**
* Highlights the text in the node based on the provided regular expression.
*
* @param {Node} node Node which you want to process
* @param {RegExp} regex Regular expression to highlight
* @param {(text: string) => Node} createNode Highlight node creator
* @returns {number} Number of matches
*/
export const highlightNode = (
node,
regex,
createNode = createHighlightNode,
) => {
if (!createNode) {
createNode = createHighlightNode;
}
let n = 0;
const childNodes = node.childNodes;
for (let i = 0; i < childNodes.length; i++) {
const node = childNodes[i];
// Is a text node
if (node.nodeType === 3) {
n += replaceInTextNode(regex, createNode)(node);
}
else {
n += highlightNode(node, regex, createNode);
}
}
return n;
};
// Linkify
// --------------------------------------------------------
const URL_REGEX = /(?:(?:https?:\/\/)|(?:www\.))(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&@#/%?=~_|$!:,.;()]+/ig;
/**
* Highlights the text in the node based on the provided regular expression.
*
* @param {Node} node Node which you want to process
* @returns {number} Number of matches
*/
export const linkifyNode = node => {
let n = 0;
const childNodes = node.childNodes;
for (let i = 0; i < childNodes.length; i++) {
const node = childNodes[i];
const tag = String(node.nodeName).toLowerCase();
// Is a text node
if (node.nodeType === 3) {
n += linkifyTextNode(node);
}
else if (tag !== 'a') {
n += linkifyNode(node);
}
}
return n;
};
const linkifyTextNode = replaceInTextNode(URL_REGEX, text => {
const node = document.createElement('a');
node.href = text;
node.textContent = text;
return node;
});