From d351ba2b3a9101c6927791642211464c6e35ee2a Mon Sep 17 00:00:00 2001 From: maik Date: Tue, 18 Aug 2026 17:58:53 +0200 Subject: [PATCH] feat: Externe Quellen mit klickbaren Links (neuer Tab) + Kiosk-Modus - Label "Externe Quelle (Links)" -> "Externe Quellen" (de/en) - Quelle-URLs werden via linkify-Partial anklickbar, oeffnen in neuem Tab - Neuer Kiosk-Modus: ?kiosk=true macht externe Links unklickbar (bleibt fuer die Session aktiv, verlassen mit ?kiosk=false) --- assets/js/kiosk.js | 71 +++++++++++++++++++ i18n/de.toml | 2 +- i18n/en.toml | 2 +- themes/op-dz/assets/css/main.css | 6 ++ themes/op-dz/layouts/partials/linkify.html | 10 +++ themes/op-dz/layouts/partials/scripts.html | 5 ++ themes/op-dz/layouts/schauplaetze/single.html | 2 +- 7 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 assets/js/kiosk.js create mode 100644 themes/op-dz/layouts/partials/linkify.html diff --git a/assets/js/kiosk.js b/assets/js/kiosk.js new file mode 100644 index 0000000..5c0e13f --- /dev/null +++ b/assets/js/kiosk.js @@ -0,0 +1,71 @@ +/* + * Kiosk-Modus für Ausstellungs-/Terminal-Betrieb. + * + * Aktivieren: beliebige URL mit ?kiosk=true (oder ?kiosk=1) aufrufen. + * Deaktivieren: ?kiosk=false (oder ?kiosk=0). + * + * Ist der Modus aktiv, bleibt er für die gesamte Browser-Session erhalten + * (auch beim Weiterklicken im Menü, ohne den Parameter erneut anzuhängen). + * Externe Links (anderer Host) sowie mailto:/tel: werden dann unklickbar + * gemacht – der Linktext bleibt lesbar. Interne Navigation bleibt möglich, + * damit Karte und Schauplätze weiter erkundet werden können. + */ +(function () { + "use strict"; + + var KEY = "op-kiosk"; + var params = new URLSearchParams(window.location.search); + + // Parameter setzt/löscht den Session-Zustand + if (params.has("kiosk")) { + var v = params.get("kiosk").toLowerCase(); + var on = v === "" || v === "true" || v === "1" || v === "yes"; + try { + if (on) { sessionStorage.setItem(KEY, "1"); } + else { sessionStorage.removeItem(KEY); } + } catch (e) { /* Storage evtl. gesperrt – dann nur für diese Seite aktiv */ } + if (!on) { document.documentElement.classList.remove("op-kiosk"); return; } + } + + var active = false; + try { active = sessionStorage.getItem(KEY) === "1"; } catch (e) { /* ignore */ } + if (!active && !params.has("kiosk")) { return; } + if (!active && params.has("kiosk")) { + // Storage gesperrt, aber Parameter aktiv → nur für diese Seite + active = params.get("kiosk").toLowerCase() !== "false"; + } + if (!active) { return; } + + document.documentElement.classList.add("op-kiosk"); + + function neutralize() { + var host = window.location.hostname; + var links = document.querySelectorAll("a[href]"); + for (var i = 0; i < links.length; i++) { + var a = links[i]; + var url; + try { url = new URL(a.href, window.location.href); } catch (e) { continue; } + + var web = url.protocol === "http:" || url.protocol === "https:"; + var block = + (web && url.hostname !== host) || + url.protocol === "mailto:" || + url.protocol === "tel:"; + + if (!block) { continue; } + + a.classList.add("op-link-disabled"); + a.setAttribute("aria-disabled", "true"); + a.setAttribute("tabindex", "-1"); + a.removeAttribute("target"); + a.dataset.kioskHref = a.getAttribute("href"); + a.removeAttribute("href"); + } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", neutralize); + } else { + neutralize(); + } +})(); diff --git a/i18n/de.toml b/i18n/de.toml index d034c85..7fe3748 100644 --- a/i18n/de.toml +++ b/i18n/de.toml @@ -35,7 +35,7 @@ other = "Schauplätze" other = "Alle Schauplätze" [externalSource] -other = "Externe Quelle (Links):" +other = "Externe Quellen" [directions] other = "Anfahrt per OpenStreetMap" diff --git a/i18n/en.toml b/i18n/en.toml index 2883b65..7c2c777 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -35,7 +35,7 @@ other = "Locations" other = "All locations" [externalSource] -other = "External source (links):" +other = "External sources" [directions] other = "Directions via OpenStreetMap" diff --git a/themes/op-dz/assets/css/main.css b/themes/op-dz/assets/css/main.css index f15bdfa..d8aa48c 100644 --- a/themes/op-dz/assets/css/main.css +++ b/themes/op-dz/assets/css/main.css @@ -694,6 +694,12 @@ a:hover { color: var(--op-blau-dunkel); } .op-theme-blau .op-bildnachweis { color: rgba(255,255,255,.7); } .op-theme-blau .op-bildnachweis a { color: var(--op-gelb); } .op-content-box .op-quelle-head { font-weight: 700; color: var(--op-blau); margin-top: 1.5rem; } +.op-content-box .op-quelle-link { color: var(--op-blau); text-decoration: underline; word-break: break-word; } +.op-theme-blau .op-content-box .op-quelle-link { color: var(--op-gelb); } + +/* Kiosk-Modus (?kiosk=true): externe Links werden von kiosk.js entschärft und + sollen wie normaler Fliesstext wirken – nicht anklickbar, keine Link-Optik. */ +.op-link-disabled { color: inherit; text-decoration: none; cursor: default; pointer-events: none; } /* ========================================================================= 16. TOUCH-KIOSK & BARRIEREFREIHEIT diff --git a/themes/op-dz/layouts/partials/linkify.html b/themes/op-dz/layouts/partials/linkify.html new file mode 100644 index 0000000..9248d6b --- /dev/null +++ b/themes/op-dz/layouts/partials/linkify.html @@ -0,0 +1,10 @@ +{{- /* + linkify.html – wandelt nackte http(s)-URLs in einem Textblock in anklickbare + Links um (neuer Tab). Alles andere bleibt unveraendert. + Aufruf: {{ partial "linkify.html" .Params.quelle }} + Die erzeugten Links tragen die Klasse op-quelle-link; im Kiosk-Modus + (?kiosk=true) werden externe Links per kiosk.js ohnehin deaktiviert. +*/ -}} +{{- $s := . -}} +{{- $s = replaceRE `(https?://[^\s<>\]]+)` `${1}` $s -}} +{{- $s | safeHTML -}} diff --git a/themes/op-dz/layouts/partials/scripts.html b/themes/op-dz/layouts/partials/scripts.html index 4cb698d..0927057 100644 --- a/themes/op-dz/layouts/partials/scripts.html +++ b/themes/op-dz/layouts/partials/scripts.html @@ -1,3 +1,8 @@ +{{/* Kiosk-Modus (?kiosk=true) – global auf allen Seiten */}} +{{ $kiosk := resources.Get "js/kiosk.js" }} +{{ if hugo.IsProduction }}{{ $kiosk = $kiosk | minify | fingerprint }}{{ end }} + + {{/* Karten-JS nur auf Karten-Seiten (Home + Schauplätze) laden */}} {{ if or .IsHome (eq .Type "schauplaetze") (eq .Type "kategorien") (eq .Type "erinnerungsorte") }} diff --git a/themes/op-dz/layouts/schauplaetze/single.html b/themes/op-dz/layouts/schauplaetze/single.html index d8b9a4e..bcdfffc 100644 --- a/themes/op-dz/layouts/schauplaetze/single.html +++ b/themes/op-dz/layouts/schauplaetze/single.html @@ -27,7 +27,7 @@ {{ .Content }} {{ with .Params.quelle }}

{{ i18n "externalSource" }}

-

{{ . | safeHTML }}

+

{{ partial "linkify.html" . }}

{{ end }}