From bb14cdff5a0f1ee8c8dfa92d5ae135c1e3884d4d Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 9 Jul 2025 16:36:34 +0200 Subject: [PATCH] Asciidoctor rendering of Asciimath, LaTeX, PlantUML, and SVG placeholders for BPMN and TikZ --- package-lock.json | 7 + package.json | 1 + src/app.html | 22 ++ src/lib/components/PublicationSection.svelte | 15 +- src/lib/components/cards/BlogHeader.svelte | 1 + src/lib/parser.ts | 42 ++- src/lib/utils/markup/MarkupInfo.md | 74 +++++ .../advancedAsciidoctorPostProcessor.ts | 311 ++++++++++++++++++ src/lib/utils/markup/asciidoctorExtensions.ts | 208 ++++++++++++ .../utils/markup/asciidoctorPostProcessor.ts | 18 + src/lib/utils/markup/tikzRenderer.ts | 60 ++++ src/types/global.d.ts | 5 + src/types/plantuml-encoder.d.ts | 5 + test_data/AsciidocFiles/SimpleTest.adoc | 73 ++++ 14 files changed, 830 insertions(+), 12 deletions(-) create mode 100644 src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts create mode 100644 src/lib/utils/markup/asciidoctorExtensions.ts create mode 100644 src/lib/utils/markup/tikzRenderer.ts create mode 100644 src/types/global.d.ts create mode 100644 src/types/plantuml-encoder.d.ts create mode 100644 test_data/AsciidocFiles/SimpleTest.adoc diff --git a/package-lock.json b/package-lock.json index e824fb8..2b5e949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "highlight.js": "^11.11.1", "node-emoji": "^2.2.0", "nostr-tools": "2.10.x", + "plantuml-encoder": "^1.4.0", "qrcode": "^1.5.4" }, "devDependencies": { @@ -5496,6 +5497,12 @@ "node": ">= 6" } }, + "node_modules/plantuml-encoder": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz", + "integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==", + "license": "MIT" + }, "node_modules/playwright": { "version": "1.53.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", diff --git a/package.json b/package.json index 787d2e7..f335d20 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "highlight.js": "^11.11.1", "node-emoji": "^2.2.0", "nostr-tools": "2.10.x", + "plantuml-encoder": "^1.4.0", "qrcode": "^1.5.4" }, "devDependencies": { diff --git a/src/app.html b/src/app.html index d025d7c..b38ec6a 100644 --- a/src/app.html +++ b/src/app.html @@ -4,6 +4,28 @@ + + + + + + + + + %sveltekit.head% diff --git a/src/lib/components/PublicationSection.svelte b/src/lib/components/PublicationSection.svelte index 301cb51..cb60e0c 100644 --- a/src/lib/components/PublicationSection.svelte +++ b/src/lib/components/PublicationSection.svelte @@ -1,4 +1,5 @@
{#await Promise.all([leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches])} {:then [leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches]} + {@const contentString = leafContent.toString()} + {@const _ = (() => { console.log('leafContent HTML:', contentString); return null; })()} {#each divergingBranches as [branch, depth]} {@render sectionHeading(getMatchingTags(branch, 'title')[0]?.[1] ?? '', depth)} {/each} @@ -120,6 +129,6 @@ {@const leafDepth = leafHierarchy.length - 1} {@render sectionHeading(leafTitle, leafDepth)} {/if} - {@render contentParagraph(leafContent.toString(), publicationType ?? 'article', false)} + {@render contentParagraph(contentString, publicationType ?? 'article', false)} {/await}
diff --git a/src/lib/components/cards/BlogHeader.svelte b/src/lib/components/cards/BlogHeader.svelte index 49cd6cd..b335074 100644 --- a/src/lib/components/cards/BlogHeader.svelte +++ b/src/lib/components/cards/BlogHeader.svelte @@ -6,6 +6,7 @@ import Interactions from "$components/util/Interactions.svelte"; import { quintOut } from "svelte/easing"; import CardActions from "$components/util/CardActions.svelte"; + import { getMatchingTags } from '$lib/utils/nostrUtils'; const { rootId, event, onBlogUpdate, active = true } = $props<{ rootId: string, event: NDKEvent, onBlogUpdate?: any, active: boolean }>(); diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 860a8c6..c908db5 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -150,6 +150,23 @@ export default class Pharos { pharos.treeProcessor(treeProcessor, document); }); }); + + // Add advanced extensions for math, PlantUML, BPMN, and TikZ + this.loadAdvancedExtensions(); + } + + /** + * Loads advanced extensions for math, PlantUML, BPMN, and TikZ rendering + */ + private async loadAdvancedExtensions(): Promise { + try { + const { createAdvancedExtensions } = await import('./utils/markup/asciidoctorExtensions'); + const advancedExtensions = createAdvancedExtensions(); + // Note: Extensions merging might not be available in this version + // We'll handle this in the parse method instead + } catch (error) { + console.warn('Advanced extensions not available:', error); + } } parse(content: string, options?: ProcessorOptions | undefined): void { @@ -158,9 +175,15 @@ export default class Pharos { content = ensureAsciiDocHeader(content); try { + const mergedAttributes = Object.assign( + {}, + options && typeof options.attributes === 'object' ? options.attributes : {}, + { 'source-highlighter': 'highlightjs' } + ); this.html = this.asciidoctor.convert(content, { - 'extension_registry': this.pharosExtensions, ...options, + 'extension_registry': this.pharosExtensions, + attributes: mergedAttributes, }) as string | Document | undefined; } catch (error) { console.error(error); @@ -783,7 +806,7 @@ export default class Pharos { authors: document .getAuthors() .map(author => author.getName()) - .filter(name => name != null), + .filter((name): name is string => name != null), version: document.getRevisionNumber(), edition: document.getRevisionRemark(), publicationDate: document.getRevisionDate(), @@ -794,13 +817,14 @@ export default class Pharos { } if (this.rootIndexMetadata.version || this.rootIndexMetadata.edition) { - event.tags.push( - [ - 'version', - this.rootIndexMetadata.version!, - this.rootIndexMetadata.edition! - ].filter(value => value != null) - ); + const versionTags: string[] = ['version']; + if (this.rootIndexMetadata.version) { + versionTags.push(this.rootIndexMetadata.version); + } + if (this.rootIndexMetadata.edition) { + versionTags.push(this.rootIndexMetadata.edition); + } + event.tags.push(versionTags); } if (this.rootIndexMetadata.publicationDate) { diff --git a/src/lib/utils/markup/MarkupInfo.md b/src/lib/utils/markup/MarkupInfo.md index 70eb7fe..22a108f 100644 --- a/src/lib/utils/markup/MarkupInfo.md +++ b/src/lib/utils/markup/MarkupInfo.md @@ -42,8 +42,82 @@ AsciiDoc supports a much broader set of formatting, semantic, and structural fea - Advanced tables, callouts, admonitions - Cross-references, footnotes, and bibliography - Custom attributes and macros +- **Math rendering** (Asciimath and LaTeX) +- **Diagram rendering** (PlantUML, BPMN, TikZ) - And much more +### Advanced Content Types + +Alexandria supports rendering of advanced content types commonly used in academic, technical, and business documents: + +#### Math Rendering + +Use `[stem]` blocks for mathematical expressions: + +```asciidoc +[stem] +++++ +\frac{\partial f}{\partial x} = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h} +++++ +``` + +Inline math is also supported using `$...$` or `\(...\)` syntax. + +#### PlantUML Diagrams + +PlantUML diagrams are automatically detected and rendered: + +```asciidoc +[source,plantuml] +---- +@startuml +participant User +participant System +User -> System: Login Request +System --> User: Login Response +@enduml +---- +``` + +#### BPMN Diagrams + +BPMN (Business Process Model and Notation) diagrams are supported: + +```asciidoc +[source,bpmn] +---- + + + + + + + + +---- +``` + +#### TikZ Diagrams + +TikZ diagrams for mathematical illustrations: + +```asciidoc +[source,tikz] +---- +\begin{tikzpicture} + \draw[thick,red] (0,0) circle (1cm); + \draw[thick,blue] (2,0) rectangle (3,1); +\end{tikzpicture} +---- +``` + +### Rendering Features + +- **Automatic Detection**: Content types are automatically detected based on syntax +- **Fallback Display**: If rendering fails, the original source code is displayed +- **Source Code**: Click "Show source" to view the original code +- **Responsive Design**: All rendered content is responsive and works on mobile devices + For more information on AsciiDoc, see the [AsciiDoc documentation](https://asciidoc.org/). --- diff --git a/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts new file mode 100644 index 0000000..33f3a16 --- /dev/null +++ b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts @@ -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 { + 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:
...
+ html = html.replace( + /
\s*
([\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>/g, '') + .replace(/\$\$<\/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 `
$$${cleanMath}$$
`; + } + ); + // Inline math: $ ... $ (allow whitespace/newlines) + html = html.replace( + /\$<\/span>\s*([\s\S]+?)\s*\$<\/span>/g, + (_match, mathContent) => `$${mathContent.trim()}$` + ); + // Inline math: stem:[...] or latexmath:[...] + html = html.replace( + /stem:\[([^\]]+?)\]/g, + (_match, content) => `$${content.trim()}$` + ); + html = html.replace( + /latexmath:\[([^\]]+?)\]/g, + (_match, content) => `\\(${content.trim().replace(/\\\\/g, '\\')}\\)` + ); + html = html.replace( + /asciimath:\[([^\]]+?)\]/g, + (_match, content) => `\`${content.trim()}\`` + ); + 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( + /
\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; +} \ 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 `PlantUML diagram`; +} + +/** + * 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 ` + + + TikZ Diagram + + + (Rendering not yet implemented) + + +
+
${escapeHtml(tikzCode)}
+
+
+
`; +} + +/** + * 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