Changeset 1e2e9e7 in Klonkt


Ignore:
Timestamp:
06/19/2026 09:38:20 AM (3 months ago)
Author:
roboburr <roboburr@โ€ฆ>
Branches:
main
Children:
8cdb377
Parents:
f4b7b6a
Message:

ui(fan-gate): show Newer/Older post navigation on the fan gate too

The fan gate (login wall of a fan_only post, e.g. /fans) was a dead end:
no navigation. Nav is now in a shared partial partials/post-nav.ejs, used
by both post.ejs and fan-gate.ejs. posts.js calculates the neighbours via
a shared postNeighbors() helper, also in the fan-gate branch.

Co-Authored-By: Claude <noreply@โ€ฆ>

Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG.md

    rf4b7b6a r1e2e9e7  
    1515
    1616### Gewijzigd
     17- De Newer/Older-postnavigatie staat nu รณรณk op de fan-gate (login-wall van een
     18  fan-only post), zodat een bezoeker niet vastloopt maar verder kan bladeren.
     19  Nav verhuisd naar een gedeeld partial (`partials/post-nav.ejs`).
    1720- Login gesplitst: de publieke loginpagina (`/auth/login`) toont voor bezoekers nu
    1821  **alleen** Google-login (luisteraars/fans). De beheerders-login (gebruikersnaam +
  • src/routes/posts.js

    rf4b7b6a r1e2e9e7  
    394394});
    395395
     396// Newer/Older-buren over ALLE posts in feed-volgorde. Gedeeld door de volledige
     397// post-render รฉn de fan-gate (premium fan_only), zodat de navigatie overal gelijk
     398// is. Solo: binnen de site (pinned eerst, dan datum). Hub: globaal op datum.
     399function postNeighbors(site, post, isHub) {
     400  const urlBaseFor = (p) => (isHub && p && p.site_slug) ? `/user/${p.site_slug}` : '';
     401  const ordered = isHub
     402    ? db.prepare(`
     403        SELECT p.id, p.slug, p.title, s.slug AS site_slug
     404        FROM posts p JOIN sites s ON s.id = p.site_id
     405        WHERE p.status = 'published'
     406        ORDER BY p.published_at DESC
     407      `).all()
     408    : db.prepare(`
     409        SELECT id, slug, title FROM posts
     410        WHERE site_id = ? AND status = 'published'
     411        ORDER BY (pinned = 0) ASC, pinned ASC, published_at DESC
     412      `).all(site.id);
     413  const idx = ordered.findIndex((p) => p.id === post.id);
     414  const newerPost = idx > 0 ? ordered[idx - 1] : null;
     415  const olderPost = (idx >= 0 && idx < ordered.length - 1) ? ordered[idx + 1] : null;
     416  if (newerPost) newerPost._urlBase = urlBaseFor(newerPost);
     417  if (olderPost) olderPost._urlBase = urlBaseFor(olderPost);
     418  return { newerPost, olderPost };
     419}
     420
    396421// ==================== VIEW POST (last route รขโ‚ฌโ€ catches /:slug) ====================
    397422router.get('/:slug', (req, res, next) => {
     
    419444  // teaser mag elders wel als lokkertje verschijnen).
    420445  if (post.fan_only && !(req.session && req.session.user)) {
     446    // Zelfde Newer/Older-navigatie als op een gewone post, zodat de bezoeker op
     447    // de fan-gate niet vastloopt maar verder kan bladeren.
     448    const { newerPost, olderPost } = postNeighbors(site, post, res.locals.tenancy === 'hub');
    421449    return renderPage(req, res, 'pages/fan-gate', {
    422450      pageTitle: post.title || 'Alleen voor fans',
     
    424452      fgTitle: post.title || '',
    425453      fgNext: (res.locals.siteUrlBase || '') + '/' + post.slug,
     454      newerPost,
     455      olderPost,
    426456    });
    427457  }
     
    545575  const urlBaseFor = (p) => (isHub && p && p.site_slug) ? `/user/${p.site_slug}` : '';
    546576
    547   // Newer/Older over ALLE posts, in de canonieke feed-volgorde โ€” niet alleen de
    548   // pinned-stack. Solo: binnen de site, pinned eerst op rank, dan op datum
    549   // (zelfde volgorde als de homepage-feed). Hub: globaal op datum over alle
    550   // sites. De vorige positie in de lijst = "Newer" (โ† omhoog), de volgende =
    551   // "Older" (โ†’ omlaag). Pinned posts zitten zo gewoon in de doorlopende reeks.
    552   const ordered = isHub
    553     ? db.prepare(`
    554         SELECT p.id, p.slug, p.title, s.slug AS site_slug
    555         FROM posts p JOIN sites s ON s.id = p.site_id
    556         WHERE p.status = 'published'
    557         ORDER BY p.published_at DESC
    558       `).all()
    559     : db.prepare(`
    560         SELECT id, slug, title FROM posts
    561         WHERE site_id = ? AND status = 'published'
    562         ORDER BY (pinned = 0) ASC, pinned ASC, published_at DESC
    563       `).all(site.id);
    564   const _idx = ordered.findIndex((p) => p.id === post.id);
    565   const newerPost = _idx > 0 ? ordered[_idx - 1] : null;
    566   const olderPost = (_idx >= 0 && _idx < ordered.length - 1) ? ordered[_idx + 1] : null;
    567   if (newerPost) newerPost._urlBase = urlBaseFor(newerPost);
    568   if (olderPost) olderPost._urlBase = urlBaseFor(olderPost);
     577  // Newer/Older over ALLE posts (gedeelde helper โ€” ook door de fan-gate gebruikt).
     578  const { newerPost, olderPost } = postNeighbors(site, post, isHub);
    569579
    570580  // โ”€โ”€ Related posts: same-tag matching with recency fallback โ”€โ”€โ”€โ”€โ”€
  • src/views/pages/fan-gate.ejs

    rf4b7b6a r1e2e9e7  
     1<div class="container">
     2  <%# Zelfde post-navigatie (Newer/Older) als op een gewone post, zodat een
     3      bezoeker op de fan-gate niet vastloopt maar verder kan bladeren. %>
     4  <%- include('../partials/post-nav', { newerPost: (typeof newerPost !== 'undefined' ? newerPost : null), olderPost: (typeof olderPost !== 'undefined' ? olderPost : null) }) %>
     5</div>
     6
    17<section class="fg">
    28  <div class="fg-card">
  • src/views/pages/post.ejs

    rf4b7b6a r1e2e9e7  
    77<article class="container post-page">
    88
    9   <%# Navigatie over ALLE posts โ€” BOVEN de post (Newer links, Older rechts). %>
    10   <% if (newerPost || olderPost) { %>
    11     <nav class="post-nav post-nav--top">
    12       <% if (newerPost) { %>
    13         <a class="post-nav-prev" href="<%= newerPost._urlBase || '' %>/<%= newerPost.slug %>"<% if (!newerPost._urlBase) { %>
    14            hx-get="/<%= newerPost.slug %>?partial=1" hx-target="#pcms-main"
    15            hx-push-url="/<%= newerPost.slug %>" hx-indicator="#pcms-loading"<% } %>>
    16           <span class="post-nav-label">โ† Newer</span>
    17           <span class="post-nav-title"><%= newerPost.title %></span>
    18         </a>
    19       <% } else { %>
    20         <div class="post-nav-prev is-empty" aria-hidden="true">
    21           <span class="post-nav-label">โ† Newer</span>
    22           <span class="post-nav-title">Nieuwste post</span>
    23         </div>
    24       <% } %>
    25       <% if (olderPost) { %>
    26         <a class="post-nav-next" href="<%= olderPost._urlBase || '' %>/<%= olderPost.slug %>"<% if (!olderPost._urlBase) { %>
    27            hx-get="/<%= olderPost.slug %>?partial=1" hx-target="#pcms-main"
    28            hx-push-url="/<%= olderPost.slug %>" hx-indicator="#pcms-loading"<% } %>>
    29           <span class="post-nav-label">Older โ†’</span>
    30           <span class="post-nav-title"><%= olderPost.title %></span>
    31         </a>
    32       <% } else { %>
    33         <div class="post-nav-next is-empty" aria-hidden="true">
    34           <span class="post-nav-label">Older โ†’</span>
    35           <span class="post-nav-title">Oudste post</span>
    36         </div>
    37       <% } %>
    38     </nav>
    39   <% } %>
     9  <%# Navigatie over ALLE posts โ€” BOVEN de post (gedeeld partial). %>
     10  <%- include('../partials/post-nav', { newerPost: newerPost, olderPost: olderPost }) %>
    4011
    4112  <% if (post.cover_image_url) { %>
     
    290261.tag:hover { background: var(--accent); color: white; }
    291262.post-actions { display: flex; gap: 0.5rem; margin: 2rem 0; padding: 1rem; background: var(--paper-2); border-radius: 6px; flex-wrap: wrap; }
    292 .post-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--rule); }
    293 /* Boven de post: geen lijn/gap erboven, maar een scheidingslijn ERONDER (naar de content). */
    294 .post-nav--top { margin-top: 0; padding-top: 0; border-top: 0; margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid var(--rule); }
    295 .post-nav-prev, .post-nav-next { display: flex; flex-direction: column; padding: 1rem; background: var(--paper-2); border-radius: 6px; text-decoration: none; color: var(--ink); }
    296 /* Lege plek (geen nieuwere/oudere post): zelfde kaart, uitgegrijsd + niet klikbaar,
    297    zodat elke post dezelfde twee-kaarten-structuur houdt. */
    298 .post-nav-prev.is-empty, .post-nav-next.is-empty { opacity: 0.4; cursor: default; }
    299 .post-nav-prev.is-empty .post-nav-title, .post-nav-next.is-empty .post-nav-title { font-weight: 400; font-style: italic; color: var(--ink-muted); }
    300 .post-nav-next { text-align: right; align-items: flex-end; }
    301 .post-nav-label { font-size: 0.8rem; color: var(--ink-muted); margin-bottom: 0.25rem; }
    302 .post-nav-title { font-weight: 500; }
    303 @media (max-width: 600px) { .post-nav { grid-template-columns: 1fr; } }
    304263
    305264/* ============================================================
Note: See TracChangeset for help on using the changeset viewer.