// static/js/10-reactions.js "use strict"; window.Glow = window.Glow || {}; Glow.reactions = Glow.reactions || { enabled: true, emojis: [ "👍", "👎", "❤️", "🔥", "😂", "😄", "🙂", "😭", "👏", "🙌", "🎉", "✨", "💯", "✅", "👀", "🤔", "🙏", "💡", "⭐", "🚀", "😮", "😢", "😡", "👋" ], activeMessage: null, selectedIndex: 0 }; Glow.submitCustomReaction = async () => { const message = Glow.reactions.activeMessage; const input = Glow.els.reactionCustomInput; if (!message || !input) return; const emoji = input.value.trim(); if (!emoji) return; try { const existing = (message.reactions || []).find(item => item.emoji === emoji); if (existing?.reacted) { await Glow.apiRemoveReaction(message.id, emoji); } else { await Glow.apiAddReaction(message.id, emoji); } const data = await Glow.apiGetMessageReactions(message.id); message.reactions = data.reactions || []; Glow.updateMessageReactionsDom?.(message.id, message.reactions); Glow.renderReactionPopupUsers(message.reactions); input.value = ""; if (Glow.state.activeView === "feed") { Glow.loadFeed?.(); } } catch (error) { Glow.showToast(error.message || "Could not update reaction.", "error"); } }; Glow.highlightReactionSelection = () => { const grid = Glow.els.reactionPopupGrid; if (!grid) return; const buttons = [...grid.querySelectorAll(".reaction-popup-button")]; buttons.forEach((button, index) => { button.classList.toggle("active", index === Glow.reactions.selectedIndex); }); }; Glow.activateReactionAtIndex = async index => { const message = Glow.reactions.activeMessage; const emoji = Glow.reactions.emojis[index]; if (!message || !emoji) return; try { const existing = (message.reactions || []).find(item => item.emoji === emoji); if (existing?.reacted) { await Glow.apiRemoveReaction(message.id, emoji); } else { await Glow.apiAddReaction(message.id, emoji); } const data = await Glow.apiGetMessageReactions(message.id); message.reactions = data.reactions || []; Glow.updateMessageReactionsDom?.(message.id, message.reactions); Glow.renderReactionPopupUsers(message.reactions); Glow.highlightReactionSelection(); if (Glow.state.activeView === "feed") { Glow.loadFeed?.(); } } catch (error) { Glow.showToast(error.message || "Could not update reaction.", "error"); } }; Glow.openReactionPopup = async message => { Glow.reactions.activeMessage = message; Glow.reactions.selectedIndex = 0; const grid = Glow.els.reactionPopupGrid; const list = Glow.els.reactionPopupList; const customInput = Glow.els.reactionCustomInput; if (!grid || !list) return; grid.replaceChildren(); list.replaceChildren(); Glow.reactions.emojis.forEach((emoji, index) => { const button = document.createElement("button"); button.className = "reaction-popup-button"; button.type = "button"; button.textContent = emoji; button.setAttribute("aria-label", `React with ${emoji}`); button.addEventListener("click", async () => { Glow.reactions.selectedIndex = index; Glow.highlightReactionSelection(); await Glow.activateReactionAtIndex(index); }); grid.append(button); }); try { const data = await Glow.apiGetMessageReactions(message.id); message.reactions = data.reactions || []; } catch (_) { message.reactions = []; } if (customInput) { customInput.value = ""; } Glow.renderReactionPopupUsers(message.reactions || []); Glow.els.reactionPopupOverlay?.classList.remove("hidden"); Glow.highlightReactionSelection(); setTimeout(() => { const first = grid.querySelector(".reaction-popup-button"); first?.focus(); }, 0); }; Glow.moveReactionSelection = delta => { const total = Glow.reactions.emojis.length; if (!total) return; Glow.reactions.selectedIndex = (Glow.reactions.selectedIndex + delta + total) % total; Glow.highlightReactionSelection(); const grid = Glow.els.reactionPopupGrid; const selected = grid?.querySelectorAll(".reaction-popup-button")[Glow.reactions.selectedIndex]; selected?.scrollIntoView({ block: "nearest", inline: "nearest" }); }; Glow.renderReactionPopupUsers = reactions => { const list = Glow.els.reactionPopupList; if (!list) return; list.replaceChildren(); if (!reactions.length) { const empty = document.createElement("div"); empty.className = "reaction-popup-user"; empty.textContent = "No reactions yet."; list.append(empty); return; } for (const reaction of reactions) { const section = document.createElement("div"); section.className = "reaction-popup-section"; const title = document.createElement("div"); title.className = "reaction-popup-user"; title.innerHTML = `${reaction.emoji} ${reaction.count}`; section.append(title); const names = document.createElement("div"); names.className = "reaction-popup-user"; names.textContent = (reaction.users || []) .map(user => user.username) .join(", "); section.append(names); list.append(section); } }; Glow.closeReactionPopup = () => { Glow.reactions.activeMessage = null; Glow.reactions.selectedIndex = 0; if (Glow.els.reactionCustomInput) { Glow.els.reactionCustomInput.value = ""; } Glow.els.reactionPopupOverlay?.classList.add("hidden"); }; Glow.els.reactionCustomButton?.addEventListener("click", () => { Glow.submitCustomReaction?.(); }); Glow.els.reactionCustomInput?.addEventListener("keydown", event => { if (event.key === "Enter") { event.preventDefault(); Glow.submitCustomReaction?.(); } if (event.key === "Escape") { event.preventDefault(); Glow.closeReactionPopup?.(); } }); document.addEventListener("keydown", async event => { if (Glow.els.reactionPopupOverlay?.classList.contains("hidden")) return; if (event.key === "ArrowRight") { event.preventDefault(); Glow.moveReactionSelection(1); return; } if (event.key === "ArrowLeft") { event.preventDefault(); Glow.moveReactionSelection(-1); return; } if (event.key === "ArrowDown") { event.preventDefault(); Glow.moveReactionSelection(4); return; } if (event.key === "ArrowUp") { event.preventDefault(); Glow.moveReactionSelection(-4); return; } if (event.key === "Enter") { const active = document.activeElement; if (active === Glow.els.reactionCustomInput) return; event.preventDefault(); await Glow.activateReactionAtIndex(Glow.reactions.selectedIndex); return; } if (event.key === "Escape") { event.preventDefault(); Glow.closeReactionPopup?.(); } });