Criar temas
Tudo o que escreve no FolioPress — páginas, artigos, etiquetas — vive separado da forma como é mostrado. Essa camada de apresentação é um tema, e este guia desmonta um: a pasta, os templates, o ficheiro de definições e exatamente que dados cada template recebe. Se está à vontade com HTML e CSS, tem tudo o que precisa.
Um tema é uma pasta
Troque o tema e as mesmas páginas, artigos e menu são redesenhados — nada no conteúdo muda, só a roupa. Um tema é uma pasta com três coisas: um ficheiro de definições, templates e recursos estáticos.
Quatro templates são obrigatórios — base.html, home.html, page.html e post.html — e um tema sem eles não carrega. Tudo o resto é opcional: um arquivo de etiquetas, uma página de pesquisa, páginas de erro personalizadas, um layout próprio para uma página específica e uma pasta locales/ para as palavras do próprio tema. Os temas podem ser editados diretamente na administração, em Temas, exportados como zip e importados noutro site.
De um URL a uma página
Ajuda ver o que o FolioPress faz com um pedido antes de o seu template sequer correr, porque explica por que razão os templates são tão curtos.
Duas coisas a reter. O idioma é lido do prefixo do URL, pelo que /pt-pt/sobre e /about são a mesma página em dois idiomas, e o contexto entregue ao template já vem no idioma certo — cada título, slug, rótulo de menu e conteúdo. E o template é escolhido por si: para uma página cujo slug no idioma principal é about, o FolioPress procura primeiro page-about.html e recorre a page.html se não existir. Não há nada para ligar.
Uma moldura, muitas páginas
Os templates são Jinja2 — HTML com dois tipos extra de parênteses. {{ page.title }} imprime um valor. {% if page.poster_image_url %} … {% endif %} é uma instrução: ciclos, condições e herança.
O base.html contém tudo o que as páginas partilham — o <head>, a navegação, o seletor de idioma, o rodapé, os scripts — com um único espaço vazio no meio. Todos os outros templates começam da mesma forma e preenchem esse espaço:
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock %}
Tudo o que se repete entre templates — um cartão de artigo, uma barra de paginação — vai para partials/ e é incluído com {% include "partials/post-card.html" %}.
theme.yml: o painel de definições
É aqui que um tema se descreve e configura alguns comportamentos, sem tocar num template. Isto é o que os temas incluídos usam:
title: "Ink"
description: "Um tema limpo e minimalista"
version: "1.0.0"
author: "FolioPress"
meta:
supports_dark_mode: true
responsive: true
categories: [minimal, portfolio]
settings: # livre; disponível nos templates como theme_settings
primary_color: "#2563eb"
home:
posts: 10 # quantos artigos a página inicial recebe
order_by: "-published_at" # mais recentes primeiro
has_pagination: false # um corte, ou ligações de página reais?
image_ratios:
poster: "16:9" # o que o recorte de imagens da administração oferece aqui
gallery: "free"
page: { posts: 10, order_by: "-published_at", has_pagination: false }
post: { posts: 3, order_by: "-published_at" } # artigos relacionados sob um artigo
tag: { posts: 12, order_by: "-published_at", has_pagination: true }
page-about: # substituição para uma página, pelo slug no idioma principal
image_ratios: { poster: "1:1" }
post-tutorials: # substituição para artigos cuja primeira etiqueta tem este slug
image_ratios: { poster: "4:3" }
| Chave | O que controla |
|---|---|
title, description, version, author |
Mostrados na página Temas da administração. |
meta.supports_dark_mode, meta.responsive, meta.categories |
Descritivos; usados na galeria de temas. |
meta.social_image |
Um ficheiro em static/ do tema, com 1200x630, usado como imagem de pré-visualização quando uma página não tem imagem de destaque própria. Sem ele, essa página é partilhada com o ícone de 180x180. |
settings |
O que quiser. Entregue a todos os templates como theme_settings, para que uma cor ou um slogan possam viver aqui em vez de no CSS. |
home, page, post, tag |
A lista de artigos que esse tipo de template recebe: posts (quantos), order_by, e se has_pagination acrescenta ligações de página reais ou apenas corta a lista. |
order_by |
-published_at (mais recentes primeiro) ou published_at; o mesmo para created_at e updated_at, com ou sem o - inicial; ou random. |
image_ratios |
O que o recorte de imagens da administração oferece ao editar esse tipo de conteúdo: uma proporção como 16:9, uma lista delas, ou free. Valores separados para poster e gallery. |
page-<slug>, post-<tag-slug> |
As mesmas chaves, substituindo page/post para uma página (pelo slug no idioma principal) ou para os artigos cuja primeira etiqueta tem esse slug. |
O que cada template consegue ver
Um template nunca vai buscar nada. Recebe um conjunto de valores prontos a usar — o FolioPress chama-lhe o contexto — e imprime o que precisa. Parte do conjunto é igual em todas as páginas; o resto depende do template que está a ser renderizado.
Comum a todos os templates
| Variável | O que é |
|---|---|
page |
A página ou artigo em exibição, já no idioma atual (ver abaixo). None no arquivo de etiquetas e na pesquisa. |
site_title, site_description, footer |
Das Definições do site, no idioma atual. O footer já vem renderizado a partir de Markdown. |
navigation_menu |
O menu configurado na administração: uma lista de {title, url, slug, page_id}, cada url pronto a usar neste idioma. |
languages |
Os idiomas do site, indexados por código. Cada entrada: code, name, native_name, short_code, menu_label, flag. |
current_language, current_language_code, default_language_code |
O idioma a ser renderizado (com a mesma forma de uma entrada de languages), o seu código e o idioma principal do site. |
language_switch_urls |
Indexado por código de idioma: o URL desta mesma página nesse idioma, slug traduzido incluído. |
home_url, url_language_code |
A página inicial no idioma atual (/ ou /pt-pt) e o prefixo em minúsculas a pôr à frente de qualquer URL que construa por si ('' para o idioma principal). |
static_url, media_url |
De onde são servidos os ficheiros deste tema (/static/<tema>) e onde vivem as imagens carregadas. |
page_title, meta_description, canonical_url, hreflang_urls, social_image_url, twitter_card_type, json_ld |
Tudo o que o <head> precisa: um título de separador sensato, URLs canónico e alternativos por idioma absolutos, uma imagem Open Graph e dados estruturados — tudo pré-calculado. |
theme_name, theme_info, theme_settings |
O nome do tema ativo, o seu theme.yml completo e o bloco settings. |
t |
O tradutor do próprio tema: {{ t('Read more') }} procura a frase em locales/<idioma>.json (ver abaixo). |
request |
O pedido FastAPI, para o raro caso em que precisa do caminho ou de um parâmetro. |
O objeto page
Tem a mesma forma quer seja uma página, um artigo ou uma entrada em qualquer lista de artigos:
| Campo | Notas |
|---|---|
title, slug, url |
No idioma atual. O url é completo, incluindo o prefixo de idioma ou o domínio próprio do idioma. |
content |
Já convertido de Markdown para HTML — imprima-o com {{ page.content | safe }}. |
excerpt, meta_description |
Formas curtas, no idioma atual. |
poster_image_url |
URL completo da imagem de capa, ou None. |
gallery, content_gallery |
Listas de imagens: {id, url, title, width, height, original_filename}. Use image.url diretamente. |
tags |
{slug, title, url} para cada etiqueta pública. As etiquetas privadas nunca são incluídas. |
is_post, is_featured, published_at, created_at, updated_at |
Se é um artigo, se está em destaque, e as suas datas. |
id |
O id na base de dados — útil para âncoras e chaves de cache. |
Listas de artigos
| Template | Variável | Conteúdo |
|---|---|---|
home.html |
posts (alias recent_posts), pages, is_home |
Artigos que partilham uma etiqueta com a página inicial, na quantidade e ordem da secção home do theme.yml. Uma página inicial sem etiquetas não recebe artigos — etiquete-a para a preencher. |
page.html |
posts (alias page_posts) |
Artigos que partilham uma etiqueta com esta página, da secção page. É assim que uma página “Blogue” lista os seus artigos. |
post.html |
related_posts, is_post |
Outros artigos que partilham uma etiqueta com este, da secção post. |
tag.html |
tag (title, slug), posts |
Todos os artigos publicados com esta etiqueta, da secção tag. |
search.html |
search_query, search_results, results_count |
O termo pesquisado e as páginas e artigos correspondentes. |
Cada lista de artigos vem com paginação na mesma forma: has_pagination, current_page, total_pages, has_previous, has_next, previous_page, next_page e pagination_urls com first, previous, next e last — None onde não há para onde ir. Com `has_pagination` desligado no `theme.yml`, a lista é simplesmente cortada em `posts` e os indicadores ficam a falso.
Idiomas e o seletor
Dois dos campos de idioma são calculados por si. short_code é só o código do idioma — EN — a menos que o site tenha também outra variante do mesmo idioma ativa, caso em que mantém a região: EN-US e EN-GB. menu_label segue a mesma regra com nomes: English, ou English (US) e English (UK). É exatamente a regra que o seletor de idioma da própria administração usa, pelo que o site e a administração nunca discordam de um rótulo. Um seletor completo é, por isso, curto:
{% 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 %}
O language_switch_urls já aponta para a mesma página em cada idioma — o slug traduzido, ou a página inicial se essa tradução ainda não existir —, pelo que um seletor nunca constrói um URL por si.
As palavras do próprio tema
O conteúdo é traduzido pelo autor na administração. As palavras do próprio tema — “Ler mais”, “Pesquisar”, “Todos os direitos reservados” — são traduzidas pelo tema, em templates/locales/<idioma>.json:
{
"Read more": "Ler mais",
"Search": "Pesquisar",
"Change language": "Mudar idioma"
}
Envolva a frase em {{ t('Read more') }} e ela aparece no idioma do visitante. Uma frase em falta no ficheiro desse idioma recorre ao ficheiro do idioma principal, e depois à própria frase — pelo que um tema meio traduzido continua a funcionar, apenas em inglês onde ainda não há tradução. Os ficheiros viajam com o tema quando é exportado.
Uma página com o seu próprio layout
Uma página de apresentação, um formulário de contacto, uma grelha de portefólio — às vezes uma página merece um layout de que nenhuma outra precisa. Adicione um template chamado page-<slug>.html, em que <slug> é o slug dessa página no idioma principal, e o FolioPress usa-o para essa página em todos os idiomas. Apague o ficheiro e a página volta discretamente ao 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 %}
A página que um administrador escolhe como página do formulário de contacto recebe um pequeno extra: is_contact_page é verdadeiro, contact_form_action é para onde o formulário deve submeter, e contact_guard_token / contact_honeypot_field são dois campos ocultos que mantêm os robôs de fora. Depois de uma submissão, contact_status diz-lhe se deve mostrar um agradecimento ou um erro.
Comece por um tema que funciona
A forma mais rápida de construir um tema é abrir um dos incluídos — todos têm uma demonstração ao vivo — e lê-lo ao lado desta página. Duplique-o na administração, mude o CSS, depois os templates, e terá algo seu sem nunca partir de um ficheiro em branco.
Dois hábitos que poupam tempo. Imprima os valores que o contexto lhe dá em vez de os reconstruir — page.url, tag.url e language_switch_urls já sabem dos prefixos de idioma e dos domínios personalizados. E mantenha a moldura no base.html: se der por si a colar um cabeçalho num segundo template, ele pertence à base.