- Timestamp:
- 07/28/2026 06:17:36 PM (6 weeks ago)
- Branches:
- main
- Children:
- a7bcf66
- Parents:
- 2708282
- Location:
- src/views
- Files:
-
- 1 added
- 4 edited
-
pages/news.ejs (modified) (2 diffs)
-
partials/msg-item.ejs (modified) (2 diffs)
-
partials/note-body.ejs (added)
-
partials/shared-styles.ejs (modified) (1 diff)
-
partials/tl-item.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/views/pages/news.ejs
r2708282 rd9ad6c5 124 124 .tl-time { color: var(--ink-soft, #999); font-size: .78rem; flex: 0 0 auto; align-self: flex-start; white-space: nowrap; } 125 125 126 .tl-content { line-height: 1.55; overflow-wrap: anywhere; } 127 .tl-content p { margin: .4rem 0; } .tl-content p:first-child { margin-top: 0; } .tl-content p:last-child { margin-bottom: 0; } 128 .tl-content a { color: var(--accent, #06c); } 129 .tl-content img { max-width: 100%; height: auto; border-radius: 10px; background: #fff; } 126 /* .tl-content, .tl-quote* and .tl-media* now live in partials/shared-styles, 127 next to partials/note-body: the markup is shared with Berichten and the 128 Guardian PWA, so the styling has to be too. What stays here is Krant-only. */ 130 129 /* Long posts collapse to a max height with a fade + "read more" (added by JS only when it overflows). */ 131 130 .tl-content.tl-clamp { max-height: 20em; overflow: hidden; … … 137 136 .tl-readmore:hover { text-decoration: underline; } 138 137 139 /* FEP-044f embedded quote card (mirror of the Shaer QuoteCard). */140 .tl-quote { margin: .75rem 0 0; padding: .6rem .7rem; border-radius: 12px;141 border: 1px solid color-mix(in srgb, var(--ink, #000) 12%, transparent);142 background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent); }143 .tl-quote-head { display: flex; align-items: center; gap: .4rem; margin: 0 0 .35rem; min-width: 0; }144 .tl-quote-avatar { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; object-fit: cover; background: #fff; }145 .tl-quote-name { font-weight: 700; font-size: .85rem; color: var(--ink, inherit); white-space: nowrap; }146 .tl-quote-handle { color: var(--ink-soft, #888); font-size: .78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }147 .tl-quote-body { line-height: 1.5; font-size: .92rem; color: var(--ink-soft, #555);148 overflow-wrap: anywhere; max-height: 16em; overflow: hidden; }149 .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; }150 .tl-quote-body a { color: var(--accent, #06c); }151 .tl-quote-media { display: block; margin: .45rem 0 0; }152 .tl-quote-media img { max-width: 100%; height: auto; border-radius: 8px; }153 154 /* Always show the FULL image at its natural ratio â never cropped, no letterbox. */155 .tl-media { display: flex; flex-direction: column; gap: .4rem; margin: .75rem 0 0; }156 .tl-media-img { display: block; border-radius: 12px; overflow: hidden;157 background-image: linear-gradient(135deg,158 color-mix(in srgb, var(--accent, #888) 22%, var(--paper-2, var(--paper))) 0%,159 color-mix(in srgb, var(--accent, #888) 6%, var(--paper-2, var(--paper))) 100%); }160 .tl-media-img img { width: 100%; height: auto; display: block; background: #fff; transition: opacity .4s ease; }161 .tl-media-img img.is-loading { opacity: 0; }162 /* Reserve placeholder space while the (natural-aspect) feed image loads. */163 .tl-media-img:has(img.is-loading) { min-height: 220px; }164 165 .tl-media-video, .tl-media-audio { width: 100%; margin: .75rem 0 0; border-radius: 12px; display: block; }166 .tl-media-video { max-height: 480px; background: #000; }167 138 .tl-poll { margin: .75rem 0 0; display: flex; flex-direction: column; gap: 8px; } 168 139 .tl-poll-form { display: flex; flex-direction: column; gap: 8px; } -
src/views/partials/msg-item.ejs
r2708282 rd9ad6c5 55 55 <% } else if (_t === 'boost') { %><%= t('notif.boosted') %> 56 56 <% } else if (_t === 'report') { %><%= t('notif.reported') %> 57 <% } else if (_t === 'mention' && n.help_request) { %>đ <%= t('msg.help_request') %> 57 58 <% } else if (_t === 'mention' && n.wave) { %>đ <%= t('msg.waved_at_you') %> 58 59 <% } else if (_t === 'mention') { %><%= t('notif.mentioned') %> … … 67 68 68 69 <% if (_t === 'report' && n.content) { %><div class="msg-content"><%= n.content %></div> 69 <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content "><%- emojiHtml(n.content, n.emoji_json) %></div>70 <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content msg-note"><%- include('../partials/note-body', { nb: n }) %></div> 70 71 <% if (_t === 'mention' && n.wave && n.actorUri && canMutate) { %> 71 72 <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"> -
src/views/partials/shared-styles.ejs
r2708282 rd9ad6c5 202 202 .tl-author img.emoji, .tl-boost-by img.emoji, .tl-quote-name img.emoji, .msg-who img.emoji, .comment-author img.emoji { 203 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; } 204 251 </style> -
src/views/partials/tl-item.ejs
r2708282 rd9ad6c5 10 10 </div> 11 11 12 <% 13 var media = []; try { media = JSON.parse(p.media_json || '[]'); } catch (e) { media = []; } 14 var imgs = media.filter(function(m){ return m && m.url && (!m.type || /^image\//.test(m.type)); }); 15 var vids = media.filter(function(m){ return m && m.url && m.type && /^video\//.test(m.type); }); 16 var auds = media.filter(function(m){ return m && m.url && m.type && /^audio\//.test(m.type); }); 17 var _nsfwVisual = !!p.nsfw && (imgs.length || vids.length || auds.length); 18 var _nsfwText = !!p.nsfw && !(imgs.length || vids.length || auds.length); 19 %> 20 <% if (_nsfwText) { %> 21 <div class="tl-content nsfw-media"><span class="nsfw-veil"><%- include('../partials/nsfw-veil', { cw: p.cw }) %></span><%- emojiHtml(p.content, p.emoji_json) %></div> 22 <% } else { %> 23 <div class="tl-content"><%- emojiHtml(p.content, p.emoji_json) %></div> 24 <% } %> 25 <% 26 // One card for both: a fediverse quote and an external embed look 27 // identical; only where they came from differs. An embed carries a 28 // title and a thumbnail, never an iframe. 29 var _quote = noteQuote(p.quote_json); 30 if (!_quote) { 31 var _emb = noteQuote(p.embed_json); 32 // The title comes from a third-party oEmbed provider, so it is 33 // escaped here: the quote card renders `content` as HTML (fine for 34 // AP content, which we sanitise on the way in, but not for this). 35 if (_emb) _quote = { url: _emb.url, author: _emb.author || (_emb.provider ? { name: _emb.provider } : null), 36 content: _emb.title ? ('<p>' + emojiName(_emb.title, null) + '</p>') : '', media: _emb.media || [] }; 37 } 38 %> 39 <% if (_quote) { %><%- include('../partials/quote-card', { q: _quote }) %><% } %> 40 <% if (_nsfwVisual) { %><div class="nsfw-media"><% } %> 41 <% if (imgs.length) { %> 42 <div class="tl-media"> 43 <% imgs.forEach(function(m){ %> 44 <a class="tl-media-img" href="<%= m.url %>" target="_blank" rel="noopener"><img src="<%= thumb(m.url, 1280) %>" alt="" loading="lazy" decoding="async"></a> 45 <% }); %> 46 </div> 47 <% } %> 48 <% vids.forEach(function(m){ %><video class="tl-media-video" src="<%= m.url %>" poster="<%= thumb(m.url, 1280) %>" controls preload="metadata" playsinline></video><% }); %> 49 <% /* A Klonkt audio post carries an embed player (embedHtml/embedUrl) that already 50 covers these tracks, so don't ALSO render the raw Audio attachments as bare 51 players here. Posts without a Klonkt embed (e.g. a plain remote audio) keep them. */ %> 52 <% if (!p.embedHtml && !p.embedUrl) { auds.forEach(function(m){ %><audio class="tl-media-audio" src="<%= m.url %>" controls preload="none"></audio><% }); } %> 53 <% if (_nsfwVisual) { %><div class="nsfw-veil"><%- include('../partials/nsfw-veil', { cw: p.cw }) %></div></div><% } %> 12 <% /* Content, quote/preview card and media: shared with Berichten and the 13 Guardian PWA so a post looks the same wherever it turns up. 14 suppressAudio: a Klonkt audio post carries an embed player below 15 that already covers these tracks, so don't ALSO render the raw 16 Audio attachments as bare players. */ %> 17 <%- include('../partials/note-body', { nb: Object.assign({}, p, { suppressAudio: !!(p.embedHtml || p.embedUrl) }) }) %> 54 18 55 19 <% if (p.embedHtml) { %><div class="tl-embed"><%- p.embedHtml %></div><% } %>
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)