import NDK from "@nostr-dev-kit/ndk"; import { postProcessAsciidoctorHtml } from "./asciidoctorPostProcessor.ts"; import plantumlEncoder from "plantuml-encoder"; /** * Unified post-processor for Asciidoctor HTML that handles: * - Math rendering (Asciimath/Latex, stem blocks) * - PlantUML diagrams * - BPMN diagrams * - TikZ diagrams */ export async function postProcessAdvancedAsciidoctorHtml( html: string, ndk?: NDK, ): Promise { if (!html) return html; try { // First apply the basic post-processing (wikilinks, nostr addresses) let processedHtml = await postProcessAsciidoctorHtml(html, ndk); // Unified math block processing processedHtml = fixAllMathBlocks(processedHtml); // Process PlantUML blocks processedHtml = processPlantUMLBlocks(processedHtml); // Process BPMN blocks processedHtml = processBPMNBlocks(processedHtml); // Process TikZ blocks processedHtml = processTikZBlocks(processedHtml); // After all processing, apply highlight.js if available if ( typeof globalThis !== "undefined" && typeof globalThis.hljs?.highlightAll === "function" ) { setTimeout(() => globalThis.hljs!.highlightAll(), 0); } if ( typeof globalThis !== "undefined" && // deno-lint-ignore no-explicit-any typeof (globalThis as any).MathJax?.typesetPromise === "function" ) { // deno-lint-ignore no-explicit-any setTimeout(() => (globalThis as any).MathJax.typesetPromise(), 0); } return processedHtml; } catch (error) { console.error("Error in postProcessAdvancedAsciidoctorHtml:", error); return html; // Return original HTML if processing fails } } /** * Fixes all math blocks for MathJax rendering. * Now only processes LaTeX within inline code blocks. */ function fixAllMathBlocks(html: string): string { // Unescape \$ to $ for math delimiters html = html.replace(/\\\$/g, "$"); // Process inline code blocks that contain LaTeX html = html.replace( /]*class="[^"]*language-[^"]*"[^>]*>([\s\S]*?)<\/code>/g, (match, codeContent) => { const trimmedCode = codeContent.trim(); if (isLaTeXContent(trimmedCode)) { return `$${trimmedCode}$`; } return match; // Return original if not LaTeX }, ); // Also process code blocks without language class html = html.replace( /]*>([\s\S]*?)<\/code>/g, (match, codeContent) => { const trimmedCode = codeContent.trim(); if (isLaTeXContent(trimmedCode)) { return `$${trimmedCode}$`; } return match; // Return original if not LaTeX }, ); return html; } /** * Checks if content contains LaTeX syntax */ function isLaTeXContent(content: string): boolean { const trimmed = content.trim(); // Check for common LaTeX patterns const latexPatterns = [ /\\[a-zA-Z]+/, // LaTeX commands like \frac, \sum, etc. /\\[\(\)\[\]]/, // LaTeX delimiters like \(, \), \[, \] /\\begin\{/, // LaTeX environments /\\end\{/, // LaTeX environments /\$\$/, // Display math delimiters /\$[^$]+\$/, // Inline math delimiters /\\text\{/, // LaTeX text command /\\mathrm\{/, // LaTeX mathrm command /\\mathbf\{/, // LaTeX bold command /\\mathit\{/, // LaTeX italic command /\\sqrt/, // Square root /\\frac/, // Fraction /\\sum/, // Sum /\\int/, // Integral /\\lim/, // Limit /\\infty/, // Infinity /\\alpha/, // Greek letters /\\beta/, /\\gamma/, /\\delta/, /\\theta/, /\\lambda/, /\\mu/, /\\pi/, /\\sigma/, /\\phi/, /\\omega/, /\\partial/, // Partial derivative /\\nabla/, // Nabla /\\cdot/, // Dot product /\\times/, // Times /\\div/, // Division /\\pm/, // Plus-minus /\\mp/, // Minus-plus /\\leq/, // Less than or equal /\\geq/, // Greater than or equal /\\neq/, // Not equal /\\approx/, // Approximately equal /\\equiv/, // Equivalent /\\propto/, // Proportional /\\in/, // Element of /\\notin/, // Not element of /\\subset/, // Subset /\\supset/, // Superset /\\cup/, // Union /\\cap/, // Intersection /\\emptyset/, // Empty set /\\mathbb\{/, // Blackboard bold /\\mathcal\{/, // Calligraphic /\\mathfrak\{/, // Fraktur /\\mathscr\{/, // Script ]; return latexPatterns.some((pattern) => pattern.test(trimmed)); } /** * Processes PlantUML blocks in HTML content */ function processPlantUMLBlocks(html: string): string { // Only match code blocks with class 'language-plantuml' or 'plantuml' html = html.replace( /
\s*
\s*
\s*]*class="[^"]*(?:language-plantuml|plantuml)[^"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      try {
        // Unescape HTML for PlantUML server, but escape for 
        const rawContent = decodeHTMLEntities(content);
        const encoded = plantumlEncoder.encode(rawContent);
        const plantUMLUrl = `https://www.plantuml.com/plantuml/svg/${encoded}`;
        return `
PlantUML diagram
Show PlantUML source
              ${escapeHtml(rawContent)}
            
`; } catch (error) { console.warn("Failed to process PlantUML block:", error); return match; } }, ); // Fallback: match
 blocks whose content starts with @startuml or @start (global, robust)
  html = html.replace(
    /
\s*
\s*
([\s\S]*?)<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      const lines = content.trim().split("\n");
      if (
        lines[0].trim().startsWith("@startuml") ||
        lines[0].trim().startsWith("@start")
      ) {
        try {
          const rawContent = decodeHTMLEntities(content);
          const encoded = plantumlEncoder.encode(rawContent);
          const plantUMLUrl =
            `https://www.plantuml.com/plantuml/svg/${encoded}`;
          return `
PlantUML diagram
Show PlantUML source
                ${escapeHtml(rawContent)}
              
`; } catch (error) { console.warn("Failed to process PlantUML fallback block:", error); return match; } } return match; }, ); return html; } function decodeHTMLEntities(text: string): string { const textarea = document.createElement("textarea"); textarea.innerHTML = text; return textarea.value; } /** * Processes BPMN blocks in HTML content */ function processBPMNBlocks(html: string): string { // Only match code blocks with class 'language-bpmn' or 'bpmn' html = html.replace( /
\s*
\s*
\s*]*class="[^"]*(?:language-bpmn|bpmn)[^\"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      try {
        return `
BPMN Diagram
Show BPMN source
                ${escapeHtml(content)}
              
`; } catch (error) { console.warn("Failed to process BPMN block:", error); return match; } }, ); // Fallback: match
 blocks whose content contains 'bpmn:' or '\s*
