\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 `
+
+
+
+ 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 `
+
+
+
+ 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;
+}
\ No newline at end of file
diff --git a/src/lib/utils/markup/asciidoctorExtensions.ts b/src/lib/utils/markup/asciidoctorExtensions.ts
new file mode 100644
index 0000000..7121c35
--- /dev/null
+++ b/src/lib/utils/markup/asciidoctorExtensions.ts
@@ -0,0 +1,208 @@
+import { renderTikZ } from './tikzRenderer';
+import asciidoctor from 'asciidoctor';
+
+// Simple math rendering using MathJax CDN
+function renderMath(content: string): string {
+ return `
+ ${content}
+
+ `;
+}
+
+// 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 `
`;
+}
+
+/**
+ * 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 = `$$${content}$$`;
+ 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 = `$${content}$`;
+ 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')
+ );
+}
\ No newline at end of file
diff --git a/src/lib/utils/markup/asciidoctorPostProcessor.ts b/src/lib/utils/markup/asciidoctorPostProcessor.ts
index ad543d6..e85ab75 100644
--- a/src/lib/utils/markup/asciidoctorPostProcessor.ts
+++ b/src/lib/utils/markup/asciidoctorPostProcessor.ts
@@ -74,6 +74,23 @@ async function processNostrAddresses(html: string): Promise {
return processedHtml;
}
+/**
+ * Fixes AsciiDoctor stem blocks for MathJax rendering.
+ * Joins split spans and wraps content in $$...$$ for block math.
+ */
+function fixStemBlocks(html: string): string {
+ // Replace $...$
+ // with $$...$$
+ return html.replace(
+ /\s*\s*\$<\/span>([\s\S]*?)\$<\/span>\s*<\/div>\s*<\/div>/g,
+ (_match, mathContent) => {
+ // Remove any extra tags inside mathContent
+ const cleanMath = mathContent.replace(/<\/?span[^>]*>/g, '').trim();
+ return `$$${cleanMath}$$`;
+ }
+ );
+}
+
/**
* Post-processes asciidoctor HTML output to add wikilink and nostr address rendering.
* This function should be called after asciidoctor.convert() to enhance the HTML output.
@@ -87,6 +104,7 @@ export async function postProcessAsciidoctorHtml(html: string): Promise
// Then process nostr addresses (but not those already in links)
processedHtml = await processNostrAddresses(processedHtml);
+ processedHtml = fixStemBlocks(processedHtml); // Fix math blocks for MathJax
return processedHtml;
} catch (error) {
diff --git a/src/lib/utils/markup/tikzRenderer.ts b/src/lib/utils/markup/tikzRenderer.ts
new file mode 100644
index 0000000..68c7e91
--- /dev/null
+++ b/src/lib/utils/markup/tikzRenderer.ts
@@ -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 `
+ TikZ Rendering Error
+ Failed to render TikZ diagram. Original code:
+ ${tikzCode}
+ `;
+ }
+}
+
+/**
+ * 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 ``;
+}
+
+/**
+ * Escapes HTML characters for safe display
+ */
+function escapeHtml(text: string): string {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
\ No newline at end of file
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 0000000..a1ade26
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,5 @@
+interface Window {
+ hljs?: {
+ highlightAll: () => void;
+ };
+}
\ No newline at end of file
diff --git a/src/types/plantuml-encoder.d.ts b/src/types/plantuml-encoder.d.ts
new file mode 100644
index 0000000..8149f62
--- /dev/null
+++ b/src/types/plantuml-encoder.d.ts
@@ -0,0 +1,5 @@
+declare module 'plantuml-encoder' {
+ export function encode(text: string): string;
+ const _default: { encode: typeof encode };
+ export default _default;
+}
\ No newline at end of file
diff --git a/test_data/AsciidocFiles/SimpleTest.adoc b/test_data/AsciidocFiles/SimpleTest.adoc
new file mode 100644
index 0000000..144a1c9
--- /dev/null
+++ b/test_data/AsciidocFiles/SimpleTest.adoc
@@ -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]
+----
+
+
+
+
+
+
+
+
+
+
+----
+
+== 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!
\ No newline at end of file