| 1 | <%
|
|---|
| 2 | // Homepage — feed of posts.
|
|---|
| 3 | //
|
|---|
| 4 | // Renders BOTH a timeline view (.post-list, default) AND a grid view
|
|---|
| 5 | // (.feed-grid, square Instagram-style tiles). CSS toggles between the
|
|---|
| 6 | // two based on body[data-feed-view="timeline"|"grid"]. This mirrors the
|
|---|
| 7 | // v9 pattern in lib/template-content.php so the same view-switcher works.
|
|---|
| 8 | //
|
|---|
| 9 | // Pinned posts sort first inside the timeline list; they are not given a
|
|---|
| 10 | // separate "Pinned" header (v9 just lets order speak for itself + a ★ chip).
|
|---|
| 11 |
|
|---|
| 12 | const allPosts = [...pinnedPosts, ...posts];
|
|---|
| 13 | const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
|
|---|
| 14 | %>
|
|---|
| 15 |
|
|---|
| 16 | <%# View switcher used to be rendered here; moved to shell.ejs (above
|
|---|
| 17 | #pcms-main) so it survives HTMX swaps and is visible on every
|
|---|
| 18 | non-admin page. %>
|
|---|
| 19 |
|
|---|
| 20 | <%# FEP-7628 slice 3: this account moved. The signpost comes FIRST, before
|
|---|
| 21 | any post: a visitor should not scroll a dead feed to find out we left.
|
|---|
| 22 | typeof-guard because an old route may render this template for one
|
|---|
| 23 | request right after a deploy (view cache fills on first hit). %>
|
|---|
| 24 | <% if (typeof movedTo !== 'undefined' && movedTo) { %>
|
|---|
| 25 | <div class="container">
|
|---|
| 26 | <aside class="moved-banner">
|
|---|
| 27 | <p class="moved-banner-lead"><%= t('phome.moved_lead') %></p>
|
|---|
| 28 | <a class="moved-banner-link" href="<%= movedTo %>" rel="me noopener"><%= movedToLabel || movedTo %></a>
|
|---|
| 29 | <p class="moved-banner-hint"><%= t('phome.moved_hint') %></p>
|
|---|
| 30 | </aside>
|
|---|
| 31 | </div>
|
|---|
| 32 | <% } %>
|
|---|
| 33 |
|
|---|
| 34 | <!-- ========== TIMELINE ========== -->
|
|---|
| 35 | <div class="container feed-timeline">
|
|---|
| 36 |
|
|---|
| 37 | <% if (allPosts.length === 0) { %>
|
|---|
| 38 | <div class="empty">
|
|---|
| 39 | <h1><%= t('phome.empty_title') %></h1>
|
|---|
| 40 | <p><%= t('phome.empty_sub') %></p>
|
|---|
| 41 | <% if (user && canMutate && permissions.canCreatePost(user, site)) { %>
|
|---|
| 42 | <p style="margin-top:2rem">
|
|---|
| 43 | <a class="btn" href="<%= _base %>/posts/new"
|
|---|
| 44 | hx-get="<%= _base %>/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
|
|---|
| 45 | hx-push-url="<%= _base %>/posts/new" hx-indicator="#pcms-loading">
|
|---|
| 46 | <%= t('phome.write_first') %> →
|
|---|
| 47 | </a>
|
|---|
| 48 | </p>
|
|---|
| 49 | <% } %>
|
|---|
| 50 | </div>
|
|---|
| 51 | <% } else { %>
|
|---|
| 52 | <ul class="post-list feed-timeline-list" id="post-list">
|
|---|
| 53 | <% allPosts.forEach(function(post) { %>
|
|---|
| 54 | <%- include('../partials/post-card', { post: post }) %>
|
|---|
| 55 | <% }); %>
|
|---|
| 56 | </ul>
|
|---|
| 57 | <% } %>
|
|---|
| 58 |
|
|---|
| 59 | </div>
|
|---|
| 60 |
|
|---|
| 61 | <!-- ========== LEZEN (alleen zichtbaar bij body[data-feed-view="reader"]) ==========
|
|---|
| 62 | Hetzelfde stel berichten, maar heel: één per scherm, met de grenzen door
|
|---|
| 63 | CSS-snapping. Geen eigen route en geen eigen shell -- de balken blijven
|
|---|
| 64 | staan, want je bent nog gewoon op de feed en moet terug kunnen schakelen.
|
|---|
| 65 | Leeg? Dan valt de lege staat van de tijdlijn hierboven in beeld; deze
|
|---|
| 66 | sectie rendert dan niet. -->
|
|---|
| 67 | <% if (typeof readerItems !== 'undefined' && readerItems && readerItems.length) { %>
|
|---|
| 68 | <section class="feed-reader" aria-label="<%= t('switch.reader') %>">
|
|---|
| 69 | <%# Terug naar boven. Staat IN de HTML en niet in JavaScript, zodat hij er ook
|
|---|
| 70 | is als de module niet laadt -- dan doet hij niets, maar hij springt ook niet
|
|---|
| 71 | opeens in beeld. Verschijnt pas als je ver genoeg bent (mod/read.js). %>
|
|---|
| 72 | <button type="button" id="read-top" class="read-top" aria-label="<%= t('read.to_top') %>" title="<%= t('read.to_top') %>">
|
|---|
| 73 | <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
|
|---|
| 74 | </button>
|
|---|
| 75 | <div class="read-stream" id="read-stream" data-base="<%= _base %>">
|
|---|
| 76 | <% readerItems.forEach(function(it) { %>
|
|---|
| 77 | <%- include('../partials/read-article', { post: it.post, entry: it.entry }) %>
|
|---|
| 78 | <% }); %>
|
|---|
| 79 | </div>
|
|---|
| 80 | </section>
|
|---|
| 81 | <% } %>
|
|---|
| 82 |
|
|---|
| 83 | <!-- ========== GRID (only visible when body[data-feed-view="grid"]) ========== -->
|
|---|
| 84 | <% if (allPosts.length > 0) { %>
|
|---|
| 85 | <section class="feed-grid container" aria-label="<%= t('phome.grid_view') %>">
|
|---|
| 86 | <div class="grid-tiles" id="grid-tiles">
|
|---|
| 87 | <% allPosts.forEach(function(post) { %>
|
|---|
| 88 | <%- include('../partials/post-tile', { post: post }) %>
|
|---|
| 89 | <% }); %>
|
|---|
| 90 | </div>
|
|---|
| 91 | </section>
|
|---|
| 92 | <% } %>
|
|---|
| 93 | <% if (typeof hasMore !== 'undefined' && allPosts.length > 0) { %>
|
|---|
| 94 | <div class="container feed-more-wrap">
|
|---|
| 95 | <%- include('../partials/load-more', { hasMore: hasMore, nextOffset: nextOffset, moreBase: moreBase, moreTarget: '#post-list', morePath: '/' }) %>
|
|---|
| 96 | </div>
|
|---|
| 97 | <% } %>
|
|---|