Files
EpicNext-Cms/src/components/admin/article-preview.tsx
T
Simo db4acbb46e
CI / check (push) Successful in 57s
CI / deploy (push) Successful in 18s
CI / publish-container (push) Successful in 1m20s
feat(editorial): validate publications and preserve partial event updates
2026-09-11 00:36:55 +02:00

62 lines
1.8 KiB
TypeScript

"use client";
import { useTranslations } from "next-intl";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
function escapeHtml(value: string) {
return value.replace(
/[&<>"']/g,
(c) =>
({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[
c
] ?? c,
);
}
export interface ArticlePreviewData {
title: string;
image: string;
summary: string;
body: string;
}
export function ArticlePreview({
data,
onClose,
title,
}: {
data: ArticlePreviewData | null;
onClose: () => void;
title?: string;
}) {
const t = useTranslations("pages.admin.articles.form");
const document = data
? `<!doctype html><html><head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: http: data:; style-src 'unsafe-inline'"><meta name="viewport" content="width=device-width, initial-scale=1"><style>:root{color-scheme:light;background:Canvas;color:CanvasText}body{font:16px/1.6 system-ui;padding:20px;margin:auto;max-width:760px;overflow-wrap:anywhere}img{max-width:100%;height:auto}h1{line-height:1.2}pre{white-space:pre-wrap}</style></head><body><h1>${escapeHtml(data.title)}</h1>${data.image ? `<img src="${escapeHtml(data.image)}" alt="">` : ""}<p>${escapeHtml(data.summary)}</p>${data.body}</body></html>`
: "";
return (
<Dialog
open={!!data}
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<DialogContent className="sm:max-w-4xl h-[85dvh] flex flex-col min-h-0">
<DialogHeader>
<DialogTitle>{title ?? t("previewTitle")}</DialogTitle>
</DialogHeader>
<p className="text-xs text-muted-foreground">
{t("previewDescription")}
</p>
<iframe
title={title ?? t("previewFrame")}
sandbox=""
srcDoc={document}
className="min-h-0 flex-1 w-full bg-background rounded border"
/>
</DialogContent>
</Dialog>
);
}