14 changed files with 830 additions and 12 deletions
@ -0,0 +1,311 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
interface Window { |
||||
hljs?: { |
||||
highlightAll: () => void; |
||||
}; |
||||
}
|
||||
@ -0,0 +1,5 @@
@@ -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 @@
@@ -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