Das Feld "Vorschaubild beim Teilen" gab es seit dem SEO-Paket, ausgegeben wurde es nie: keine mit KantSites gebaute Seite hatte ein og:image. Es wird jetzt aufgeloest - erst das gesetzte Vorschaubild, sonst das Beitragsbild - als absolute Adresse bis 1600 px, dazu og:site_name, og:locale, og:image:alt und twitter:card (klein ohne Bild, gross mit). Dasselbe Bild steht im strukturierten Datensatz eines Beitrags; Archive haben jetzt ebenfalls Open-Graph-Angaben. Eigene robots.txt-Zeilen: 200 statt 30, und Leerzeilen bleiben als Trenner erhalten - Regeln je Bot (welche KI-Crawler lesen duerfen) brauchen Bloecke, nicht einen Klumpen. Behoben: die Symbolerzeugung suchte feste Breiten statt der Varianten, die das Bild wirklich hat - ein 192 px grosses Zeichen, also der haeufigste Fall, scheiterte damit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
7.0 KiB
Twig
90 lines
7.0 KiB
Twig
<!doctype html>
|
||
<html lang="{{ page.locale }}">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>{{ page.document.title }}</title>
|
||
<meta name="description" content="{{ page.document.description }}">
|
||
<link rel="canonical" href="{{ canonical }}">
|
||
{% if page.robots|default('index') != 'index' %}<meta name="robots" content="noindex, follow">{% endif %}
|
||
<meta property="og:title" content="{{ page.document.title }}">
|
||
<meta property="og:description" content="{{ page.document.description }}">
|
||
<meta property="og:type" content="{{ page.type|default('page') == 'post' ? 'article' : 'website' }}">
|
||
<meta property="og:url" content="{{ canonical }}">
|
||
{# In der Vorschau des Editors gibt es den Websitenamen nicht - dort fehlt das Feld einfach. #}
|
||
{% if page.website_name|default('') %}<meta property="og:site_name" content="{{ page.website_name }}">{% endif %}
|
||
<meta property="og:locale" content="{{ page.locale == 'de' ? 'de_DE' : page.locale }}">
|
||
{% if socialImage|default(null) %}
|
||
<meta property="og:image" content="{{ socialImage.url }}">
|
||
{% if socialImage.alt %}<meta property="og:image:alt" content="{{ socialImage.alt }}">{% endif %}
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
{% else %}
|
||
{# Ohne Bild ist die große Karte eine leere Fläche; die kleine zeigt Titel und Text. #}
|
||
<meta name="twitter:card" content="summary">
|
||
{% endif %}
|
||
{% include 'favicon.html.twig' %}
|
||
<link rel="stylesheet" href="/assets/site.css">
|
||
{% if googleFonts|default(null) %}<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="{{ googleFonts }}">{% endif %}
|
||
{% if hasPosts|default(false) %} <link rel="alternate" type="application/rss+xml" title="Beiträge" href="/feed.xml">
|
||
{% endif %}
|
||
{% if page.document.schemaVersion >= 2 %}<link rel="stylesheet" href="/assets/design.css">{% endif %}
|
||
{% if page.document.schemaVersion >= 3 %}<link rel="stylesheet" href="/assets/enhancements.css">{% endif %}
|
||
{% if needsTabs|default(false) %}<script src="/assets/tabs.js" defer></script>{% endif %}
|
||
{% if needsSlider|default(false) %}<script src="/assets/slider.js" defer></script>{% endif %}
|
||
{# Own scripts of this page, as a file. Never rendered in the editor preview: the preview must
|
||
show the layout, not run code that could change the page under the editor's hands. #}
|
||
{% if pageScript|default(null) and not editing|default(false) %}<script src="{{ pageScript }}" defer></script>{% endif %}
|
||
{# A data block, not a script: CSP script-src does not apply to application/ld+json. #}
|
||
{% for block in structuredData|default([]) %}<script type="application/ld+json">{{ block|json_encode(constant('JSON_UNESCAPED_SLASHES') b-or constant('JSON_UNESCAPED_UNICODE'))|raw }}</script>
|
||
{% endfor %}
|
||
{% if pageStyles|default('') %}
|
||
{# In the editor the preview inherits the administration's policy, which forbids inline
|
||
styles. The stylesheet therefore travels as inert template text and the editor applies
|
||
it as a constructed stylesheet – same result, no policy violation and a clean console. #}
|
||
{% if editing|default(false) %}<template id="kant-styles">{{ pageStyles|raw }}</template>
|
||
{% else %}<style>{{ pageStyles|raw }}</style>{% endif %}
|
||
{% endif %}
|
||
</head>
|
||
{% set consentOpen = not editing|default(false) and consentGate.needsBanner(app.request) %}
|
||
<body class="kant{{ consentOpen ? ' consent-open' : '' }}">
|
||
<a class="skip-link" href="#main">Zum Inhalt springen</a>
|
||
{# Header and footer are block documents like any page, so they are built from elements
|
||
instead of being written into this template. #}
|
||
{% if areas.header|default(null) %}
|
||
<header class="site-header{{ siteSettings.load().stickyHeader ? ' is-sticky' : '' }}">
|
||
{% for block in areas.header.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: areas.header.schemaVersion|default(1) >= 12, richHeadings: areas.header.schemaVersion|default(1) >= 17, eagerImage: eagerImage|default(null)} only %}{% endfor %}
|
||
</header>
|
||
{% endif %}
|
||
<main id="main" tabindex="-1">
|
||
{% if postMeta is defined and postMeta is not null and siteSettings.load().postMetaHeader %}
|
||
<p class="post-header shell">
|
||
{% if page.published_at %}<time datetime="{{ page.published_at|date('Y-m-d') }}">{{ page.published_at|date('d.m.Y') }}</time>{% endif %}
|
||
{% if page.author %}<span>von {{ page.author }}</span>{% endif %}
|
||
{% for term in postMeta.category %}<a href="{{ term.href }}">{{ term.name }}</a>{% endfor %}
|
||
{% for term in postMeta.tag %}<a class="post-tag" href="{{ term.href }}">#{{ term.name }}</a>{% endfor %}
|
||
</p>
|
||
{% endif %}
|
||
{% for block in page.document.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: page.document.schemaVersion|default(1) >= 12, richHeadings: page.document.schemaVersion|default(1) >= 17, neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
|
||
</main>
|
||
{% if areas.footer|default(null) %}
|
||
<footer class="site-footer">
|
||
{% for block in areas.footer.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: areas.footer.schemaVersion|default(1) >= 12, richHeadings: areas.footer.schemaVersion|default(1) >= 17, eagerImage: null} only %}{% endfor %}
|
||
</footer>
|
||
{% endif %}
|
||
{# Privacy statement, imprint and the way back to the consent decision. All three have to be
|
||
reachable from every page, so they are part of the template and not of the footer area,
|
||
which someone could delete. One labelled landmark, so they are not loose text either. #}
|
||
{% set legal = consentGate.legalLinks() %}
|
||
{% set revocable = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %}
|
||
{% if legal is not empty or revocable %}
|
||
<nav class="legal-bar" aria-label="Rechtliches">
|
||
{% for path, label in legal %}{% if not loop.first %} · {% endif %}<a href="/{{ path }}">{{ label }}</a>{% endfor %}
|
||
{% if revocable %}{% if legal is not empty %} · {% endif %}<a href="/einwilligung">Einwilligung ändern</a>{% endif %}
|
||
</nav>
|
||
{% endif %}
|
||
{% if not editing|default(false) and consentGate.needsBanner(app.request) %}
|
||
{% include 'consent-banner.html.twig' with {consent: consentGate.state(app.request), back: app.request.pathInfo} only %}
|
||
{% endif %}
|
||
</body>
|
||
</html>
|