chrome-extension/src/components/Chat/MessageDisplay.tsx
2024-11-14 06:28:43 +02:00

162 lines
4.0 KiB
TypeScript

import React, { useEffect } from "react";
import DOMPurify from "dompurify";
import "./styles.css";
import { executeEvent } from "../../utils/events";
const extractComponents = (url) => {
if (!url || !url.startsWith("qortal://")) {
// Check if url exists and starts with "qortal://"
return null;
}
url = url.replace(/^(qortal\:\/\/)/, ""); // Safe to use replace now
if (url.includes("/")) {
let parts = url.split("/");
const service = parts[0].toUpperCase();
parts.shift();
const name = parts[0];
parts.shift();
let identifier;
const path = parts.join("/");
return { service, name, identifier, path };
}
return null;
};
function processText(input) {
const linkRegex = /(qortal:\/\/\S+)/g;
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
const parts = node.textContent.split(linkRegex);
if (parts.length > 0) {
const fragment = document.createDocumentFragment();
parts.forEach((part) => {
if (part.startsWith("qortal://")) {
const link = document.createElement("span");
link.setAttribute("data-url", part);
link.textContent = part;
link.style.color = "var(--code-block-text-color)";
link.style.textDecoration = "underline";
link.style.cursor = "pointer";
fragment.appendChild(link);
} else {
fragment.appendChild(document.createTextNode(part));
}
});
node.replaceWith(fragment);
}
} else {
Array.from(node.childNodes).forEach(processNode);
}
}
const wrapper = document.createElement("div");
wrapper.innerHTML = input;
processNode(wrapper);
return wrapper.innerHTML;
}
export const MessageDisplay = ({ htmlContent, isReply }) => {
const linkify = (text) => {
if (!text) return ""; // Return an empty string if text is null or undefined
let textFormatted = text;
const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g;
textFormatted = text.replace(urlPattern, (url) => {
const href = url.startsWith("http") ? url : `https://${url}`;
return `<a href="${DOMPurify.sanitize(
href
)}" class="auto-link">${DOMPurify.sanitize(url)}</a>`;
});
return processText(textFormatted);
};
const sanitizedContent = DOMPurify.sanitize(linkify(htmlContent), {
ALLOWED_TAGS: [
"a",
"b",
"i",
"em",
"strong",
"p",
"br",
"div",
"span",
"img",
"ul",
"ol",
"li",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"blockquote",
"code",
"pre",
"table",
"thead",
"tbody",
"tr",
"th",
"td",
],
ALLOWED_ATTR: [
"href",
"target",
"rel",
"class",
"src",
"alt",
"title",
"width",
"height",
"style",
"align",
"valign",
"colspan",
"rowspan",
"border",
"cellpadding",
"cellspacing",
"data-url",
],
});
const handleClick = async (e) => {
e.preventDefault();
const target = e.target;
if (target.tagName === "A") {
const href = target.getAttribute("href");
if (chrome && chrome.tabs) {
chrome.tabs.create({ url: href }, (tab) => {
if (chrome.runtime.lastError) {
console.error("Error opening tab:", chrome.runtime.lastError);
} else {
console.log("Tab opened successfully:", tab);
}
});
}
} else if (target.getAttribute("data-url")) {
const url = target.getAttribute("data-url");
const res = extractComponents(url);
if (res) {
const { service, name, identifier, path } = res;
executeEvent("addTab", { data: { service, name, identifier, path } });
executeEvent("open-apps-mode", {});
}
}
};
return (
<div
className={`tiptap ${isReply ? "isReply" : ""}`}
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
onClick={handleClick}
/>
);
};