This repository has no description
0

Configure Feed

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

web/api: add reaction record and fetcher

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
date (Jul 30, 2026, 6:02 PM +0100) commit 1424032c parent 2627bbab change-id ltqnxpxn
+63 -4
+14 -4
web/src/lib/api/records.ts
··· 2 2 import { jsonGet } from "./_request"; 3 3 import type * as ShTangledActorProfile from "./lexicons/types/sh/tangled/actor/profile"; 4 4 import type * as ShTangledFeedComment from "./lexicons/types/sh/tangled/feed/comment"; 5 + import type * as ShTangledFeedReaction from "./lexicons/types/sh/tangled/feed/reaction"; 5 6 import type * as ShTangledRepo from "./lexicons/types/sh/tangled/repo"; 6 7 import type * as ShTangledRepoIssue from "./lexicons/types/sh/tangled/repo/issue"; 7 8 import type * as ShTangledRepoIssueState from "./lexicons/types/sh/tangled/repo/issue/state"; ··· 25 26 export type PullRecord = ShTangledRepoPull.Main; 26 27 export type StringRecord = ShTangledString.Main; 27 28 export type CommentRecord = ShTangledFeedComment.Main; 29 + export type ReactionRecord = ShTangledFeedReaction.Main; 28 30 29 31 export const BULK_LIMIT = 50; 30 32 ··· 84 86 init?: XrpcRequestInit 85 87 ) => jsonGet<IssueListPage>(ctx, "sh.tangled.repo.listIssues", { subject, ...filter }, init); 86 88 87 - // getIssue returns the raw record only; the derived open/closed state lives in 88 - // separate sh.tangled.repo.issue.state records, latest-wins. 89 89 export interface IssueStateList { 90 90 cursor?: string | null; 91 91 items: RecordView<IssueStateRecord>[]; ··· 98 98 init?: XrpcRequestInit 99 99 ) => jsonGet<IssueStateList>(ctx, "sh.tangled.repo.issue.listStates", { subject, ...filter }, init); 100 100 101 - // comments on any record (issue/pull/string) are flat sh.tangled.feed.comment records; 102 - // threading is derived client-side from each comment's optional replyTo strongRef. 103 101 export interface CommentListPage { 104 102 cursor?: string | null; 105 103 items: RecordView<CommentRecord>[]; ··· 111 109 filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, 112 110 init?: XrpcRequestInit 113 111 ) => jsonGet<CommentListPage>(ctx, "sh.tangled.feed.listComments", { subject, ...filter }, init); 112 + 113 + export interface ReactionListPage { 114 + cursor?: string | null; 115 + items: RecordView<ReactionRecord>[]; 116 + } 117 + 118 + export const listReactions = ( 119 + ctx: BobbinContext, 120 + subject: string, 121 + filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, 122 + init?: XrpcRequestInit 123 + ) => jsonGet<ReactionListPage>(ctx, "sh.tangled.feed.listReactions", { subject, ...filter }, init); 114 124 115 125 export const getPull = (ctx: BobbinContext, pull: string, init?: XrpcRequestInit) => 116 126 jsonGet<RecordView<PullRecord>>(ctx, "sh.tangled.repo.getPull", { pull }, init);
+30
web/src/lib/components/reaction/Reaction.svelte
··· 1 + <script lang="ts"> 2 + import type { ReactionGroup } from "./reactions"; 3 + 4 + interface Props { 5 + reaction: ReactionGroup; 6 + } 7 + 8 + let { reaction }: Props = $props(); 9 + 10 + // keep the tooltip bounded; overflow becomes "and N more", like the go appview 11 + const MAX_NAMES = 10; 12 + const title = $derived.by(() => { 13 + const shown = reaction.users.slice(0, MAX_NAMES); 14 + const more = reaction.count - shown.length; 15 + const names = shown.join(", "); 16 + return more > 0 ? `${names}, and ${more} more` : names; 17 + }); 18 + </script> 19 + 20 + <span 21 + {title} 22 + class={`flex min-h-6 items-center justify-center gap-1.5 rounded border px-2 text-sm leading-4 ${ 23 + reaction.isReacted 24 + ? "border-border-strong bg-background-muted text-foreground-default" 25 + : "border-border-default text-foreground-subtle" 26 + }`} 27 + > 28 + <span>{reaction.kind}</span> 29 + <span>{reaction.count}</span> 30 + </span>
+19
web/src/lib/components/reaction/Reactions.svelte
··· 1 + <script lang="ts"> 2 + import Reaction from "./Reaction.svelte"; 3 + import type { ReactionGroup } from "./reactions"; 4 + 5 + interface Props { 6 + reactions: ReactionGroup[]; 7 + class?: string; 8 + } 9 + 10 + let { reactions, class: className = "" }: Props = $props(); 11 + </script> 12 + 13 + {#if reactions.length} 14 + <div class={`flex flex-wrap items-center gap-2 ${className}`}> 15 + {#each reactions as reaction (reaction.kind)} 16 + <Reaction {reaction} /> 17 + {/each} 18 + </div> 19 + {/if}