Changeset 43dbf9c in Klonkt


Ignore:
Timestamp:
06/16/2026 01:52:09 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
9f81a09
Parents:
aa7f548
Message:

Circles: /cirkel uses the home view (timeline + grid + switcher)

  • post-card/post-tile external-aware: post.external_url -> external link (target=_blank, no htmx) instead of local /slug. Additive; normal posts unchanged. post-card shows 'via <source>' when external.
  • circle.js maps remote_posts to the local post shape + bodyClass on-cirkel.
  • circle-feed.ejs: feed-timeline (.post-list) + feed-grid (.grid-tiles), same classes as home -> the view-switcher works. shell: on-cirkel = feed-page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

Location:
src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/routes/circle.js

    raa7f548 r43dbf9c  
    3737      .filter((m) => m.url);
    3838    const image = media.find((m) => m.type === 'image') || null;
     39    // Vorm de remote-post naar het lokale post-shape zodat post-card/post-tile
     40    // (dezelfde timeline/grid-view als de home) 'm renderen. external_url maakt
     41    // de partials extern-linkend (naar de bron) i.p.v. lokaal/htmx.
    3942    return {
    4043      id: r.id,
     44      slug: encodeURIComponent(r.id),
    4145      title: r.title || '(zonder titel)',
    42       summary: r.summary || '',
    43       url: safeUrl(r.url),
    44       published: r.published,
    45       actorName: r.actor_name || 'Onbekend',
    46       actorUrl: safeUrl(r.actor_url),
    47       actorAvatar: safeUrl(r.actor_avatar),
    48       image: image ? image.url : null,
     46      excerpt: r.summary || '',
     47      cover_image_url: image ? image.url : null,
     48      published_at: r.published,
     49      created_at: r.published,
     50      type: 'post',
     51      tags: '',
     52      pinned: 0,
     53      status: 'published',
     54      external_url: safeUrl(r.url),
     55      source_name: r.actor_name || 'Onbekend',
     56      source_url: safeUrl(r.actor_url),
    4957    };
    5058  });
    5159
    52   renderPage(req, res, 'pages/circle-feed', { pageTitle: 'Cirkel', posts });
     60  renderPage(req, res, 'pages/circle-feed', { pageTitle: 'Cirkel', bodyClass: 'on-cirkel', posts });
    5361});
    5462
  • src/views/pages/circle-feed.ejs

    raa7f548 r43dbf9c  
    1 <div class="container circle-page">
    2   <header class="circle-head">
    3     <h1>Cirkel</h1>
    4     <p class="circle-sub">Wat er speelt bij de andere sites in deze cirkel.</p>
    5   </header>
     1<div class="container cirkel-head">
     2  <h1>Cirkel</h1>
     3  <p class="cirkel-sub">Wat er speelt bij de andere sites in deze cirkel.</p>
     4</div>
    65
    7   <% if (!posts.length) { %>
    8     <p class="circle-empty">Nog niets in je cirkel. Voeg sites toe via
     6<% if (!posts.length) { %>
     7  <div class="container">
     8    <p class="cirkel-empty">Nog niets in je cirkel. Voeg sites toe via
    99      <a href="/admin/circle">Beheer &rsaquo; Cirkel</a> en ververs ze.</p>
    10   <% } else { %>
    11     <div class="circle-grid">
    12       <% posts.forEach(function(p){ %>
    13         <article class="circle-card">
    14           <% if (p.image) { %>
    15             <a class="cc-cover" <% if (p.url) { %>href="<%= p.url %>" target="_blank" rel="noopener"<% } %>>
    16               <img loading="lazy" src="<%= p.image %>" alt="">
    17             </a>
    18           <% } %>
    19           <div class="cc-body">
    20             <div class="cc-src">
    21               <% if (p.actorAvatar) { %><img class="cc-avatar" src="<%= p.actorAvatar %>" alt=""><% } %>
    22               <% if (p.actorUrl) { %>
    23                 <a href="<%= p.actorUrl %>" target="_blank" rel="noopener"><%= p.actorName %></a>
    24               <% } else { %><span><%= p.actorName %></span><% } %>
    25             </div>
    26             <h2 class="cc-title">
    27               <% if (p.url) { %><a href="<%= p.url %>" target="_blank" rel="noopener"><%= p.title %></a>
    28               <% } else { %><%= p.title %><% } %>
    29             </h2>
    30             <% if (p.summary) { %><p class="cc-summary"><%= p.summary %></p><% } %>
    31             <div class="cc-foot">
    32               <% if (p.published) { %><span class="cc-date"><%= p.published.slice(0,10) %></span><% } %>
    33               <% if (p.url) { %><a class="cc-link" href="<%= p.url %>" target="_blank" rel="noopener">Lees bij de bron &rarr;</a><% } %>
    34             </div>
    35           </div>
    36         </article>
     10  </div>
     11<% } else { %>
     12
     13  <%# Dezelfde view als de hoofdpagina: timeline (post-card) + grid (post-tile),
     14      gewisseld door de view-switcher via body[data-feed-view]. De remote-posts
     15      zijn in circle.js naar het lokale post-shape gevormd (met external_url,
     16      zodat de kaarten naar de bron-site linken). %>
     17
     18  <!-- ===== TIMELINE (standaard) ===== -->
     19  <div class="container feed-timeline">
     20    <ul class="post-list feed-timeline-list">
     21      <% posts.forEach(function(post){ %>
     22        <%- include('../partials/post-card', { post: post }) %>
     23      <% }); %>
     24    </ul>
     25  </div>
     26
     27  <!-- ===== GRID (body[data-feed-view="grid"]) ===== -->
     28  <section class="feed-grid container" aria-label="Grid-weergave">
     29    <div class="grid-tiles">
     30      <% posts.forEach(function(post){ %>
     31        <%- include('../partials/post-tile', { post: post }) %>
    3732      <% }); %>
    3833    </div>
    39   <% } %>
    40 </div>
     34  </section>
     35
     36<% } %>
    4137
    4238<style>
    43 .circle-page { max-width: 1000px; margin: 2.5rem auto; padding: 0 1rem; }
    44 .circle-head h1 { font-family: var(--font-display, serif); font-size: 2rem; margin: 0 0 .25rem; }
    45 .circle-sub { color: var(--ink-muted); margin: 0 0 2rem; }
    46 .circle-empty { color: var(--ink-muted); }
    47 .circle-empty a { color: var(--accent); }
    48 .circle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.1rem; }
    49 .circle-card { display: flex; flex-direction: column; border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; background: var(--paper-2); }
    50 .cc-cover { display: block; aspect-ratio: 16/9; overflow: hidden; }
    51 .cc-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
    52 .cc-body { padding: .9rem 1rem 1rem; display: flex; flex-direction: column; gap: .5rem; }
    53 .cc-src { display: flex; align-items: center; gap: .45rem; font-size: .8rem; color: var(--ink-muted); }
    54 .cc-src a { color: var(--ink-muted); text-decoration: none; }
    55 .cc-src a:hover { color: var(--accent); }
    56 .cc-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; }
    57 .cc-title { font-family: var(--font-display, serif); font-size: 1.15rem; margin: 0; line-height: 1.25; }
    58 .cc-title a { color: var(--ink); text-decoration: none; }
    59 .cc-title a:hover { color: var(--accent); }
    60 .cc-summary { margin: 0; font-size: .9rem; color: var(--ink-soft, var(--ink-muted)); line-height: 1.5;
    61   display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
    62 .cc-foot { display: flex; justify-content: space-between; align-items: center; gap: .5rem; margin-top: auto; padding-top: .35rem; font-size: .78rem; }
    63 .cc-date { color: var(--ink-muted); }
    64 .cc-link { color: var(--accent); text-decoration: none; white-space: nowrap; }
     39.cirkel-head { margin-top: 2rem; }
     40.cirkel-head h1 { font-family: var(--font-display, serif); font-size: 2rem; margin: 0 0 .25rem; }
     41.cirkel-sub { color: var(--ink-muted); margin: 0 0 1.5rem; }
     42.cirkel-empty { color: var(--ink-muted); }
     43.cirkel-empty a { color: var(--accent); }
    6544</style>
  • src/views/partials/post-card.ejs

    raa7f548 r43dbf9c  
    1212
    1313const _base      = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
    14 const _href      = _base + '/' + post.slug;
     14const _external  = post.external_url || null;
     15const _href      = _external || (_base + '/' + post.slug);
     16const _ext       = !!_external;
     17const _src       = post.source_name || '';
    1518const _hasCover  = !!post.cover_image_url;
    1619const _typeLabel = (post.type && post.type !== 'overig' && post.type !== 'post') ? post.type : '';
     
    4346  <% if (_hasCover) { %>
    4447    <a class="post-list-cover" href="<%= _href %>"
    45        hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    46        hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"
     48       <% if (_ext) { %>target="_blank" rel="noopener"<% } else { %>hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"<% } %>
    4749       aria-label="<%= post.title || '(zonder titel)' %>" tabindex="-1">
    4850      <img src="<%= post.cover_image_url %>" alt="" loading="lazy" decoding="async">
     
    5456      <% if (_isPinned) { %><span class="post-list-pin" aria-label="Vastgepind">★</span><% } %>
    5557      <time datetime="<%= post.published_at || post.created_at %>"><%= _ddmmyyyy(post.published_at || post.created_at) %></time>
     58      <% if (_src) { %><span class="post-list-type">&middot; via <%= _src %></span><% } %>
    5659      <% if (_typeLabel) { %>
    5760        <span class="post-list-type">· <%= _typeLabel.charAt(0).toUpperCase() + _typeLabel.slice(1) %></span>
     
    6467    <h3 class="post-list-title">
    6568      <a href="<%= _href %>"
    66          hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    67          hx-push-url="<%= _href %>" hx-indicator="#pcms-loading">
     69         <% if (_ext) { %>target="_blank" rel="noopener"<% } else { %>hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"<% } %>>
    6870        <%= post.title || '(zonder titel)' %>
    6971      </a>
  • src/views/partials/post-tile.ejs

    raa7f548 r43dbf9c  
    88const _typeLabel = (post.type && post.type !== 'post') ? post.type : '';
    99const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
    10 const _href = _base + '/' + post.slug;
     10const _external = post.external_url || null;
     11const _href = _external || (_base + '/' + post.slug);
     12const _ext = !!_external;
    1113%>
    1214<a class="grid-tile<%= _hasCover ? '' : ' grid-tile-gradient' %><%= _isPinned ? ' is-pinned' : '' %>"
    1315   href="<%= _href %>"
    1416   <% if (_hasCover) { %>style="background-image: url('<%= post.cover_image_url %>'); --tile-hue: <%= _tileHue %>;"<% } else { %>style="--tile-hue: <%= _tileHue %>;"<% } %>
    15    hx-get="<%= _href %>?partial=1"
    16    hx-target="#pcms-main"
    17    hx-swap="innerHTML"
    18    hx-push-url="<%= _href %>"
    19    hx-indicator="#pcms-loading">
     17   <% if (_ext) { %>target="_blank" rel="noopener"<% } else { %>hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"<% } %>>
    2018
    2119  <% if (_typeLabel) { %>
  • src/views/shell.ejs

    raa7f548 r43dbf9c  
    385385  // navigate to home in the chosen view, so the switcher is never
    386386  // a dead control.
    387   var FEED_PAGE_CLASSES = ['on-home', 'on-archive', 'on-tag', 'on-type', 'on-user'];
     387  var FEED_PAGE_CLASSES = ['on-home', 'on-archive', 'on-tag', 'on-type', 'on-user', 'on-cirkel'];
    388388  function isFeedPage() {
    389389    for (var i = 0; i < FEED_PAGE_CLASSES.length; i++) {
Note: See TracChangeset for help on using the changeset viewer.