source: Klonkt/src/views/pages/circle-feed.ejs@ 462a931

main
Last change on this file since 462a931 was 462a931, checked in by roboburr <roboburr@…>, 2 months ago

feat(circle): filter the Circle feed by member (clickable chips + Following "Show" link)

The member chips on the Circle page are now clickable filters: clicking one shows only that
account's posts, with an "Everyone" chip to clear and an active highlight. The Following page
gets a "Show" link per featured account that lands on the filtered Circle view. So Featured =
"include in my Circle", and Show lets you view just that one person here.

  • src/services/ActivityPubService.js — getCirkelPosts(slug, limit, actorUri) filter; members expose actor_uri
  • src/routes/circle.js — honour ?actor=<uri> (validated against the member list) + active flags
  • src/views/pages/circle-feed.ejs — chips + modal as filter links + Everyone + active state
  • src/views/pages/following.ejs — Show link for featured accounts
  • src/services/i18n.js — cfeed.all, tl.show (nl/en/de)

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

  • Property mode set to 100644
File size: 8.9 KB
RevLine 
[8ea3d0d]1<div class="container cirkel-hero">
[050aa02]2 <span class="cirkel-badge" aria-hidden="true">
3 <svg viewBox="0 0 100 100" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
4 <defs>
[5eff581]5 <marker id="cf-a" viewBox="0 0 10 10" refX="6.5" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M1.5 1.5 L8.5 5 L1.5 8.5 Z" fill="currentColor" stroke="none"/></marker>
[050aa02]6 </defs>
[b4c845e]7 <line x1="28.95" y1="43.5" x2="63.05" y2="26.5" stroke-width="2.6" marker-start="url(#cf-a)" marker-end="url(#cf-a)"/>
8 <line x1="72" y1="32" x2="72" y2="66" stroke-width="2.6" marker-end="url(#cf-a)"/>
9 <line x1="28.8" y1="52.7" x2="63.2" y2="71.3" stroke-width="2.6" marker-end="url(#cf-a)"/>
10 <circle cx="20" cy="48" r="7" stroke-width="2.4"/>
11 <circle cx="72" cy="22" r="7" stroke-width="2.4"/>
12 <circle cx="72" cy="76" r="7" stroke-width="2.4"/>
[050aa02]13 </svg>
14 </span>
[4885eab]15 <h1><%= t('cfeed.title') %></h1>
[462a931]16 <p class="cirkel-sub"><%= (typeof activeName !== 'undefined' && activeName) ? activeName : t('cfeed.sub') %></p>
17 <% var _everyoneActive = (typeof activeActor === 'undefined' || !activeActor); %>
[8ea3d0d]18 <% if (typeof sites !== 'undefined' && sites && sites.length) { %>
[3f893bf]19 <% if (sites.length <= 5) { %>
[8ea3d0d]20 <div class="cirkel-members">
[462a931]21 <a class="cirkel-member cirkel-everyone <%= _everyoneActive ? 'is-active' : '' %>" href="/cirkel"><span class="cirkel-member-name"><%= t('cfeed.all') %></span></a>
[3f893bf]22 <% sites.forEach(function(s){ %>
[462a931]23 <% if (s.actor_uri) { %><a class="cirkel-member <%= s.active ? 'is-active' : '' %>" href="/cirkel?actor=<%= encodeURIComponent(s.actor_uri) %>" title="<%= s.name %>"><% } else { %><span class="cirkel-member" title="<%= s.name %>"><% } %>
[aadb11e]24 <span class="cirkel-avatar"<% if (s.avatar) { %> style="background-image:url('<%= s.avatar %>');background-color:transparent"<% } %>><% if (!s.avatar) { %><%= (s.name || '?').charAt(0).toUpperCase() %><% } %></span>
[8ea3d0d]25 <span class="cirkel-member-name"><%= s.name %></span>
[462a931]26 <% if (s.actor_uri) { %></a><% } else { %></span><% } %>
[8ea3d0d]27 <% }); %>
28 </div>
[3f893bf]29 <% } else { %>
[3f9f71d]30 <%# More than 5: the count pill becomes a button that opens a modal listing every
31 member (avatar + name + link). CSS-only via a hidden checkbox — no JS, so it's
32 CSP- (no inline handler) and htmx-safe. Checkbox must precede .cirkel-modal so
33 the `:checked ~` sibling selector matches. %>
34 <input type="checkbox" id="cirkel-more-cb" class="cirkel-more-cb" hidden>
35 <label for="cirkel-more-cb" class="cirkel-count cirkel-count-btn" role="button" tabindex="0"><%= t('cfeed.count', { n: sites.length }) %></label>
36 <div class="cirkel-modal" role="dialog" aria-modal="true" aria-label="<%= t('cfeed.title') %>">
37 <label for="cirkel-more-cb" class="cirkel-modal-backdrop" aria-label="<%= t('cfeed.close') %>"></label>
38 <div class="cirkel-modal-card">
39 <label for="cirkel-more-cb" class="cirkel-modal-x" aria-label="<%= t('cfeed.close') %>" role="button" tabindex="0">&times;</label>
40 <h2 class="cirkel-modal-h"><%= t('cfeed.count', { n: sites.length }) %></h2>
41 <ul class="cirkel-modal-list">
[462a931]42 <li>
43 <a class="cirkel-modal-link <%= _everyoneActive ? 'is-active' : '' %>" href="/cirkel">
44 <span class="cirkel-member-name"><%= t('cfeed.all') %></span>
45 </a>
46 </li>
[3f9f71d]47 <% sites.forEach(function(s){ %>
48 <li>
[462a931]49 <% if (s.actor_uri) { %><a class="cirkel-modal-link <%= s.active ? 'is-active' : '' %>" href="/cirkel?actor=<%= encodeURIComponent(s.actor_uri) %>"><% } else { %><span class="cirkel-modal-link"><% } %>
[3f9f71d]50 <span class="cirkel-avatar"<% if (s.avatar) { %> style="background-image:url('<%= s.avatar %>');background-color:transparent"<% } %>><% if (!s.avatar) { %><%= (s.name || '?').charAt(0).toUpperCase() %><% } %></span>
51 <span class="cirkel-member-name"><%= s.name %></span>
[462a931]52 <% if (s.actor_uri) { %></a><% } else { %></span><% } %>
[3f9f71d]53 </li>
54 <% }); %>
55 </ul>
56 </div>
57 </div>
[04a6b15]58 <% } %>
[8ea3d0d]59 <% } %>
[43dbf9c]60</div>
[0091cb7]61
[43dbf9c]62<% if (!posts.length) { %>
63 <div class="container">
[4885eab]64 <p class="cirkel-empty"><%= t('cfeed.empty') %></p>
[43dbf9c]65 </div>
66<% } else { %>
67
[834bcc3]68 <%# Same view as the home page: timeline (post-card) + grid (post-tile),
69 switched by the view-switcher via body[data-feed-view]. The remote posts
70 are shaped to the local post format in circle.js (with external_url,
71 so cards link to the source site). %>
[43dbf9c]72
73 <!-- ===== TIMELINE (standaard) ===== -->
74 <div class="container feed-timeline">
75 <ul class="post-list feed-timeline-list">
76 <% posts.forEach(function(post){ %>
77 <%- include('../partials/post-card', { post: post }) %>
78 <% }); %>
79 </ul>
80 </div>
81
82 <!-- ===== GRID (body[data-feed-view="grid"]) ===== -->
[4885eab]83 <section class="feed-grid container" aria-label="<%= t('cfeed.grid_view') %>">
[43dbf9c]84 <div class="grid-tiles">
85 <% posts.forEach(function(post){ %>
86 <%- include('../partials/post-tile', { post: post }) %>
[0091cb7]87 <% }); %>
88 </div>
[43dbf9c]89 </section>
90
91<% } %>
[0091cb7]92
93<style>
[8ea3d0d]94.cirkel-hero { text-align: center; max-width: 640px; margin: 2.5rem auto 2rem; }
[050aa02]95.cirkel-badge { display: block; width: 100px; height: 100px; margin: 0 auto .7rem; color: var(--accent);
96 filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 65%, transparent)) drop-shadow(0 0 1px var(--accent)); }
[8ea3d0d]97.cirkel-badge svg { width: 100%; height: 100%; }
98.cirkel-hero h1 { font-family: var(--font-display, serif); font-size: 2.4rem; margin: 0 0 .3rem; }
99.cirkel-sub { color: var(--ink-muted); margin: 0 0 1.25rem; font-size: 1.02rem; }
100.cirkel-members { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: center; }
101.cirkel-member { display: inline-flex; align-items: center; gap: .45rem;
102 padding: .28rem .8rem .28rem .3rem; border: 1px solid var(--rule); border-radius: 999px;
103 text-decoration: none; color: var(--ink); background: var(--paper-2);
104 transition: border-color .15s, transform .15s; }
105.cirkel-member:hover { border-color: var(--accent); transform: translateY(-1px); }
[462a931]106.cirkel-member.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
107.cirkel-everyone { padding: .28rem .9rem; }
108.cirkel-modal-link.is-active { background: color-mix(in srgb, var(--accent) 14%, transparent); }
[8ea3d0d]109.cirkel-avatar { width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
110 background-size: cover; background-position: center; background-color: var(--accent);
111 color: #fff; display: inline-flex; align-items: center; justify-content: center;
112 font-weight: 700; font-size: .82rem; }
113.cirkel-member-name { font-size: .9rem; font-weight: 600; }
[3f893bf]114.cirkel-count { display: inline-block; color: var(--ink-muted); font-size: .92rem; font-weight: 600;
115 padding: .35rem 1rem; border: 1px solid var(--rule); border-radius: 999px; background: var(--paper-2); }
[3f9f71d]116.cirkel-count-btn { cursor: pointer; user-select: none; transition: border-color .15s, color .15s, transform .15s; }
117.cirkel-count-btn:hover { border-color: var(--accent); color: var(--ink); transform: translateY(-1px); }
[8ea3d0d]118.cirkel-empty { color: var(--ink-muted); text-align: center; margin: 2rem auto; }
[43dbf9c]119.cirkel-empty a { color: var(--accent); }
[3f9f71d]120/* CSS-only "more members" modal (toggled by the hidden checkbox, no JS) */
121.cirkel-more-cb { position: absolute; width: 0; height: 0; opacity: 0; pointer-events: none; }
122.cirkel-modal { display: none; position: fixed; inset: 0; z-index: 1000; align-items: center; justify-content: center; padding: 1rem; }
123.cirkel-more-cb:checked ~ .cirkel-modal { display: flex; }
124.cirkel-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.45); cursor: pointer; }
125.cirkel-modal-card { position: relative; background: var(--paper); color: var(--ink); border-radius: 18px;
126 padding: 1.4rem 1.2rem; width: min(420px, 92vw); max-height: 78vh; overflow-y: auto; text-align: left;
127 box-shadow: 0 20px 60px rgba(0,0,0,.32); }
128.cirkel-modal-x { position: absolute; top: .55rem; right: .65rem; width: 34px; height: 34px; border-radius: 50%;
129 display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1.45rem;
130 line-height: 1; color: var(--ink-muted); background: var(--paper-2); }
131.cirkel-modal-x:hover { color: var(--ink); }
132.cirkel-modal-h { font-family: var(--font-display, serif); font-size: 1.3rem; margin: .1rem 0 1rem; text-align: center; }
133.cirkel-modal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .25rem; }
134.cirkel-modal-link { display: flex; align-items: center; gap: .6rem; padding: .45rem .5rem; border-radius: 12px;
135 text-decoration: none; color: var(--ink); transition: background .15s; }
136.cirkel-modal-link:hover { background: var(--paper-2); }
137.cirkel-modal-go { margin-left: auto; color: var(--accent); font-weight: 700; }
[0091cb7]138</style>
Note: See TracBrowser for help on using the repository browser.