Hwaro::Models::Page
Constants
Extract summary from content using <!-- more --> marker Returns content before the marker, or nil if no marker found Note: @raw_content has front matter already stripped during parsing
Fence-aware: a marker shown inside a fenced code block (a docs page
demonstrating the feature) is literal text, not a split point. The
per-line pattern no longer matches a marker whose parts sit on
different lines — degenerate input the old multi-line \s* accepted.
Constructors
Instance methods
Automatic summary: plain text cut from the rendered body when the
page has neither a <!-- more --> marker nor a description
([content] summary_length). Populated by
ParseContent#render_page_summaries; nil when the fallback is
disabled, when a higher-precedence source exists, or when the body
has no prose. summary_truncated is true only when the text was cut.
Automatic summary: plain text cut from the rendered body when the
page has neither a <!-- more --> marker nor a description
([content] summary_length). Populated by
ParseContent#render_page_summaries; nil when the fallback is
disabled, when a higher-precedence source exists, or when the body
has no prose. summary_truncated is true only when the text was cut.
The automatic excerpt as HTML: text is escaped (it was decoded from
the rendered body, so </& are literal characters) and wrapped in
one paragraph. Nil when there is no automatic summary.
Published pages in the same language linking here ([content] backlinks = true); see Transform#compute_backlinks.
Published pages in the same language linking here ([content] backlinks = true); see Transform#compute_backlinks.
Name for a breadcrumb crumb (page.ancestors, JSON-LD
BreadcrumbList). A title-less section or bundle index — typically a
transparent year folder — falls back to its directory name ("2022")
instead of publishing the placeholder (gh#863).
Build warnings collected during rendering (used for error overlay in serve mode)
Build warnings collected during rendering (used for error overlay in serve mode)
Reading time in minutes from the counts calculate_word_count set:
~200 words per minute, and ~500 per minute for Chinese/Japanese
characters (each counted as a word, but read much faster).
Calculate word count from raw content Note: @raw_content already has front matter stripped during parsing, so we only need to remove HTML tags and markdown syntax.
Fingerprint of the merged section [cascade] values applied to this page (empty when none apply). Stored in the build cache so editing a parent _index.md's cascade invalidates descendant pages whose own source files did not change.
Fingerprint of the merged section [cascade] values applied to this page (empty when none apply). Stored in the build cache so editing a parent _index.md's cascade invalidates descendant pages whose own source files did not change.
Collect assets from page directory
When content_files is configured ([content.files] with
allow_extensions), co-located bundle assets are filtered through the
SAME allow/disallow rules as standalone content files. Without this, a
root _index.md turns the whole content/ tree into one recursive
bundle and republishes arbitrary non-markdown files (e.g.
content/public/robots.txt) to the output regardless of
allow_extensions. Without an allowlist every non-markdown file the
deny rules leave is collected.
Two structural limits keep the recursion inside the bundle it belongs
to (both hold regardless of [content.files], which many hand-written
configs omit entirely):
content/itself is never a bundle.content/index.mdis the homepage, not a bundle spanning the site, and treating it as one republished every non-markdown file anywhere undercontent/(content/private/internal.pdf,notes.txt,*.bak, …) into the output root.- Recursion stops at nested bundles/sections. A subdirectory holding
its own
index.md/_index.mdis a different page and publishes its own assets; descending into it made every ancestor index re-copy the same files.bundle_dirs(content-relative directories that host their own bundle index, computed by Transform#collect_assets from the page set) extends nested-bundle detection to language-suffixed indexes (photos/index.ko.md) that the literal on-disk probe cannot see.
Get effective summary: <!-- more --> chunk > description > automatic
excerpt. The excerpt is returned as a single escaped <p> so
{{ page.summary | safe }} keeps working for every source.
True when a page should be omitted from generated listings (taxonomy
indexes, related posts, …): drafts, preview-only unpublished pages,
headless (render = false) pages that are never written, and
synthetic generated pages.
New: Extra field for arbitrary custom metadata from front matter.
Values are recursive (ExtraValue) so nested [extra.*] subtables
and arrays-of-tables round-trip into {{ page.extra.a.b }} in templates.
New: Extra field for arbitrary custom metadata from front matter.
Values are recursive (ExtraValue) so nested [extra.*] subtables
and arrays-of-tables round-trip into {{ page.extra.a.b }} in templates.
Commit metadata for this page's source file ([git] enabled = true).
Nil when the feature is off, the file is uncommitted, the site is not
a git checkout, or the page is synthesized/generated.
Commit metadata for this page's source file ([git] enabled = true).
Nil when the feature is off, the file is uncommitted, the site is not
a git checkout, or the page is synthesized/generated.
Is this the site (or per-language) homepage — the root index.md /
_index.md? Its source sits directly under content/, so path has
no / (index.md, index.ko.md, _index.md, …). Not
is_index && section.empty?: a one-level page bundle like
content/about/index.md also has an empty section (gh#601).
New: In search index - whether to include in search index
New: Insert anchor links - whether to add anchor links to headings.
Tri-state: nil (front matter silent) falls back to the site-wide
[markdown] insert_anchor_links config; an explicit true/false wins.
New: Insert anchor links - whether to add anchor links to headings.
Tri-state: nil (front matter silent) falls back to the site-wide
[markdown] insert_anchor_links config; an explicit true/false wins.
True when the render phase resolved an output-path collision AGAINST this page, or found its URL unpublishable (a segment traverses out of the output directory), so no file was written for its URL (see compute_output_url_winners). Recomputed from scratch on every build and rerender — a collision introduced or resolved during a serve session must not leave a stale verdict behind.
The discovery surfaces have to honour it: without this the build warned about the collision, declined to write the page, and then advertised its URL anyway in sitemap.xml, llms.txt, the feeds and the search index — trading a silent overwrite for a guaranteed 404 that the site's own sitemap points at.
True when the render phase resolved an output-path collision AGAINST this page, or found its URL unpublishable (a segment traverses out of the output directory), so no file was written for its URL (see compute_output_url_winners). Recomputed from scratch on every build and rerender — a collision introduced or resolved during a serve session must not leave a stale verdict behind.
The discovery surfaces have to honour it: without this the build warned about the collision, declined to write the page, and then advertised its URL anyway in sitemap.xml, llms.txt, the feeds and the search index — trading a silent overwrite for a guaranteed 404 that the site's own sitemap points at.
Whether parsing (front-matter / markdown) failed for this page
Deferred [permalinks] error from the parse fan-out (a date-token
pattern matched a dateless page). Whether it's fatal isn't knowable
until cascades and draft/expiry filtering ran: a page filtered out
of the build (or a headless render: false page) never publishes
the URL, so only surviving renderable pages raise — see
ParseContent#raise_on_permalink_errors!.
Deferred [permalinks] error from the parse fan-out (a date-token
pattern matched a dateless page). Whether it's fatal isn't knowable
until cascades and draft/expiry filtering ran: a page filtered out
of the build (or a headless render: false page) never publishes
the URL, so only surviving renderable pages raise — see
ParseContent#raise_on_permalink_errors!.
Plain-text rendering of the <!-- more --> summary, safe to embed
in single-line contexts like og:description,
twitter:description, or a feed <description>. Uses the
already-rendered summary_html and strips markup so raw markdown
(## headings, code fences, math, literal newlines) never leaks
into meta tags — see https://github.com/hahwul/hwaro/issues/491.
Returns nil when the page has no summary. Soft-truncates to limit
characters on a word boundary. Falls back to the automatic body
excerpt (already plain text) when no marker summary exists.
True when the page's own HTML is published as content: rendered, not
a draft / preview-only unpublished page, not suppressed by an output
collision, and not a redirect_to stub. The sitemap and feeds
advertise only such URLs.
Recompute unpublished from the publication window (date in the
future or expires in the past) relative to the build's single now.
Called by the parse-phase filter and the incremental (--cache)
re-parse path so every downstream consumer sees a consistent value.
True when a page is eligible for the search index / llms.txt: it emits
HTML, isn't a draft or preview-only unpublished page, opts into the
search index, isn't a synthetic generated listing page, and isn't a
redirect_to stub (a meta-refresh bounce whose canonical is elsewhere).
Stamp of the source file as parse read it: {mtime ms, size, digest}. The digest is only taken when the mtime was still inside its timestamp tick (racy-git); see Cache#update. Nil for pages without a disk file.
Stamp of the source file as parse read it: {mtime ms, size, digest}. The digest is only taken when the mtime was still inside its timestamp tick (racy-git); see Cache#update. Nil for pages without a disk file.
New: Summary - content before <!-- more --> marker or auto-generated
Rendered HTML for the chunk before <!-- more -->. Populated by
the build pipeline after extract_summary runs and the markdown
processor is available; nil for pages without a <!-- more -->
marker (in which case page.summary falls back to description).
See https://github.com/hahwul/hwaro/issues/491.
Rendered HTML for the chunk before <!-- more -->. Populated by
the build pipeline after extract_summary runs and the markdown
processor is available; nil for pages without a <!-- more -->
marker (in which case page.summary falls back to description).
See https://github.com/hahwul/hwaro/issues/491.
Resolve the term list for a configured taxonomy name. Most
taxonomies live in @taxonomies, but a few — tags and authors
— are stored on dedicated Page properties so other features
(the tags shortcut, the site.authors aggregation) can reach
them without a hash lookup. Centralizing the fallback here keeps
call sites (Taxonomies generator, related-posts scoring, …) from
forgetting any of the special cases.
page.summary as templates see it: the rendered summary, else the
description or excerpt. Never the raw <!-- more --> chunk — its
shortcodes and Markdown unrendered — which is all a page has before
the summary pass renders it (and what a summary shortcode reading
that page would otherwise publish).
A content/<taxonomy>/<slug>/_index.md matched to the term page this
build generates at term_page_url (Content::Taxonomies.
match_term_metadata). Such a section is headless: term_metadata (the
file that won that page) lends it its title, description, extra,
image, body and aliases; a losing duplicate only stays unpublished.
Set after parsing (Builder#mark_term_metadata), reset by every parse.
A content/<taxonomy>/<slug>/_index.md matched to the term page this
build generates at term_page_url (Content::Taxonomies.
match_term_metadata). Such a section is headless: term_metadata (the
file that won that page) lends it its title, description, extra,
image, body and aliases; a losing duplicate only stays unpublished.
Set after parsing (Builder#mark_term_metadata), reset by every parse.
Front matter translation_key: pages sharing it (within one version)
are translations of each other, whatever their file names. Nil
groups by file name (Content::Multilingual.link_translations!).
Front matter translation_key: pages sharing it (within one version)
are translations of each other, whatever their file names. Nil
groups by file name (Content::Multilingual.link_translations!).
True when the page is outside its publication window at build time
(future date or past expires). Such pages only survive the parse
filter under --include-future / --include-expired — preview flags —
and must then behave exactly like drafts under --drafts: the HTML
page renders, but the page stays out of every public discovery
surface (sitemap, feeds, search index, llms.txt) and out of
generated listings (taxonomies, series, related posts, authors).
True when the page is outside its publication window at build time
(future date or past expires). Such pages only survive the parse
filter under --include-future / --include-expired — preview flags —
and must then behave exactly like drafts under --drafts: the HTML
page renders, but the page stays out of every public discovery
surface (sitemap, feeds, search index, llms.txt) and out of
generated listings (taxonomies, series, related posts, authors).
No authored title: front matter left it unset (the "Untitled" placeholder) or blank.
# and ? are legal in a filename (and in an explicit slug/path)
but they END the path component of a URL: /posts/a#b/ is the page
/posts/a with the fragment b. A page named a#b.md was written
correctly to public/posts/a#b/index.html, yet every link hwaro
emitted for it — page.url in listings, page.permalink, the sitemap
<loc> — pointed at /posts/a, a 404. Encode them here, at the one
place a page's URL is assigned, so templates that use page.url
directly are right too and not just the permalink.
Little else is escaped. The output path is derived from this string
by decoding it again (PathUtils.split_safe_segments), so %23 still
lands in the directory named a#b that the URL now addresses —
whereas encoding more (a non-ASCII slug, say) would rename the
directory every existing site already publishes to. A valid %XX
escape is kept as is, which also keeps this idempotent when a URL is
assigned twice.
" is escaped for the same reason from the HTML side: templates print
page.url into double-quoted attributes unescaped (autoescape is
off), so with"quote.md cut its own href short and linked a 404.
A % that starts no escape (100%.md) is escaped: /posts/100%/ is
not a valid URL, and servers that reject it answer 400. So is every
% when the name's %XX runs do not decode to UTF-8 (100%deal.md):
the output path decodes them and scrubs the invalid bytes, so
/posts/100%deal/ was written to a 100<U+FFFD>al/ directory no
request for that URL reaches. Either way the URL then names the
literal file name.
Documentation version this page belongs to ([[versions.list]]),
resolved from its content directory in ReadContent; nil for
unversioned content. See Content::Versions.
Documentation version this page belongs to ([[versions.list]]),
resolved from its content directory in ReadContent; nil for
unversioned content. See Content::Versions.
Switcher rows, one per configured version (see VersionLink); empty for unversioned pages. Filled by Content::Versions.link!.
Switcher rows, one per configured version (see VersionLink); empty for unversioned pages. Filled by Content::Versions.link!.
Warnings already logged for this page by its summary pass and body
render (see Logger.dedupe_warnings); cleared when the summary pass
starts and when the body render ends.
imageas the social / structured-data surfaces must resolve it. A relative value naming one of this page's own bundle assets (image = "cover.png"besideindex.md) is published under the page's URL, like a relativein its body — resolving it against the site root (/cover.png) advertised a 404 og:image. Anything else is returned as written (root-relative, external, or a relative path meant from the site root, e.g. astatic/file).