This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / reaction / reactions.ts
2.9 kB 90 lines
1import { didFromUri, rkeyFromUri } from "$lib/api/uri"; 2import type { ReactionRecord, RecordView } from "$lib/api/records"; 3 4export const ORDERED_REACTION_KINDS = ["👍", "👎", "😆", "🎉", "🫤", "❤️", "🚀", "👀"] as const; 5export type ReactionKind = (typeof ORDERED_REACTION_KINDS)[number]; 6 7const KIND_SET = new Set<string>(ORDERED_REACTION_KINDS); 8 9export interface ReactionGroup { 10 kind: ReactionKind; 11 count: number; 12 users: string[]; 13 isReacted: boolean; 14 viewerRkey?: string; 15} 16 17// groups flat reaction records by emoji, keeping OrderedReactionKinds order and 18// dropping kinds nobody used. handleOf resolves a reactor did to a display handle. 19export function buildReactions( 20 items: RecordView<ReactionRecord>[], 21 viewerDid?: string, 22 handleOf?: (did: string) => string 23): ReactionGroup[] { 24 const groups = new Map<ReactionKind, ReactionGroup>(); 25 26 for (const item of items) { 27 const kind = item.value.reaction; 28 if (!KIND_SET.has(kind)) continue; 29 const did = didFromUri(item.uri); 30 let group = groups.get(kind as ReactionKind); 31 if (!group) { 32 group = { kind: kind as ReactionKind, count: 0, users: [], isReacted: false }; 33 groups.set(kind as ReactionKind, group); 34 } 35 group.count++; 36 group.users.push(handleOf ? handleOf(did) : did); 37 if (viewerDid && did === viewerDid) { 38 group.isReacted = true; 39 group.viewerRkey = rkeyFromUri(item.uri); 40 } 41 } 42 43 return ORDERED_REACTION_KINDS.filter((k) => groups.has(k)).map((k) => groups.get(k)!); 44} 45 46// optimistic addition for a reaction group 47export function withViewerReaction( 48 group: ReactionGroup, 49 viewerHandle: string, 50 rkey: string 51): ReactionGroup { 52 return { 53 ...group, 54 count: group.count + 1, 55 users: [...group.users, viewerHandle], 56 isReacted: true, 57 viewerRkey: rkey 58 }; 59} 60 61// optimistic removal for a reaction group 62export function withoutViewerReaction(group: ReactionGroup, viewerHandle: string): ReactionGroup { 63 // drop one occurrence of the viewer; count never dips below zero 64 const idx = group.users.indexOf(viewerHandle); 65 const users = idx === -1 ? group.users : group.users.filter((_, i) => i !== idx); 66 return { 67 ...group, 68 count: Math.max(0, group.count - 1), 69 users, 70 isReacted: false, 71 viewerRkey: undefined 72 }; 73} 74 75// optimistic add of a fresh reaction (e.g. from the picker): bumps the group if the 76// kind is already present, otherwise inserts a new one, keeping canonical emoji order. 77export function upsertViewerReaction( 78 groups: ReactionGroup[], 79 kind: ReactionKind, 80 viewerHandle: string, 81 rkey: string 82): ReactionGroup[] { 83 const exists = groups.some((g) => g.kind === kind); 84 const next = exists 85 ? groups.map((g) => (g.kind === kind ? withViewerReaction(g, viewerHandle, rkey) : g)) 86 : [...groups, { kind, count: 1, users: [viewerHandle], isReacted: true, viewerRkey: rkey }]; 87 return ORDERED_REACTION_KINDS.filter((k) => next.some((g) => g.kind === k)).map((k) => 88 next.find((g) => g.kind === k)! 89 ); 90}