source: Klonkt/src/views/pages/connect.ejs@ 439f095

main
Last change on this file since 439f095 was 439f095, checked in by Robin Genis <roboburr@…>, 6 weeks ago

De guardians-lijst verhuist van Berichten naar Connect

Connect is de pagina die vertelt met wie je verbonden bent, en een guardian is
precies de verbinding die een kind nooit zou moeten hoeven zoeken. In Berichten
stond hij boven een stroom meldingen, waar hij bij elk bezoek langsschuift; op
Connect staat hij bij de andere relaties, met een eigen kader omdat het geen
mensen zijn die je zelf gekozen hebt te volgen.

De stippen en de labels gaan mee zoals ze waren: beschikbaar, afwezig tot een
datum, offline. In Berichten blijft het guardianship-aanbod staan, want dat is
wel een bericht: er wordt iets aan je gevraagd.

Changed files:
src/routes/posts.js

  • myGuardians van /messages naar /connect, met de handle-helper mee; de helper in /messages was daarmee dood en is weg

src/views/pages/messages.ejs

  • het blok en zijn opmaak eruit

src/views/pages/connect.ejs

  • de guardians-sectie bovenaan, met eigen opmaak

remarks: in de browser nagelopen, ook op 375px: drie guardians in drie staten
op Connect, Berichten houdt alleen nog het aanbod. 276 tests groen.

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

  • Property mode set to 100644
