import "./Text.css"; import { ReactNode, useState } from "react"; import { HexKey, ParsedFragment } from "@snort/system"; import Invoice from "Element/Embed/Invoice"; import Hashtag from "Element/Embed/Hashtag"; import HyperText from "Element/HyperText"; import CashuNuts from "Element/Embed/CashuNuts"; import RevealMedia from "./Event/RevealMedia"; import { ProxyImg } from "./ProxyImg"; import { SpotlightMediaModal } from "./Deck/SpotlightMedia"; import HighlightedText from "./HighlightedText"; import { useTextTransformer } from "Hooks/useTextTransformCache"; import CodeBlock from "./CodeBlock"; export interface TextProps { id: string; content: string; creator: HexKey; tags: Array>; disableMedia?: boolean; disableMediaSpotlight?: boolean; disableLinkPreview?: boolean; depth?: number; truncate?: number; className?: string; highlighText?: string; onClick?: (e: React.MouseEvent) => void; } const gridConfigMap = new Map([ [1, [[4, 3]]], [ 2, [ [2, 2], [2, 2], ], ], [ 3, [ [2, 2], [2, 1], [2, 1], ], ], [ 4, [ [2, 1], [2, 1], [2, 1], [2, 1], ], ], [ 5, [ [2, 1], [2, 1], [2, 1], [1, 1], [1, 1], ], ], [ 6, [ [2, 2], [1, 1], [1, 1], [2, 2], [1, 1], [1, 1], ], ], ]); const ROW_HEIGHT = 140; const GRID_GAP = 2; export default function Text({ id, content, tags, creator, disableMedia, depth, disableMediaSpotlight, disableLinkPreview, truncate, className, highlighText, onClick, }: TextProps) { const [showSpotlight, setShowSpotlight] = useState(false); const [imageIdx, setImageIdx] = useState(0); const elements = useTextTransformer(id, content, tags); const images = elements.filter(a => a.type === "media" && a.mimeType?.startsWith("image")).map(a => a.content); function renderContentWithHighlightedText(content: string, textToHighlight: string) { const textToHighlightArray = textToHighlight.trim().toLowerCase().split(" "); const re = new RegExp(`(${textToHighlightArray.join("|")})`, "gi"); const splittedContent = content.split(re); const fragments = splittedContent.map(c => { if (textToHighlightArray.includes(c.toLowerCase())) { return { type: "highlighted_text", content: c, } as ParsedFragment; } return c; }); return ( <> {fragments.map(f => { if (typeof f === "string") { return f; } return ; })} ); } const DisableMedia = ({ content }: { content: string }) => ( e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> {content} ); const RevealMediaInstance = ({ content }: { content: string }) => ( { if (!disableMediaSpotlight) { e.stopPropagation(); e.preventDefault(); setShowSpotlight(true); const selected = images.findIndex(b => b === content); setImageIdx(selected === -1 ? 0 : selected); } }} /> ); const renderContent = () => { let lenCtr = 0; const chunks: Array = []; for (let i = 0; i < elements.length; i++) { const element = elements[i]; if (truncate) { if (lenCtr + element.content.length > truncate) { lenCtr += element.content.length; chunks.push(
{element.content.slice(0, truncate - lenCtr)}...
); return chunks; } else { lenCtr += element.content.length; } } if (element.type === "media" && element.mimeType?.startsWith("image")) { if (disableMedia ?? false) { chunks.push(); } else { const galleryImages: ParsedFragment[] = [element]; // If the current element is of type media and mimeType starts with image, // we verify if the next elements are of the same type and mimeType and push to the galleryImages // Whenever one of the next elements is not longer of the type we are looking for, we break the loop for (let j = i; j < elements.length; j++) { const nextElement = elements[j + 1]; if (nextElement && nextElement.type === "media" && nextElement.mimeType?.startsWith("image")) { galleryImages.push(nextElement); i++; } else { break; } } if (galleryImages.length === 1) { chunks.push(); } else { // We build a grid layout to render the grouped images const imagesWithGridConfig = galleryImages.map((gi, index) => { const config = gridConfigMap.get(galleryImages.length); let height = ROW_HEIGHT; if (config && config[index][1] > 1) { height = config[index][1] * ROW_HEIGHT + GRID_GAP; } return { content: gi.content, gridColumn: config ? config[index][0] : 1, gridRow: config ? config[index][1] : 1, height, }; }); const gallery = (
{imagesWithGridConfig.map(img => (
))}
); chunks.push(gallery); } } } if ( element.type === "media" && (element.mimeType?.startsWith("audio") || element.mimeType?.startsWith("video")) ) { if (disableMedia ?? false) { chunks.push(); } else { chunks.push(); } } if (element.type === "invoice") { chunks.push(); } if (element.type === "hashtag") { chunks.push(); } if (element.type === "cashu") { chunks.push(); } if (element.type === "link" || (element.type === "media" && element.mimeType?.startsWith("unknown"))) { chunks.push( , ); } if (element.type === "custom_emoji") { chunks.push(); } if (element.type === "code_block") { chunks.push(); } if (element.type === "text") { chunks.push(
{highlighText ? renderContentWithHighlightedText(element.content, highlighText) : element.content}
, ); } } return chunks; }; return (
{renderContent()} {showSpotlight && setShowSpotlight(false)} idx={imageIdx} />}
); }