4.8 KiB
4.8 KiB
Content model — blog / news / events (EN / MK / SI)
Source of truth for editable content. Implements U1 of
docs/plans/2026-08-08-001-feat-no-code-cms-plan.md.
Today the detail pages under en|mk|si/{blog,news,projects}/<slug>/index.html are
hand-authored HTML and are the source of truth. This model moves the source of
truth into content/: one Markdown-with-front-matter file per entry per
language. The detail HTML pages become build output (produced by U2).
Layout
content/
blog/<slug>/{en,mk,si}.md
news/<slug>/{en,mk,si}.md
events/<slug>/{en,mk,si}.md # renders to the existing <lang>/projects/ output
- One folder per entry (the slug), one file per language.
- A language file may be absent — that language simply is not published for that entry (U2 skips it; it does not emit a broken half-page).
content/events/maps to the site's existingprojects/output folder. The URL path and the SEO date-registry key stayprojects/<slug>for compatibility (seesource_folderbelow).
File shape
Standard front-matter: a YAML block delimited by ---, then the body.
---
type: blog # blog | news | event
slug: how-to-open-slovenian-bank-account
lang: en # en | mk | si
source_folder: blog # blog | news | projects (URL + date-registry key)
title: "How to open a Slovenian bank account as an international student"
subtitle: "A practical guide for Macedonian students…" # optional (news has none)
category: "Student life" # optional; blog category shown before the date
date: "2026-08-01" # ISO 8601 — authoritative for sorting (from ARTICLE_DATES)
date_line: "Student life · Last checked 1 August 2026" # verbatim human date line, optional
hero:
src: "/images/blog-slovenian-bank-account.webp" # root-relative
alt: "International student using mobile banking…"
width: "6048"
height: "4024"
caption_html: "<span class=\"fig-illustrative\">…</span> · Photo by …" # optional
body_format: html # html | markdown (see below)
---
<the article body, verbatim inner HTML of .article-body / .news-article-body>
Field reference
| Field | Required | Notes |
|---|---|---|
type |
yes | blog, news, or event. |
slug |
yes | URL slug; matches the folder name. |
lang |
yes | en / mk / si. |
source_folder |
yes | blog / news / projects. Drives the output URL and the ARTICLE_DATES key (<source_folder>/<slug>). |
title |
yes | Plain text; rendered into <h1> and <title>. |
subtitle |
no | Blog/event lead paragraph (.article-subtitle). News has none. |
category |
no | Shown before the date on blog pages. |
date |
yes | ISO date, authoritative for listing order. Sourced from tools/seo_inject.py ARTICLE_DATES, else extracted from the page. |
date_line |
no | The exact human-readable date line from the page, preserved for fidelity. U2 may instead regenerate it per-language from date. |
hero.* |
no | Lead image + caption. src is root-relative (/images/…). caption_html is verbatim (may contain attribution links). |
body_format |
yes | See below. |
body_format: html vs markdown
html— the body is a verbatim HTML fragment: the page's.article-body/.news-article-bodyelement with its wrapper, plus any following sibling sections inside the article container (notablysection.event-galleryon event pages, which sits after.article-body). This is what migration produces, so rich elements the site already uses —guide-boxasides,article-inline-imagefigures,event-gallerylightbox grids, ordered lists, nested links — survive losslessly. The U2 builder injects this fragment as-is, in place of everything after the hero figure. Because migrated output pages sit at the same directory depth as the originals (<lang>/<type>/<slug>/index.html), relative asset paths inside the body (../../../images/…) remain valid without rewriting. The blog scrollspy sidebar is not stored — it is derived from the body's<h2 id>headings by U2.markdown— the body is Markdown. This is the intended format for new posts authored in the CMS; the U2 builder converts it to article HTML using the existing converter intools/build_blog.py. The blog scrollspy sidebar is regenerated from the##(h2) headings.
Migration deliberately chooses html over a lossy HTML→Markdown conversion.
The scaffold is a starting point a human proofs, not a guaranteed lossless
transform — pages the extractor cannot parse cleanly are reported, not
silently emitted.
Non-goals for U1
- No CMS wiring (U7), no build/render of pages (U2), no listings/date refactor
(U3). U1 only defines this model and back-fills
content/from current pages.