14 changed files with 830 additions and 12 deletions
@ -0,0 +1,311 @@ |
|||||||
|
import { postProcessAsciidoctorHtml } from './asciidoctorPostProcessor'; |
||||||
|
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): Promise<string> { |
||||||
|
if (!html) return html; |
||||||
|
try { |
||||||
|
// First apply the basic post-processing (wikilinks, nostr addresses)
|
||||||
|
let processedHtml = await postProcessAsciidoctorHtml(html); |
||||||
|
// 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 window !== 'undefined' && typeof window.hljs?.highlightAll === 'function') { |
||||||
|
setTimeout(() => window.hljs!.highlightAll(), 0); |
||||||
|
} |
||||||
|
if (typeof window !== 'undefined' && typeof (window as any).MathJax?.typesetPromise === 'function') { |
||||||
|
setTimeout(() => (window 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. |
||||||
|
* Handles stem blocks, inline math, and normalizes delimiters. |
||||||
|
*/ |
||||||
|
function fixAllMathBlocks(html: string): string { |
||||||
|
// Unescape \$ to $ for math delimiters
|
||||||
|
html = html.replace(/\\\$/g, '$'); |
||||||
|
|
||||||
|
// DEBUG: Log the HTML before MathJax runs
|
||||||
|
if (html.includes('latexmath')) { |
||||||
|
console.debug('Processed HTML for latexmath:', html); |
||||||
|
} |
||||||
|
|
||||||
|
// Block math: <div class="stemblock"><div class="content">...</div></div>
|
||||||
|
html = html.replace( |
||||||
|
/<div class="stemblock">\s*<div class="content">([\s\S]*?)<\/div>\s*<\/div>/g, |
||||||
|
(_match, mathContent) => { |
||||||
|
// DEBUG: Log the original and cleaned math content
|
||||||
|
console.debug('Block math original:', mathContent); |
||||||
|
console.debug('Block math char codes:', Array.from(mathContent as string).map((c: string) => c.charCodeAt(0))); |
||||||
|
let cleanMath = mathContent |
||||||
|
.replace(/<span>\$<\/span>/g, '') |
||||||
|
.replace(/<span>\$\$<\/span>/g, '') |
||||||
|
// Remove $ or $$ on their own line, or surrounded by whitespace/newlines
|
||||||
|
.replace(/(^|[\n\r\s])\$([\n\r\s]|$)/g, '$1$2') |
||||||
|
.replace(/(^|[\n\r\s])\$\$([\n\r\s]|$)/g, '$1$2') |
||||||
|
// Remove all leading and trailing whitespace and $
|
||||||
|
.replace(/^[\s$]+/, '').replace(/[\s$]+$/, '') |
||||||
|
.trim(); // Final trim to remove any stray whitespace or $
|
||||||
|
console.debug('Block math cleaned:', cleanMath); |
||||||
|
console.debug('Block math cleaned char codes:', Array.from(cleanMath as string).map((c: string) => c.charCodeAt(0))); |
||||||
|
// Always wrap in $$...$$
|
||||||
|
return `<div class="stemblock"><div class="content">$$${cleanMath}$$</div></div>`; |
||||||
|
} |
||||||
|
); |
||||||
|
// Inline math: <span>$</span> ... <span>$</span> (allow whitespace/newlines)
|
||||||
|
html = html.replace( |
||||||
|
/<span>\$<\/span>\s*([\s\S]+?)\s*<span>\$<\/span>/g, |
||||||
|
(_match, mathContent) => `<span class="math-inline">$${mathContent.trim()}$</span>` |
||||||
|
); |
||||||
|
// Inline math: stem:[...] or latexmath:[...]
|
||||||
|
html = html.replace( |
||||||
|
/stem:\[([^\]]+?)\]/g, |
||||||
|
(_match, content) => `<span class="math-inline">$${content.trim()}$</span>` |
||||||
|
); |
||||||
|
html = html.replace( |
||||||
|
/latexmath:\[([^\]]+?)\]/g, |
||||||
|
(_match, content) => `<span class="math-inline">\\(${content.trim().replace(/\\\\/g, '\\')}\\)</span>` |
||||||
|
); |
||||||
|
html = html.replace( |
||||||
|
/asciimath:\[([^\]]+?)\]/g, |
||||||
|
(_match, content) => `<span class="math-inline">\`${content.trim()}\`</span>` |
||||||
|
); |
||||||
|
return html; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes PlantUML blocks in HTML content |
||||||
|
*/ |
||||||
|
function processPlantUMLBlocks(html: string): string { |
||||||
|
// Only match code blocks with class 'language-plantuml' or 'plantuml'
|
||||||
|
html = html.replace( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre class="highlight">\s*<code[^>]*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 <code>
|
||||||
|
const rawContent = decodeHTMLEntities(content); |
||||||
|
const encoded = plantumlEncoder.encode(rawContent); |
||||||
|
const plantUMLUrl = `https://www.plantuml.com/plantuml/svg/${encoded}`; |
||||||
|
return `<div class="plantuml-block my-4">
|
||||||
|
<img src="${plantUMLUrl}" alt="PlantUML diagram"
|
||||||
|
class="plantuml-diagram max-w-full h-auto rounded-lg shadow-lg"
|
||||||
|
loading="lazy" decoding="async"> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show PlantUML source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(rawContent)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div>`;
|
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to process PlantUML block:', error); |
||||||
|
return match; |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
// Fallback: match <pre> blocks whose content starts with @startuml or @start (global, robust)
|
||||||
|
html = html.replace( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre>([\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 `<div class="plantuml-block my-4">
|
||||||
|
<img src="${plantUMLUrl}" alt="PlantUML diagram"
|
||||||
|
class="plantuml-diagram max-w-full h-auto rounded-lg shadow-lg"
|
||||||
|
loading="lazy" decoding="async"> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show PlantUML source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(rawContent)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div>`;
|
||||||
|
} 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( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre class="highlight">\s*<code[^>]*class="[^"]*(?:language-bpmn|bpmn)[^\"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>\s*<\/div>\s*<\/div>/g, |
||||||
|
(match, content) => { |
||||||
|
try { |
||||||
|
return `<div class="bpmn-block my-4">
|
||||||
|
<div class="bpmn-diagram p-4 bg-blue-50 dark:bg-blue-900 rounded-lg border border-blue-200 dark:border-blue-700"> |
||||||
|
<div class="text-center text-blue-600 dark:text-blue-400 mb-2"> |
||||||
|
<svg class="inline w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20"> |
||||||
|
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/> |
||||||
|
</svg> |
||||||
|
BPMN Diagram |
||||||
|
</div> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show BPMN source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(content)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
</div>`;
|
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to process BPMN block:', error); |
||||||
|
return match; |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
// Fallback: match <pre> blocks whose content contains 'bpmn:' or '<?xml' and 'bpmn'
|
||||||
|
html = html.replace( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre>([\s\S]*?)<\/pre>\s*<\/div>\s*<\/div>/g, |
||||||
|
(match, content) => { |
||||||
|
const text = content.trim(); |
||||||
|
if (text.includes('bpmn:') || (text.startsWith('<?xml') && text.includes('bpmn'))) { |
||||||
|
try { |
||||||
|
return `<div class="bpmn-block my-4">
|
||||||
|
<div class="bpmn-diagram p-4 bg-blue-50 dark:bg-blue-900 rounded-lg border border-blue-200 dark:border-blue-700"> |
||||||
|
<div class="text-center text-blue-600 dark:text-blue-400 mb-2"> |
||||||
|
<svg class="inline w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20"> |
||||||
|
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/> |
||||||
|
</svg> |
||||||
|
BPMN Diagram |
||||||
|
</div> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show BPMN source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(content)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
</div>`;
|
||||||
|
} 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( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre class="highlight">\s*<code[^>]*class="[^"]*(?:language-tikz|tikz)[^"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>\s*<\/div>\s*<\/div>/g, |
||||||
|
(match, content) => { |
||||||
|
try { |
||||||
|
return `<div class="tikz-block my-4">
|
||||||
|
<div class="tikz-diagram p-4 bg-green-50 dark:bg-green-900 rounded-lg border border-green-200 dark:border-green-700"> |
||||||
|
<div class="text-center text-green-600 dark:text-green-400 mb-2"> |
||||||
|
<svg class="inline w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20"> |
||||||
|
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/> |
||||||
|
</svg> |
||||||
|
TikZ Diagram |
||||||
|
</div> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show TikZ source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(content)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
</div>`;
|
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to process TikZ block:', error); |
||||||
|
return match; |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
// Fallback: match <pre> blocks whose content starts with \begin{tikzpicture} or contains tikz
|
||||||
|
html = html.replace( |
||||||
|
/<div class="listingblock">\s*<div class="content">\s*<pre>([\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 `<div class="tikz-block my-4">
|
||||||
|
<div class="tikz-diagram p-4 bg-green-50 dark:bg-green-900 rounded-lg border border-green-200 dark:border-green-700"> |
||||||
|
<div class="text-center text-green-600 dark:text-green-400 mb-2"> |
||||||
|
<svg class="inline w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20"> |
||||||
|
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/> |
||||||
|
</svg> |
||||||
|
TikZ Diagram |
||||||
|
</div> |
||||||
|
<details class="mt-2"> |
||||||
|
<summary class="cursor-pointer text-sm text-gray-600 dark:text-gray-400"> |
||||||
|
Show TikZ source |
||||||
|
</summary> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-x-auto"> |
||||||
|
<code>${escapeHtml(content)}</code> |
||||||
|
</pre> |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
</div>`;
|
||||||
|
} 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; |
||||||
|
}
|
||||||
@ -0,0 +1,208 @@ |
|||||||
|
import { renderTikZ } from './tikzRenderer'; |
||||||
|
import asciidoctor from 'asciidoctor'; |
||||||
|
|
||||||
|
// Simple math rendering using MathJax CDN
|
||||||
|
function renderMath(content: string): string { |
||||||
|
return `<div class="math-block" data-math="${encodeURIComponent(content)}">
|
||||||
|
<div class="math-content">${content}</div> |
||||||
|
<script> |
||||||
|
if (typeof MathJax !== 'undefined') { |
||||||
|
MathJax.typesetPromise([document.querySelector('.math-content')]); |
||||||
|
} |
||||||
|
</script> |
||||||
|
</div>`;
|
||||||
|
} |
||||||
|
|
||||||
|
// Simple PlantUML rendering using PlantUML server
|
||||||
|
function renderPlantUML(content: string): string { |
||||||
|
// Encode content for PlantUML server
|
||||||
|
const encoded = btoa(unescape(encodeURIComponent(content))); |
||||||
|
const plantUMLUrl = `https://www.plantuml.com/plantuml/svg/${encoded}`; |
||||||
|
|
||||||
|
return `<img src="${plantUMLUrl}" alt="PlantUML diagram" class="plantuml-diagram max-w-full h-auto rounded-lg shadow-lg my-4" loading="lazy" decoding="async">`; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates Asciidoctor extensions for advanced content rendering |
||||||
|
* including Asciimath/Latex, PlantUML, BPMN, and TikZ |
||||||
|
*/ |
||||||
|
export function createAdvancedExtensions(): any { |
||||||
|
const Asciidoctor = asciidoctor(); |
||||||
|
const extensions = Asciidoctor.Extensions.create(); |
||||||
|
|
||||||
|
// Math rendering extension (Asciimath/Latex)
|
||||||
|
extensions.treeProcessor(function (this: any) { |
||||||
|
const dsl = this; |
||||||
|
dsl.process(function (this: any, document: any) { |
||||||
|
const treeProcessor = this; |
||||||
|
processMathBlocks(treeProcessor, document); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// PlantUML rendering extension
|
||||||
|
extensions.treeProcessor(function (this: any) { |
||||||
|
const dsl = this; |
||||||
|
dsl.process(function (this: any, document: any) { |
||||||
|
const treeProcessor = this; |
||||||
|
processPlantUMLBlocks(treeProcessor, document); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// TikZ rendering extension
|
||||||
|
extensions.treeProcessor(function (this: any) { |
||||||
|
const dsl = this; |
||||||
|
dsl.process(function (this: any, document: any) { |
||||||
|
const treeProcessor = this; |
||||||
|
processTikZBlocks(treeProcessor, document); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// --- NEW: Support [plantuml], [tikz], [bpmn] as source blocks ---
|
||||||
|
// Helper to register a block for a given name and treat it as a source block
|
||||||
|
function registerDiagramBlock(name: string) { |
||||||
|
extensions.block(name, function (this: any) { |
||||||
|
const self = this; |
||||||
|
self.process(function (parent: any, reader: any, attrs: any) { |
||||||
|
// Read the block content
|
||||||
|
const lines = reader.getLines(); |
||||||
|
// Create a source block with the correct language and lang attributes
|
||||||
|
const block = self.createBlock(parent, 'source', lines, { |
||||||
|
...attrs, |
||||||
|
language: name, |
||||||
|
lang: name, |
||||||
|
style: 'source', |
||||||
|
role: name, |
||||||
|
}); |
||||||
|
block.setAttribute('language', name); |
||||||
|
block.setAttribute('lang', name); |
||||||
|
block.setAttribute('style', 'source'); |
||||||
|
block.setAttribute('role', name); |
||||||
|
block.setOption('source', true); |
||||||
|
block.setOption('listing', true); |
||||||
|
block.setStyle('source'); |
||||||
|
return block; |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
registerDiagramBlock('plantuml'); |
||||||
|
registerDiagramBlock('tikz'); |
||||||
|
registerDiagramBlock('bpmn'); |
||||||
|
// --- END NEW ---
|
||||||
|
|
||||||
|
return extensions; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes math blocks (stem blocks) and converts them to rendered HTML |
||||||
|
*/ |
||||||
|
function processMathBlocks(treeProcessor: any, document: any): void { |
||||||
|
const blocks = document.getBlocks(); |
||||||
|
for (const block of blocks) { |
||||||
|
if (block.getContext() === 'stem') { |
||||||
|
const content = block.getContent(); |
||||||
|
if (content) { |
||||||
|
try { |
||||||
|
// Output as a single div with delimiters for MathJax
|
||||||
|
const rendered = `<div class="math-block">$$${content}$$</div>`; |
||||||
|
block.setContent(rendered); |
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to render math:', error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Inline math: context 'inline' and style 'stem' or 'latexmath'
|
||||||
|
if (block.getContext() === 'inline' && (block.getStyle() === 'stem' || block.getStyle() === 'latexmath')) { |
||||||
|
const content = block.getContent(); |
||||||
|
if (content) { |
||||||
|
try { |
||||||
|
const rendered = `<span class="math-inline">$${content}$</span>`; |
||||||
|
block.setContent(rendered); |
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to render inline math:', error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes PlantUML blocks and converts them to rendered SVG |
||||||
|
*/ |
||||||
|
function processPlantUMLBlocks(treeProcessor: any, document: any): void { |
||||||
|
const blocks = document.getBlocks(); |
||||||
|
|
||||||
|
for (const block of blocks) { |
||||||
|
if (block.getContext() === 'listing' && isPlantUMLBlock(block)) { |
||||||
|
const content = block.getContent(); |
||||||
|
if (content) { |
||||||
|
try { |
||||||
|
// Use simple PlantUML rendering
|
||||||
|
const rendered = renderPlantUML(content); |
||||||
|
|
||||||
|
// Replace the block content with the image
|
||||||
|
block.setContent(rendered); |
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to render PlantUML:', error); |
||||||
|
// Keep original content if rendering fails
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes TikZ blocks and converts them to rendered SVG |
||||||
|
*/ |
||||||
|
function processTikZBlocks(treeProcessor: any, document: any): void { |
||||||
|
const blocks = document.getBlocks(); |
||||||
|
|
||||||
|
for (const block of blocks) { |
||||||
|
if (block.getContext() === 'listing' && isTikZBlock(block)) { |
||||||
|
const content = block.getContent(); |
||||||
|
if (content) { |
||||||
|
try { |
||||||
|
// Render TikZ to SVG
|
||||||
|
const svg = renderTikZ(content); |
||||||
|
|
||||||
|
// Replace the block content with the SVG
|
||||||
|
block.setContent(svg); |
||||||
|
} catch (error) { |
||||||
|
console.warn('Failed to render TikZ:', error); |
||||||
|
// Keep original content if rendering fails
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if a block contains PlantUML content |
||||||
|
*/ |
||||||
|
function isPlantUMLBlock(block: any): boolean { |
||||||
|
const content = block.getContent() || ''; |
||||||
|
const lines = content.split('\n'); |
||||||
|
|
||||||
|
// Check for PlantUML indicators
|
||||||
|
return lines.some((line: string) =>
|
||||||
|
line.trim().startsWith('@startuml') ||
|
||||||
|
line.trim().startsWith('@start') || |
||||||
|
line.includes('plantuml') || |
||||||
|
line.includes('uml') |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if a block contains TikZ content |
||||||
|
*/ |
||||||
|
function isTikZBlock(block: any): boolean { |
||||||
|
const content = block.getContent() || ''; |
||||||
|
const lines = content.split('\n'); |
||||||
|
|
||||||
|
// Check for TikZ indicators
|
||||||
|
return lines.some((line: string) =>
|
||||||
|
line.trim().startsWith('\\begin{tikzpicture}') ||
|
||||||
|
line.trim().startsWith('\\tikz') || |
||||||
|
line.includes('tikzpicture') || |
||||||
|
line.includes('tikz') |
||||||
|
); |
||||||
|
}
|
||||||
@ -0,0 +1,60 @@ |
|||||||
|
/** |
||||||
|
* TikZ renderer using node-tikzjax |
||||||
|
* Converts TikZ LaTeX code to SVG for browser rendering |
||||||
|
*/ |
||||||
|
|
||||||
|
// We'll use a simple approach for now since node-tikzjax might not be available
|
||||||
|
// This is a placeholder implementation that can be enhanced later
|
||||||
|
|
||||||
|
export function renderTikZ(tikzCode: string): string { |
||||||
|
try { |
||||||
|
// For now, we'll create a simple SVG placeholder
|
||||||
|
// In a full implementation, this would use node-tikzjax or similar library
|
||||||
|
|
||||||
|
// Extract TikZ content and create a basic SVG
|
||||||
|
const svgContent = createBasicSVG(tikzCode); |
||||||
|
|
||||||
|
return svgContent; |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to render TikZ:', error); |
||||||
|
return `<div class="tikz-error text-red-500 p-4 border border-red-300 rounded">
|
||||||
|
<p class="font-bold">TikZ Rendering Error</p> |
||||||
|
<p class="text-sm">Failed to render TikZ diagram. Original code:</p> |
||||||
|
<pre class="mt-2 p-2 bg-gray-100 rounded text-xs overflow-x-auto">${tikzCode}</pre> |
||||||
|
</div>`;
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a basic SVG placeholder for TikZ content |
||||||
|
* This is a temporary implementation until proper TikZ rendering is available |
||||||
|
*/ |
||||||
|
function createBasicSVG(tikzCode: string): string { |
||||||
|
// Create a simple SVG with the TikZ code as text
|
||||||
|
const width = 400; |
||||||
|
const height = 300; |
||||||
|
|
||||||
|
return `<svg width="${width}" height="${height}" class="tikz-diagram max-w-full h-auto rounded-lg shadow-lg my-4" viewBox="0 0 ${width} ${height}">
|
||||||
|
<rect width="${width}" height="${height}" fill="white" stroke="#ccc" stroke-width="1"/> |
||||||
|
<text x="10" y="20" font-family="monospace" font-size="12" fill="#666"> |
||||||
|
TikZ Diagram |
||||||
|
</text> |
||||||
|
<text x="10" y="40" font-family="monospace" font-size="10" fill="#999"> |
||||||
|
(Rendering not yet implemented) |
||||||
|
</text> |
||||||
|
<foreignObject x="10" y="60" width="${width - 20}" height="${height - 70}"> |
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="font-family: monospace; font-size: 10px; color: #666; overflow: hidden;"> |
||||||
|
<pre style="margin: 0; white-space: pre-wrap; word-break: break-all;">${escapeHtml(tikzCode)}</pre> |
||||||
|
</div> |
||||||
|
</foreignObject> |
||||||
|
</svg>`;
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Escapes HTML characters for safe display |
||||||
|
*/ |
||||||
|
function escapeHtml(text: string): string { |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.textContent = text; |
||||||
|
return div.innerHTML; |
||||||
|
}
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
interface Window { |
||||||
|
hljs?: { |
||||||
|
highlightAll: () => void; |
||||||
|
}; |
||||||
|
}
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
declare module 'plantuml-encoder' { |
||||||
|
export function encode(text: string): string; |
||||||
|
const _default: { encode: typeof encode }; |
||||||
|
export default _default; |
||||||
|
}
|
||||||
@ -0,0 +1,73 @@ |
|||||||
|
= Simple Advanced Rendering Test |
||||||
|
|
||||||
|
This is a simple test document to verify that Alexandria's advanced rendering features are working correctly. |
||||||
|
|
||||||
|
== Math Test |
||||||
|
|
||||||
|
Here's a simple math expression: |
||||||
|
|
||||||
|
[stem] |
||||||
|
++++ |
||||||
|
E = mc^2 |
||||||
|
++++ |
||||||
|
|
||||||
|
And a more complex one: |
||||||
|
|
||||||
|
[stem] |
||||||
|
++++ |
||||||
|
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi} |
||||||
|
++++ |
||||||
|
|
||||||
|
== PlantUML Test |
||||||
|
|
||||||
|
A simple sequence diagram: |
||||||
|
|
||||||
|
[source,plantuml] |
||||||
|
---- |
||||||
|
@startuml |
||||||
|
participant User |
||||||
|
participant System |
||||||
|
User -> System: Hello |
||||||
|
System --> User: Hi there! |
||||||
|
@enduml |
||||||
|
---- |
||||||
|
|
||||||
|
== BPMN Test |
||||||
|
|
||||||
|
A simple BPMN process: |
||||||
|
|
||||||
|
[source,bpmn] |
||||||
|
---- |
||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"> |
||||||
|
<bpmn:process id="Process_1"> |
||||||
|
<bpmn:startEvent id="StartEvent_1" name="Start"/> |
||||||
|
<bpmn:task id="Task_1" name="Test Task"/> |
||||||
|
<bpmn:endEvent id="EndEvent_1" name="End"/> |
||||||
|
<bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"/> |
||||||
|
<bpmn:sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="EndEvent_1"/> |
||||||
|
</bpmn:process> |
||||||
|
</bpmn:definitions> |
||||||
|
---- |
||||||
|
|
||||||
|
== TikZ Test |
||||||
|
|
||||||
|
A simple TikZ diagram: |
||||||
|
|
||||||
|
[source,tikz] |
||||||
|
---- |
||||||
|
\begin{tikzpicture} |
||||||
|
\draw[thick,red] (0,0) circle (1cm); |
||||||
|
\draw[thick,blue] (2,0) rectangle (3,1); |
||||||
|
\end{tikzpicture} |
||||||
|
---- |
||||||
|
|
||||||
|
== Conclusion |
||||||
|
|
||||||
|
If you can see: |
||||||
|
1. Rendered math expressions |
||||||
|
2. A PlantUML diagram |
||||||
|
3. A BPMN diagram placeholder with source |
||||||
|
4. A TikZ diagram placeholder with source |
||||||
|
|
||||||
|
Then the advanced rendering is working correctly! |
||||||
Loading…
Reference in new issue