Next.js 15 App Router mit öffentlicher Übersicht und geschütztem Admin-Bereich zur Pflege von Datenschutzerklärungen nach Art. 12ff. DSGVO. - Oberfläche auf Basis von shadcn/ui, Inter lokal eingebunden - Prisma/SQLite, Auth.js mit Credentials-Provider und Rollen - Massenimport der Merkblätter des Amtes Leezen aus PDF - Docker-Image (standalone) und Gitea-Workflow zur Veröffentlichung Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.6 KiB
TypeScript
81 lines
2.6 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.)
|
|
.use(remarkHtml, { sanitize: false }) // Convert to HTML without sanitization
|
|
|
|
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 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 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 {
|
|
// Simple, clean markdown processing
|
|
return text
|
|
// Escape HTML
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
// Basic markdown formatting
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
// Headers
|
|
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
|
|
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
|
|
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
|
|
// Lists
|
|
.replace(/^\* (.*$)/gm, '• $1')
|
|
.replace(/^- (.*$)/gm, '• $1')
|
|
.replace(/^(\d+)\. (.*$)/gm, '$1. $2')
|
|
// Line breaks
|
|
.replace(/\n/g, '<br>')
|
|
// Clean up multiple <br> tags
|
|
.replace(/<br><br>/g, '<br>')
|
|
} |