Changeset 3597763 in Klonkt


Ignore:
Timestamp:
07/26/2026 09:37:48 AM (6 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
ef1853c
Parents:
b5c2538
Message:

Web-timeline: custom-emoji + embedded quote-kaart (afgekeken van Shaer)

De Shaer-apps renderen FEP-9098 custom-emoji's en de FEP-044f quote-kaart
inline; de Klonkt web-timeline toonde nog letterlijke :shortcodes: en geen
quote. Nu rendert de web-nieuwsfeed dezelfde dingen server-side: emoji's in
content, auteur-naam en booster-naam, plus de geneste quote-kaart (avatar +
naam + content + thumbnail). De byline (avatar/naam/handle) en de boost-icon
(SVG, themebaar) had de web-UI al.

De data was al aanwezig op de timeline-rijen (emoji_json, author_emoji_json,
reblog_emoji_json, quote_json); dit is puur de render-kant.

New file:
src/services/NoteRender.js

  • pure helpers: emojiMap (beide vormen), emojiHtml (tag-bewust, skip code/pre), emojiName (escape + emoji), parseQuote

src/views/partials/quote-card.ejs

  • geneste quote-kaart (mirror van de Shaer QuoteCard)

test/note-render.test.js

  • 7 tests voor de render-helpers

Changed files:
src/middleware/render.js

  • emojiHtml/emojiName/noteQuote als template-helpers in de render-locals

src/views/partials/tl-item.ejs

  • content, auteur-naam en booster-naam via de emoji-helpers; quote-kaart erna

src/views/pages/news.ejs

  • CSS: img.emoji (inline, geen media-styling) + .tl-quote* (themebaar)

remarks: 187 tests groen (was 180). Visueel geverifieerd via een standalone
render van tl-item met echte emoji-urls. Volgende surfaces (messages, profiel,
cirkel) kunnen dezelfde helpers hergebruiken.

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

Files:
3 added
3 edited

Legend:

Unmodified
Added
Removed
  • src/middleware/render.js

    rb5c2538 r3597763  
    1919import { getSetting, apEnabled } from '../services/SettingsService.js';
    2020import { isPremium as isPremiumInstance, premiumEnabled, premiumUnlocked } from '../services/PatreonService.js';
     21import { emojiHtml, emojiName, parseQuote } from '../services/NoteRender.js';
    2122import ActivityPubService from '../services/ActivityPubService.js';
    2223import { imgProxyUrl } from '../services/ThumbnailService.js';
     
    164165    formatDate,
    165166    formatDateTime,
     167    // Render the Shaer-native bits server-side so the web timeline matches the
     168    // apps: FEP-9098 custom emojis in content/names, and the FEP-044f quote.
     169    emojiHtml,   // (html, emoji_json) → HTML with :shortcode: as <img>
     170    emojiName,   // (text, emoji_json) → escaped name with :shortcode: as <img>
     171    noteQuote: parseQuote,   // (quote_json) → the resolved quoted-post object or null
    166172    // Rewrite a local /media/<file> cover to its on-demand downscaled thumbnail
    167173    // (crisp grid/list images). External URLs + already-thumb URLs pass through.
  • src/views/pages/news.ejs

    rb5c2538 r3597763  
    137137  .tl-readmore:hover { text-decoration: underline; }
    138138
     139  /* FEP-9098 custom emojis: small inline images, never treated as media. */
     140  img.emoji { height: 1.35em; width: auto; margin: 0 .04em; vertical-align: -0.22em;
     141    display: inline-block; border-radius: 0; background: none; box-shadow: none; }
     142  .tl-content img.emoji, .tl-quote-body img.emoji { max-width: none; }
     143
     144  /* FEP-044f embedded quote card (mirror of the Shaer QuoteCard). */
     145  .tl-quote { margin: .75rem 0 0; padding: .6rem .7rem; border-radius: 12px;
     146    border: 1px solid color-mix(in srgb, var(--ink, #000) 12%, transparent);
     147    background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent); }
     148  .tl-quote-head { display: flex; align-items: center; gap: .4rem; margin: 0 0 .35rem; min-width: 0; }
     149  .tl-quote-avatar { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; object-fit: cover; background: #fff; }
     150  .tl-quote-name { font-weight: 700; font-size: .85rem; color: var(--ink, inherit); white-space: nowrap; }
     151  .tl-quote-handle { color: var(--ink-soft, #888); font-size: .78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
     152  .tl-quote-body { line-height: 1.5; font-size: .92rem; color: var(--ink-soft, #555);
     153    overflow-wrap: anywhere; max-height: 16em; overflow: hidden; }
     154  .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; }
     155  .tl-quote-body a { color: var(--accent, #06c); }
     156  .tl-quote-media { display: block; margin: .45rem 0 0; }
     157  .tl-quote-media img { max-width: 100%; height: auto; border-radius: 8px; }
     158
    139159  /* Always show the FULL image at its natural ratio — never cropped, no letterbox. */
    140160  .tl-media { display: flex; flex-direction: column; gap: .4rem; margin: .75rem 0 0; }
  • src/views/partials/tl-item.ejs

    rb5c2538 r3597763  
    11<li class="tl-item">
    2           <% if (p.reblog_name) { %><div class="tl-boost-by"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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> <strong><%= p.reblog_name %></strong> <%= t('tl.boosted') %></div><% } %>
     2          <% if (p.reblog_name) { %><div class="tl-boost-by"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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> <strong><%- emojiName(p.reblog_name, p.reblog_emoji_json) %></strong> <%= t('tl.boosted') %></div><% } %>
    33          <div class="tl-head">
    44            <span class="tl-avatar"><% if (p.author_icon) { %><img src="<%= avatar(p.author_icon, 96) %>" alt="" loading="lazy"><% } else { %><%= (p.author_name || '?').charAt(0).toUpperCase() %><% } %></span>
    55            <span class="tl-id">
    6               <a class="tl-author" href="<%= p.author_url || p.author_uri %>" target="_blank" rel="nofollow noopener"><%= p.author_name %></a>
     6              <a class="tl-author" href="<%= p.author_url || p.author_uri %>" target="_blank" rel="nofollow noopener"><%- emojiName(p.author_name, p.author_emoji_json) %></a>
    77              <span class="tl-handle"><%= p.author_handle %></span>
    88            </span>
     
    1919          %>
    2020          <% if (_nsfwText) { %>
    21             <div class="tl-content nsfw-media"><span class="nsfw-veil"><%- include('../partials/nsfw-veil', { cw: p.cw }) %></span><%- p.content %></div>
     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>
    2222          <% } else { %>
    23             <div class="tl-content"><%- p.content %></div>
     23            <div class="tl-content"><%- emojiHtml(p.content, p.emoji_json) %></div>
    2424          <% } %>
     25          <% var _quote = noteQuote(p.quote_json); if (_quote) { %><%- include('../partials/quote-card', { q: _quote }) %><% } %>
    2526          <% if (_nsfwVisual) { %><div class="nsfw-media"><% } %>
    2627          <% if (imgs.length) { %>
Note: See TracChangeset for help on using the changeset viewer.