Referência de Templates
Um tema Chinaski é um diretório plano de templates Template Toolkit 2 (.tt). O processo de build renderiza seus templates em HTML estático para cada página e post, uma vez por idioma configurado. Esta página cobre os arquivos do tema, as variáveis que recebem, e os helpers e as regras que os acompanham.
Onde os temas vivem
- O tema padrão embutido vem com a aplicação e serve como fallback.
- Temas personalizados vivem em
builder/themes/<slug-do-seu-tema>/. Quando um tema com o slug do tema ativo existe lá, o builder o usa; caso contrário, faz fallback para os templates embutidos. - Qual tema está ativo é escolhido em Settings.
Arquivos de template obrigatórios e opcionais
| Arquivo | Propósito | Obrigatório? |
|---|---|---|
page.tt | Template padrão para cada página publicada | Sim |
post.tt | Template para cada post de blog publicado | Sim |
blog_index.tt | Listagem paginada do blog e páginas de categoria | Sim |
404.tt | Página 404 por idioma | Sim |
_head.tt | Partial de <head>: CSS, meta tags, importações de fonte | Convenção |
_header.tt | Header do site: logo, navegação, tagline | Convenção |
_footer.tt | Rodapé do site: copyright, navegação secundária | Convenção |
_cookie_notice.tt | Banner GDPR opcional | Não |
_sidebar.tt | Partial de barra lateral opcional | Não |
search.tt | Página de resultados de busca opcional | Não |
_password_gate.tt | UI de portão de senha no lado do cliente para páginas protegidas | Obrigatório se page.tt/post.tt suportam password_hash |
<custom>.tt | Qualquer página que o selecione via a configuração template | Não |
Arquivos que começam com _ são partials e nunca são renderizados como páginas independentes.
Variáveis de template
Os templates recebem um conjunto plano de variáveis: não há objetos page. ou site.. Use site_name, title, lang e assim por diante diretamente.
page.tt e 404.tt
| Variável | Tipo | Notas |
|---|---|---|
title | string | Título da página |
body | string HTML | Pré-renderizado: não escape com | html |
slug | string | Slug da URL |
lang / language | string | Código de 2 letras, por exemplo en |
meta_title | string? | Sobrescreve o <title> |
meta_description | string? | Descrição SEO |
og_title | string? | Título OpenGraph |
og_description | string? | Descrição OG |
og_image_url | string? | Imagem OG |
canonical_url | string | Gerada automaticamente |
featured_image_url | string? | Imagem hero |
featured_image_alt | string? | Texto alt |
updated_at | string | datetime do SQLite |
site_name | string | Das configurações |
site_tagline | string | Das configurações |
footer_copyright | string | Das configurações |
year | inteiro | Ano do build |
url_base | string | Prefixo de URL: pode ser vazio; acrescente-o como prefixo a TODOS os links internos |
cookie_notice_active | 0/1 | Se deve renderizar _cookie_notice.tt |
noindex | 0/1 | Emite <meta name="robots" content="noindex"> |
nav_items | array | {label, url, slot, sort_order}: slot é header ou footer |
hreflang_links | array | {hreflang, href} para traduções |
breadcrumbs | array | {name, url, is_current} |
json_ld | string HTML | JSON-LD pré-serializado: emita cru |
ga4_id | string? | ID do Google Analytics |
gtm_id | string? | ID de contêiner do GTM |
twitter_site | string? | Handle do Twitter com @ |
password_hash | string? | Se definido, você DEVE envolver o conteúdo em _password_gate.tt (veja abaixo) |
post.tt: tudo de page.tt, mais
| Variável | Tipo | Notas |
|---|---|---|
excerpt | string | Excerto fornecido pelo autor; pode estar vazio |
category | string? | Nome de categoria única |
tags | string | Separado por vírgulas; divida com tags.split(',') |
author | string | Nome de exibição |
is_sticky | 0/1 | Fixado no topo do índice do blog |
published_at | string? | datetime do SQLite; momento da primeira publicação |
date | string | Fatia YYYY-MM-DD de published_at |
feed_url | string | URL absoluta do feed RSS |
og_type | string | Sempre 'article' |
blog_index.tt
| Variável | Tipo | Notas |
|---|---|---|
posts | array | Hashes de post ordenados com as chaves p.title, p.slug, p.date, p.body, p.category |
pagination | hash? | {current_page, total_pages, prev_url, next_url, abs_prev_url, abs_next_url}: undef quando há apenas uma página |
title | string | “Blog” localizado ou nome da categoria |
category_name | string? | Definido ao filtrar por categoria |
search.tt
| Variável | Tipo | Notas |
|---|---|---|
query | string | Termo de busca |
results | array | {title, url, excerpt} |
result_count | inteiro | Total de correspondências |
Blocos
Autores colocam blocos escrevendo {{block:my-cta}} em um corpo de página ou post. Em tempo de build, o builder encontra o shortcode, busca o bloco publicado com aquele nome e idioma, o renderiza com seu template, e substitui o HTML renderizado no corpo.
Os templates de bloco são compartilhados entre temas e vivem fora do diretório do seu tema; seu tema é responsável pelo CSS que estiliza sua saída.
Classes CSS que seu tema deve estilizar
Cada tipo de bloco emite uma classe wrapper previsível e classes internas. Seu _head.tt deve estilizar estas:
| Tipo | Wrapper | Classes internas |
|---|---|---|
cta | .cta-block | .cta-inner .cta-heading .cta-subheading .cta-button |
image | .image-block | .image-caption |
quote | .quote-block | .quote-text .quote-attribution |
hero | .hero-block | .hero-media .hero-inner .hero-heading .hero-subheading .hero-button |
testimonial | .testimonial-block | .testimonial-label .testimonial-body .testimonial-avatar .testimonial-text .testimonial-attribution |
stat | .stat-block | .stat-number .stat-label |
promo | .promo-block | .promo-media .promo-content .promo-heading .promo-body .promo-button |
embed | .embed-block | .embed-caption .embed-content |
video | .video-block | .video-heading .video-wrapper .video-caption |
form | .form-block | .form-heading .form-subheading .block-form .block-form__field |
newsletter | .newsletter-block | .newsletter-inner .newsletter-heading .newsletter-subheading .newsletter-form .newsletter-form__row |
accordion | .accordion-block | .accordion-heading .accordion .accordion__item .accordion__summary .accordion__body |
table | .table-block | .table-heading .table-wrapper .block-table |
columns | .columns-block | .columns-heading .columns .columns--2 .columns--3 .columns--4 .column .column__image .column__heading .column__body |
gallery | .gallery-block | .gallery-heading .gallery .gallery__item |
Estilo mínimo requerido para as classes utilitárias:
.video-wrapper:position: relative; padding-bottom: 56.25%;com o iframeposition: absolute; inset: 0; width: 100%; height: 100%;(vídeo responsivo 16:9).columns--2/.columns--3/.columns--4:display: gridcomgrid-template-columnsapropriadas.table-wrapper:overflow-x: autopara que tabelas rolem no mobile.gallery: grade responsiva com gap.accordion__item/.accordion__summary: reset básico dedetails/summary
Páginas multilíngues
- Cada página é renderizada uma vez por idioma configurado.
- Links internos devem incluir o prefixo de idioma: use
[% url_base %]/[% lang %]/contact/ou similar. A única exceção énav_items[].url, que já contém a URL completa correta: use-as como estão. - O elemento
<html>deve carregar o idioma:<html lang="[% lang | html %]">.
Regras de segurança
As seguintes diretivas são proibidas em conteúdo de tema e bloco e são rejeitadas:
[% PERL %]/[% RAWPERL %][% INSERT '/caminho/absoluto' %][% USE: .. %]de módulos como File/IO/Socket/Net/LWP/WWW/HTTP/System/Cmd/Process/Pipe/Expect
Páginas protegidas por senha
A proteção por senha é um portão voltado ao leitor: o conteúdo da página ainda está presente no HTML publicado, e _password_gate.tt o esconde atrás de uma checagem em JavaScript até o visitante digitar a senha correta. page.tt e post.tt devem implementá-lo assim:
[% IF password_hash %]
[% WRAPPER '_password_gate.tt' %][% PROCESS page_content %][% END %]
[% ELSE %]
[% PROCESS page_content %]
[% END %] (Envolva o bloco que contém seu conteúdo principal; o nome exato do bloco não importa, apenas que esteja definido e processado dentro do wrapper.) Se o seu template omite o ramo password_hash inteiramente, um administrador ainda pode definir uma senha em uma página usando seu tema, e a página será renderizada publicamente sem nenhum aviso de que o portão está ausente.
Resumo rápido
[% variable %]: saída crua[% variable | html %]: escapada para HTML (use para TODO texto controlado por usuário)[% variable | uri %]: codificação URI (para segmentos de caminho de URL)[% IF / ELSE / END %],[% UNLESS: .. %]: condicionais[% FOREACH item IN items %]: loops;loop.index/loop.countdisponíveis[% INCLUDE '_partial.tt' %]: inclui um partial, compartilhando as variáveis atuais; adicionex = 42para passar ou sombrear valores[% PROCESS '_macros.tt' %]: como INCLUDE mas sem introduzir um novo escopo[%-: .. -%]: apara os espaços em branco ao redor
Principais pegadinhas
- Stash plano: sem objetos aninhados.
site.title,page.title,post.titletodos estão errados; usesite_nameetitle. - Campos de item de navegação são
label,url,slot, e nãoname,href,active. - Datas: nunca chame formatação de data em
published_at; use a variável pré-computadadate, ou tome os primeiros 10 caracteres depublished_at. - O body é HTML pré-renderizado: emita
[% body %]sem escapar. - Partials têm o prefixo
_. - Todos os links internos precisam de
url_baseelang.