diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index f4cdce7..0800186 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -1,67 +1,108 @@ import React, { useEffect } from 'react'; import DOMPurify from 'dompurify'; -import './styles.css'; // Ensure this CSS file is imported +import './styles.css'; +import { executeEvent } from '../../utils/events'; -export const MessageDisplay = ({ htmlContent , isReply}) => { +const extractComponents = (url) => { + console.log('url', url); + if (!url.startsWith("qortal://")) { + return null; + } + url = url.replace(/^(qortal\:\/\/)/, ""); + 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) => { - // Regular expression to find URLs starting with https://, http://, or www. + let textFormatted = text; const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g; - - // Replace plain text URLs with anchor tags - return text?.replace(urlPattern, (url) => { + textFormatted = text.replace(urlPattern, (url) => { const href = url.startsWith('http') ? url : `https://${url}`; - return `${DOMPurify.sanitize(url)}`; + return `${DOMPurify.sanitize(url)}`; }); + return processText(textFormatted); }; - // Sanitize and linkify the content 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: [ + ], + ALLOWED_ATTR: [ 'href', 'target', 'rel', 'class', 'src', 'alt', 'title', - 'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing' - ], + 'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing', 'data-url' + ], }); - // Function to handle link clicks - const handleClick = (e) => { + const handleClick = async (e) => { e.preventDefault(); - // Ensure we are targeting an element - const target = e.target.closest('a'); - if (target) { + const target = e.target; + if (target.tagName === 'A') { const href = target.getAttribute('href'); + window.electronAPI.openExternal(href); + } 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-dev-mode", { }); - 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 { - console.error('chrome.tabs API is not available.'); } - } else { - console.error('No tag found or href is null.'); } }; + return (
{ - // Delegate click handling to the parent div - if (e.target.tagName === 'A') { - handleClick(e); - } - }} + onClick={handleClick} /> ); }; - diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 9206914..f1b4189 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -1281,6 +1281,46 @@ export const Group = ({ initiatedGetMembers.current = false; }; + const openDevModeFunc = () => { + if (isMobile) { + setMobileViewMode("apps"); + } + if (!isMobile) { + setDesktopViewMode('apps') + + } + setIsOpenSideViewDirects(false) + setIsOpenSideViewGroups(false) + setGroupSection("default"); + setSelectedGroup(null); + setNewChat(false); + setSelectedDirect(null); + setSecretKey(null); + setGroupOwner(null) + lastFetchedSecretKey.current = null; + initiatedGetMembers.current = false; + setSecretKeyPublishDate(null); + setAdmins([]); + setSecretKeyDetails(null); + setAdminsWithNames([]); + setMembers([]); + setMemberCountFromSecretKeyData(null); + setTriedToFetchSecretKey(false); + setFirstSecretKeyInCreation(false); + setIsOpenSideViewDirects(false) + setIsOpenSideViewGroups(false) + + }; + + useEffect(() => { + subscribeToEvent("open-dev-mode", openDevModeFunc); + + return () => { + unsubscribeFromEvent("open-dev-mode", openDevModeFunc); + }; + }, []); + + const logoutEventFunc = () => { resetAllStatesAndRefs(); clearStatesMessageQueueProvider();