All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 1m33s
Der Player wird als iframe eingebunden, aber nicht als rohes HTML aus dem Textfeld: Die Anwendung setzt die Attribute, im Admin steht nur die Adresse. Damit laesst sich ueber das Feld nichts anderes einschleusen. Freigegeben ist alangu.de samt Unterdomains, sonst nichts. Die Pruefung haengt am Hostnamen, nicht an einem Textvergleich - getestet mit boese-alangu.de.angreifer.example und evil.example/?a=alangu.de, beide werden abgewiesen, ebenso http statt https. Vom uebergebenen Einbettungscode weggelassen: accelerometer und gyroscope. Ein Videoplayer braucht keine Bewegungssensoren. Ausserdem: Die Gebaerdensprach-Seite erscheint jetzt auch ohne Text, wenn eine Videoadresse hinterlegt ist - dort ist das Video der Inhalt. Fusszeile und Seite entscheiden das ueber dieselbe Funktion, sonst gaebe es eine erreichbare Seite ohne Link. Geprueft am echten Player: laedt mit HTTP 200 samt CSS, JS und hls.js, axe-core meldet in hell und dunkel keine Verstoesse, der Rahmen ist per Tastatur erreichbar, und die Fusszeile verlinkt die Seite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
import { notFound } from "next/navigation"
|
||
|
||
import { PublicHeader } from "@/components/public-header"
|
||
import { SiteFooter } from "@/components/site-footer"
|
||
import { markdownToHtml } from "@/lib/markdown"
|
||
import {
|
||
SITE_PAGE_META,
|
||
SitePageKey,
|
||
hasSitePageContent,
|
||
isAllowedEmbedUrl,
|
||
isSafeMediaUrl,
|
||
} from "@/lib/site-pages"
|
||
import { readSitePages } from "@/lib/site-pages.server"
|
||
|
||
/**
|
||
* Rendert eine der rechtlichen Seiten aus dem im Admin gepflegten Markdown.
|
||
*
|
||
* Serverseitig gerendert: Der Text steht ohne JavaScript im Dokument und ist
|
||
* damit auch für Screenreader, Suchmaschinen und Archivierung erreichbar.
|
||
* Ist nichts gepflegt, gibt es die Seite nicht – ein leeres Impressum wäre
|
||
* irreführender als ein 404.
|
||
*/
|
||
export async function LegalPage({ page }: { page: SitePageKey }) {
|
||
const pages = await readSitePages()
|
||
const content = pages[page].trim()
|
||
const meta = SITE_PAGE_META[page]
|
||
|
||
// Auf der Gebärdensprach-Seite ist das Video der eigentliche Inhalt. Sie
|
||
// erscheint deshalb auch dann, wenn nur eine Videoadresse hinterlegt ist –
|
||
// ein zusätzlicher Text ist willkommen, aber keine Bedingung.
|
||
const video =
|
||
page === "signLanguage" && isSafeMediaUrl(pages.signLanguageVideo)
|
||
? pages.signLanguageVideo.trim()
|
||
: ""
|
||
|
||
if (!hasSitePageContent(pages, page)) notFound()
|
||
|
||
const html = content ? await markdownToHtml(content) : ""
|
||
|
||
const captions = isSafeMediaUrl(pages.signLanguageCaptions)
|
||
? pages.signLanguageCaptions.trim()
|
||
: ""
|
||
|
||
return (
|
||
<div className="flex min-h-screen flex-col bg-background">
|
||
<PublicHeader subtitle={meta.title} />
|
||
|
||
<main id="inhalt" tabIndex={-1} className="mx-auto w-full max-w-3xl flex-1 px-4 py-6 sm:px-6 sm:py-8">
|
||
<h1 className="mb-6 text-2xl font-semibold tracking-tight">
|
||
{meta.title}
|
||
</h1>
|
||
|
||
{/* Erläuterungen in Deutscher Gebärdensprache nach § 4 BITV 2.0.
|
||
Der native Player ist mit der Tastatur bedienbar; Untertitel
|
||
werden eingebunden, sobald eine WebVTT-Datei hinterlegt ist. */}
|
||
{page === "signLanguage" && video && (
|
||
<figure className="mb-8">
|
||
{isAllowedEmbedUrl(video) ? (
|
||
// Player eines zugelassenen Anbieters. Die Attribute setzt die
|
||
// Anwendung, nicht der Text im Admin – so lässt sich über das
|
||
// Adressfeld nichts anderes einschleusen.
|
||
<div className="aspect-video w-full overflow-hidden rounded-lg border bg-muted">
|
||
<iframe
|
||
src={video}
|
||
title="Video in Deutscher Gebärdensprache mit Erläuterungen zu diesem Portal"
|
||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||
allowFullScreen
|
||
className="size-full border-0"
|
||
/>
|
||
</div>
|
||
) : (
|
||
<video
|
||
controls
|
||
preload="metadata"
|
||
className="w-full rounded-lg border bg-muted"
|
||
>
|
||
<source src={video} />
|
||
{captions && (
|
||
<track
|
||
src={captions}
|
||
kind="captions"
|
||
srcLang="de"
|
||
label="Deutsch"
|
||
default
|
||
/>
|
||
)}
|
||
Ihr Browser kann dieses Video nicht abspielen.{" "}
|
||
<a href={video}>Video herunterladen</a>
|
||
</video>
|
||
)}
|
||
<figcaption className="mt-2 text-sm text-muted-foreground">
|
||
Erläuterungen zu diesem Portal in Deutscher Gebärdensprache
|
||
</figcaption>
|
||
</figure>
|
||
)}
|
||
|
||
<div
|
||
className="markdown-content"
|
||
dangerouslySetInnerHTML={{ __html: html }}
|
||
/>
|
||
</main>
|
||
|
||
<SiteFooter />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** Seitentitel für den Browser-Tab. */
|
||
export function legalPageMetadata(page: SitePageKey) {
|
||
return { title: SITE_PAGE_META[page].title }
|
||
}
|