HTML rendering functions for Publishing.Web.Controller.
Summary
Functions
Pre-computes date counts for timestamp-mode posts to avoid per-post DB queries.
Builds a post URL based on mode. Omits the locale prefix when the site is effectively single-language. Can also omit the default-language prefix when that setting is enabled.
Builds a public path with explicit date and time (always includes time). Used when redirecting from date-only URLs to full timestamp URLs.
Builds language data for the publishing_language_switcher component on public pages. Converts the @translations assign to the format expected by the component.
Extracts and renders an excerpt from post content. Returns content before <!-- more --> tag, or first paragraph if no tag. Renders markdown and strips HTML tags for plain text display.
Resolves a featured image URL for a post, falling back to the original variant.
Builds the public URL for a group's RSS feed (…/<group>/feed.xml) or a
term archive's (…/<group>/category/<slug>/feed.xml, …/tag/<tag>/feed.xml),
with the same locale-prefix rules as group_listing_path/3.
Whether group RSS feeds are served (publishing_feeds_enabled, default on).
Read by the controller's feed branch and by the listing's autodiscovery link.
Formats a date for display using locale-aware month names.
Formats a date for URL.
Formats a date with time for display. Used when multiple posts exist on the same date.
Formats a post's publication date, including time only when multiple posts exist on the same date.
Formats time for URL (HH:MM).
Builds the public URL for a group listing page. Omits the locale prefix when the site is effectively single-language. Can also omit the default-language prefix when that setting is enabled.
Checks if a post has a publication date to display. For timestamp mode, the date comes from the DB fields. For slug mode, it comes from metadata.published_at.
Drives <Gallery motion="scroll"> where the browser has no scroll-driven
animations.
Renders the JSON-LD Article structured-data script for a post page, built
from the same :og map the meta tags use (title/description/image/url are
already override- and plugin-refined there) plus the post's publish date.
Rendered in-page for the same reason as the OG tags: search engines read
body placement, and the host owns <head>.
The right-side slide-out panels for "panel" notes style. CSS-only: hidden off-canvas until the panel is the URL :target (the note ref in the body links to it); the backdrop and ✕ link back to the ref, which both closes the panel and returns the reader to the annotated phrase. Public so the editor preview can render the same panels (with commenting off) — preview must match production.
Renders the OpenGraph + Twitter Card meta tags for a public page from the
:og map the controller builds.
Pluralizes a word based on count.
Renders a post's publication date (calendar icon + formatted date).
Resolves the exact language-switcher code to highlight on public pages.
Renders a slim heading-anchor rail on post pages: a fixed side rail with a
tick per <h2>/<h3>/<h4> in the article; hover reveals the heading text,
click smooth-scrolls to it, and the current section is highlighted as you
scroll. Built client-side (assigns heading ids in the browser, so the cached
render pipeline is untouched) as a real <nav> of links — keyboard and
screen-reader accessible. Hidden on narrow screens; honors reduced-motion.
Renders a thin reading-progress bar fixed to the top of the viewport that
fills as the reader scrolls the article. Decorative (aria-hidden), pointer
transparent, and honors prefers-reduced-motion.
Renders a date-timeline rail on the group listing: a fixed side rail with a
marker per distinct year found across the rendered post cards (which carry a
data-post-date); click a year to smooth-scroll to its first post, and the
current year highlights as you scroll. Only appears when 2+ years are present.
Built client-side as an accessible <nav> of links; hidden on narrow screens.
Emits a <style> that recolors the page's native scrollbar to the daisyUI
theme when the group opts into "branded"/"thin". "default" renders nothing
(the browser's native bar is untouched). Only recolors/resizes the real
scrollbar — scrolling stays native.
Public URL for a category archive (…/<group>/category/<slug>) or tag
archive (…/<group>/tag/<tag>).
Functions
Pre-computes date counts for timestamp-mode posts to avoid per-post DB queries.
Returns a map of %{date_string => count} for use with build_post_url/4
and format_post_date/3.
Builds a post URL based on mode. Omits the locale prefix when the site is effectively single-language. Can also omit the default-language prefix when that setting is enabled.
For slug mode posts, uses the language-specific URL slug (from post.url_slug or post.language_slugs[language]) for SEO-friendly localized URLs.
For timestamp mode posts:
- If only one post exists on the date, uses date-only URL (e.g., /group/2025-12-09)
- If multiple posts exist on the date, includes time (e.g., /group/2025-12-09/16:26)
Builds a public path with explicit date and time (always includes time). Used when redirecting from date-only URLs to full timestamp URLs.
Builds language data for the publishing_language_switcher component on public pages. Converts the @translations assign to the format expected by the component.
Extracts and renders an excerpt from post content. Returns content before <!-- more --> tag, or first paragraph if no tag. Renders markdown and strips HTML tags for plain text display.
Resolves a featured image URL for a post, falling back to the original variant.
Builds the public URL for a group's RSS feed (…/<group>/feed.xml) or a
term archive's (…/<group>/category/<slug>/feed.xml, …/tag/<tag>/feed.xml),
with the same locale-prefix rules as group_listing_path/3.
Whether group RSS feeds are served (publishing_feeds_enabled, default on).
Read by the controller's feed branch and by the listing's autodiscovery link.
Formats a date for display using locale-aware month names.
Formats a date for URL.
Formats a date with time for display. Used when multiple posts exist on the same date.
Formats a post's publication date, including time only when multiple posts exist on the same date.
Formats time for URL (HH:MM).
Builds the public URL for a group listing page. Omits the locale prefix when the site is effectively single-language. Can also omit the default-language prefix when that setting is enabled.
Checks if a post has a publication date to display. For timestamp mode, the date comes from the DB fields. For slug mode, it comes from metadata.published_at.
Drives <Gallery motion="scroll"> where the browser has no scroll-driven
animations.
The gallery expresses scroll-linking in pure CSS (animation-timeline), and
that is the real implementation — this does nothing at all where it works.
But support is only around 84% of browsers: Firefox has it from 156, Safari
from 26. Everywhere else the @supports gate falls through to the drift
animation, which is a perfectly good gallery but silently not the feature
that was asked for.
So this is enhancement in the strict sense: no JS still gives you a turning helix, a modern browser gives you the CSS one, and only the gap in between is filled here.
It works by pausing the drift animations and setting their currentTime
from scroll position rather than re-implementing the geometry. Every card's
phase already lives in its own negative delay, so a single virtual clock
driven by scroll reproduces the exact arrangement CSS would have produced —
there is no second copy of the maths to drift out of step with the first.
Renders the JSON-LD Article structured-data script for a post page, built
from the same :og map the meta tags use (title/description/image/url are
already override- and plugin-refined there) plus the post's publish date.
Rendered in-page for the same reason as the OG tags: search engines read
body placement, and the host owns <head>.
Attributes
og(:map) - Defaults tonil.post(:map) (required)language(:string) (required)
The right-side slide-out panels for "panel" notes style. CSS-only: hidden off-canvas until the panel is the URL :target (the note ref in the body links to it); the backdrop and ✕ link back to the ref, which both closes the panel and returns the reader to the annotated phrase. Public so the editor preview can render the same panels (with commenting off) — preview must match production.
Renders the OpenGraph + Twitter Card meta tags for a public page from the
:og map the controller builds.
These are emitted in-page (inside the rendered body) so a social preview
works out of the box even when the host's root layout doesn't render the
forwarded :og assign in <head> — most host apps ship their own root
layout. The same :og map is ALSO forwarded via module_assigns, so a host
that does render it in <head> gets the strictly-correct placement; such a
host disables the in-page copy via publishing_render_og_tags to avoid
duplicate tags. Renders nothing when :og is absent (e.g. the groups index).
Attributes
og(:map) - Defaults tonil.
Pluralizes a word based on count.
Renders a post's publication date (calendar icon + formatted date).
Attributes
post(:map) (required)group_slug(:string) (required)class(:any) - Defaults tonil.
Resolves the exact language-switcher code to highlight on public pages.
Renders a slim heading-anchor rail on post pages: a fixed side rail with a
tick per <h2>/<h3>/<h4> in the article; hover reveals the heading text,
click smooth-scrolls to it, and the current section is highlighted as you
scroll. Built client-side (assigns heading ids in the browser, so the cached
render pipeline is untouched) as a real <nav> of links — keyboard and
screen-reader accessible. Hidden on narrow screens; honors reduced-motion.
Attributes
enabled(:boolean) - Defaults tofalse.
Renders a thin reading-progress bar fixed to the top of the viewport that
fills as the reader scrolls the article. Decorative (aria-hidden), pointer
transparent, and honors prefers-reduced-motion.
Attributes
enabled(:boolean) - Defaults tofalse.
Renders a date-timeline rail on the group listing: a fixed side rail with a
marker per distinct year found across the rendered post cards (which carry a
data-post-date); click a year to smooth-scroll to its first post, and the
current year highlights as you scroll. Only appears when 2+ years are present.
Built client-side as an accessible <nav> of links; hidden on narrow screens.
Attributes
enabled(:boolean) - Defaults tofalse.granularity(:string) - Defaults to"auto".
Emits a <style> that recolors the page's native scrollbar to the daisyUI
theme when the group opts into "branded"/"thin". "default" renders nothing
(the browser's native bar is untouched). Only recolors/resizes the real
scrollbar — scrolling stays native.
Attributes
style(:string) - Defaults to"default".
Public URL for a category archive (…/<group>/category/<slug>) or tag
archive (…/<group>/tag/<tag>).