Changeset a7bcf66 in Klonkt
- Timestamp:
- 07/28/2026 06:30:43 PM (6 weeks ago)
- Branches:
- main
- Children:
- 70677e96
- Parents:
- d9ad6c5
- Files:
-
- 1 added
- 5 edited
-
src/assets/js/guardian.js (modified) (3 diffs)
-
src/middleware/render.js (modified) (1 diff)
-
src/routes/guardian.js (modified) (3 diffs)
-
src/services/ActivityPubService.js (modified) (3 diffs)
-
src/views/partials/msg-item.ejs (modified) (1 diff)
-
test/timestamps.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/guardian.js
rd9ad6c5 ra7bcf66 32 32 catch (e) { return uri; } 33 33 } 34 function when(s) { return String(s || '').slice(0, 16).replace('T', ' '); } 34 // The server hands over a timestamp already formatted in the site's timezone 35 // (Beheer -> Instellingen), the same clock de Krant and Berichten show. The 36 // slice is only a fallback for a row that predates that field: it shows raw 37 // UTC, which is what made a 20:20 call for help read 18:20. 38 function when(item, raw) { 39 if (item && item.when_text) return item.when_text; 40 return String(raw || '').slice(0, 16).replace('T', ' '); 41 } 35 42 function show(id, on) { document.getElementById(id).hidden = !on; } 36 43 … … 49 56 else who.textContent = h.actor_name || handleOf(h.actor_uri, h.actor_handle); 50 57 row.appendChild(who); 51 row.appendChild(el('span', 'when', when(h .published || h.created_at)));58 row.appendChild(el('span', 'when', when(h, h.published || h.created_at))); 52 59 card.appendChild(row); 53 60 var body = el('div', 'body g-note'); … … 197 204 var head = el('div', 'row'); 198 205 head.appendChild(el('span', 'who grow', p.author)); 199 if (p.published) head.appendChild(el('span', 'g-when', when(p .published)));206 if (p.published) head.appendChild(el('span', 'g-when', when(p, p.published))); 200 207 card.appendChild(head); 201 208 var body = el('div', 'feed-body'); -
src/middleware/render.js
rd9ad6c5 ra7bcf66 85 85 const siteTimezone = () => getSetting('timezone') || undefined; 86 86 87 /** 88 * Read a stored timestamp as the moment it actually is. 89 * 90 * SQLite's CURRENT_TIMESTAMP writes UTC without saying so ("2026-07-28 91 * 18:20:33"), and new Date() reads a string in that shape as LOCAL time. That 92 * is right only as long as the server runs on UTC; set the machine to 93 * Europe/Amsterdam and every stored date silently shifts two hours. So say UTC 94 * out loud. Anything already carrying a zone (AP `published` ends in Z) is left 95 * to the normal parser. 96 */ 97 const parseStamp = (v) => { 98 if (!v) return null; 99 const s = String(v); 100 const d = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?$/.test(s) 101 ? new Date(`${s.replace(' ', 'T')}Z`) 102 : new Date(s); 103 return Number.isNaN(d.getTime()) ? null : d; 104 }; 105 87 106 const formatDate = (iso) => { 88 if (!iso) return '';89 return new Date(iso).toLocaleDateString('nl-NL', { timeZone: siteTimezone(), day: 'numeric', month: 'long', year: 'numeric' });107 const d = parseStamp(iso); 108 return d ? d.toLocaleDateString('nl-NL', { timeZone: siteTimezone(), day: 'numeric', month: 'long', year: 'numeric' }) : ''; 90 109 }; 91 110 92 const formatDateTime = (iso) => { 93 if (!iso) return ''; 94 return new Date(iso).toLocaleString('nl-NL', { timeZone: siteTimezone(), dateStyle: 'medium', timeStyle: 'short' }); 111 /** A timestamp in the site's own timezone (Beheer → Instellingen). Exported so 112 * surfaces outside the EJS pages (the Guardian PWA) read the same clock. */ 113 export const formatDateTime = (iso) => { 114 const d = parseStamp(iso); 115 return d ? d.toLocaleString('nl-NL', { timeZone: siteTimezone(), dateStyle: 'medium', timeStyle: 'short' }) : ''; 95 116 }; 96 117 -
src/routes/guardian.js
rd9ad6c5 ra7bcf66 19 19 import * as Guardianship from '../services/guardianship/index.js'; 20 20 import { t as i18nT, resolveLang } from '../services/i18n.js'; 21 import { injectCspNonce, renderNoteBody } from '../middleware/render.js';21 import { injectCspNonce, renderNoteBody, formatDateTime } from '../middleware/render.js'; 22 22 import { emojiName } from '../services/NoteRender.js'; 23 23 … … 62 62 body_html: renderNoteBody(h, L), 63 63 name_html: emojiName(h.actor_name || '', h.actor_emoji_json), 64 // In the site's own timezone, the same as everywhere else in Klonkt. The 65 // PWA used to slice the raw UTC string, so a 20:20 call for help read 18:20. 66 when_text: formatDateTime(h.published || h.created_at), 64 67 })); 65 68 return { … … 141 144 url: p.url, 142 145 published: p.published || p.created_at, 146 when_text: formatDateTime(p.published || p.created_at), 143 147 cw: p.cw || null, 144 148 media: p.media_json ? JSON.parse(p.media_json) : [], -
src/services/ActivityPubService.js
rd9ad6c5 ra7bcf66 3521 3521 try { 3522 3522 const rows = db.prepare(` 3523 SELECT i.kind, i.actor_name, i.actor_handle, i.actor_url, i.actor_icon, i.content, i.created_at, i. visibility,3523 SELECT i.kind, i.actor_name, i.actor_handle, i.actor_url, i.actor_icon, i.content, i.created_at, i.published, i.visibility, 3524 3524 i.emoji_json, i.actor_emoji_json, i.media_json, i.quote_json, i.embed_json, 3525 3525 p.slug AS post_slug, p.title AS post_title … … 3531 3531 type: r.kind, name: r.actor_name, handle: r.actor_handle, url: r.actor_url, icon: r.actor_icon, 3532 3532 content: stripLeadingMentions(r.content), post_slug: r.post_slug, post_title: r.post_title, created_at: r.created_at, 3533 // When the post was written, for display. created_at (when it reached us) 3534 // stays the sort key and the unread watermark: a note that federated late 3535 // is still new to you. 3536 published: r.published, 3533 3537 emoji_json: r.emoji_json, actor_emoji_json: r.actor_emoji_json, // FEP-9098 (messages render) 3534 3538 media_json: r.media_json, quote_json: r.quote_json, embed_json: r.embed_json, // rendered like a Krant post … … 3555 3559 } catch { /* ignore */ } 3556 3560 try { 3557 for (const r of db.prepare(`SELECT object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, wave, help_request, created_at, 3561 for (const r of db.prepare(`SELECT object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, wave, help_request, created_at, published, 3558 3562 emoji_json, actor_emoji_json, media_json, quote_json, embed_json 3559 3563 FROM ap_mentions WHERE slug = ? ORDER BY created_at DESC LIMIT ?`).all(slug, L)) { 3560 out.push({ type: 'mention', name: r.actor_name, handle: r.actor_handle, url: r.actor_url || r.actor_uri, icon: r.actor_icon, content: stripLeadingMentions(r.content), note_url: r.note_url || r.object_uri, wave: r.wave ? 1 : 0, help_request: r.help_request ? 1 : 0, actorUri: r.actor_uri, created_at: r.created_at, 3564 out.push({ type: 'mention', name: r.actor_name, handle: r.actor_handle, url: r.actor_url || r.actor_uri, icon: r.actor_icon, content: stripLeadingMentions(r.content), note_url: r.note_url || r.object_uri, wave: r.wave ? 1 : 0, help_request: r.help_request ? 1 : 0, actorUri: r.actor_uri, created_at: r.created_at, published: r.published, 3561 3565 // Same trimmings a Krant row has, so Berichten renders the post identically. 3562 3566 emoji_json: r.emoji_json, actor_emoji_json: r.actor_emoji_json, media_json: r.media_json, quote_json: r.quote_json, embed_json: r.embed_json }); -
src/views/partials/msg-item.ejs
rd9ad6c5 ra7bcf66 44 44 <% } %> 45 45 <% if (_new) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %> 46 <% if (n.created_at) { %><span class="msg-time"><%= formatDateTime(n.created_at) %></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><% } %> 47 50 </div> 48 51
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)