Budowanie Motywu
Budowanie pełnego motywu dla notACMS — warstwy szablonów, API Twig, kontrakty kontekstu i zasady przenośności.
Hierarchia Warstw Szablonów
notACMS rozwiązuje szablony z trzech warstw, stosowanych według priorytetu:
local/templates/— nadpisania specyficzne dla strony (najwyższy priorytet)- Szablony motywu — rejestrowane przez
twig.pathswlocal/config/packages/twig.yaml - Szablony bare core w
templates/
Każdy plik w local/templates/ nadpisuje odpowiednik w zarejestrowanym motywie lub core. Motyw bare dostarcza minimalny, kompletny zestaw szablonów renderujący wszystkie typy treści bez zewnętrznych zależności. Motyw demo (ta strona) dodaje stylowanie i komponenty na wierzch.
Kontrakty Szablon ↔ Kontekst
Każdy szablon otrzymuje globalne Twig plus kontekst specyficzny dla strony. locale jest zawsze obecny.
Szablony Stron
| Route | Szablon | Kluczowy kontekst |
|---|---|---|
home_{locale} |
page/home.html.twig |
content (?ContentItem), recentPosts (ContentItem[]), card_layouts |
static_page_{locale} |
frontmatter template: |
content (ContentItem) |
contact_{locale} |
page/contact.html.twig |
content, form, contact_form_available (bool) |
projects_{locale} |
page/projects.html.twig |
content, featured_projects, total_projects |
search_{locale} |
search/index.html.twig |
tylko locale |
| strony błędów | page/error.html.twig |
status_code, status_text, locale, content (null) |
Szablony Bloga
| Route | Szablon | Kluczowy kontekst |
|---|---|---|
| lista bloga + paginacja | blog/list.html.twig |
posts, current_page, total_pages, total_posts, filter_type, filter_value, index_content, card_layouts |
| pojedynczy wpis | blog/post.html.twig |
content, related_posts, prev_post, next_post, series_posts, series_current_part, card_layouts |
| zaplanowany wpis | page/coming-soon.html.twig |
post, card_layouts |
Wartości filter_type: 'category', 'tag', 'archive'. card_layouts rotuje przez ['layout-top', 'layout-right', 'layout-text', 'layout-left'].
Szablony Feedów i E-mail
| Szablon | Kontekst |
|---|---|
feed/rss.xml.twig |
posts (ContentItem[]), locale |
feed/sitemap.xml.twig |
items_by_locale (array<locale, ContentItem[]>) |
feed/llms.txt.twig |
posts_by_locale, pages |
feed/robots.txt.twig |
tylko globalne |
email/contact.html.twig |
contact (tablica: name, email, website, subject, message), locale |
Globalne Twig
Dostępne w każdym szablonie:
| Zmienna globalna | Typ | Źródło |
|---|---|---|
site_name, site_base_url, site_description |
string |
_site.yaml |
site_social |
array |
_site.yaml social: (format zdefiniowany przez stronę) |
site_author |
array |
_site.yaml author: — zawsze zabezpiecz przez |default() |
site_locales |
array<locale, array> |
dla każdego locale: label, og_locale, date_format, … |
site_locales_list |
string[] |
kody locale, pierwszy = domyślny |
site_default_locale |
string |
pierwszy klucz locales: |
image_variant_widths |
int[] |
_site.yaml (domyślnie [640, 960]) |
new_post_days, coming_soon_reveal_days, meta_description_length |
int |
_site.yaml |
translation_map |
array<directoryKey, array<locale, url>> |
tylko opublikowane tłumaczenia; klucze to pełne relatywne ścieżki treści |
cf_analytics_token, turnstile_site_key, notacms_project_url |
string |
zmienne środowiskowe twig.yaml |
Funkcje i Filtry Twig
| Funkcja / filtr | Sygnatura | Zwraca |
|---|---|---|
content_item(key, locale) |
(string, string): ?ContentItem |
Element po kluczu katalogu — nullable, zawsze zabezpiecz |
content_url(key, locale) |
(string, string): string |
URL elementu; '/' gdy nie znaleziono |
breadcrumbs(content, locale, options) |
(?ContentItem, string, array): Breadcrumb[] |
.label i .url (null = bieżąca strona) |
lang_switch_urls(other_locales) |
(string[]): array<locale, url> |
URL-e przełącznika językowego |
og_image_url(content, site_base_url) |
(?ContentItem, string): string |
URL wyróżnionego obrazu lub domyślny obraz OG |
post_badge(post, new_post_days) |
(ContentItem, int): ?string |
'new', 'updated' lub null |
sidebar_data(locale) |
(string): SidebarData |
.recentPosts, .categories, .tags, .archiveMonths |
structured_data() |
(): StructuredDataBuilderInterface |
Builder Schema.org — webSite(), blogPosting(), breadcrumbList(), itd. |
json_ld(data) |
(array): string |
Blok <script type="application/ld+json"> |
srcset_media (filtr) |
(string): string |
Wstrzykuje srcset/sizes do tagów <img> — standardowy pipeline treści: content.htmlContent|srcset_media|raw |
API ContentItem
Metody dostępne na każdym ContentItem w szablonach:
$item->title() // string
$item->description() // string
$item->slug() // string — ostatni segment frontmatter slug
$item->url() // string — pełna ścieżka wraz z prefiksem locale
$item->date() // ?DateTimeImmutable — zabezpiecz przed |date()
$item->updatedDate() // ?DateTimeImmutable
$item->tags() // string[]
$item->category() // ?string
$item->image() // ?string
$item->imageAlt() // string (domyślnie title)
$item->excerpt() // string
$item->readingTime() // int
$item->wordCount() // int
$item->menuLabel() // string
$item->menuWeight() // int
$item->series() // ?string
$item->seriesOrder() // ?int
$item->directoryKey() // ?string — pełna relatywna ścieżka treści
$item->isDraft() // bool
$item->isScheduled() // bool
$item->isPinned() // bool
$item->isFeatured() // bool
$item->hasToc() // bool
$item->htmlContent // string (właściwość public) — wyrenderowany HTML
$item->locale // string (właściwość public)
Wymagane Klucze Tłumaczeń
Każdy katalog motywu musi definiować te klucze w każdym locale:
| Klucz | Używany przez |
|---|---|
blog.title |
Fallback blog_filter_title() gdy nie istnieje strona indeksu bloga |
contact.form.error, contact.form.success |
Odpowiedzi JSON API kontaktowego |
contact.form.name, .email, .website, .subject, .message |
Etykiety pól formularza |
contact.form.unavailable |
Wyświetlane gdy formularz kontaktowy nie jest skonfigurowany |
Klucze używane przez szablony, których motyw nie nadpisuje, muszą również istnieć w katalogach — tłumacz sięga do core translations/ tylko dla kluczy, których sam nie definiujesz.
Zasady Przenośności Motywu
- Iteruj po
site_locales_list— nigdy nie koduj na stałe kodów locale ani nie zakładaj dokładnie dwóch locale. - Zabezpieczaj nullable lookups —
content_item()zwraca null; kluczesite_author.*są zdefiniowane przez stronę (site_author.name|default(site_name));content.date()może być null. - Nie porównuj
directoryKey()do literałów chyba że kontrolujesz układ treści — zwraca pełną relatywną ścieżkę. - Każdy klucz
|transw szablonach musi istnieć w każdym katalogu locale, który dostarczasz — brakujące klucze renderowane są jako surowe ciągi kluczy. - Nie używaj dopasowywania wzorców URL do wykrywania sekcji gdy metadane treści mogą odpowiedzieć na to pytanie.
- Brak
|rawna wyjściu katalogu tłumaczeń — używaj|sanitize_html('app.trans_inline')gdy motyw naprawdę potrzebuje inline markup w tłumaczonym tekście.
Assety
Assety motywu w local/assets/ są referencjonowane jako asset('local/<file>'). Assety core używają asset('<file>'). Obrazy treści są serwowane pod /media/{basename}/{filename}. Pipeline obrazów responsywnych jest wyłącznie WebP: URL-e wariantów to <base>-<width>w.webp dla każdego wpisu image_variant_widths.
Pełny kontrakt API — w tym wszystkie pola kontekstu, kształty obiektów wartości i uwagi o przypadkach brzegowych — jest utrzymywany w docs/THEME_BUILDING.md. Zmiany niekompatybilne wstecz są dokumentowane w UPGRADE-X.Y.md.