Changeset 952baf3 in Klonkt for src/routes/posts.js
- Timestamp:
- 08/07/2026 05:15:52 PM (5 weeks ago)
- Branches:
- main
- Children:
- ba76bf5
- Parents:
- f85b2c3 (diff), 0d5bd2c (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - File:
-
- 1 edited
-
src/routes/posts.js (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/posts.js
rf85b2c3 r952baf3 256 256 257 257 renderPage(req, res, 'pages/post-edit', { 258 // post-edit neemt de playlist-editor op. 259 pageJs: 'post-edit playlist-editor', 258 260 post: { 259 261 id: uuid(), … … 795 797 } 796 798 renderPage(req, res, 'pages/authorize-interaction', { 799 pageJs: 'authorize-interaction', 797 800 pageTitleKey: 'fedi.remote_interact', // i18n: was hardcoded Dutch on non-NL sites 798 801 bodyClass: 'on-special', … … 806 809 liked: !!req.query.liked, 807 810 boosted: !!req.query.boosted, 808 reacted: (site && uri) ? ActivityPubService.get MyReactions(site.slug, uri) : { liked: false, boosted: false },811 reacted: (site && uri) ? ActivityPubService.getReaction(site.slug, uri) : { liked: false, boosted: false }, 809 812 siteTitle: site ? site.title : '', 810 813 }); … … 839 842 let on = false; 840 843 if (site && uri) { 841 on = !ActivityPubService.get MyReactions(site.slug, uri).liked;844 on = !ActivityPubService.getReaction(site.slug, uri).liked; 842 845 ActivityPubService.resolveRemoteNote(uri) 843 846 .then((note) => note && ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note.object_uri || uri, note.actor_uri)) 844 847 .catch((e) => console.warn('[AP] remote like failed:', e.message)); 845 ActivityPubService.setMyReaction(site.slug, uri, 'like', on); 848 // Eén schrijfpad (shaer-9e9): tussentabel + afgeleide vlag. 849 ActivityPubService.setReaction(site.slug, uri, 'like', on); 846 850 } 847 851 if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on }); … … 856 860 let on = false; 857 861 if (site && uri) { 858 on = !ActivityPubService.get MyReactions(site.slug, uri).boosted;862 on = !ActivityPubService.getReaction(site.slug, uri).boosted; 859 863 ActivityPubService.resolveRemoteNote(uri) 860 864 .then((note) => { … … 862 866 const id = note.object_uri || uri; 863 867 return Promise.resolve(ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', id, note.actor_uri)) 864 // Boost → store the post in the timeline (even if you don't follow the author) so it 865 // surfaces in the Cirkel; unboost → just clear the flag. 866 .then(() => on ? ActivityPubService.upsertBoostedNote(site.slug, note) : ActivityPubService.unmarkBoosted(site.slug, id)); 868 // De note gaat mee: een boost zet niet alleen een vlag maar trekt de 869 // post je tijdlijn in, ook als je de auteur niet volgt, zodat hij in 870 // de Cirkel verschijnt. 871 .then(() => ActivityPubService.setReaction(site.slug, uri, 'boost', on, { flagUri: id, note: on ? note : null })); 867 872 }) 868 873 .catch((e) => console.warn('[AP] remote boost failed:', e.message)); 869 ActivityPubService.setMyReaction(site.slug, uri, 'boost', on); 874 // Meteen zetten, zodat de knop klopt voordat de resolve terug is. Via 875 // setReaction en niet via setMyReaction: ook dit korte moment mag geen 876 // halve schrijfactie zijn. De resolve hierboven werkt hem daarna bij met de 877 // note, zodat de post ook in je tijdlijn belandt. 878 ActivityPubService.setReaction(site.slug, uri, 'boost', on); 870 879 } 871 880 if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on }); … … 930 939 : []).filter((o) => o['shaer:ward'] === gMe && o['shaer:needsMyAccept']); 931 940 renderPage(req, res, 'pages/messages', { 932 pageTitleKey: 'msg.title', bodyClass: 'on-special', items, seenAt,941 pageTitleKey: 'msg.title', bodyClass: 'on-special', pageJs: 'messages', items, seenAt, 933 942 hasMore, nextOffset: offset + FEED_PAGE, moreBase, guardianOffers, 934 943 success: req.query.success || null, error: req.query.error || null, … … 965 974 } catch { /* fall through */ } 966 975 res.redirect(back + '?error=quickreply'); 976 }); 977 978 // Antwoorden vanuit een gesprek in Berichten. Twee paden, en welke het wordt 979 // bepaalt de draad zelf (zie groupConversations → replyTo): 980 // - hangt de draad aan een post van jou, dan is dit een gewone reply op het 981 // nieuwste ontvangen bericht erin: deliverReply, publiek zoals de thread; 982 // - hangt hij aan een persoon, dan is het een direct bericht terug. 983 // Rijk in beide gevallen: `content` is de HTML uit de reply-editor, `text` de 984 // platte versie die de editor er altijd bij levert (en die het no-JS-formulier 985 // als enige stuurt). 986 router.post('/messages/reply', requireSiteManager, async (req, res) => { 987 const site = res.locals.site; 988 const back = `${res.locals.siteUrlBase || ''}/messages`; 989 if (!site) return res.status(404).send('Site required'); 990 const text = String(req.body.text || ''); 991 const html = String(req.body.content || ''); 992 let attachments = []; 993 try { attachments = JSON.parse(req.body.attachments || '[]'); } catch { /* geen media */ } 994 let mentions; 995 try { if (req.body.mentions !== undefined) mentions = JSON.parse(req.body.mentions || '[]'); } catch { mentions = undefined; } 996 const language = String(req.body.language || ''); 997 // Leeg is leeg: een bericht zonder tekst EN zonder media is geen bericht. 998 if (!text.trim() && !html.trim() && !attachments.length) return res.redirect(back + '?error=reply_empty'); 999 1000 const interactionId = parseInt(req.body.interaction_id, 10) || 0; 1001 const postSlug = String(req.body.post_slug || ''); 1002 const toActor = String(req.body.to || ''); 1003 try { 1004 if (interactionId && postSlug) { 1005 const post = db.prepare('SELECT id, slug FROM posts WHERE site_id = ? AND slug = ?').get(site.id, postSlug); 1006 const parent = ActivityPubService.getInteractionById(interactionId); 1007 // De parent MOET bij deze post horen: anders zou een gemanipuleerd 1008 // formulier een antwoord onder andermans draad kunnen hangen. 1009 if (!post || !parent || parent.post_id !== post.id) return res.redirect(back + '?error=reply_target'); 1010 await ActivityPubService.deliverReply(site, { 1011 postId: post.id, postSlug: post.slug, parent, text, html, attachments, mentions, language, 1012 }); 1013 } else if (/^https?:\/\//i.test(toActor)) { 1014 const r = await Guardianship.deliverDirectNote(site, { recipients: [toActor], text, html, language, attachments }); 1015 if (!r) return res.redirect(back + '?error=reply_failed'); 1016 } else { 1017 return res.redirect(back + '?error=reply_target'); 1018 } 1019 } catch (e) { 1020 console.warn('[AP] reply from Berichten failed:', e.message); 1021 return res.redirect(back + '?error=reply_failed'); 1022 } 1023 res.redirect(back + '?success=reply_sent'); 967 1024 }); 968 1025 … … 1139 1196 } 1140 1197 renderPage(req, res, 'pages/news', { 1198 pageJs: 'news', 1141 1199 pageTitle: 'News', bodyClass: 'on-special', 1142 1200 timeline, hasMore, nextOffset: offset + FEED_PAGE, moreBase, … … 1229 1287 let on = false; 1230 1288 if (site && note) { 1231 on = !ActivityPubService.get TimelineReaction(site.slug, note).liked;1289 on = !ActivityPubService.getReaction(site.slug, note).liked; 1232 1290 try { await ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 1233 if (on) ActivityPubService.markLiked(site.slug, note); else ActivityPubService.unmarkLiked(site.slug, note);1291 ActivityPubService.setReaction(site.slug, note, 'like', on); 1234 1292 } 1235 1293 if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on }); … … 1243 1301 let on = false; 1244 1302 if (site && note) { 1245 on = !ActivityPubService.get TimelineReaction(site.slug, note).boosted;1303 on = !ActivityPubService.getReaction(site.slug, note).boosted; 1246 1304 try { await ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ } 1305 ActivityPubService.setReaction(site.slug, note, 'boost', on); // instant UI state 1247 1306 if (on) { 1248 ActivityPubService.markBoosted(site.slug, note); // instant UI state1249 1307 // Fire-and-forget: re-resolve the note so the cached row is refreshed 1250 1308 // (cover/content) — boosting again heals a stale copy from EVERY boost 1251 1309 // path, not just the interact page. 1252 1310 ActivityPubService.resolveRemoteNote(note) 1253 .then((n) => { if (n) ActivityPubService. upsertBoostedNote(site.slug, n); })1311 .then((n) => { if (n) ActivityPubService.setReaction(site.slug, note, 'boost', true, { note: n }); }) 1254 1312 .catch(() => { /* best-effort */ }); 1255 } else {1256 ActivityPubService.unmarkBoosted(site.slug, note);1257 1313 } 1258 1314 } … … 1338 1394 const { newerPost, olderPost } = postNeighbors(site, post); 1339 1395 return renderPage(req, res, 'pages/paid-gate', { 1396 pageJs: 'paid-gate', 1340 1397 pageTitle: post.title || 'Voor supporters', 1341 1398 bodyClass: 'on-special', … … 1454 1511 1455 1512 renderPage(req, res, 'pages/post', { 1513 pageJs: 'post', 1456 1514 post, 1457 1515 poll: ActivityPubService.ownPollView(post), … … 1505 1563 const kind = req.body.kind === 'boost' ? 'boost' : 'like'; 1506 1564 if (parent && parent.post_id === post.id && parent.object_uri) { 1507 if (kind === 'boost') { 1508 // Toggle: boost an unboosted comment, or retract it (Undo Announce) if already boosted. 1509 const on = !parent.acted_boost; 1510 ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', parent.object_uri, parent.actor_uri) 1511 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1512 ActivityPubService.setInteractionBoosted(parent.id, on); 1513 } else { 1514 // Toggle: like an unliked comment, or un-favourite (Undo Like) if already liked. 1515 const on = !parent.acted_like; 1516 ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', parent.object_uri, parent.actor_uri) 1517 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1518 ActivityPubService.setInteractionLiked(parent.id, on); 1519 } 1565 // Toggle: react, or retract it (Undo Announce / Undo Like) if already on. 1566 // De stand komt uit dezelfde bron als de knop die je zag; leest de toggle uit 1567 // de kolom en de knop uit de tussentabel, dan draait een divergentie de 1568 // richting om en stuur je een Undo voor iets dat nooit is verstuurd. 1569 const ik = ActivityPubService.getReaction(site.slug, parent.object_uri); 1570 const on = kind === 'boost' ? !ik.boosted : !ik.liked; 1571 ActivityPubService.sendInteraction(site, on ? kind : `un${kind}`, parent.object_uri, parent.actor_uri) 1572 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1573 // De tussentabel is de waarheid (shaer-ipb), gesleuteld op object_uri -- net 1574 // als de Like die hierboven de fediverse in gaat. acted_* blijft voorlopig 1575 // als afgeleide meelopen, hetzelfde vangnet dat ap_timeline.liked na 1576 // shaer-9e9 is: pas weghalen als deze migratie een release heeft ingelopen. 1577 ActivityPubService.setReaction(site.slug, parent.object_uri, kind, on); 1578 if (kind === 'boost') ActivityPubService.setInteractionBoosted(parent.id, on); 1579 else ActivityPubService.setInteractionLiked(parent.id, on); 1520 1580 } 1521 1581 res.redirect(`${res.locals.siteUrlBase || ''}/${post.slug}#fediverse`);
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)