"use client"; import { Accordion, AccordionContent } from "@/lib/accordion"; import { Poppable } from "@/lib/poppables/components/poppable"; import { createElements } from "@/lib/tcmd"; import Link from "next/link"; import React, { FC, Fragment, Suspense, use, useEffect, useMemo, useState, } from "react"; import { sanitize } from "isomorphic-dompurify"; import { MDSkeletonLoader } from "../loader"; export const TTCMD: FC< { body: string; escapeTOC?: (tokens: Token[]) => boolean } > = ({ body, escapeTOC = () => false }) => { const elements = useMemo(() => createElements(body), [body]); const [toc, start, end] = useMemo(() => { const tocHead = elements.findIndex((t) => t.content.includes("Table of Contents") ); if (tocHead > -1) { const hr = elements.slice(tocHead).findIndex((t) => t.type === "hr"); if (hr > -1) { const end = hr + 1; return [elements.slice(tocHead, end), tocHead, end - tocHead]; } } return [[], 0, 0]; }, [elements]); // const hasEscapedTOC = useMemo(() => toc && escapeTOC(toc), [escapeTOC, toc]) const [hasEscapedTOC, setHasEscapedTOC] = useState(); useEffect(() => { setHasEscapedTOC(escapeTOC(toc)); }, [escapeTOC, toc]); return ( }> {hasEscapedTOC !== undefined && ( )} ); }; export const TTCMDRenderer: FC<{ tokens: Token[] }> = ({ tokens }) => { const tada = useMemo( () => ( <> {renderer(tokens)} ), [tokens], ); if (!tokens.length) { const audio = new Audio( "https://assets.mixkit.co/active_storage/sfx/221/221-preview.mp3", ); audio.onload = () => { audio.play(); }; } return (
{tada}
); }; function renderer(tokens: Token[]) { const usedIds: string[] = []; return tokens.map((t) => (
{render(t, usedIds)}
)); } function render(token: Token, usedIds: string[]) { switch (token.type) { case "heading": return (
{token.content}
); case "grid": return (
{token.children?.map((c, i) => (
{render(c, usedIds)}
))}
); case "code": return (
          {token.content}
        
); case "card": return (
{token.children?.map((e) => ( {render(e, usedIds)} ))}
); case "anchor": return ( {token.content} ); case "image": { token.metadata.src = token.metadata.src as string; if (token.metadata.src.startsWith(" ); } // eslint-disable-next-line @next/next/no-img-element return {token.content}; } case "inline-code": return ( {token.content} ); case "popover": return ( render(c, usedIds)) || token.content} preferredAlign="centered" preferredEdge="bottom" className="cursor-pointer mx-2" > {token.metadata.title} ); case "text": return ( {token.content.replaceAll("\\n", "\n")} ); case "p": return (
{token.children?.map((e, i) => ( {render(e, usedIds)} ))}
); case "accordion": return (
{token.children?.map((e, i) => ( {render(e, usedIds)} ))}
); case "bold": return ( {token.content} ); case "italic": return ( {token.content} ); case "list": const items = token.children || []; return ( <> ); case "list-item": // This probably doesn't need to exist, but I'm leaving it anyway return (
  • {token.children?.map((c) => render(c, usedIds))}
  • ); case "hr": return
    ; case "comment": return <>; default: return (
    Block or paragraph missing implementation: {token.type}
    ); } } function generateId(t: string, usedIds: string[]) { let id = t.toLowerCase().replace(/[^a-z\s]/ig, "").trim().replaceAll( " ", "-", ); let idNum = 1; while (usedIds.includes(id)) { id = id.replace(/-[0-9]+$/g, ""); id += "-" + idNum; idNum++; } return id; }