Changeset 520e477 in Klonkt


Ignore:
Timestamp:
07/20/2026 07:35:55 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
22d2f5a8
Parents:
1485933
git-author:
Robin <roboburr@…> (07/20/2026 07:26:51 AM)
git-committer:
Robin <roboburr@…> (07/20/2026 07:35:55 AM)
Message:

Feature: Load more on Solo + Cirkel, page 72 (klonkt-demo-r9u, slice 3/4)

The home feed (Solo) and the Cirkel feed now page in 72s instead of a
hard 30/80 cap. Both render two containers (list + grid, CSS-toggled),
so the append fragment (partials/home-append) sends the post-cards as
the primary beforeend swap into #post-list and OOB-appends the same
posts as tiles into #grid-tiles. htmx 1.9.12 unwraps the OOB wrapper's
children on a positional swap, so the tiles land as direct grid items
(the display:contents wrapper is a belt-and-braces fallback). The
button lives once below both views and OOB-replaces itself with the
next offset, dropping on the last page. Pinned posts stay on page 1
only. getCirkelPosts gains an offset arg; FEED_PAGE hoisted to the top
of posts.js.

Browser-verified both feeds: 72 -> 144 -> 150 in list AND grid, then
the button disappears.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

Location:
src
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • src/routes/circle.js

    r1485933 r520e477  
    4242}
    4343
     44const CIRKEL_PAGE = 72; // matches FEED_PAGE in posts.js: divisible by 2/3/4
     45
    4446router.get('/cirkel', (req, res, next) => {
    4547  const site = res.locals.site;
    4648  if (!site || !apEnabled() || (ActivityPubService.autoBoostCount(site.slug) === 0 && ActivityPubService.boostedCount(site.slug) === 0)) return next();
    4749
    48   const posts = ActivityPubService.getCirkelPosts(site.slug, 80).map((r) => {
     50  const append = req.query.append === '1';
     51  const offset = Math.max(0, parseInt(req.query.offset, 10) || 0);
     52  const rows = ActivityPubService.getCirkelPosts(site.slug, CIRKEL_PAGE + 1, offset);
     53  const hasMore = rows.length > CIRKEL_PAGE;
     54  const posts = rows.slice(0, CIRKEL_PAGE).map((r) => {
    4955    const text = tidySnippet(htmlToText(r.content));
    5056    // Show ONLY the title (the bold first line a Klonkt note carries), not the whole
     
    7783  });
    7884
     85  const moreBase = res.locals.siteUrlBase || '';
     86  if (append) {
     87    return renderPage(req, res, 'partials/home-append', { posts, hasMore, nextOffset: offset + CIRKEL_PAGE, moreBase, morePath: '/cirkel' });
     88  }
     89
    7990  const sites = ActivityPubService.getCirkelMembers(site.slug)
    8091    .map((s) => ({ name: s.name || 'Onbekend', url: safeUrl(s.url), avatar: safeUrl(s.icon) }));
    8192
    82   renderPage(req, res, 'pages/circle-feed', { pageTitle: 'Cirkel', bodyClass: 'on-cirkel', posts, sites });
     93  renderPage(req, res, 'pages/circle-feed', {
     94    pageTitle: 'Cirkel', bodyClass: 'on-cirkel', posts, sites,
     95    hasMore, nextOffset: offset + CIRKEL_PAGE, moreBase,
     96  });
    8397});
    8498
  • src/routes/posts.js

    r1485933 r520e477  
    9595const router = express.Router();
    9696
     97// Feed page size for "Load more" (Solo, News, Messages, Cirkel). 72 is divisible
     98// by 2/3/4 so every grid column count ends on a full row.
     99const FEED_PAGE = 72;
     100
    97101// ==================== UPLOAD IMAGE (cover or content) ====================
    98102// Returns JSON {url} so the editor can stick it into the cover field or
     
    210214  `).all(site.id);
    211215
    212   // Regular posts: anything with pinned = 0
    213   const posts = db.prepare(`
     216  // Regular posts: anything with pinned = 0. Paged in blocks of 72 (Load more).
     217  const append = req.query.append === '1';
     218  const offset = Math.max(0, parseInt(req.query.offset, 10) || 0);
     219  const rows = db.prepare(`
    214220    SELECT p.*, u.username as author_username
    215221    FROM posts p JOIN users u ON p.author_id = u.id
    216222    WHERE p.site_id = ? AND p.status = 'published' AND p.pinned = 0
    217223    ORDER BY p.published_at DESC
    218     LIMIT 30
    219   `).all(site.id);
     224    LIMIT ? OFFSET ?
     225  `).all(site.id, FEED_PAGE + 1, offset);
     226  const hasMore = rows.length > FEED_PAGE;
     227  const posts = rows.slice(0, FEED_PAGE);
     228  const moreBase = res.locals.siteUrlBase || '';
     229
     230  if (append) {
     231    return renderPage(req, res, 'partials/home-append', { posts, hasMore, nextOffset: offset + FEED_PAGE, moreBase });
     232  }
    220233
    221234  recordPageview(site.id, req);
     
    224237    pinnedPosts,
    225238    posts,
     239    hasMore, nextOffset: offset + FEED_PAGE, moreBase,
    226240    pageTitle: site.title,
    227241    socialDescr: site.description || site.tagline || '',
     
    893907}
    894908
    895 const FEED_PAGE = 72; // divisible by 2/3/4 → every grid column count keeps full rows
    896909router.get('/news', requireSiteManager, (req, res) => {
    897910  const site = res.locals.site;
  • src/services/ActivityPubService.js

    r1485933 r520e477  
    22452245  try { if (!_abCount) _abCount = db.prepare('SELECT COUNT(*) AS n FROM ap_following WHERE slug = ? AND auto_boost = 1'); return _abCount.get(slug).n; } catch { return 0; }
    22462246}
    2247 export function getCirkelPosts(slug, limit) {
     2247export function getCirkelPosts(slug, limit, offset) {
    22482248  try {
    22492249    // Cirkel = posts from featured (auto_boost) accounts + posts you boosted
     
    22562256      WHERE t.slug = ? AND (f.auto_boost = 1 OR t.boosted = 1)
    22572257      ORDER BY COALESCE(t.published, t.created_at) DESC, t.rowid DESC
    2258       LIMIT ?`);
    2259     return _cirkelPosts.all(slug, limit || 60);
     2258      LIMIT ? OFFSET ?`);
     2259    return _cirkelPosts.all(slug, limit || 60, offset || 0);
    22602260  } catch { return []; }
    22612261}
  • src/views/pages/circle-feed.ejs

    r1485933 r520e477  
    6767  <!-- ===== TIMELINE (standaard) ===== -->
    6868  <div class="container feed-timeline">
    69     <ul class="post-list feed-timeline-list">
     69    <ul class="post-list feed-timeline-list" id="post-list">
    7070      <% posts.forEach(function(post){ %>
    7171        <%- include('../partials/post-card', { post: post }) %>
     
    7676  <!-- ===== GRID (body[data-feed-view="grid"]) ===== -->
    7777  <section class="feed-grid container" aria-label="<%= t('cfeed.grid_view') %>">
    78     <div class="grid-tiles">
     78    <div class="grid-tiles" id="grid-tiles">
    7979      <% posts.forEach(function(post){ %>
    8080        <%- include('../partials/post-tile', { post: post }) %>
     
    8282    </div>
    8383  </section>
     84
     85  <% if (typeof hasMore !== 'undefined') { %>
     86  <div class="container feed-more-wrap">
     87    <%- include('../partials/load-more', { hasMore: hasMore, nextOffset: nextOffset, moreBase: moreBase, moreTarget: '#post-list', morePath: '/cirkel' }) %>
     88  </div>
     89  <% } %>
    8490
    8591<% } %>
  • src/views/pages/home.ejs

    r1485933 r520e477  
    3636    </div>
    3737  <% } else { %>
    38     <ul class="post-list feed-timeline-list">
     38    <ul class="post-list feed-timeline-list" id="post-list">
    3939      <% allPosts.forEach(function(post) { %>
    4040        <%- include('../partials/post-card', { post: post }) %>
     
    4848<% if (allPosts.length > 0) { %>
    4949<section class="feed-grid container" aria-label="<%= t('phome.grid_view') %>">
    50   <div class="grid-tiles">
     50  <div class="grid-tiles" id="grid-tiles">
    5151    <% allPosts.forEach(function(post) { %>
    5252      <%- include('../partials/post-tile', { post: post }) %>
     
    5555</section>
    5656<% } %>
     57<% if (typeof hasMore !== 'undefined' && allPosts.length > 0) { %>
     58<div class="container feed-more-wrap">
     59  <%- include('../partials/load-more', { hasMore: hasMore, nextOffset: nextOffset, moreBase: moreBase, moreTarget: '#post-list', morePath: '/' }) %>
     60</div>
     61<% } %>
Note: See TracChangeset for help on using the changeset viewer.