diff --git a/static/js/10-reactions.js b/static/js/10-reactions.js index b41cb59..534da41 100644 --- a/static/js/10-reactions.js +++ b/static/js/10-reactions.js @@ -5,8 +5,13 @@ window.Glow = window.Glow || {}; Glow.reactions = Glow.reactions || { enabled: true, - emojis: ["👍", "❤️", "🔥", "😂", "🎉", "👏", "😮", "😢", "👀", "✅", "🚀", "💡"], - activeMessage: null + emojis: [ + "👍", "👎", "❤️", "🔥", "😂", "😄", "🙂", "😭", + "👏", "🙌", "🎉", "✨", "💯", "✅", "👀", "🤔", + "🙏", "💡", "⭐", "🚀", "😮", "😢", "😡", "👋" + ], + activeMessage: null, + selectedIndex: 0 }; Glow.submitCustomReaction = async () => { @@ -41,8 +46,47 @@ Glow.submitCustomReaction = async () => { } }; +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; @@ -53,38 +97,21 @@ Glow.openReactionPopup = async message => { grid.replaceChildren(); list.replaceChildren(); - for (const emoji of Glow.reactions.emojis) { + 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 () => { - 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); - - if (Glow.state.activeView === "feed") { - Glow.loadFeed?.(); - } - } catch (error) { - Glow.showToast(error.message || "Could not update reaction.", "error"); - } + Glow.reactions.selectedIndex = index; + Glow.highlightReactionSelection(); + await Glow.activateReactionAtIndex(index); }); grid.append(button); - } + }); try { const data = await Glow.apiGetMessageReactions(message.id); @@ -99,12 +126,26 @@ Glow.openReactionPopup = async message => { Glow.renderReactionPopupUsers(message.reactions || []); Glow.els.reactionPopupOverlay?.classList.remove("hidden"); + Glow.highlightReactionSelection(); setTimeout(() => { - Glow.els.reactionCustomInput?.focus(); + 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; @@ -128,19 +169,12 @@ Glow.renderReactionPopupUsers = reactions => { title.innerHTML = `${reaction.emoji} ${reaction.count}`; section.append(title); - for (const user of reaction.users || []) { - const row = document.createElement("div"); - row.className = "reaction-popup-user"; - - const left = document.createElement("span"); - left.textContent = user.username; - - const right = document.createElement("span"); - right.textContent = new Date(user.created_at).toLocaleString(); - - row.append(left, right); - section.append(row); - } + 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); } @@ -148,6 +182,7 @@ Glow.renderReactionPopupUsers = reactions => { Glow.closeReactionPopup = () => { Glow.reactions.activeMessage = null; + Glow.reactions.selectedIndex = 0; if (Glow.els.reactionCustomInput) { Glow.els.reactionCustomInput.value = ""; } @@ -163,4 +198,49 @@ Glow.els.reactionCustomInput?.addEventListener("keydown", event => { 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?.(); + } +}); \ No newline at end of file