<% // ── Helpers used inside this template ───────────────────────────── function _e(s) { return String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } const safeSite = site || {}; const safeUrlBase = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : ''; const safeAccent = safeSite.accent && /^#[0-9a-fA-F]{6}$/.test(safeSite.accent) ? safeSite.accent : '#c2410c'; const lang = safeSite.language || 'nl'; const ogLocale = safeSite.og_locale || (lang === 'nl' ? 'nl_NL' : (lang.length === 2 ? lang + '_' + lang.toUpperCase() : 'en_US')); const homePath = safeUrlBase + '/'; const isPostPage = bodyClass && bodyClass.indexOf('on-post') >= 0; const isHomePage = bodyClass && bodyClass.indexOf('on-home') >= 0; const isSpecialPg = bodyClass && bodyClass.indexOf('on-special') >= 0; const isAdminPage = bodyClass && bodyClass.indexOf('on-admin') >= 0; // ── via site.title_template ────────────────────────────── // Template: '{title} — {site}'. If pageTitle equals site.title (homepage) we // just use the site title alone, otherwise apply the template. const _siteTitle = safeSite.title || 'PrutCMS'; const _rawTitle = pageTitle || _siteTitle; const _tpl = safeSite.title_template || '{title} — {site}'; const _finalTitle = (_rawTitle === _siteTitle) ? _siteTitle : _tpl.replace('{title}', _rawTitle).replace('{site}', _siteTitle); // ── Robots: noindex on listing pages and on per-post override ───── let _shouldIndex = safeSite.robots_index !== 0; if (typeof post !== 'undefined' && post && post.noindex) _shouldIndex = false; // Listing pages (search/tag/type/archive) shouldn't be indexed (dupe content) if (currentPath) { if (/^\/(?:search|tag|type|archive|users|prutter|account|admin)(?:$|\/)/.test(currentPath)) { _shouldIndex = false; } } // Special-flagged views from routes opt out too if (isSpecialPg && (currentPath === '/search' || /^\/(tag|type|archive|users)\//.test(currentPath))) { _shouldIndex = false; } // ── Canonical URL (only when we have one configured) ───────────── let _canonical = null; if (safeSite.canonical) { const _base = safeSite.canonical.replace(/\/+$/, ''); let _path = '/'; if (typeof post !== 'undefined' && post && post.slug) _path = '/' + post.slug; else if (currentPath) _path = currentPath; _canonical = _base + _path; } // ── Social bits (OG/Twitter) ────────────────────────────────────── const _socialTitle = (typeof post !== 'undefined' && post && post.title) ? post.title : _siteTitle; const _socialDescr = (typeof socialDescr !== 'undefined' && socialDescr) ? socialDescr : (safeSite.default_description || safeSite.description || ''); const _socialImage = (typeof socialImage !== 'undefined' && socialImage) ? socialImage : (safeSite.og_image_default || safeSite.default_cover || ''); const _ogType = isPostPage ? 'article' : 'website'; // ── JSON-LD ─────────────────────────────────────────────────────── const _publisher = { '@type': safeSite.schema_type === 'Organization' ? 'Organization' : 'Person', name: safeSite.publisher_name || _siteTitle, url: safeSite.publisher_url || (_canonical ? _canonical.split(/(?<=^[^/]*\/\/[^/]+)\//)[0] + '/' : null), }; if (safeSite.publisher_logo) { _publisher.logo = { '@type': 'ImageObject', url: safeSite.publisher_logo }; } let _jsonLd = null; if (typeof post !== 'undefined' && post && post.slug) { _jsonLd = { '@context': 'https://schema.org', '@type': (post.type === 'foto' || post.type === 'video') ? 'CreativeWork' : 'Article', headline: post.title || _siteTitle, description: _socialDescr, datePublished: post.published_at || post.created_at || new Date().toISOString(), dateModified: post.updated_at || post.published_at || new Date().toISOString(), publisher: _publisher, }; if (_socialImage) _jsonLd.image = _socialImage; if (post.author_username) _jsonLd.author = { '@type': 'Person', name: post.author_username }; if (Array.isArray(post.tags) && post.tags.length) _jsonLd.keywords = post.tags.join(', '); } else if (isHomePage) { _jsonLd = { '@context': 'https://schema.org', '@type': 'WebSite', name: _siteTitle, description: safeSite.description || safeSite.default_description || '', publisher: _publisher, }; } %><!DOCTYPE html> <html lang="<%= _e(lang) %>" data-palette="<%= _e(safeSite.palette || 'sage') %>"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <meta name="color-scheme" content="dark light"> <title><%= _finalTitle %> <% if (safeSite.author) { %><% } %> <% if (_canonical) { %><% } %> <% if (safeSite.google_verification) { %><% } %> <% if (safeSite.bing_verification) { %><% } %> <% if (safeSite.pinterest_verification) { %><% } %> <% if (safeSite.yandex_verification) { %><% } %> <% if (site) { %> <% } %> <% if (_socialImage) { %> <% } %> <% if (_canonical) { %><% } %> <% if (typeof post !== 'undefined' && post && post.published_at) { %> <% if (post.author_username) { %><% } %> <% } %> <% if (safeSite.facebook_app_id) { %><% } %> <% if (_socialImage) { %><% } %> <% if (safeSite.twitter) { %><% } %> <% if (_jsonLd) { %> <% } %> <%- include('partials/shared-styles') %> <% if (safeSite.custom_css) { %> <% } %> <% if (safeSite.custom_head_html) { %> <%- safeSite.custom_head_html %> <% } %> <%- include('partials/topnav') %> <%- include('partials/profile-header') %> <%# View switcher — visible on every non-admin page. On feed pages (home, archive, tag, type, user) it toggles the visual layout; on non-feed pages (post, account, search, auth) it navigates to / in the chosen view. Click logic is in shell.ejs at the bottom. %> <% if (!isAdminPage) { %> <%- include('partials/view-switcher') %> <% } %> <%# hx-history-elt scopes HTMX history snapshots to #pcms-main only, so when the user navigates back the switcher (which lives in the shell OUTSIDE this element) keeps its place rather than being replaced along with the rest of the body. %>
<%- pageContent %>
<%- include('partials/footer') %> <%- include('partials/bottom-tab') %> <% if (user) { %> <%- include('partials/profile-sheet') %> <% } %> <% if (site && site.enable_audio_player && audioTracks && audioTracks.length > 0) { %> <% } %> <% if (safeSite.custom_foot_html) { %> <%- safeSite.custom_foot_html %> <% } %>