51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import React from 'react';
|
|
import { Page } from './_util';
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
export default function LegalPage() {
|
|
const { page } = useParams();
|
|
const [loading, setLoading] = React.useState(true);
|
|
const [title, setTitle] = React.useState('');
|
|
const [body, setBody] = React.useState('');
|
|
|
|
React.useEffect(() => {
|
|
async function load() {
|
|
setLoading(true);
|
|
const res = await fetch(`/api/v1/legal/${encodeURIComponent(page || '')}?lang=de`, { headers: { 'Cache-Control': 'no-store' }});
|
|
if (res.ok) {
|
|
const j = await res.json();
|
|
setTitle(j.title || '');
|
|
setBody(j.body_markdown || '');
|
|
}
|
|
setLoading(false);
|
|
}
|
|
if (page) load();
|
|
}, [page]);
|
|
|
|
return (
|
|
<Page title={title || `Rechtliches: ${page}` }>
|
|
{loading ? <p>Lädt…</p> : <Markdown md={body} />}
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
function Markdown({ md }: { md: string }) {
|
|
// Tiny, safe Markdown: paragraphs + basic bold/italic + links; no external dependency
|
|
const html = React.useMemo(() => {
|
|
let s = md
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
// bold **text**
|
|
s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
// italic *text*
|
|
s = s.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
|
|
// links [text](url)
|
|
s = s.replace(/\[(.+?)\]\((https?:[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1<\/a>');
|
|
// paragraphs
|
|
s = s.split(/\n{2,}/).map(p => `<p>${p.replace(/\n/g, '<br/>')}<\/p>`).join('\n');
|
|
return s;
|
|
}, [md]);
|
|
return <div className="prose prose-sm dark:prose-invert" dangerouslySetInnerHTML={{ __html: html }} />;
|
|
}
|