Changeset c745659 in Klonkt


Ignore:
Timestamp:
06/26/2026 11:25:14 PM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
3289a64
Parents:
eacd3d6
Message:

feat(fedi): comment boost is a toggle with state + coloured action icons

Inbound fediverse comments: the owner like/boost/reply controls are now coloured SVG
icons (gold star / green repeat / accent arrow), matching the News feed. Boost is a
real toggle — ap_interactions.acted_boost remembers what you boosted, the button shows
an 'on' state, and clicking again retracts it (Undo Announce). Like stays fire-and-forget
(no unlike in AS).

Location:
src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/config/database.js

    reacd3d6 rc745659  
    356356  `);
    357357  ensureColumn('ap_interactions', 'parent_uri', 'TEXT'); // nesting (existing DBs)
     358  ensureColumn('ap_interactions', 'acted_boost', 'INTEGER DEFAULT 0'); // owner boosted this comment (🔁) → can undo
    358359
    359360  // Fediverse CLIENT: accounts WE follow (outbound) + the home timeline of their posts.
  • src/routes/posts.js

    reacd3d6 rc745659  
    10151015  const kind = req.body.kind === 'boost' ? 'boost' : 'like';
    10161016  if (parent && parent.post_id === post.id && parent.object_uri) {
    1017     ActivityPubService.sendInteraction(site, kind, parent.object_uri, parent.actor_uri)
    1018       .catch((e) => console.warn('[AP] reaction failed:', e.message));
     1017    if (kind === 'boost') {
     1018      // Toggle: boost an unboosted comment, or retract it (Undo Announce) if already boosted.
     1019      const on = !parent.acted_boost;
     1020      ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', parent.object_uri, parent.actor_uri)
     1021        .catch((e) => console.warn('[AP] reaction failed:', e.message));
     1022      ActivityPubService.setInteractionBoosted(parent.id, on);
     1023    } else {
     1024      ActivityPubService.sendInteraction(site, 'like', parent.object_uri, parent.actor_uri)
     1025        .catch((e) => console.warn('[AP] reaction failed:', e.message));
     1026    }
    10191027  }
    10201028  res.redirect(`${res.locals.siteUrlBase || ''}/${post.slug}#fediverse`);
  • src/services/ActivityPubService.js

    reacd3d6 rc745659  
    352352    _delLA = db.prepare('DELETE FROM ap_interactions WHERE kind = ? AND post_id = ? AND actor_uri = ?');
    353353    _delReply = db.prepare("DELETE FROM ap_interactions WHERE kind = 'reply' AND object_uri = ?");
    354     _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');
     354    _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at, acted_boost FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');
    355355    _getI = db.prepare('SELECT * FROM ap_interactions WHERE id = ?');
    356356    _insO = db.prepare('INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, created_at) VALUES (?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     
    362362
    363363export function getInteractionById(id) { return iStmts().getI.get(id); }
     364export function setInteractionBoosted(id, on) {
     365  db.prepare('UPDATE ap_interactions SET acted_boost = ? WHERE id = ?').run(on ? 1 : 0, id);
     366}
    364367
    365368const localPostExists = (id) => { try { return !!db.prepare('SELECT 1 FROM posts WHERE id = ?').get(id); } catch { return false; } };
     
    420423      actor_name: r.actor_name, actor_handle: r.actor_handle, actor_url: r.actor_url,
    421424      actor_icon: r.actor_icon, content: r.content, created_at: r.published || r.created_at,
     425      acted_boost: !!r.acted_boost,
    422426      children: [],
    423427    });
     
    13271331  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers, buildFeatured,
    13281332  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate, deliverActorUpdate, resyncFeaturedPins,
    1329   getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
     1333  getInteractions, getInteractionById, setInteractionBoosted, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
    13301334  listOutbox, deliverOutboxDelete,
    13311335  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction,
  • src/views/pages/post.ejs

    reacd3d6 rc745659  
    325325.comment-delete-btn:hover { color: #c33; }
    326326
     327/* Owner comment-action icons — same visual language as the News feed, compact (30px). */
     328.fedi-cact { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 50%; padding: 0; box-sizing: border-box;
     329  display: inline-flex; align-items: center; justify-content: center; list-style: none;
     330  border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent);
     331  background: color-mix(in srgb, var(--ink, #000) 3%, transparent);
     332  color: var(--ink-soft, #888); cursor: pointer; -webkit-tap-highlight-color: transparent;
     333  transition: background .12s, border-color .12s, transform .1s; }
     334.fedi-cact::-webkit-details-marker { display: none; }
     335.fedi-cact svg { width: 15px; height: 15px; display: block; }
     336.fedi-cact:active { transform: scale(.9); }
     337.fedi-cact-like svg { color: #e8b04b; }
     338.fedi-cact-like:hover { background: color-mix(in srgb, #e8b04b 15%, transparent); border-color: color-mix(in srgb, #e8b04b 50%, transparent); }
     339.fedi-cact-boost svg { color: #2fa85a; }
     340.fedi-cact-boost:hover { background: color-mix(in srgb, #2fa85a 15%, transparent); border-color: color-mix(in srgb, #2fa85a 50%, transparent); }
     341.fedi-cact-boost.is-on { background: color-mix(in srgb, #2fa85a 18%, transparent); border-color: color-mix(in srgb, #2fa85a 60%, transparent); }
     342.fedi-cact-reply svg { color: var(--accent, #06c); }
     343.fedi-cact-reply:hover { background: color-mix(in srgb, var(--accent, #888) 14%, transparent); border-color: color-mix(in srgb, var(--accent, #888) 50%, transparent); }
     344
    327345.comment-replies {
    328346  margin-top: 1rem;
  • src/views/partials/fedi-node.ejs

    reacd3d6 rc745659  
    2222        <input type="hidden" name="interaction_id" value="<%= n.id %>">
    2323        <input type="hidden" name="kind" value="like">
    24         <button type="submit" class="comment-reply-btn">⭐ <%= t('fedi.like_short') %></button>
     24        <button type="submit" class="fedi-cact fedi-cact-like" title="<%= t('fedi.like_short') %>" aria-label="<%= t('fedi.like_short') %>"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><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></button>
    2525      </form>
    2626      <form method="post" action="<%= _base %>/posts/<%= postSlug %>/fedi-react" class="fedi-owner-react">
    2727        <input type="hidden" name="interaction_id" value="<%= n.id %>">
    2828        <input type="hidden" name="kind" value="boost">
    29         <button type="submit" class="comment-reply-btn">🔁 <%= t('fedi.boost_short') %></button>
     29        <button type="submit" class="fedi-cact fedi-cact-boost<%= n.acted_boost ? ' is-on' : '' %>" title="<%= n.acted_boost ? t('tl.unboost') : t('fedi.boost_short') %>" aria-label="<%= n.acted_boost ? t('tl.unboost') : t('fedi.boost_short') %>"><svg viewBox="0 0 24 24" 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></button>
    3030      </form>
    3131      <details class="fedi-owner-reply">
    32         <summary class="comment-reply-btn">
    33           <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg>
    34           <%= t('fedi.remote_reply_short') %>
    35         </summary>
     32        <summary class="fedi-cact fedi-cact-reply" title="<%= t('fedi.remote_reply_short') %>" aria-label="<%= t('fedi.remote_reply_short') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg></summary>
    3633        <form method="post" action="<%= _base %>/posts/<%= postSlug %>/fedi-reply" class="comment-reply-form">
    3734          <input type="hidden" name="interaction_id" value="<%= n.id %>">
Note: See TracChangeset for help on using the changeset viewer.