Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
902ab6244e | ||
|
|
6d32fd2b71 | ||
|
|
176859b15a | ||
|
|
70bc700a11 |
@@ -13,8 +13,9 @@ API_TOKEN = os.environ["AUTHENTIK_API_TOKEN"]
|
|||||||
SECRET_KEY = os.environ["SESSION_SECRET"]
|
SECRET_KEY = os.environ["SESSION_SECRET"]
|
||||||
REDIRECT_URI = os.environ.get("OIDC_REDIRECT_URI", "https://weircon.dk/auth/callback")
|
REDIRECT_URI = os.environ.get("OIDC_REDIRECT_URI", "https://weircon.dk/auth/callback")
|
||||||
ADMIN_GROUP = os.environ.get("ADMIN_GROUP", "authentik Admins")
|
ADMIN_GROUP = os.environ.get("ADMIN_GROUP", "authentik Admins")
|
||||||
# Hvem der må høre familiens podcasts (/podcast); admins kan altid.
|
# Hvem der må høre familiens podcasts (/podcast); admins kan altid. Kommasepareret:
|
||||||
PODCAST_GROUP = os.environ.get("PODCAST_GROUP", "Familie")
|
# Familie er den nære familie, Gramps er slægten der kun har Gramps + podcast + portal.
|
||||||
|
PODCAST_GROUPS = {g.strip() for g in os.environ.get("PODCAST_GROUP", "Familie,Gramps").split(",") if g.strip()}
|
||||||
|
|
||||||
MATOMO_BASE = os.environ.get("MATOMO_BASE", "https://matomo.weircon.dk").rstrip("/")
|
MATOMO_BASE = os.environ.get("MATOMO_BASE", "https://matomo.weircon.dk").rstrip("/")
|
||||||
MATOMO_TOKEN = os.environ.get("MATOMO_TOKEN", "")
|
MATOMO_TOKEN = os.environ.get("MATOMO_TOKEN", "")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
|
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
@@ -71,14 +72,14 @@ def require_admin(request: Request):
|
|||||||
|
|
||||||
|
|
||||||
def require_familie(request: Request):
|
def require_familie(request: Request):
|
||||||
"""Logget ind og i PODCAST_GROUP (eller admin). Uden login: login og tilbage hertil."""
|
"""Logget ind og i en af PODCAST_GROUPS (eller admin). Uden login: login og tilbage hertil."""
|
||||||
user = current_user(request)
|
user = current_user(request)
|
||||||
if not user and request.method == "GET":
|
if not user and request.method == "GET":
|
||||||
nxt = request.url.path + (f"?{request.url.query}" if request.url.query else "")
|
nxt = request.url.path + (f"?{request.url.query}" if request.url.query else "")
|
||||||
raise HTTPException(303, headers={"Location": "/auth/login?next=" + urllib.parse.quote(nxt, safe="")})
|
raise HTTPException(303, headers={"Location": "/auth/login?next=" + urllib.parse.quote(nxt, safe="")})
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(401, "Log ind først")
|
raise HTTPException(401, "Log ind først")
|
||||||
if not (is_admin(user) or config.PODCAST_GROUP in (user.get("groups") or [])):
|
if not (is_admin(user) or config.PODCAST_GROUPS & set(user.get("groups") or [])):
|
||||||
raise HTTPException(403, "Kun for familien")
|
raise HTTPException(403, "Kun for familien")
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ async def dashboard_content(request: Request):
|
|||||||
|
|
||||||
def _podcast_kort(user) -> list[dict]:
|
def _podcast_kort(user) -> list[dict]:
|
||||||
"""Kort til forsiden: én pr. podcast med udgivne episoder (admins ser også kladder)."""
|
"""Kort til forsiden: én pr. podcast med udgivne episoder (admins ser også kladder)."""
|
||||||
if not (is_admin(user) or config.PODCAST_GROUP in (user.get("groups") or [])):
|
if not (is_admin(user) or config.PODCAST_GROUPS & set(user.get("groups") or [])):
|
||||||
return []
|
return []
|
||||||
ud = []
|
ud = []
|
||||||
for name in podcast.shows():
|
for name in podcast.shows():
|
||||||
@@ -441,16 +442,50 @@ async def podcast_ny_noegle(request: Request):
|
|||||||
|
|
||||||
# Det fælles familielink: ingen login, ingen kladder, noindex. Står før de
|
# Det fælles familielink: ingen login, ingen kladder, noindex. Står før de
|
||||||
# personlige ruter, så "f" aldrig forveksles med en nøgle.
|
# personlige ruter, så "f" aldrig forveksles med en nøgle.
|
||||||
|
# Messenger/Facebooks link-forhåndsvisning og deres indbyggede browsere.
|
||||||
|
_FORHAANDSVISNING = re.compile(r"facebookexternalhit|meta-external(agent|fetcher)|Facebot|WhatsApp", re.I)
|
||||||
|
_INDBYGGET = re.compile(r"FBAN|FBAV|FB_IAB|FBIOS|Messenger|Instagram|LinkedInApp", re.I)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/podcast/f/{key}/", response_class=HTMLResponse)
|
@app.get("/podcast/f/{key}/", response_class=HTMLResponse)
|
||||||
async def podcast_faelles_side(request: Request, key: str):
|
async def podcast_faelles_side(request: Request, key: str):
|
||||||
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key, "side"):
|
ua = request.headers.get("user-agent", "")
|
||||||
|
forhaandsvisning = bool(_FORHAANDSVISNING.search(ua))
|
||||||
|
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key, "" if forhaandsvisning else "side"):
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
rod = f"/podcast/f/{key}"
|
rod = f"/podcast/f/{key}"
|
||||||
|
base = str(request.base_url).rstrip("/")
|
||||||
|
udsendelser = _podcast_kontekst(request, rod, med_kladder=False)
|
||||||
|
s = udsendelser[0] if udsendelser else {"titel": "Podcast", "slug": "", "har_cover": False}
|
||||||
|
# Et lille omslag (300 px), så Messenger viser en miniature ved teksten i
|
||||||
|
# stedet for et billede i hele beskedens bredde.
|
||||||
|
lille = bool(s.get("slug")) and podcast.cover(s["slug"], lille=True) is not None
|
||||||
|
og = {"titel": s["titel"], "url": f"{base}{rod}/",
|
||||||
|
"beskrivelse": "Familiens podcast. Lyt i browseren, eller abonnér i din podcast-app.",
|
||||||
|
"billede": (f"{base}{rod}/{s['slug']}/cover-lille.jpg" if lille else
|
||||||
|
f"{base}{rod}/{s['slug']}/cover.jpg" if s.get("har_cover") else ""),
|
||||||
|
"str": 300 if lille else 3000}
|
||||||
|
headers = {"X-Robots-Tag": "noindex, nofollow, noarchive", "Referrer-Policy": "no-referrer",
|
||||||
|
"Cache-Control": "private, no-store"}
|
||||||
|
if forhaandsvisning:
|
||||||
|
# Forhåndsvisningen får kun titel, en neutral tekst og omslaget, aldrig
|
||||||
|
# episodelisten (den nævner familiemedlemmer ved navn).
|
||||||
|
return templates.TemplateResponse(request, "podcast_og.html", {"request": request, "og": og}, headers=headers)
|
||||||
|
indbygget = None
|
||||||
|
if _INDBYGGET.search(ua):
|
||||||
|
# Messenger på iPhone blokerer også x-safari-https://, så dér er der ingen
|
||||||
|
# knap ud af appen: vejledning til menuen + kopiér feedet til Apple Podcasts.
|
||||||
|
# Android-udgaven af Facebooks browser følger intent://-links.
|
||||||
|
sti = f"{request.url.hostname}{rod}/"
|
||||||
|
if re.search(r"iPhone|iPad|iOS", ua):
|
||||||
|
indbygget = {"platform": "ios"}
|
||||||
|
elif "Android" in ua:
|
||||||
|
indbygget = {"platform": "android", "href": f"intent://{sti}#Intent;scheme=https;end"}
|
||||||
|
else:
|
||||||
|
indbygget = {"platform": "andet"}
|
||||||
return templates.TemplateResponse(request, "podcast.html", {
|
return templates.TemplateResponse(request, "podcast.html", {
|
||||||
"request": request, "user": None, "is_admin": False, "rod": rod, "delt": True,
|
"request": request, "user": None, "is_admin": False, "rod": rod, "delt": True, "og": og,
|
||||||
"udsendelser": _podcast_kontekst(request, rod, med_kladder=False), "saved": ""},
|
"indbygget": indbygget, "udsendelser": udsendelser, "saved": ""}, headers=headers)
|
||||||
headers={"X-Robots-Tag": "noindex, nofollow, noarchive", "Referrer-Policy": "no-referrer",
|
|
||||||
"Cache-Control": "private, no-store"})
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/podcast/f/{key}/{name}.xml")
|
@app.get("/podcast/f/{key}/{name}.xml")
|
||||||
@@ -464,6 +499,14 @@ async def podcast_faelles_feed(request: Request, key: str, name: str):
|
|||||||
headers={"Cache-Control": "private, max-age=300", "X-Robots-Tag": "noindex, nofollow"})
|
headers={"Cache-Control": "private, max-age=300", "X-Robots-Tag": "noindex, nofollow"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/podcast/f/{key}/{name}/cover-lille.jpg")
|
||||||
|
async def podcast_faelles_cover_lille(request: Request, key: str, name: str):
|
||||||
|
p = podcast.cover(name, lille=True)
|
||||||
|
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key) or not p:
|
||||||
|
_ikke_fundet(request)
|
||||||
|
return FileResponse(p, media_type="image/jpeg", headers={"Cache-Control": "private, max-age=86400"})
|
||||||
|
|
||||||
|
|
||||||
@app.get("/podcast/f/{key}/{name}/cover.jpg")
|
@app.get("/podcast/f/{key}/{name}/cover.jpg")
|
||||||
async def podcast_faelles_cover(request: Request, key: str, name: str):
|
async def podcast_faelles_cover(request: Request, key: str, name: str):
|
||||||
p = podcast.cover(name)
|
p = podcast.cover(name)
|
||||||
|
|||||||
+3
-2
@@ -115,8 +115,9 @@ def saet_kladde(name: str, slug: str, kladde: bool) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cover(name: str) -> Path | None:
|
def cover(name: str, lille: bool = False) -> Path | None:
|
||||||
p = ROOT / name / "cover.jpg"
|
"""Omslaget; lille = 300 px-udgaven til link-forhåndsvisninger (cover-lille.jpg)."""
|
||||||
|
p = ROOT / name / ("cover-lille.jpg" if lille else "cover.jpg")
|
||||||
return p if SHOW.match(name or "") and p.exists() else None
|
return p if SHOW.match(name or "") and p.exists() else None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,18 @@
|
|||||||
|
|
||||||
<p style="margin:.9rem 0 .4rem"><strong>Grupper de skal med i</strong></p>
|
<p style="margin:.9rem 0 .4rem"><strong>Grupper de skal med i</strong></p>
|
||||||
<div class="wt-grid wt-grid--tight">
|
<div class="wt-grid wt-grid--tight">
|
||||||
|
{% set gruppe_hjaelp = {
|
||||||
|
"Familie": "den nære familie: Sønderlund, Mealie, Emby, Gramps (redigér), podcast m.m.",
|
||||||
|
"Gramps": "kun Gramps (læse), familiens podcast og portalen",
|
||||||
|
"Crawlkit-team": "kun crawlkit",
|
||||||
|
} %}
|
||||||
{% for g in grupper %}
|
{% for g in grupper %}
|
||||||
<label class="wt-select-card">
|
<label class="wt-select-card">
|
||||||
<input type="checkbox" name="grupper" value="{{ g.pk }}" {% if g.name == "Familie" %}checked{% endif %}>
|
<input type="checkbox" name="grupper" value="{{ g.pk }}" {% if g.name == "Familie" %}checked{% endif %}>
|
||||||
<span class="wt-select-card-ini">{{ g.name[0]|upper }}</span>
|
<span class="wt-select-card-ini">{{ g.name[0]|upper }}</span>
|
||||||
<span class="wt-select-card-body">
|
<span class="wt-select-card-body">
|
||||||
<span class="wt-select-card-name">{{ g.name }}</span>
|
<span class="wt-select-card-name">{{ g.name }}</span>
|
||||||
<span class="wt-select-card-sub">{{ g.members }} medlemmer{% if g.name == "authentik Admins" %} · ⚠️ fuld admin-adgang{% endif %}</span>
|
<span class="wt-select-card-sub">{{ g.members }} medlemmer{% if g.name == "authentik Admins" %} · ⚠️ fuld admin-adgang{% elif g.name in gruppe_hjaelp %} · {{ gruppe_hjaelp[g.name] }}{% endif %}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="wt-select-card-chk">✓</span>
|
<span class="wt-select-card-chk">✓</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -12,19 +12,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
{% if error %}<div class="err">{{ error }}</div>{% endif %}
|
{% if error %}<div class="err">{{ error }}</div>{% endif %}
|
||||||
{% if groups %}
|
|
||||||
<div class="toolbar">
|
|
||||||
<div class="filter"><span class="mag">⌕</span>
|
|
||||||
<input id="appfilter" type="search" placeholder="Filtrér…" autocomplete="off" aria-label="Filtrér tjenester"></div>
|
|
||||||
<div class="viewtoggle" role="group" aria-label="Visning">
|
|
||||||
<button type="button" data-view="grouped" class="{{ 'on' if view != 'alpha' }}">Grupperet</button>
|
|
||||||
<button type="button" data-view="alpha" class="{{ 'on' if view == 'alpha' }}">Alfabetisk</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<section id="favsec" class="appgroup"{% if not fav_apps %} style="display:none"{% endif %}>
|
|
||||||
<h2>★ Favoritter</h2>
|
|
||||||
<div class="grid" id="favgrid">{% for a in fav_apps %}{{ card(a) }}{% endfor %}</div>
|
|
||||||
</section>
|
|
||||||
{% if podcasts %}
|
{% if podcasts %}
|
||||||
<section class="appgroup" id="podsec">
|
<section class="appgroup" id="podsec">
|
||||||
<h2>🎙️ Familiens podcast</h2>
|
<h2>🎙️ Familiens podcast</h2>
|
||||||
@@ -40,6 +27,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if groups %}
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="filter"><span class="mag">⌕</span>
|
||||||
|
<input id="appfilter" type="search" placeholder="Filtrér…" autocomplete="off" aria-label="Filtrér tjenester"></div>
|
||||||
|
<div class="viewtoggle" role="group" aria-label="Visning">
|
||||||
|
<button type="button" data-view="grouped" class="{{ 'on' if view != 'alpha' }}">Grupperet</button>
|
||||||
|
<button type="button" data-view="alpha" class="{{ 'on' if view == 'alpha' }}">Alfabetisk</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section id="favsec" class="appgroup"{% if not fav_apps %} style="display:none"{% endif %}>
|
||||||
|
<h2>★ Favoritter</h2>
|
||||||
|
<div class="grid" id="favgrid">{% for a in fav_apps %}{{ card(a) }}{% endfor %}</div>
|
||||||
|
</section>
|
||||||
{% if mc_servers %}
|
{% if mc_servers %}
|
||||||
<section class="appgroup" id="mcsec">
|
<section class="appgroup" id="mcsec">
|
||||||
<h2>⛏️ Minecraft-servere</h2>
|
<h2>⛏️ Minecraft-servere</h2>
|
||||||
|
|||||||
+45
-1
@@ -1,8 +1,24 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}{% if delt and udsendelser %}{{ udsendelser[0].titel }}{% else %}Podcast{% endif %}{% endblock %}
|
{% block title %}{% if delt and udsendelser %}{{ udsendelser[0].titel }}{% else %}Podcast{% endif %}{% endblock %}
|
||||||
{% block head %}{% if delt %}<meta name="robots" content="noindex, nofollow, noarchive">
|
{% block head %}{% if delt %}<meta name="robots" content="noindex, nofollow, noarchive">
|
||||||
<meta name="referrer" content="no-referrer">{% endif %}{% endblock %}
|
<meta name="referrer" content="no-referrer">
|
||||||
|
{% if og %}<meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="{{ og.titel }}">
|
||||||
|
<meta property="og:description" content="{{ og.beskrivelse }}">
|
||||||
|
<meta property="og:url" content="{{ og.url }}">
|
||||||
|
{% if og.billede %}<meta property="og:image" content="{{ og.billede }}">{% endif %}{% endif %}{% endif %}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
<style>
|
||||||
|
/* Ingen vandret scroll på iPhone: afspiller og felter må aldrig blive bredere end
|
||||||
|
kortet, og felter skal have mindst 16 px skrift, ellers zoomer iOS ind. */
|
||||||
|
html, body { overflow-x: hidden; }
|
||||||
|
.pod-side { max-width: 100%; overflow-wrap: anywhere; }
|
||||||
|
.pod-side img, .pod-side audio, .pod-side input, .pod-side .wt-card { max-width: 100%; box-sizing: border-box; }
|
||||||
|
.pod-side audio { display: block; width: 100%; min-width: 0; }
|
||||||
|
.pod-side input { font-size: 16px !important; min-width: 0; }
|
||||||
|
.pod-side .wt-btn { white-space: normal; }
|
||||||
|
</style>
|
||||||
|
<div class="pod-side">
|
||||||
{% if saved %}<div class="wt-flash">{{ saved }}</div>{% endif %}
|
{% if saved %}<div class="wt-flash">{{ saved }}</div>{% endif %}
|
||||||
{% if not udsendelser %}
|
{% if not udsendelser %}
|
||||||
<h1>Podcast</h1>
|
<h1>Podcast</h1>
|
||||||
@@ -23,6 +39,19 @@
|
|||||||
|
|
||||||
<div class="wt-card" style="margin:1.4rem 0;padding:1rem 1.2rem">
|
<div class="wt-card" style="margin:1.4rem 0;padding:1rem 1.2rem">
|
||||||
<h2 style="font-size:1.05rem;margin:0 0 .5rem">Abonnér i din podcast-app</h2>
|
<h2 style="font-size:1.05rem;margin:0 0 .5rem">Abonnér i din podcast-app</h2>
|
||||||
|
{% if indbygget %}
|
||||||
|
<div class="wt-flash" style="margin:.4rem 0 .9rem">
|
||||||
|
<p style="margin:0 0 .5rem">Du har åbnet siden inde i Messenger, og den kan ikke åbne podcast-apps. Du kan stadig lytte herunder. Vil du abonnere:</p>
|
||||||
|
{% if indbygget.platform == "android" %}
|
||||||
|
<p style="margin:.4rem 0"><a class="wt-btn wt-btn--primary" href="{{ indbygget.href }}">Åbn i Chrome</a></p>
|
||||||
|
<p style="margin:.4rem 0">Virker det ikke: tryk på ⋮ øverst til højre og vælg "Åbn i ekstern browser".</p>
|
||||||
|
{% else %}
|
||||||
|
<p style="margin:.4rem 0"><strong>1.</strong> Tryk på ⋯ øverst til højre og vælg "Åbn i ekstern browser" (eller "Åbn i Safari"). Så virker knapperne herunder.</p>
|
||||||
|
<p style="margin:.6rem 0 .3rem"><strong>2.</strong> Eller kopiér adressen og tilføj den selv i Apple Podcasts: <em>Bibliotek → ⋯ øverst → Følg en serie via URL</em>.</p>
|
||||||
|
<button class="wt-btn wt-btn--primary" type="button" data-kopier-tekst="{{ udsendelser[0].feed if udsendelser else '' }}">Kopiér adressen</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<p class="wt-muted" style="margin:.2rem 0 .8rem">{% if delt %}Tryk på din app, så kommer nye episoder af sig selv. Linket er familiens fælles, så del det kun inden for familien.{% else %}Tryk på din app. Nye episoder kommer så af sig selv. Adressen er din egen, så del den ikke; familien får hver sin.{% endif %}</p>
|
<p class="wt-muted" style="margin:.2rem 0 .8rem">{% if delt %}Tryk på din app, så kommer nye episoder af sig selv. Linket er familiens fælles, så del det kun inden for familien.{% else %}Tryk på din app. Nye episoder kommer så af sig selv. Adressen er din egen, så del den ikke; familien får hver sin.{% endif %}</p>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:.5rem">
|
<div style="display:flex;flex-wrap:wrap;gap:.5rem">
|
||||||
<a class="wt-btn wt-btn--primary" href="podcast://{{ u.uden_skema }}">Apple Podcasts</a>
|
<a class="wt-btn wt-btn--primary" href="podcast://{{ u.uden_skema }}">Apple Podcasts</a>
|
||||||
@@ -68,6 +97,20 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script>
|
<script>
|
||||||
|
function kopierTekst(t, knap) {
|
||||||
|
var ok = function () { knap.textContent = 'Kopieret'; };
|
||||||
|
var fallback = function () {
|
||||||
|
var ta = document.createElement('textarea'); ta.value = t; ta.setAttribute('readonly', '');
|
||||||
|
ta.style.position = 'fixed'; ta.style.top = '0'; ta.style.opacity = '0'; ta.style.fontSize = '16px';
|
||||||
|
document.body.appendChild(ta); ta.focus(); ta.setSelectionRange(0, t.length);
|
||||||
|
try { document.execCommand('copy'); ok(); } catch (e) { prompt('Kopiér adressen:', t); }
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
};
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(t).then(ok, fallback); else fallback();
|
||||||
|
}
|
||||||
|
document.querySelectorAll('[data-kopier-tekst]').forEach(function (b) {
|
||||||
|
b.addEventListener('click', function () { kopierTekst(b.dataset.kopierTekst, b); });
|
||||||
|
});
|
||||||
document.querySelectorAll('[data-kopier]').forEach(function (b) {
|
document.querySelectorAll('[data-kopier]').forEach(function (b) {
|
||||||
b.addEventListener('click', function () {
|
b.addEventListener('click', function () {
|
||||||
var i = document.getElementById(b.dataset.kopier);
|
var i = document.getElementById(b.dataset.kopier);
|
||||||
@@ -78,4 +121,5 @@ document.querySelectorAll('[data-kopier]').forEach(function (b) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="da">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ og.titel }}</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||||
|
<meta name="description" content="{{ og.beskrivelse }}">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="{{ og.titel }}">
|
||||||
|
<meta property="og:description" content="{{ og.beskrivelse }}">
|
||||||
|
<meta property="og:url" content="{{ og.url }}">
|
||||||
|
<meta property="og:locale" content="da_DK">
|
||||||
|
{% if og.billede %}<meta property="og:image" content="{{ og.billede }}">
|
||||||
|
<meta property="og:image:width" content="{{ og.str }}">
|
||||||
|
<meta property="og:image:height" content="{{ og.str }}">
|
||||||
|
<meta name="twitter:card" content="summary">{% endif %}
|
||||||
|
</head>
|
||||||
|
<body><h1>{{ og.titel }}</h1><p>{{ og.beskrivelse }}</p></body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user