source: Klonkt/src/views/partials/shared-styles.ejs@ a7bcf66

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

Een hulpvraag hoort in Berichten, niet in de Krant

Een 🛟 van een ward kwam bij de guardian op twee plekken binnen: als mention in
Berichten en de Guardian PWA, maar ook als gewone post in de Krant. Op
sound-fabrics.com stonden vijf van de zes hulpvragen in allebei.

De oorzaak zat in de inbox: de tijdlijn-insert vroeg alleen "is dit een
top-level post van iemand die ik volg" en keek niet naar wie de post geadresseerd
was. Dat is nu belongsInTimeline: een directe note is aan iemand persoonlijk
gericht, dus een bericht en geen post. Dat dekt meteen de wave en de gewone DM,
die om dezelfde reden in de Krant terechtkwamen. De self-heal ruimt de al
opgeslagen exemplaren op, beperkt tot de twee soorten die achteraf nog te
herkennen zijn; een publieke mention van iemand die je volgt is wel een post en
blijft staan.

Tweede helft: de weergave gelijkgetrokken. De Krant rendert een post met
emoji's, een quote- of linkkaart en de media; Berichten liet daar niks van zien
(zelfs de shortcodes bleven staan, want ap_mentions had geen emoji_json) en de
Guardian PWA plakte de kale content in een div. Die opmaak zat bovendien in de
<style> van de Krant zelf, dus een post buiten de Krant kwam sowieso ongestyled
binnen.

Nu is er ÊÊn partial, note-body, met de opmaak in shared-styles ernaast. Alle
drie de oppervlakken gebruiken hem: de PWA bouwt zijn kaarten in de browser en
krijgt de body server-side gerenderd mee. ap_mentions en ap_interactions kregen
de kolommen die daarvoor nodig zijn, gevuld bij binnenkomst, met de quote- of
linkkaart out of band zoals de tijdlijn dat al deed.

En passant: de embed-gate stond alleen op de C2S-read, dus een ward zag in de
web-Krant nog steeds linkvoorbeelden die de guardians hadden uitgezet. Die gate
zit nu ook op /news en /messages.

Changed files:
src/services/ActivityPubService.js

  • belongsInTimeline: een directe note is geen tijdlijn-post
  • self-heal v21 verwijdert al opgeslagen hulpvragen en waves uit ap_timeline
  • resolveCard: quote of linkvoorbeeld, ÊÊn kaart, out of band opgelost
  • mentions en replies slaan emoji's, media en die kaart op
  • getNotifications geeft die kolommen door aan Berichten

src/config/database.js

  • kolommen op ap_mentions en ap_interactions voor emoji's, media, quote, embed

src/middleware/render.js

  • renderNoteBody: dezelfde partial als string, voor niet-EJS oppervlakken

src/routes/posts.js

  • gateEmbeds op /news en /messages (FEP-633c gated feature)

src/routes/guardian.js

  • hulpvragen krijgen body_html en name_html mee

src/assets/js/guardian.js

  • kaart rendert die body in plaats van de kale content

src/assets/css/guardian.css

  • opmaak voor de gedeelde post-body in de kleuren van de PWA

src/views/partials/tl-item.ejs

  • body vervangen door de gedeelde partial

src/views/partials/msg-item.ejs

  • idem, plus een 🛟-markering bij een hulpvraag

src/views/partials/shared-styles.ejs

  • .tl-content, .tl-quote* en .tl-media* hierheen verhuisd

src/views/pages/news.ejs

  • die regels weggehaald, alleen Krant-eigen opmaak blijft

src/services/i18n.js

  • msg.help_request in nl, en, de

New file:
src/views/partials/note-body.ejs

  • de body van een post: content, quote/linkkaart, media

test/help-request-timeline.test.js

  • een 🛟 blijft uit de Krant en in Berichten

test/note-body-shared.test.js

  • ÊÊn renderer, en beide views gaan er doorheen

remarks: geverifieerd op een wegwerp-database in de browser: de hulpvraag
verdwijnt bij het opstarten uit de Krant en staat mÊt quote en capture in
Berichten en de PWA. Nog niet uitgerold.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@â€Ļ>

  • Property mode set to 100644
