Changeset 0a75356 in Klonkt
- Timestamp:
- 06/26/2026 11:55:09 PM (2 months ago)
- Branches:
- main
- Children:
- 7e66e7e
- Parents:
- 9d34855
- Location:
- src
- Files:
-
- 3 edited
-
routes/posts.js (modified) (1 diff)
-
services/ActivityPubService.js (modified) (2 diffs)
-
views/pages/news.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/posts.js
r9d34855 r0a75356 693 693 }); 694 694 695 // Like / unlike a feed post — a toggle. Fetch request → JSON {on} (stay on the page, 696 // no banner); no-JS → redirect back. 695 697 router.post('/news/like', requireSiteManager, async (req, res) => { 696 698 const site = res.locals.site; 697 699 const note = (req.body.note || '').toString(); 700 let on = false; 698 701 if (site && note) { 699 try { await ActivityPubService.sendInteraction(site, 'like', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 700 ActivityPubService.markLiked(site.slug, note); 701 } 702 res.redirect('/news?success=' + encodeURIComponent('Geliket ⭐')); 703 }); 704 705 router.post('/news/unlike', requireSiteManager, async (req, res) => { 702 on = !ActivityPubService.getTimelineReaction(site.slug, note).liked; 703 try { await ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 704 if (on) ActivityPubService.markLiked(site.slug, note); else ActivityPubService.unmarkLiked(site.slug, note); 705 } 706 if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on }); 707 res.redirect('/news'); 708 }); 709 710 // Boost / unboost a feed post — a toggle. markBoosted also surfaces it in the Cirkel. 711 router.post('/news/boost', requireSiteManager, async (req, res) => { 706 712 const site = res.locals.site; 707 713 const note = (req.body.note || '').toString(); 714 let on = false; 708 715 if (site && note) { 709 try { await ActivityPubService.sendInteraction(site, 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 710 ActivityPubService.unmarkLiked(site.slug, note); 711 } 712 res.redirect('/news?success=' + encodeURIComponent('Like ingetrokken')); 713 }); 714 715 router.post('/news/boost', requireSiteManager, async (req, res) => { 716 const site = res.locals.site; 717 const note = (req.body.note || '').toString(); 718 if (site && note) { 719 try { await ActivityPubService.sendInteraction(site, 'boost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 720 ActivityPubService.markBoosted(site.slug, note); // also surface it in the Cirkel (mixed by date) 721 } 722 res.redirect('/news?success=' + encodeURIComponent('Geboost 🔁')); 723 }); 724 725 // Unboost (retract): send Undo(Announce) + clear the local flag. 726 router.post('/news/unboost', requireSiteManager, async (req, res) => { 727 const site = res.locals.site; 728 const note = (req.body.note || '').toString(); 729 if (site && note) { 730 try { await ActivityPubService.sendInteraction(site, 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 731 ActivityPubService.unmarkBoosted(site.slug, note); 732 } 733 res.redirect('/news?success=' + encodeURIComponent('Boost ingetrokken')); 716 on = !ActivityPubService.getTimelineReaction(site.slug, note).boosted; 717 try { await ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 718 if (on) ActivityPubService.markBoosted(site.slug, note); else ActivityPubService.unmarkBoosted(site.slug, note); 719 } 720 if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on }); 721 res.redirect('/news'); 734 722 }); 735 723 -
src/services/ActivityPubService.js
r9d34855 r0a75356 1106 1106 try { if (!_unmarkLike) _unmarkLike = db.prepare('UPDATE ap_timeline SET liked = 0 WHERE slug = ? AND id = ?'); _unmarkLike.run(slug, noteId); } catch { /* ignore */ } 1107 1107 } 1108 export function getTimelineReaction(slug, noteId) { 1109 try { const r = db.prepare('SELECT liked, boosted FROM ap_timeline WHERE slug = ? AND id = ?').get(slug, noteId); return { liked: !!(r && r.liked), boosted: !!(r && r.boosted) }; } catch { return { liked: false, boosted: false }; } 1110 } 1108 1111 export function boostedCount(slug) { 1109 1112 try { if (!_boostedCount) _boostedCount = db.prepare('SELECT COUNT(*) AS n FROM ap_timeline WHERE slug = ? AND boosted = 1'); return _boostedCount.get(slug).n; } catch { return 0; } … … 1356 1359 listOutbox, deliverOutboxDelete, 1357 1360 webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction, 1358 autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, get CirkelPosts, getCirkelMembers, selfHealTimeline,1361 autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, getCirkelPosts, getCirkelMembers, selfHealTimeline, 1359 1362 getNotifications, listBlocks, isBlockedAny, blockTarget, unblock, 1360 1363 deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker, -
src/views/pages/news.ejs
r9d34855 r0a75356 47 47 48 48 <div class="tl-actions"> 49 <% if (p.liked) { %> 50 <form method="post" action="/news/unlike" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like is-on" title="<%= t('fedi.unlike_short') %>" aria-label="<%= t('fedi.unlike_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></form> 51 <% } else { %> 52 <form method="post" action="/news/like" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like" title="<%= t('fedi.likes') %>" aria-label="<%= t('fedi.likes') %>"><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></form> 53 <% } %> 54 <% if (p.boosted) { %> 55 <form method="post" action="/news/unboost" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost is-on" title="<%= t('tl.unboost') %>" aria-label="<%= t('tl.unboost') %>"><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></form> 56 <% } else { %> 57 <form method="post" action="/news/boost" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost" title="<%= t('fedi.boosts') %>" aria-label="<%= t('fedi.boosts') %>"><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></form> 58 <% } %> 49 <form method="post" action="/news/like" class="tl-act-form tl-react-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like<%= p.liked ? ' is-on' : '' %>" title="<%= p.liked ? t('fedi.unlike_short') : t('fedi.likes') %>" aria-label="<%= p.liked ? t('fedi.unlike_short') : t('fedi.likes') %>"><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></form> 50 <form method="post" action="/news/boost" class="tl-act-form tl-react-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost<%= p.boosted ? ' is-on' : '' %>" title="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>" aria-label="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>"><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></form> 59 51 <button type="button" class="tl-act tl-act-reply fedi-remote-reply-btn" data-fedi-uri="<%= p.id %>" data-fedi-ph="<%= t('fedi.remote_ph') %>" title="<%= t('fedi.remote_reply') %>" aria-label="<%= t('fedi.remote_reply') %>"><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></button> 60 52 <form method="post" action="/blocking/add" class="tl-act-form" onsubmit="return confirm('<%= t('tl.block') %>?');"><input type="hidden" name="target" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-block" title="<%= t('tl.block') %>" aria-label="<%= t('tl.block') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></svg></button></form> … … 168 160 window.addEventListener('scroll', close, true); 169 161 })(); 162 163 /* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page (no reload, no banner). */ 164 (function(){ 165 if (window.__newsReactWired) return; window.__newsReactWired = true; 166 document.addEventListener('submit', function(e){ 167 var f = e.target.closest && e.target.closest('.tl-react-form'); 168 if (!f) return; 169 e.preventDefault(); 170 var btn = f.querySelector('button'); if (!btn || btn.disabled) return; 171 btn.disabled = true; 172 var body = new URLSearchParams(); 173 new FormData(f).forEach(function(v, k){ body.append(k, v); }); 174 fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' }) 175 .then(function(r){ return r.ok ? r.json() : null; }) 176 .then(function(j){ if (j) btn.classList.toggle('is-on', !!j.on); }) 177 .catch(function(){}) 178 .then(function(){ btn.disabled = false; }); 179 }); 180 })(); 170 181 </script>
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)