\s*
([\s\S]*?)<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      const text = content.trim();
      if (
        text.includes("bpmn:") ||
        (text.startsWith("
            
BPMN Diagram
Show BPMN source
                  ${escapeHtml(content)}
                
`; } catch (error) { console.warn("Failed to process BPMN fallback block:", error); return match; } } return match; }, ); return html; } /** * Processes TikZ blocks in HTML content */ function processTikZBlocks(html: string): string { // Only match code blocks with class 'language-tikz' or 'tikz' html = html.replace( /
\s*
\s*
\s*]*class="[^"]*(?:language-tikz|tikz)[^"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      try {
        return `
TikZ Diagram
Show TikZ source
                ${escapeHtml(content)}
              
`; } catch (error) { console.warn("Failed to process TikZ block:", error); return match; } }, ); // Fallback: match
 blocks whose content starts with \begin{tikzpicture} or contains tikz
  html = html.replace(
    /
\s*
\s*
([\s\S]*?)<\/pre>\s*<\/div>\s*<\/div>/g,
    (match, content) => {
      const lines = content.trim().split("\n");
      if (
        lines[0].trim().startsWith("\\begin{tikzpicture}") ||
        content.includes("tikz")
      ) {
        try {
          return `
TikZ Diagram
Show TikZ source
                  ${escapeHtml(content)}
                
`; } catch (error) { console.warn("Failed to process TikZ fallback block:", error); return match; } } return match; }, ); return html; } /** * Escapes HTML characters for safe display */ function escapeHtml(text: string): string { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; }