File size: 10.2 KB
Line 
1<div class="tl-wrap">
2 <%- include('../partials/fedi-tabs', { active: 'connect' }) %>
3 <h1 class="tl-title"><%= t('tl.connect') %></h1>
4 <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success %></div><% } %>
5 <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error %></div><% } %>
6
7 <%# FEP-633c §2 + §3.6: who guards this account, and how available each of
8 them is. Connect is the "who am I connected to" page, and a guardian is
9 the one connection a ward should never have to hunt for. Availability in
10 buddy-list language on the responsibility axis; the labels are UI, the
11 wire keeps the spec terms. Never public: this page is the owner's. %>
12 <% if (typeof myGuardians !== 'undefined' && myGuardians.length) { %>
13 <section class="conn-guardians">
14 <h2 class="conn-gtitle">&#128737; <%= t('msg.guardians_label') %></h2>
15 <ul class="conn-glist">
16 <% myGuardians.forEach(function(g){
17 var _st = g.availability === 'away' ? 'away' : (g.availability === 'dormant' ? 'dormant' : 'active');
18 var _lbl = _st === 'away' ? t('msg.g_away', { date: g.awayUntil ? formatDate(g.awayUntil) : '?' })
19 : (_st === 'dormant' ? t('msg.g_offline') : t('msg.g_available')); %>
20 <li class="conn-guardian">
21 <span class="conn-gdot conn-gdot--<%= _st %>" aria-hidden="true"></span>
22 <span class="conn-ghandle"><%= g.handle %></span>
23 <span class="conn-gstate"><%= _lbl %></span>
24 </li>
25 <% }); %>
26 </ul>
27 </section>
28 <% } %>
29
30 <form method="post" action="/news/follow" class="tl-follow">
31 <input type="text" name="handle" placeholder="@naam@server.social of site.com" autocomplete="off" spellcheck="false" required>
32 <button type="submit" class="btn btn-primary"><%= t('tl.follow_btn') %></button>
33 <label class="tl-follow-ab" title="<%= t('tl.autoboost_hint') %>"><input type="checkbox" name="auto_boost" value="1"> &#10024; <%= t('tl.autoboost_follow') %></label>
34 </form>
35
36 <%
37 var conns = connections || [];
38 var reachable = conns.filter(function (c) { return !c.unreachable; });
39 var unreachable = conns.filter(function (c) { return c.unreachable; });
40 // Display fields for one connection: name/handle (fallback: derive from actor_uri).
41 function connView(c) {
42 var host = '', user = '';
43 try { var u = new URL(c.actor_uri); host = u.hostname; user = (u.pathname.split('/').filter(Boolean).pop() || ''); } catch (e) {}
44 var handle = c.handle || (user ? ('@' + user + '@' + host) : c.actor_uri);
45 var display = c.name || handle;
46 return { handle: handle, display: display,
47 initial: String(display || '?').replace(/^@/, '').charAt(0).toUpperCase(),
48 profileUrl: c.url || c.actor_uri };
49 }
50 function arrow(dir) { return dir === 'mutual' ? '↔' : (dir === 'following' ? '→' : '←'); }
51 %>
52
53 <% if (reachable.length) { %>
54 <ul class="tl-foll-list">
55 <% reachable.forEach(function (c) { var v = connView(c); %>
56 <li class="tl-foll">
57 <span class="tl-foll-av"><% if (c.icon) { %><img src="<%= avatar(c.icon, 96) %>" alt=""><% } else { %><%= v.initial %><% } %></span>
58 <span class="tl-foll-meta">
59 <a href="<%= v.profileUrl %>" target="_blank" rel="nofollow noopener"><%= v.display %></a>
60 <span class="conn-sub">
61 <span class="conn-dir conn-dir--<%= c.direction %>"><%= arrow(c.direction) %> <%= t('tl.dir_' + c.direction) %></span>
62 <% if (c.follower && c.last_delivery_at) { %><span class="conn-deliv">&middot; <%= t('tl.last_delivery') %>: <%= formatDateTime(c.last_delivery_at) %></span><% } %>
63 </span>
64 </span>
65 <% if (c.following) { %>
66 <form method="post" action="/news/autoboost" class="tl-foll-ab" title="<%= t('tl.autoboost_hint') %>">
67 <input type="hidden" name="actor_uri" value="<%= c.actor_uri %>">
68 <label><input type="checkbox" name="auto_boost" value="1" <%= c.auto_boost ? 'checked' : '' %> data-autosubmit> &#10024; <%= t('tl.autoboost') %></label>
69 </form>
70 <form method="post" action="/news/unfollow"><input type="hidden" name="actor_uri" value="<%= c.actor_uri %>"><button type="submit" class="tl-unfollow"><%= t('tl.unfollow') %></button></form>
71 <% } %>
72 </li>
73 <% }); %>
74 </ul>
75 <% } else { %>
76 <p class="tl-empty"><%= t('tl.connect_empty') %></p>
77 <% } %>
78
79 <% if (unreachable.length) { %>
80 <details class="conn-unreach">
81 <summary>&#9888; <%= t('tl.unreachable') %> (<%= unreachable.length %>)</summary>
82 <p class="conn-unreach-lead"><%= t('tl.unreachable_lead') %></p>
83 <ul class="tl-foll-list">
84 <% unreachable.forEach(function (c) { var v = connView(c); %>
85 <li class="tl-foll is-stale">
86 <span class="tl-foll-av"><% if (c.icon) { %><img src="<%= avatar(c.icon, 96) %>" alt=""><% } else { %><%= v.initial %><% } %></span>
87 <span class="tl-foll-meta">
88 <a href="<%= v.profileUrl %>" target="_blank" rel="nofollow noopener"><%= v.display %></a>
89 <span class="conn-sub">
90 <span class="conn-dir conn-dir--<%= c.direction %>"><%= arrow(c.direction) %> <%= t('tl.dir_' + c.direction) %></span>
91 <% if (c.last_delivery_at) { %><span class="conn-deliv">&middot; <%= t('tl.last_delivery') %>: <%= formatDateTime(c.last_delivery_at) %></span><% } else { %><span class="conn-never">&middot; <%= t('tl.never_delivered') %></span><% } %>
92 <% if (c.last_error_at) { %><span class="conn-err">&middot; <%= t('tl.delivery_failed') %>: <%= formatDateTime(c.last_error_at) %></span><% } %>
93 </span>
94 </span>
95 <% if (c.follower_id) { %>
96 <form method="post" action="/followers/<%= c.follower_id %>/remove" onsubmit="return confirm('<%= t('tl.remove_confirm') %>')">
97 <button type="submit" class="tl-unfollow"><%= t('tl.remove_follower') %></button>
98 </form>
99 <% } %>
100 </li>
101 <% }); %>
102 </ul>
103 </details>
104 <% } %>
105</div>
106
107<style>
108 /* Guardians (FEP-633c 3.6): the buddy-list dot on the responsibility axis.
109 Green available, yellow declared away with an end, grey observed dormant
110 (one answer restores). Set apart from the follow list on purpose: these
111 are not people you chose to follow, they are the ones looking after you. */
112 .conn-guardians { margin: 0 0 1.25rem; padding: .8rem 1rem; border-radius: 12px;
113 border: 1px solid color-mix(in srgb, var(--ink, #000) 10%, transparent);
114 background: color-mix(in srgb, var(--ink, #000) 2.5%, transparent);
115 border-left: 3px solid #35a2ff; }
116 .conn-gtitle { margin: 0 0 .5rem; font-size: .95rem; }
117 .conn-glist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .35rem; }
118 .conn-guardian { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
119 .conn-gdot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
120 .conn-gdot--active { background: #4caf7d; }
121 .conn-gdot--away { background: #e0a83a; }
122 .conn-gdot--dormant { background: #8a93a3; }
123 .conn-ghandle { font-weight: 600; }
124 .conn-gstate { color: var(--ink-soft, #888); font-size: .82rem; }
125 .tl-wrap { max-width: 640px; margin: 1rem auto; padding: 0 1rem; }
126 .tl-title { margin: 0 0 .75rem; }
127 .tl-empty { color: var(--ink-soft, #888); }
128 .tl-follow { display: flex; gap: .5rem; margin: 0 0 1.25rem; flex-wrap: wrap; align-items: center; }
129 .tl-follow input[type="text"] { flex: 1; min-width: 200px; height: 2.4rem; padding: 0 .9rem; border-radius: 999px; font: inherit;
130 border: 1px solid color-mix(in srgb, var(--ink, #000) 18%, transparent); background: color-mix(in srgb, var(--ink, #000) 4%, transparent); color: var(--ink, #000); }
131 .tl-follow-ab { font-size: .85rem; color: var(--ink-soft, #888); display: inline-flex; align-items: center; gap: .3rem; cursor: pointer; }
132 .tl-foll-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .4rem; }
133 .tl-foll { display: flex; align-items: center; gap: .6rem; padding: .45rem .6rem; border-radius: 12px; background: color-mix(in srgb, var(--ink, #000) 4%, transparent); }
134 .tl-foll.is-stale { background: color-mix(in srgb, #c0392b 10%, transparent); }
135 .tl-foll-av { flex: 0 0 36px; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; background: color-mix(in srgb, var(--accent, #888) 20%, transparent); color: var(--accent, #555); }
136 .tl-foll-av img { width: 100%; height: 100%; object-fit: cover; background: #fff; }
137 .tl-foll-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
138 .tl-foll-meta a { color: var(--ink, inherit); text-decoration: none; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
139 .tl-foll-meta a:hover { text-decoration: underline; }
140 .conn-sub { font-size: .78rem; color: var(--ink-soft, #888); display: inline-flex; flex-wrap: wrap; gap: .35rem; align-items: baseline; }
141 .conn-dir { font-weight: 600; }
142 .conn-dir--mutual { color: var(--accent, #06c); }
143 .conn-dir--following { color: color-mix(in srgb, var(--ink, #000) 62%, transparent); }
144 .conn-dir--follower { color: color-mix(in srgb, var(--ink, #000) 62%, transparent); }
145 .conn-never, .conn-err { color: #c0392b; }
146 .tl-foll-ab { margin: 0; font-size: .8rem; color: var(--ink-soft, #888); cursor: pointer; }
147 .tl-foll-ab label { display: inline-flex; align-items: center; gap: .25rem; cursor: pointer; }
148 .tl-unfollow { background: none; border: 1px solid color-mix(in srgb, var(--ink, #000) 18%, transparent); border-radius: 8px; padding: .25rem .6rem; font-size: .8rem; color: var(--ink-soft, #888); cursor: pointer; white-space: nowrap; }
149 .tl-unfollow:hover { color: var(--ink, #000); }
150 .conn-unreach { margin: 1.25rem 0 0; border-top: 1px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); padding-top: .75rem; }
151 .conn-unreach summary { cursor: pointer; font-weight: 600; color: #c0392b; }
152 .conn-unreach-lead { font-size: .8rem; color: var(--ink-soft, #888); margin: .5rem 0 .75rem; }
153</style>
Note: See TracBrowser for help on using the repository browser.