Thema’s maken
Alles wat je in FolioPress schrijft — pagina’s, berichten, tags — staat los van hoe het wordt getoond. Die presentatielaag is een thema, en deze gids haalt er een uit elkaar: de map, de sjablonen, het instellingenbestand en precies welke gegevens elk sjabloon krijgt. Wie vertrouwd is met HTML en CSS heeft alles wat nodig is.
Een thema is een map
Wissel van thema en dezelfde pagina’s, berichten en hetzelfde menu worden opnieuw getekend — aan de inhoud verandert niets, alleen aan de kleding. Een thema is een map met drie dingen erin: een instellingenbestand, sjablonen en statische bestanden.
Vier sjablonen zijn verplicht — base.html, home.html, page.html en post.html —, een thema zonder deze laadt niet. Al het andere is optioneel: een tagarchief, een zoekpagina, eigen foutpagina’s, een eigen lay-out voor één specifieke pagina en een map locales/ voor de eigen teksten van het thema. Thema’s bewerk je rechtstreeks in het beheer onder Thema’s, exporteer je als zip en importeer je in een andere website.
Van een URL naar een pagina
Het helpt om te zien wat FolioPress met een verzoek doet voordat je sjabloon überhaupt draait, want het verklaart waarom sjablonen zo kort zijn.
Twee dingen vallen op. De taal wordt uit het URL-voorvoegsel gelezen, dus /nl-nl/over-ons en /about zijn dezelfde pagina in twee talen, en de context die je sjabloon krijgt, is al in de juiste taal — elke titel, slug, menu-item en inhoud. En het sjabloon wordt voor je gekozen: voor een pagina waarvan de slug in de hoofdtaal about is, zoekt FolioPress eerst page-about.html en valt terug op page.html. Er hoeft niets aangesloten te worden.
Eén casco, veel pagina’s
Sjablonen zijn Jinja2 — HTML met twee extra soorten accolades. {{ page.title }} drukt een waarde af. {% if page.poster_image_url %} … {% endif %} is een instructie: lussen, voorwaarden en overerving.
base.html bevat alles wat elke pagina deelt — de <head>, de navigatie, de taalkiezer, de voettekst, de scripts — met één gat in het midden. Elk ander sjabloon begint hetzelfde en vult dat gat:
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock %}
Alles wat zich over sjablonen heen herhaalt — een berichtkaart, een paginering — hoort in partials/ en wordt ingevoegd met {% include "partials/post-card.html" %}.
theme.yml: het instellingenpaneel
Hier beschrijft een thema zichzelf en stelt het een paar gedragingen in, zonder een sjabloon aan te raken. Dit is wat de meegeleverde thema’s gebruiken:
title: "Ink"
description: "Een schoon, minimalistisch thema"
version: "1.0.0"
author: "FolioPress"
meta:
supports_dark_mode: true
responsive: true
categories: [minimal, portfolio]
settings: # vrij in te vullen; in sjablonen beschikbaar als theme_settings
primary_color: "#2563eb"
home:
posts: 10 # hoeveel berichten de startpagina krijgt
order_by: "-published_at" # nieuwste eerst
has_pagination: false # afkappen, of echte paginalinks?
image_ratios:
poster: "16:9" # wat de bijsnijder in het beheer hier aanbiedt
gallery: "free"
page: { posts: 10, order_by: "-published_at", has_pagination: false }
post: { posts: 3, order_by: "-published_at" } # gerelateerde berichten onder een bericht
tag: { posts: 12, order_by: "-published_at", has_pagination: true }
page-about: # overschrijving voor één pagina, via haar slug in de hoofdtaal
image_ratios: { poster: "1:1" }
post-tutorials: # overschrijving voor berichten waarvan de eerste tag deze slug heeft
image_ratios: { poster: "4:3" }
| Sleutel | Wat hij regelt |
|---|---|
title, description, version, author |
Getoond op de pagina Thema’s in het beheer. |
meta.supports_dark_mode, meta.responsive, meta.categories |
Beschrijvend; gebruikt voor de themagalerij. |
meta.social_image |
Een bestand in de static/ van het thema, 1200x630, gebruikt als voorbeeldafbeelding wanneer een pagina geen eigen omslagafbeelding heeft. Zonder dit wordt zo'n pagina gedeeld met het 180x180-icoon. |
settings |
Wat je maar wilt. Wordt aan elk sjabloon doorgegeven als theme_settings, zodat een kleur of een slogan hier kan wonen in plaats van in de CSS. |
home, page, post, tag |
De berichtenlijst die dat sjabloontype krijgt: posts (hoeveel), order_by, en of has_pagination echte paginalinks toevoegt of de lijst gewoon afkapt. |
order_by |
-published_at (nieuwste eerst) of published_at; hetzelfde voor created_at en updated_at, met of zonder de - vooraan; of random. |
image_ratios |
Wat de bijsnijder in het beheer aanbiedt bij het bewerken van dat inhoudstype: een verhouding zoals 16:9, een lijst daarvan, of free. Aparte waarden voor poster en gallery. |
page-<slug>, post-<tag-slug> |
Dezelfde sleutels, die page/post overschrijven voor één pagina (via haar slug in de hoofdtaal) of voor berichten waarvan de eerste tag die slug heeft. |
Wat elk sjabloon kan zien
Een sjabloon haalt nooit zelf iets op. Het krijgt een bundel kant-en-klare waarden — FolioPress noemt dat de context — en drukt af wat het nodig heeft. Een deel van de bundel is op elke pagina hetzelfde; de rest hangt af van welk sjabloon wordt gerenderd.
Gemeenschappelijk voor alle sjablonen
| Variabele | Wat het is |
|---|---|
page |
De getoonde pagina of het bericht, al in de huidige taal (zie hieronder). None in het tagarchief en bij zoeken. |
site_title, site_description, footer |
Uit de Website-instellingen, in de huidige taal. footer is al vanuit Markdown gerenderd. |
navigation_menu |
Het menu zoals ingesteld in het beheer: een lijst van {title, url, slug, page_id}, elke url klaar voor deze taal. |
languages |
De talen van de website, op code. Elk item: code, name, native_name, short_code, menu_label, flag. |
current_language, current_language_code, default_language_code |
De taal die wordt gerenderd (zelfde vorm als een languages-item), de code ervan en de hoofdtaal van de website. |
language_switch_urls |
Op taalcode: de URL van deze zelfde pagina in die taal, vertaalde slug inbegrepen. |
home_url, url_language_code |
De startpagina in de huidige taal (/ of /pt-pt) en het kleingeschreven voorvoegsel voor elke URL die je zelf bouwt ('' voor de hoofdtaal). |
static_url, media_url |
Waar de bestanden van dit thema vandaan worden geleverd (/static/<thema>) en waar geüploade afbeeldingen staan. |
page_title, meta_description, canonical_url, hreflang_urls, social_image_url, twitter_card_type, json_ld |
Alles wat de <head> nodig heeft: een zinnige tabbladtitel, absolute canonieke en taalalternatieve URL’s, een Open Graph-afbeelding en gestructureerde gegevens — alles vooraf berekend. |
theme_name, theme_info, theme_settings |
De naam van het actieve thema, zijn volledige theme.yml en het settings-blok daarvan. |
t |
De vertaler van het thema zelf: {{ t('Read more') }} zoekt de zin op in locales/<taal>.json (zie hieronder). |
request |
Het FastAPI-verzoek, voor het zeldzame geval dat je het pad of een queryparameter nodig hebt. |
Het page-object
Dezelfde vorm, of het nu een pagina, een bericht of een item in een berichtenlijst is:
| Veld | Opmerkingen |
|---|---|
title, slug, url |
In de huidige taal. url is volledig, inclusief taalvoorvoegsel of het eigen domein van de taal. |
content |
Al omgezet van Markdown naar HTML — afdrukken met {{ page.content | safe }}. |
excerpt, meta_description |
De korte vormen, in de huidige taal. |
poster_image_url |
Volledige URL van de omslagafbeelding, of None. |
gallery, content_gallery |
Afbeeldingslijsten: {id, url, title, width, height, original_filename}. Gebruik image.url rechtstreeks. |
tags |
{slug, title, url} voor elke openbare tag. Privétags worden nooit opgenomen. |
is_post, is_featured, published_at, created_at, updated_at |
Of het een bericht is, of het uitgelicht is, en de datums. |
id |
Het database-id — handig voor ankers en cachesleutels. |
Berichtenlijsten
| Sjabloon | Variabele | Inhoud |
|---|---|---|
home.html |
posts (alias recent_posts), pages, is_home |
Berichten die een tag delen met de startpagina, in aantal en volgorde uit de sectie home van theme.yml. Een startpagina zonder tags krijgt geen berichten — geef haar een tag om haar te vullen. |
page.html |
posts (alias page_posts) |
Berichten die een tag delen met deze pagina, uit de sectie page. Zo toont een “Blog”-pagina haar berichten. |
post.html |
related_posts, is_post |
Andere berichten die een tag delen met dit bericht, uit de sectie post. |
tag.html |
tag (title, slug), posts |
Elk gepubliceerd bericht met deze tag, uit de sectie tag. |
search.html |
search_query, search_results, results_count |
De gezochte term en de overeenkomende pagina’s en berichten. |
Elke berichtenlijst komt met een paginering van dezelfde vorm: has_pagination, current_page, total_pages, has_previous, has_next, previous_page, next_page en pagination_urls met first, previous, next en last — None waar er niets meer is. Staat `has_pagination` in `theme.yml` uit, dan wordt de lijst gewoon afgekapt op `posts` en blijven de vlaggen onwaar.
Talen en de kiezer
Twee van de taalvelden worden voor je berekend. short_code is alleen de taalcode — EN — tenzij de website ook een andere variant van dezelfde taal heeft ingeschakeld; dan behoudt hij zijn regio: EN-US en EN-GB. menu_label volgt dezelfde regel met namen: English, of English (US) en English (UK). Dat is precies de regel van de taalkiezer in het beheer, zodat website en beheer het nooit oneens zijn over een label. Een volledige kiezer is daarom kort:
{% if languages | length > 1 %}
<button type="button" aria-label="{{ t('Change language') }}">
{{ current_language.short_code }}
</button>
<ul>
{% for lang in languages.values() | sort(attribute='menu_label') %}
<li>
{% if lang.code == current_language.code %}
<span aria-current="true">{{ lang.menu_label }}</span>
{% else %}
<a href="{{ language_switch_urls[lang.code] }}" hreflang="{{ lang.code }}">{{ lang.menu_label }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
language_switch_urls wijst al naar dezelfde pagina in elke taal — de vertaalde slug, of de startpagina als die vertaling nog niet bestaat —, dus een kiezer bouwt nooit zelf een URL.
De eigen teksten van het thema
Inhoud vertaalt de auteur in het beheer. De eigen teksten van een thema — “Lees verder”, “Zoeken”, “Alle rechten voorbehouden” — vertaalt het thema, in templates/locales/<taal>.json:
{
"Read more": "Lees verder",
"Search": "Zoeken",
"Change language": "Taal wijzigen"
}
Omsluit de zin met {{ t('Read more') }} en hij verschijnt in de taal van de bezoeker. Ontbreekt een zin in het bestand van die taal, dan valt hij terug op het bestand van de hoofdtaal, en daarna op de zin zelf — een half vertaald thema blijft dus werken, alleen in het Engels waar de vertaling nog niet is geschreven. De bestanden reizen mee met het thema bij het exporteren.
Een pagina met een eigen lay-out
Een landingspagina, een contactformulier, een portfoliorooster — soms verdient één pagina een lay-out die geen andere nodig heeft. Voeg een sjabloon toe met de naam page-<slug>.html, waarbij <slug> de slug van die pagina in de hoofdtaal is, en FolioPress gebruikt het voor die pagina in elke taal. Verwijder het bestand en de pagina keert stilletjes terug naar page.html.
{# templates/page-about.html #}
{% extends "base.html" %}
{% block content %}
<section class="about">
<img src="{{ page.poster_image_url }}" alt="">
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
</section>
{% endblock %}
De pagina die een beheerder als contactformulierpagina kiest, krijgt iets extra’s: is_contact_page is waar, contact_form_action is waar het formulier naartoe moet, en contact_guard_token / contact_honeypot_field zijn twee verborgen velden die bots buiten houden. Na een inzending zegt contact_status of je een bedankje of een fout moet tonen.
Begin met een thema dat werkt
De snelste manier om een thema te bouwen is een van de meegeleverde te openen — elk heeft een live demo — en het naast deze pagina te lezen. Dupliceer het in het beheer, pas de CSS aan, dan de sjablonen, en je hebt iets eigens zonder ooit met een leeg bestand te zijn begonnen.
Twee gewoontes die tijd besparen. Druk de waarden af die de context je geeft in plaats van ze na te bouwen — page.url, tag.url en language_switch_urls kennen taalvoorvoegsels en eigen domeinen al. En houd het casco in base.html: wie zichzelf betrapt op het plakken van een header in een tweede sjabloon, weet waar die thuishoort.