Files
BRETT/app/composables/useCommentReactions.ts
2026-10-03 20:07:49 +02:00

90 lines
2.5 KiB
TypeScript

export interface CommentReaction {
emoji: string
count: number
me: boolean
names: string[]
}
export interface ReactionEventPayload {
commentId: number
emoji: string
actorId: number
actorName: string
active: boolean
count: number
}
export const REACTION_EMOJIS = ['👍', '❤️', '😂', '😮', '😢', '🔥', '🎉', '👏', '😍', '🤔', '👎', '😅', '🙏', '💯', '🚀', '💀']
export function useCommentReactions() {
const map = useState<Record<number, CommentReaction[]>>('reactions:by-comment', () => ({}))
const { user } = useAuth()
function seed(commentId: number, reactions: CommentReaction[]) {
map.value = { ...map.value, [commentId]: reactions }
}
function reactionsFor(commentId: number): CommentReaction[] {
return map.value[commentId] ?? []
}
function applyEvent(e: ReactionEventPayload) {
const list = (map.value[e.commentId] ??= [])
let entry = list.find(r => r.emoji === e.emoji)
if (e.active) {
if (!entry) {
entry = { emoji: e.emoji, count: 0, me: false, names: [] }
list.push(entry)
}
if (!entry.names.includes(e.actorName)) {
entry.names.push(e.actorName)
}
}
else if (entry) {
entry.names = entry.names.filter(n => n !== e.actorName)
}
if (!entry) return
entry.count = e.count
if (e.actorId === user.value?.id) {
entry.me = e.active
}
if (entry.count <= 0) {
list.splice(list.indexOf(entry), 1)
}
}
async function toggle(commentId: number, emoji: string) {
const me = user.value
const before = map.value[commentId]?.find(r => r.emoji === emoji)
// Optimistisch umschalten — die Reaktion ist sofort sichtbar,
// die Server-Antwort korrigiert nur noch Zähler/Status
if (me) {
applyEvent({
commentId,
emoji,
actorId: me.id,
actorName: me.name,
active: !before?.me,
count: (before?.count ?? 0) + (before?.me ? -1 : 1),
})
}
try {
const res = await $fetch<{ active: boolean, count: number }>(`/api/comments/${commentId}/reactions`, {
method: 'POST',
body: { emoji },
})
if (me) {
applyEvent({ commentId, emoji, actorId: me.id, actorName: me.name, active: res.active, count: res.count })
}
}
catch (err) {
if (me) {
applyEvent({ commentId, emoji, actorId: me.id, actorName: me.name, active: !!before?.me, count: before?.count ?? 0 })
}
throw err
}
}
return { reactionsFor, seed, applyEvent, toggle }
}