' + +local function page_title(title) + return EscapeHtml(title) .. " \226\128\148 " .. EscapeHtml(config.get("site_title")) +end + +--- Return a complete HTML document wrapping body_html in the site shell. +--- Always returns a full page; callers check GetHeader("HX-Request") and +--- call render.partial() instead when only a fragment is needed. +function M.layout(title, body_html) + local site_title = config.get("site_title") + local safe_title = page_title(title) + return "\n" + .. '\n' + .. "\n" + .. '\n' + .. '\n' + .. "" .. safe_title .. "\n" + .. '\n' + .. THEME_INIT .. "\n" + .. '\n' + .. '\n' + .. '\n' + .. '\n' + .. (radio_html() ~= "" and '\n' or "") + .. "\n" + .. "\n" + .. nav_html() .. "\n" + -- hx-boost on
makes every internal link inside page content + -- (post cards, pagination, back-links, links in markdown) swap only + -- #content, so the radio player
\n" + .. footer_html() .. "\n" + .. radio_html() .. "\n" + .. "\n" + .. "" +end + +--- Return the fragment as-is for HTMX partial responses. +function M.partial(fragment_html) + return fragment_html +end + +--- Smart dispatch: return the right response for the request type. +--- +--- Three cases: +--- 1. Direct browser navigation → full layout (no HX-Request header) +--- 2. Boosted navigation → a plus the replacement +--- #content element only (HX-Request + HX-Boosted). htmx swaps it +--- via outerHTML and picks the <title> out of the response; nav, +--- footer, and the radio player are left untouched. +--- 3. True HTMX fragment request → bare partial (HX-Request, no +--- HX-Boosted; e.g. nav search input, editor preview, tag pills) +--- +--- All page-returning handlers should call this instead of checking +--- GetHeader("HX-Request") themselves. +function M.respond(title, body_html) + if GetHeader("HX-Request") then + if GetHeader("HX-Boosted") then + return "<title>" .. page_title(title) .. "\n" + .. MAIN_OPEN .. "\n" .. body_html .. "\n