Контракт данных · часть 4
theme.yaml
Четвёртая часть контракта данных: theme.yaml, паспорт темы. Что тема умеет рисовать, чего ждёт от сайта и как связывает страницы между собой.
Страница нужна дизайнеру и разработчику темы. Человеку, который только пишет тексты, её читать не обязательно.
Простыми словами
Если контент — это текст книги, то тема — её вёрстка: шрифты, цвета, где стоят картинки. theme.yaml — паспорт темы. В нём перечислено, какие виды страниц тема умеет рисовать (главная, страница услуги, статья блога) и каких секций ждёт каждый вид.
Благодаря этому разделению дизайн можно сменить, не переписав ни одной страницы: новая тема читает те же файлы и рисует их по-своему.
Пример. Тема, которая знает два вида страниц:
name: my-theme
types:
home:
uses: [intro, "services:checklist", "faq:faq"]
service:
uses: [intro, "what:list", "prices:table", "faq:faq"]
Страница типа service с секциями what, prices и faq получает ровно те блоки, которые дизайнер для них сделал. О секции, которую тема не знает, сборка сообщит в отчёте, поэтому опечатка в якоре не пропадёт молча.
Когда нужна эта страница. Когда вы делаете или меняете тему. Если вы только пишете тексты, её можно пропустить.
Паспорт темы
theme.yaml служит документацией и основой отчёта heron check, а не жёстким валидатором.
name: main
version: "1.0"
title: Тема сайта
# что тема ждёт от site.yaml. HERON проверит наличие, не вникая в смысл
requires:
- contact.phone
- organization.name
# формы, которые тема рисует. Их описание должно быть в site.yaml
forms: [callback]
modules:
- breadcrumbs
- prose
- facts
- list
- alert-list
- steps
- timeline
- faq
- cards
- quote
- table
- gallery
- author
types:
home:
uses: [intro, about, services, faq]
jsonld: [WebSite]
category:
uses: [intro, outro]
jsonld: [CollectionPage]
service:
uses: [intro, quick-facts, what, who, how, faq]
jsonld: [Service]
article:
uses: [intro]
jsonld: [Article]
page:
uses: [intro]
# связи между страницами. HERON найдёт, проверит и построит обратную ссылку,
# не зная, что означает само отношение
links:
- field: related
type: service
back: mentioned_in
- field: topic # поле во фронтматтере страницы-источника
type: topic # на страницы какого типа ссылается (необязательно)
on: service # у страниц какого типа поле должно быть
required: 1 # сколько ссылок минимум
# пропорции и ширины, в которые HERON впишет мастера
images:
widths: [400, 800, 1200, 1600]
ratios: ["1:1", "16:9", "4:3"] # что шаблоны могут попросить; режется только попрошенное
formats: [avif, webp]
fallback_width: 800 # запасной PNG/JPEG для обычной картинки — одна ширина
Тип страницы — это шаблон
Тип страницы реализуется файлом templates/ТИП.html — обычным шаблоном Jinja2, где порядок блоков виден сверху вниз:
{% extends "base.html" %}
{% block content %}
{{ mod.breadcrumbs() }}
{{ mod.hero() }}
{{ mod.facts('quick-facts') }}
{{ mod.prose('what') }}
{{ mod.prose('who') }}
{{ mod.list('contra') }}
{{ mod.steps('how') }}
{{ mod.timeline('recovery') }}
{{ mod.prose('price') }}
{{ mod.faq('faq') }}
{% for p in page.related.get('related', []) %}
{{ p.h1 }} → {{ p.url }} {# ссылка на связанную страницу #}
{% endfor %}
{{ mod.call() }}
{{ mod.author() }}
{% endblock %}
mod.prose('what') берёт секцию what; если её нет — не выводит ничего, ни заголовка, ни пустой рамки. Обвязка условиями вокруг каждого модуля не нужна.
Как секции получают форму и что приходит в модуль — в разделе формы секций, а как это выглядит на сайте — в справочнике форм.
Связи — не секции
mod.МОДУЛЬ('id') ищет секцию с таким якорем в тексте страницы. Связанные страницы в тексте не лежат: HERON собирает их из полей связей в page.related.ПОЛЕ (и обратные — в page.related.BACK). Тема перебирает этот список сама, как в примере выше, или вызывает свой модуль без аргумента, который читает page.related. Вызов mod.cards('related') ничего не выведет — секции related на странице нет.
Обратные связи
back: ИМЯ — HERON строит обратный список: если a.md ссылается на b полем related, у b появляется page.related.ИМЯ со страницей a. Тема рисует его так же, как прямой.
- связь внутри одного типа разрешена: услуга ссылается на услуги (
field: related, type: service, back: related_from), обратный список строится как обычно; - ссылка страницы на саму себя пропускается с предупреждением в группе «связи». Ни в прямой, ни в обратный список она не попадает;
- одно имя обратной связи у нескольких объявлений (например,
topicиз услуг и из статей с однимback: mentioned_in) даёт один смешанный список: страницы всех типов вместе, без дублей; - порядок и прямого, и обратного списка — по
order, затем поh1без учёта регистра. Порядок, в котором ссылки записаны во фронтматтере, не сохраняется: его задаётorderцелевых страниц; - отфильтровать по типу тема может сама — у элемента списка есть
type, как у любой страницы:{% for p in page.related.mentioned_in if p.type == 'article' %}; onна построение связей не влияет: связь строится у любой страницы, где поле заполнено.onговорит только, у страниц какого типа пустое поле — повод для предупреждения (ниже).
Обязательные связи: required
required: N — тема ждёт у страницы минимум N ссылок в этом поле. Это предупреждение, не ошибка: недописанная перелинковка не повод не выкладывать сайт, но видеть её надо. Оно попадает в группу «связи» отчёта сборки и heron check:
- поле заполнено, но ссылок меньше
N— предупреждение на любой странице, где поле есть: «related: ссылок 0, тема ждёт минимум 1»; - поле пустое или его нет — предупреждение только у страниц типа
on: «поле 'topic' не заполнено, а тема ждёт минимум 1». БезonHERON не знает, каким страницам поле положено, и про пустое молчит; - ссылка на страницу, которой нет, — по-прежнему ошибка
E006, независимо отrequired.
Ключ on пишется без кавычек: YAML читает голое on как «истину», и HERON возвращает ему имя сам.
Связи с подписями
Поле связи принимает две формы — список имён и список карт, в одном списке их можно смешивать:
services:
- slug: express-review
note: Основной способ, когда сроки горят
- full-review
Страница называется в связях именем своего файла без расширения, а не адресом. Ключ карты исторически называется slug, но значение в нём — то же имя файла.
Адрес для этого не годится: он задаётся полем slug, у языков разный, и связь по адресу означала бы, что английская страница и её русский перевод связаны с разными вещами, хотя связь описывает предмет, а не адрес. Переименование адреса на связи не влияет никак.
Имя ищется в пределах своего языка. Два файла с одинаковым именем в разных папках одного языка — ошибка E006 про неоднозначность: HERON не угадывает, какой из них имелся в виду.
Подпись не задана — HERON подставляет promise целевой страницы, нет и его — description. Имя, которому не нашлось страницы, — ошибка сборки, как и раньше.
Источник
Эта страница повторяет раздел 6 файла docs/spec/20-data-contract.md из ветки prod. Репозиторий обновляется первым: открыть файл на GitHub.