import Alpine from "alpinejs"; interface ReactionData { id: string; name: string; count: number; users: string[]; } interface ReactionComponent extends Record { url: string; myReactions: string[]; articleReactions: ReactionData[]; allReactions: string[]; isAuthenticated: boolean; init(): void; treatArticleReactions(rawReactions: Record): void; toggleReaction(reaction: string): void; addReaction(name: string, username: string): void; removeReaction(name: string, username: string): void; generateVirtualReactionId(name: string): string; canAddReactionFromModal(name: string): boolean; userHasReaction(reaction: ReactionData | string): boolean; articleHasReaction(name: string): boolean; getReactionDataFromName(name: string): ReactionData | undefined; dispatchFlowbiteEvent(): void; $nextTick: (callback: () => void) => void; } const ArticleReactions = { init() { document.addEventListener("alpine:init", () => this.startComponent()); }, startComponent() { Alpine.data("reactions", (...args: unknown[]) => { const [myReactions = [], , url = ""] = args as [string[], Record, string]; const rawArticleReactions = args[1] as Record | undefined; return { url, myReactions: myReactions as string[], articleReactions: [] as ReactionData[], allReactions: [] as string[], isAuthenticated: false, init(this: ReactionComponent) { if (rawArticleReactions) { this.treatArticleReactions(rawArticleReactions); } this.allReactions = window.App.defaultReactions; this.isAuthenticated = window.App.isAuthenticated; this.dispatchFlowbiteEvent(); }, treatArticleReactions(this: ReactionComponent, raw: Record) { const transformed: ReactionData[] = []; Object.entries(raw).forEach(([name, reactions]) => { const values = Object.values(reactions); transformed.push({ id: this.generateVirtualReactionId(name), name, count: values.length, users: values.map((r) => r.user?.username ?? ""), }); }); this.articleReactions = transformed; }, toggleReaction(this: ReactionComponent, reaction: string) { if (!this.url.length || !this.isAuthenticated) return; window.axios .post(this.url, { reaction }) .then((response: { data: { success: boolean; added: boolean; username: string } }) => { if (!response.data.success) return; if (!response.data.added) { this.removeReaction(reaction, response.data.username); return; } this.addReaction(reaction, response.data.username); }); }, addReaction(this: ReactionComponent, name: string, username: string) { this.myReactions.push(name); const existing = this.getReactionDataFromName(name); if (existing) { existing.count++; existing.users.push(username); return; } this.articleReactions.push({ id: this.generateVirtualReactionId(name), name, count: 1, users: [username], }); this.dispatchFlowbiteEvent(); }, removeReaction(this: ReactionComponent, name: string, username: string) { this.myReactions.splice(this.myReactions.indexOf(name), 1); const data = this.getReactionDataFromName(name); if (!data) return; if (data.count > 1) { data.count--; data.users.splice(data.users.indexOf(username), 1); return; } this.$nextTick(() => { this.articleReactions.splice(this.articleReactions.indexOf(data), 1); }); }, generateVirtualReactionId(this: ReactionComponent, name: string): string { return name + Math.floor(Math.random() * 1000); }, canAddReactionFromModal(this: ReactionComponent, name: string): boolean { return !this.userHasReaction(name) && !this.articleHasReaction(name); }, userHasReaction(this: ReactionComponent, reaction: ReactionData | string): boolean { const name = typeof reaction === "string" ? reaction : reaction.name; return this.myReactions.includes(name); }, articleHasReaction(this: ReactionComponent, name: string): boolean { return typeof this.getReactionDataFromName(name) !== "undefined"; }, getReactionDataFromName(this: ReactionComponent, name: string): ReactionData | undefined { return this.articleReactions.find((r) => r.name === name); }, dispatchFlowbiteEvent(this: ReactionComponent) { this.$nextTick(() => document.dispatchEvent(new CustomEvent("reactions:loaded"))); }, } as ReactionComponent; }); }, }; export { ArticleReactions as default };