import { useQuery } from "@tanstack/react-query";
import { useParams, Link } from "wouter";
import { type Article } from "@shared/schema";
import { format } from "date-fns";
import { de } from "date-fns/locale";
import { ArrowLeft, Eye, Calendar, User, Clock } from "lucide-react";
import { usePageMeta } from "@/hooks/use-page-meta";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { InArticleAd, PageSideAds } from "@/components/adsense";
import DOMPurify from "dompurify";
import ShareButtons from "@/components/share-buttons";
import { useEffect, useMemo } from "react";
const ALLOWED_IFRAME_DOMAINS = [
"iframe.mediadelivery.net",
"video.bunny.net",
"www.facebook.com",
"www.instagram.com",
"www.tiktok.com",
"www.youtube.com",
"youtube.com",
"player.vimeo.com",
];
function sanitizeContent(html: string): string {
return DOMPurify.sanitize(html, {
ADD_TAGS: ["iframe", "blockquote"],
ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling", "src", "loading", "style", "class", "title", "data-instgrm-permalink", "data-instgrm-version", "data-instgrm-captioned", "cite"],
ALLOW_UNKNOWN_PROTOCOLS: false,
});
}
function ArticleSkeleton() {
return (
);
}
function RelatedArticles({ currentSlug }: { currentSlug: string }) {
const { data: articles } = useQuery({
queryKey: ["/api/articles/popular"],
});
const filtered = articles?.filter((a) => a.slug !== currentSlug);
if (!filtered || filtered.length === 0) return null;
return (
Weitere Artikel
{filtered.slice(0, 3).map((article) => (
{article.title}
{format(new Date(article.publishedAt), "d. MMMM yyyy", { locale: de })}
))}
);
}
export default function ArticlePage() {
const { slug } = useParams<{ slug: string }>();
const { data: article, isLoading, error } = useQuery({
queryKey: ["/api/articles", slug],
});
const articleUrl = article ? `${window.location.origin}/article/${article.slug}` : "";
const articleImage = article?.coverImage
? (article.coverImage.startsWith("http") ? article.coverImage : `${window.location.origin}${article.coverImage}`)
: "";
usePageMeta(
article ? `${article.title} - Volksmusik & Schlager` : "Volksmusik & Schlager Artikel",
article?.excerpt || "Aktuelle Nachrichten aus der Volksmusik- und Schlagerszene bei FOLX TV.",
article ? {
ogTitle: article.title,
ogDescription: article.excerpt,
ogImage: articleImage || undefined,
ogType: "article",
ogUrl: articleUrl,
articlePublishedTime: new Date(article.publishedAt).toISOString(),
articleAuthor: article.author || "Folx Music Television",
articleSection: article.category || "News",
} : undefined
);
useEffect(() => {
if (!article) return;
const jsonLd = {
"@context": "https://schema.org",
"@type": "NewsArticle",
"headline": article.title,
"description": article.excerpt,
"image": articleImage ? [articleImage] : [],
"datePublished": new Date(article.publishedAt).toISOString(),
"dateModified": new Date(article.publishedAt).toISOString(),
"author": {
"@type": "Organization",
"name": article.author || "Folx Music Television",
"url": "https://folx.tv"
},
"publisher": {
"@type": "Organization",
"name": "Folx Music Television",
"url": "https://folx.tv",
"logo": {
"@type": "ImageObject",
"url": `${window.location.origin}/favicon.png`
}
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": articleUrl
},
"articleSection": article.category || "News",
"inLanguage": "de"
};
const script = document.createElement("script");
script.type = "application/ld+json";
script.id = "article-jsonld";
script.textContent = JSON.stringify(jsonLd);
const existing = document.getElementById("article-jsonld");
if (existing) existing.remove();
document.head.appendChild(script);
return () => {
const el = document.getElementById("article-jsonld");
if (el) el.remove();
};
}, [article, articleUrl, articleImage]);
useEffect(() => {
if (!article?.content) return;
if (article.content.includes("instagram.com")) {
const existing = document.querySelector('script[src*="instagram.com/embed.js"]');
if (existing) existing.remove();
const script = document.createElement("script");
script.src = "https://www.instagram.com/embed.js";
script.async = true;
document.body.appendChild(script);
script.onload = () => {
if ((window as any).instgrm) {
(window as any).instgrm.Embeds.process();
}
};
}
if (article.content.includes("tiktok.com")) {
if (!document.querySelector('script[src*="tiktok.com/embed.js"]')) {
const script = document.createElement("script");
script.src = "https://www.tiktok.com/embed.js";
script.async = true;
document.body.appendChild(script);
}
}
}, [article?.content]);
if (isLoading) {
return (
);
}
if (error || !article) {
return (
Artikel nicht gefunden
Der gesuchte Artikel existiert nicht oder wurde entfernt.
);
}
return (
{article.coverImage && (
)}
{article.title}
{article.author}
{format(new Date(article.publishedAt), "d. MMMM yyyy", { locale: de })}
{article.views.toLocaleString()} Aufrufe
{(() => {
const sanitized = sanitizeContent(article.content);
const blocks = sanitized.split(/(?=<(?:p|h[2-4]|div)[\s>])/i).filter(Boolean);
const midPoint = Math.max(2, Math.floor(blocks.length / 2));
const firstHalf = blocks.slice(0, midPoint).join("");
const secondHalf = blocks.slice(midPoint).join("");
const proseClasses = `prose prose-lg dark:prose-invert max-w-none
prose-headings:text-foreground prose-headings:font-semibold
prose-p:text-foreground/85 prose-p:leading-relaxed
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
prose-strong:text-foreground
prose-img:rounded-md prose-img:w-full prose-img:object-cover
[&_iframe]:rounded-md [&_iframe]:my-6 [&_iframe]:max-w-full
[&_div[style]]:flex [&_div[style]]:justify-center
[&_blockquote:not(.instagram-media)]:border-l-primary [&_blockquote:not(.instagram-media)]:bg-accent/50 [&_blockquote:not(.instagram-media)]:rounded-r-md [&_blockquote:not(.instagram-media)]:py-1
[&_.instagram-media]:!bg-transparent [&_.instagram-media]:!border-0 [&_.instagram-media]:!shadow-none [&_.instagram-media]:!p-0 [&_.instagram-media]:mx-auto`;
return (
{
if (!el) return;
el.querySelectorAll("a[href]").forEach((a) => {
const href = a.getAttribute("href") || "";
const isBunny = href.includes("mediadelivery.net") || href.includes("bunny.net") || href.includes("b-cdn.net");
const isInternal = href.startsWith("/") || href.includes("folx.tv");
if (!isBunny && !isInternal && href.startsWith("http")) {
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener noreferrer");
}
});
}}>
);
})()}
);
}