All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { remark } from 'remark'
|
||
import remarkHtml from 'remark-html'
|
||
import remarkGfm from 'remark-gfm'
|
||
|
||
// Simple markdown processor with clean output
|
||
const processor = remark()
|
||
.use(remarkGfm) // Support for GitHub Flavored Markdown (tables, strikethrough, etc.)
|
||
// Das Ergebnis geht über dangerouslySetInnerHTML in die öffentliche Seite.
|
||
// Das Standard-Schema lässt Überschriften, Listen, Tabellen und http(s)- bzw.
|
||
// mailto-Links durch und entfernt Skripte, Event-Attribute, eingebettete
|
||
// Rahmen und javascript:-Ziele.
|
||
.use(remarkHtml, { sanitize: true })
|
||
|
||
/**
|
||
* Maskiert Text für die Ausgabe in HTML. Für Werte, die nicht durch den
|
||
* Markdown-Prozessor laufen – etwa Titel in der Druckfassung.
|
||
*/
|
||
export function escapeHtml(value: string): string {
|
||
return String(value)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''')
|
||
}
|
||
|
||
export async function markdownToHtml(markdown: string): Promise<string> {
|
||
if (!markdown || typeof markdown !== 'string') {
|
||
return ''
|
||
}
|
||
|
||
try {
|
||
// Simple preprocessing for line breaks
|
||
const preprocessed = simplePreprocessLineBreaks(markdown)
|
||
const result = await processor.process(preprocessed)
|
||
return wrapTables(result.toString())
|
||
} catch (error) {
|
||
console.error('Error processing markdown:', error)
|
||
// Fallback: return sanitized markdown with basic formatting
|
||
return sanitizeAndFormatMarkdown(markdown)
|
||
}
|
||
}
|
||
|
||
// Synchronous version for use in components that can't use async
|
||
export function markdownToHtmlSync(markdown: string): string {
|
||
if (!markdown || typeof markdown !== 'string') {
|
||
return ''
|
||
}
|
||
|
||
try {
|
||
// Simple preprocessing for line breaks
|
||
const preprocessed = simplePreprocessLineBreaks(markdown)
|
||
const result = processor.processSync(preprocessed)
|
||
return wrapTables(result.toString())
|
||
} catch (error) {
|
||
console.error('Error processing markdown synchronously:', error)
|
||
// Fallback: return sanitized markdown with basic formatting
|
||
return sanitizeAndFormatMarkdown(markdown)
|
||
}
|
||
}
|
||
|
||
// Simple preprocessing for consistent line breaks
|
||
function simplePreprocessLineBreaks(markdown: string): string {
|
||
return markdown
|
||
// Normalize line endings
|
||
.replace(/\r\n/g, '\n')
|
||
.replace(/\r/g, '\n')
|
||
// Add two spaces before single line breaks for hard breaks
|
||
.replace(/([^\n])\n([^\n])/g, '$1 \n$2')
|
||
}
|
||
|
||
// Fallback function for basic markdown formatting
|
||
function sanitizeAndFormatMarkdown(text: string): string {
|
||
return "<p>" + escapeHtml(text).replace(/\n/g, "<br>") + "</p>"
|
||
}
|
||
|
||
function wrapTables(html: string) {
|
||
return html.replace(/<table>/g, '<div class="markdown-table" role="region" aria-label="Tabelle" tabindex="0"><table>').replace(/<\/table>/g, "</table></div>")
|
||
}
|