diff --git a/pagerite/data.py b/pagerite/data.py index 9a105fc..056b4ad 100644 --- a/pagerite/data.py +++ b/pagerite/data.py @@ -2,8 +2,9 @@ The site structure is a tree of Nodes. Every node is a menu label with a configurable title and slug (its key in the parent's ``children``); the -URL path is the chain of slugs from the top level. ``content`` is the -node's Markdown page, or None for a pure category label, whose URL renders +URL path is the chain of slugs from the top level. ``chunks`` is the +node's Markdown page as ordered content-hash keys into ``Data.chunks`` +(docs/migrate.md), or None for a pure category label, whose URL renders a placeholder page while nav links point at its first child. """ @@ -11,6 +12,16 @@ from datetime import UTC, datetime import msgspec +from pagerite.chunks import join_chunks + + +class Patch(msgspec.Struct, omit_defaults=True): + """One editing session's overrides on a translated view, applied + independently per hunk (docs/localization.md).""" + + #: (search, replace) pairs on the served hybrid Markdown. + hunks: list[tuple[str, str]] = [] + class Node(msgspec.Struct, omit_defaults=True): """One item of the site hierarchy. @@ -28,9 +39,21 @@ class Node(msgspec.Struct, omit_defaults=True): title: str = "" order: float = 0 - #: Markdown source of the node's page; None = pure category label - #: (its URL renders a placeholder page). - content: str | None = None + #: Ordered chunk hashes into ``Data.chunks``; None = pure category + #: label (its URL renders a placeholder page), a list (possibly + #: empty) = a page. + chunks: list[str] | None = None + #: Primary language of the article (BCP-47 base tag). "" = inherit + #: (nearest ancestor, front page last, site default "en" final). + language: str = "" + #: Chunk hashes the editor marked "do not translate" (always served + #: from the original). Presence-keys, value always True. + no_trans: dict[str, bool] = {} + #: Languages this article is available in (besides its primary + #: language). Presence-keys, value always True — the availability + #: index for rendering, language selection and hreflang alternates; + #: maintained by whoever writes translation data (docs/migrate.md). + langs: dict[str, bool] = {} #: Raw HTML for the header banner (img, styled div, canvas+script...), #: rendered after the banner design's artwork so author code always #: wins over the design's own styles. @@ -78,6 +101,28 @@ class Data(msgspec.Struct): #: linked as on every page. Empty = the build's #: /favicon.ico. favicon: str = "" + #: All original-language page text, content-addressed: + #: chunk_key -> Markdown chunk. Shared by every article. + chunks: dict[str, str] = {} + #: Machine translations: f"{chunk_hash}:{lang}" -> translated + #: Markdown. Also used for node titles (hash of the title text). + trans: dict[str, str] = {} + #: User override patches per article and language: + #: f"{path}:{lang}" -> ordered patches (paths without leading slash). + patches: dict[str, list[Patch]] = {} + + +def node_markdown(data: Data, node: Node) -> str | None: + """The node's original Markdown assembled from the chunk store. + + None for category labels (chunks is None); an empty page gives "". + Hashes missing from the store (shouldn't happen) are skipped. + """ + if node.chunks is None: + return None + return join_chunks( + [t for h in node.chunks if (t := data.chunks.get(h)) is not None] + ) def prettify(slug: str) -> str: