syncova-policies/lib/markdown.ts
Weapie 265ca8ac38
All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
Improve accessibility and access controls; verify production and database recovery
2026-09-14 11:44:41 +02:00

80 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
}
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>")
}