File size: 13.2 KB
Line 
1<style>
2/* ─────────────────────────────────────────────────
3 View switcher (timeline ↔ grid + grid-cols picker)
4 ───────────────────────────────────────────────── */
5.view-switcher-wrap {
6 border-bottom: 1px solid var(--rule);
7 background: var(--paper);
8 /* No extra margin-top: this centres the pill between the profile-header
9 rule (above) and the own border (below). Equal space above/below the
10 pill comes from the symmetric container padding below. The margin-bottom
11 (1.5rem in style.css) keeps breathing room toward the feed. */
12 margin-top: 0;
13}
14.view-switcher-wrap .container {
15 display: flex; align-items: center; justify-content: center; gap: 0.75rem;
16 /* Symmetric padding → equal space above and below the pill (≈8px),
17 centred between the two dividing lines. */
18 padding: 0.5rem 1rem;
19 max-width: 1200px; margin: 0 auto;
20 flex-wrap: wrap;
21}
22.view-switcher {
23 display: inline-flex;
24 background: var(--paper-2);
25 border: 1px solid var(--rule);
26 border-radius: 999px;
27 padding: 0.15rem;
28}
29.view-switch-btn {
30 display: inline-flex; align-items: center; gap: 0.35rem;
31 padding: 0.35rem 0.85rem;
32 border: 0; background: transparent; color: var(--ink-muted, var(--ink-soft));
33 cursor: pointer; border-radius: 999px;
34 font-size: 0.85rem; font-family: inherit;
35 transition: background 120ms, color 120ms;
36}
37.view-switch-btn:hover { color: var(--ink); }
38/* Active = white (light background, dark text). Via aria-selected (element
39 attribute, set by syncAria + on htmx:afterSettle) → htmx-robust. On non-
40 feed pages syncAria sets aria-selected=false → both grey. */
41.view-switch-btn[aria-selected="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
42
43/* Agenda = its OWN pill (separate from Timeline/Grid, so the feed-toggle is never
44 disturbed). Same look as the view-switcher pill. Visible in all modes. */
45.agenda-pill { display: inline-flex; align-items: center; } /* no box — standalone icon+text link */
46.vs-agenda {
47 display: inline-flex; align-items: center; gap: 0.4rem;
48 padding: 0.35rem 0.5rem;
49 color: var(--ink-muted, var(--ink-soft));
50 text-decoration: none;
51 font-size: 0.9rem; font-family: inherit;
52 transition: color 120ms;
53}
54.vs-agenda:hover { color: var(--ink); }
55/* Active (on the agenda page) = bright/white. Element-class (in the OOB-rendered
56 chrome) rather than body-class CSS → stays reliable after htmx navigation. */
57.vs-agenda.is-active { color: var(--ink); font-weight: 600; }
58
59/* Mobile: Agenda on its own row at the top, Solo/Circle + Timeline/Grid together
60 on the row below (together ~330px → fits within a phone, instead of the old
61 unbalanced split Agenda+Solo / Timeline). */
62@media (max-width: 520px) {
63 .view-switcher-wrap .agenda-pill { flex: 0 0 100%; justify-content: center; }
64}
65
66.grid-cols-picker {
67 display: none; /* desktop-only, see media query */
68 gap: 0.2rem;
69}
70.grid-cols-btn {
71 width: 28px; height: 28px;
72 border: 1px solid var(--rule);
73 background: var(--paper-2);
74 color: var(--ink-muted, var(--ink-soft));
75 border-radius: 4px;
76 font-size: 0.8rem; font-weight: 600;
77 cursor: pointer;
78 font-variant-numeric: tabular-nums;
79}
80.grid-cols-btn:hover { color: var(--ink); border-color: var(--accent); }
81.grid-cols-btn.is-active { background: var(--accent); color: white; border-color: var(--accent); }
82
83/* Show grid-cols picker only on grid view + desktop */
84@media (min-width: 720px) {
85 body[data-feed-view="grid"] .grid-cols-picker { display: inline-flex; }
86}
87
88/* Switcher is hidden only on admin pages — admin has a dedicated layout
89 and its own back-button flow where Tijdlijn/Grid is irrelevant. On
90 every other page (home, post, tag, archive, search, account) it stays
91 visible so the user keeps a consistent return-to-feed control. */
92.on-admin .view-switcher-wrap { display: none; }
93
94/* On a standalone post there is no feed to switch, so no button should look "active"
95 (coloured). The active state lives in style.css on body[data-feed-view]
96 / body[data-grid-cols] (background:var(--ink)); we override that MORE SPECIFICALLY
97 via body.on-post so the buttons become neutral/grey — but remain clickable
98 (the click-handler then navigates to the feed in that view). The
99 body-class switches client-side on HTMX-nav, so this works without a reload too. */
100body.on-post[data-feed-view="timeline"] .view-switch-btn[data-view="timeline"],
101body.on-post[data-feed-view="grid"] .view-switch-btn[data-view="grid"],
102body.on-post[data-grid-cols="2"] .grid-cols-btn[data-cols="2"],
103body.on-post[data-grid-cols="3"] .grid-cols-btn[data-cols="3"],
104body.on-post[data-grid-cols="4"] .grid-cols-btn[data-cols="4"] {
105 background: transparent;
106 color: var(--ink-muted, var(--ink-soft));
107 border-color: var(--rule);
108}
109
110
111/* Spacing between switcher-bar and the page content below.
112 .feed-timeline (used on home + archive + post-list pages) gets a top
113 margin so the first post doesn't collide with the switcher's border.
114 .feed-grid already has its own top spacing from grid-tile padding. */
115.view-switcher-wrap + #pcms-main .feed-timeline,
116.view-switcher-wrap + #pcms-main .container:first-child {
117 margin-top: 1.5rem;
118}
119
120/* Feed layout switching is in the v9 style.css (style.css):
121 .feed-timeline always rendered, .feed-grid hidden unless on-home + grid-mode. */
122
123/* ─────────────────────────────────────────────────
124 PWA install button (lives in topnav, hidden by default)
125 ───────────────────────────────────────────────── */
126#pwa-install-btn { color: var(--accent); }
127#pwa-install-btn:hover { background: var(--paper-2); }
128
129/* Shared button + form styles (used across pages) */
130.btn {
131 display: inline-flex;
132 align-items: center;
133 gap: 0.4rem;
134 padding: 0.55rem 1rem;
135 border: 1px solid var(--rule);
136 background: var(--paper-2);
137 color: var(--ink);
138 font-family: var(--font-ui, system-ui), sans-serif;
139 font-size: 0.9rem;
140 font-weight: 500;
141 text-decoration: none;
142 border-radius: 5px;
143 cursor: pointer;
144 transition: background 150ms, border-color 150ms;
145}
146.btn:hover { border-color: var(--accent); }
147.btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
148.btn-primary:hover { opacity: 0.92; border-color: var(--accent); }
149.btn-success { background: #16a34a; color: white; border-color: #16a34a; }
150.btn-danger { background: #dc2626; color: white; border-color: #dc2626; }
151
152.alert { padding: 0.75rem 1rem; border-radius: 5px; margin-bottom: 1rem; }
153.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
154.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
155
156.auth-page { max-width: 420px; margin: 3rem auto; padding: 0 1rem; }
157.auth-page h1 { font-family: var(--font-display, serif); text-align: center; margin: 0 0 1.5rem; }
158.auth-form { display: flex; flex-direction: column; gap: 1rem; }
159.auth-form label { display: flex; flex-direction: column; gap: 0.3rem; }
160.auth-form label > span { font-size: 0.85rem; font-weight: 600; color: var(--ink-soft); }
161.auth-form input { padding: 0.6rem 0.75rem; border: 1px solid var(--rule); border-radius: 4px; background: var(--paper); color: var(--ink); font-size: 1rem; }
162.auth-form .btn { justify-content: center; padding: 0.7rem; font-size: 1rem; margin-top: 0.5rem; }
163.auth-link { text-align: center; color: var(--ink-muted); font-size: 0.9rem; margin: 1rem 0 0; }
164.auth-link a { color: var(--accent); }
165.auth-intro { text-align: center; color: var(--ink-muted); font-size: 0.95rem; margin: 0 0 1rem; }
166.auth-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; color: var(--ink-muted); font-size: 0.85rem; }
167.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--rule); }
168
169/* HTMX loading indicator */
170.pcms-loading {
171 display: none;
172 position: fixed;
173 top: 60px;
174 right: 1rem;
175 width: 24px;
176 height: 24px;
177 border: 3px solid var(--paper-2);
178 border-top-color: var(--accent);
179 border-radius: 50%;
180 animation: spin 0.8s linear infinite;
181 z-index: 1000;
182}
183.htmx-request .pcms-loading { display: block; }
184@keyframes spin { to { transform: rotate(360deg); } }
185
186/* Load more (Solo, Cirkel, News, Messages) — page size 72, htmx append. */
187.load-more-wrap { display: flex; justify-content: center; margin: 1.5rem 0 .5rem; }
188.load-more-wrap:empty { margin: 0; }
189.load-more-btn { padding: .6rem 1.4rem; border-radius: 999px; cursor: pointer;
190 font: inherit; font-weight: 600; color: var(--ink, inherit);
191 background: color-mix(in srgb, var(--ink, #000) 5%, transparent);
192 border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
193.load-more-btn:hover { background: color-mix(in srgb, var(--ink, #000) 9%, transparent); }
194.load-more-btn.htmx-request { opacity: .6; pointer-events: none; }
195
196/* FEP-9098 custom emojis: small inline images (Shaer parity), rendered in note
197 content and display names across the timeline, messages and threads. Never
198 styled as media (no rounding/background), and never blown up to media width. */
199img.emoji { height: 1.3em; width: auto; margin: 0 .05em; vertical-align: -0.2em;
200 display: inline-block; border-radius: 0; background: none; box-shadow: none; }
201.tl-content img.emoji, .tl-quote-body img.emoji, .msg-content img.emoji, .comment-content img.emoji,
202.tl-author img.emoji, .tl-boost-by img.emoji, .tl-quote-name img.emoji, .msg-who img.emoji, .comment-author img.emoji {
203 max-width: none; height: 1.3em; border-radius: 0; background: none; box-shadow: none; vertical-align: -0.2em; }
204
205/* ── The body of a post ────────────────────────────────────────────────────
206 Shared with partials/note-body.ejs, so a post looks the same in de Krant, in
207 Berichten and in the Guardian PWA. These used to live in the Krant's own
208 <style> block, which is why a post outside de Krant came out unstyled.
209 Krant-only behaviour (the clamp + "read more", the poll, the action bar)
210 stays in pages/news.ejs. */
211.tl-content { line-height: 1.55; overflow-wrap: anywhere; }
212.tl-content p { margin: .4rem 0; } .tl-content p:first-child { margin-top: 0; } .tl-content p:last-child { margin-bottom: 0; }
213.tl-content a { color: var(--accent, #06c); }
214.tl-content img { max-width: 100%; height: auto; border-radius: 10px; background: #fff; }
215
216/* FEP-044f embedded quote card (mirror of the Shaer QuoteCard). Also carries
217 an external link preview: same card, different origin. */
218.tl-quote { margin: .75rem 0 0; padding: .6rem .7rem; border-radius: 12px;
219 border: 1px solid color-mix(in srgb, var(--ink, #000) 12%, transparent);
220 background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent); }
221.tl-quote-head { display: flex; align-items: center; gap: .4rem; margin: 0 0 .35rem; min-width: 0; }
222.tl-quote-avatar { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; object-fit: cover; background: #fff; }
223.tl-quote-name { font-weight: 700; font-size: .85rem; color: var(--ink, inherit); white-space: nowrap; }
224.tl-quote-handle { color: var(--ink-soft, #888); font-size: .78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
225.tl-quote-body { line-height: 1.5; font-size: .92rem; color: var(--ink-soft, #555);
226 overflow-wrap: anywhere; max-height: 16em; overflow: hidden; }
227.tl-quote-body p { margin: .3rem 0; } .tl-quote-body p:first-child { margin-top: 0; } .tl-quote-body p:last-child { margin-bottom: 0; }
228.tl-quote-body a { color: var(--accent, #06c); }
229.tl-quote-media { display: block; margin: .45rem 0 0; }
230.tl-quote-media img { max-width: 100%; height: auto; border-radius: 8px; }
231
232/* Always show the FULL image at its natural ratio — never cropped, no letterbox. */
233.tl-media { display: flex; flex-direction: column; gap: .4rem; margin: .75rem 0 0; }
234.tl-media-img { display: block; border-radius: 12px; overflow: hidden;
235 background-image: linear-gradient(135deg,
236 color-mix(in srgb, var(--accent, #888) 22%, var(--paper-2, var(--paper))) 0%,
237 color-mix(in srgb, var(--accent, #888) 6%, var(--paper-2, var(--paper))) 100%); }
238.tl-media-img img { width: 100%; height: auto; display: block; background: #fff; transition: opacity .4s ease; }
239.tl-media-img img.is-loading { opacity: 0; }
240/* Reserve placeholder space while the (natural-aspect) feed image loads. */
241.tl-media-img:has(img.is-loading) { min-height: 220px; }
242.tl-media-video, .tl-media-audio { width: 100%; margin: .75rem 0 0; border-radius: 12px; display: block; }
243.tl-media-video { max-height: 480px; background: #000; }
244
245/* In Berichten a post sits inside a notification row, so it starts tighter and
246 its media stays modest: the row is a pointer to the post, not the post page. */
247.msg-note .tl-content { line-height: 1.5; }
248.msg-note .tl-quote, .msg-note .tl-media, .msg-note .tl-media-video, .msg-note .tl-media-audio { margin-top: .5rem; }
249.msg-note .tl-media-img:has(img.is-loading) { min-height: 120px; }
250.msg-note .tl-media-video { max-height: 320px; }
251</style>
Note: See TracBrowser for help on using the repository browser.