source: Klonkt/src/views/partials/msg-item.ejs@ a7bcf66

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

De Guardian PWA liep twee uur achter

Op sound-fabrics staat de tijdzone op Europe/Amsterdam, maar een hulpvraag van
20:20 stond in de PWA als 18:20. Het dashboard bouwt zijn kaarten in de browser
en sneed de rauwe UTC-string af (slice(0,16)) in plaats van hem om te rekenen.
De server geeft de tijd nu geformatteerd mee, met dezelfde formatDateTime die de
Krant en Berichten gebruiken; het afsnijden blijft alleen als terugval staan.

In formatDateTime zat een tweede probleem, dat nog niet zichtbaar was. SQLite
schrijft CURRENT_TIMESTAMP als UTC zonder dat erbij te zeggen ("2026-07-28
18:20:33"), en new Date() leest een string in die vorm als LOKALE tijd. Dat gaat
goed zolang de machine op UTC staat, wat nu toevallig zo is. Zet de VPS ooit op
Amsterdam en elke opgeslagen datum in de hele app schuift twee uur op. De parser
zegt nu expliciet UTC.

Onderweg bleek Berichten en de PWA ook niet dezelfde tijd te tonen voor dezelfde
post: 20:12 tegenover 20:20. Berichten liet zien wanneer wij de post ontvingen,
de PWA en de Krant wanneer hij geschreven is. Berichten toont nu ook de
publicatietijd. Sorteren en de "nieuw sinds je laatste bezoek"-stip blijven op
de ontvangsttijd: een post die laat federeert is nog steeds nieuw voor jou.

Changed files:
src/middleware/render.js

  • parseStamp leest een tijdstempel zonder zone als UTC
  • formatDateTime geexporteerd voor oppervlakken buiten de EJS-pagina's

src/routes/guardian.js

  • when_text bij hulpvragen en bij de tijdlijn van de wards

src/assets/js/guardian.js

  • when() gebruikt dat veld, afsnijden alleen nog als terugval

src/services/ActivityPubService.js

  • getNotifications geeft published mee naast created_at

src/views/partials/msg-item.ejs

  • toont de publicatietijd, met created_at als terugval

New file:
test/timestamps.test.js

  • de tijdzone-instelling wordt toegepast, en een SQLite-tijdstempel hangt niet af van de tijdzone van de machine

remarks: geverifieerd op de wegwerp-database met Europe/Amsterdam: Berichten en
de PWA tonen allebei 28 jul 2026, 20:20 voor dezelfde post.

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

  • Property mode set to 100644
File size: 9.6 KB
Line 
1<%
2 var _seen = (typeof seen !== 'undefined' && seen) ? seen : 0;
3 var _t = n.type === 'announce' ? 'boost' : n.type; // reply|mention|report|like|boost|follow|sent
4 var _new = _seen && n.created_at ? (Date.parse(n.created_at) > _seen) : false;
5 var _who = n.name || n.handle || '';
6 var _priv0 = (n.visibility === 'followers' || n.visibility === 'direct');
7 // Buckets (klonkt-demo-3jf): Messages = @mentions + private (DM) replies;
8 // Activity = likes/boosts/follows; Moderation = reports; Sent = your
9 // replies; Conversations = every other (public) reply.
10 var _kind = _t === 'sent' ? 'sent'
11 : (_t === 'like' || _t === 'boost' || _t === 'follow' || _t === 'poll_done') ? 'act'
12 : _t === 'report' ? 'mod'
13 : (_t === 'mention' || _priv0) ? 'msgs'
14 : 'conv';
15 var _init = String(_who || '?').replace(/^@/, '').charAt(0).toUpperCase();
16 var _priv = (n.visibility === 'followers' || n.visibility === 'direct');
17 %>
18 <li class="msg-item msg-<%= _t %><%= _new ? ' is-new' : '' %>" data-kind="<%= _kind %>" data-who="<%= String(_who).toLowerCase() %>">
19 <span class="msg-av<%= _t === 'sent' ? ' msg-av-sent' : '' %>" aria-hidden="true">
20 <% if (_t === 'sent') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
21 <% } else if (_t === 'poll_done') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
22 <% } else if (n.icon) { %><img src="<%= avatar(n.icon, 96) %>" alt="" loading="lazy">
23 <% } else { %><%= _init %><% } %>
24 <span class="msg-dot msg-dot-<%= _t %>">
25 <% if (_t === 'like') { %><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.6l2.9 5.88 6.49.95-4.7 4.58 1.11 6.46L12 17.96l-5.8 3.06 1.1-6.46-4.69-4.58 6.49-.95z"/></svg>
26 <% } else if (_t === 'boost') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
27 <% } else if (_t === 'follow') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
28 <% } else if (_t === 'report') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>
29 <% } else if (_t === 'mention') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"/></svg>
30 <% } else if (_t === 'sent') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg>
31 <% } else if (_t === 'poll_done') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
32 <% } else { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><% } %>
33 </span>
34 </span>
35
36 <div class="msg-body">
37 <div class="msg-line">
38 <% if (_t === 'sent') { %>
39 <span class="msg-who"><%= t('msg.you') %></span>
40 <% if (n.to_handle) { %><span class="msg-handle">→ <%= n.to_handle %></span><% } %>
41 <% } else { %>
42 <a class="msg-who" href="<%= n.url %>" target="_blank" rel="nofollow noopener"><%- emojiName(_who, n.actor_emoji_json) %></a>
43 <% if (n.handle && n.name) { %><span class="msg-handle"><%= n.handle %></span><% } %>
44 <% } %>
45 <% if (_new) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
46 <% /* When the post was written, like de Krant and de Guardian PWA
47 show it. created_at (when it reached us) stays the sort key
48 and drives the "new since your last visit" dot above. */ %>
49 <% if (n.published || n.created_at) { %><span class="msg-time"><%= formatDateTime(n.published || n.created_at) %></span><% } %>
50 </div>
51
52 <div class="msg-what">
53 <% if (n.count && n.count > 1) { %>
54 <strong><%= t('msg.and_more', { n: n.count - 1 }) %></strong>
55 <%= _t === 'like' ? t('msg.liked_many') : t('msg.boosted_many') %>
56 <% } else if (_t === 'follow') { %><%= t('notif.followed') %>
57 <% } else if (_t === 'like') { %><%= t('notif.liked') %>
58 <% } else if (_t === 'boost') { %><%= t('notif.boosted') %>
59 <% } else if (_t === 'report') { %><%= t('notif.reported') %>
60 <% } else if (_t === 'mention' && n.help_request) { %>🛟 <%= t('msg.help_request') %>
61 <% } else if (_t === 'mention' && n.wave) { %>👋 <%= t('msg.waved_at_you') %>
62 <% } else if (_t === 'mention') { %><%= t('notif.mentioned') %>
63 <% } else if (_t === 'sent') { %><%= t('msg.sent_reply') %>
64 <% } else if (_t === 'poll_done') { %><strong><%= t('msg.poll_done') %></strong>
65 <% } else { %><%= t('notif.replied') %><% } %>
66 <% if (_priv) { %><span class="msg-priv" title="<%= t('msg.private_hint') %>">🔒 <%= t('msg.private') %></span><% } %>
67 <% if (n.post_slug) { %><a class="msg-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
68 <% if (_t === 'mention' && n.note_url) { %><a class="msg-post" href="<%= n.note_url %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
69 <% if (_t === 'sent' && n.in_reply_to) { %><a class="msg-post" href="<%= n.in_reply_to %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
70 </div>
71
72 <% if (_t === 'report' && n.content) { %><div class="msg-content"><%= n.content %></div>
73 <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content msg-note"><%- include('../partials/note-body', { nb: n }) %></div>
74 <% if (_t === 'mention' && n.wave && n.actorUri && canMutate) { %>
75 <form class="msg-quickreply" method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">
76 <input type="hidden" name="to" value="<%= n.actorUri %>">
77 <button class="btn small" name="text" value="<%= t('msg.wave_r1') %>"><%= t('msg.wave_r1') %></button>
78 <button class="btn small" name="text" value="<%= t('msg.wave_r2') %>"><%= t('msg.wave_r2') %></button>
79 <button class="btn small" name="text" value="👋"><%= t('msg.wave_back') %></button>
80 </form>
81 <% } %>
82 <% } else if (_t === 'poll_done' && n.poll) { %>
83 <div class="msg-poll" role="group">
84 <% n.poll.options.forEach(function (o) { %>
85 <div class="msg-poll-opt">
86 <div class="msg-poll-top"><span class="msg-poll-name"><%= o.name %></span><span class="msg-poll-pct"><%= o.pct %>%</span></div>
87 <div class="msg-poll-bar"><span style="width:<%= o.pct %>%"></span></div>
88 </div>
89 <% }); %>
90 <div class="msg-poll-total"><%= t('msg.poll_total', { n: n.poll.voters }) %></div>
91 </div>
92 <% } %>
93
94 <% if (_t === 'sent' && n.outboxId) { %>
95 <div class="msg-actions">
96 <details class="msg-edit">
97 <summary><%= t('fedi.edit') %></summary>
98 <div class="msg-edit-form">
99 <%- include('../partials/reply-editor', {
100 action: '/fediverse/' + n.outboxId + '/edit',
101 placeholder: t('fedi.reply_ph'),
102 submitLabel: t('fedi.save_edit'),
103 rows: 2,
104 initialHtml: n.content,
105 initialText: n.editable,
106 defaultLang: n.language,
107 noAttach: true,
108 }) %>
109 </div>
110 </details>
111 <form method="post" action="/fediverse/<%= n.outboxId %>/delete" data-confirm="<%= t('fedi.delete_confirm') %>">
112 <button type="submit" class="msg-del"><%= t('comments.delete') %></button>
113 </form>
114 </div>
115 <% } %>
116 </div>
117 </li>
Note: See TracBrowser for help on using the repository browser.