/* * comment-reaction.js -- Simple emoji picker and glue for reaction endpoint * Copyright © 2023 Jakob L. Kreuze * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see * . */ function encodeAsFormData(obj) { // Turn the data object into an array of URL-encoded key/value pairs. let urlEncodedDataPairs = []; for (let key in obj) { urlEncodedDataPairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])); } return urlEncodedDataPairs.join('&'); } function postReaction(reaction) { let xhr = new XMLHttpRequest(); xhr.open('POST', "/api/comment/react", true); // Endpoint expects HTML form data. xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.onload = function(data) { switch (xhr.status) { case 200: break; case 429: alert("Failed -- you can react at most once an hour."); break; default: let resp = JSON.parse(xhr.response); alert("Failed -- " + resp.error); break; } } xhr.onerror = function(error) { throw new Error(`Request failed: ${error}`); } xhr.send(encodeAsFormData(reaction)); } // Simple Emoji picker widget. function selectorWidget() { function emojiButton(codepoint) { let span = document.createElement("span"); span.innerHTML = String.fromCodePoint(codepoint); return span; } let selector = document.createElement("div"); selector.setAttribute("class", "emoji-picker"); for (let i = 0x1F600; i < 0x1F64F; i++) { selector.appendChild(emojiButton(i)); } selector.appendChild(emojiButton(0x1F37B)); selector.appendChild(emojiButton(0x1F440)); selector.appendChild(emojiButton(0x1F446)); selector.appendChild(emojiButton(0x1F447)); selector.appendChild(emojiButton(0x1F448)); selector.appendChild(emojiButton(0x1F449)); selector.appendChild(emojiButton(0x1F450)); selector.appendChild(emojiButton(0x270A)); selector.appendChild(emojiButton(0x270B)); selector.appendChild(emojiButton(0x270C)); selector.appendChild(emojiButton(0x270D)); for (let i = 0x1F90C; i < 0x1F9FF; i++) { selector.appendChild(emojiButton(i)); } return selector; } // Add a "react" button to every local comment. window.addEventListener("load", () => { // First pass to add a separator for the buttons. let replyButtons = document.querySelectorAll(".comment-reply-button"); for (let button of replyButtons) { let parent = button.parentNode; parent.innerHTML += " - "; } // Second pass to add the actual "react" button. replyButtons = document.querySelectorAll(".comment-reply-button"); for (let button of replyButtons) { let reactButton = document.createElement("a"); reactButton.setAttribute("class", "comment-react-button"); reactButton.setAttribute("href", "#"); reactButton.setAttribute("data-react-to-id", button.getAttribute("data-reply-to-id")); reactButton.innerHTML = "react"; button.parentNode.appendChild(reactButton); reactButton.addEventListener('click', (e) => { let emojiPicker = selectorWidget(); button.parentNode.parentNode.insertBefore(emojiPicker, button.parentNode); for (let button of emojiPicker.children) { button.addEventListener('click', (e) => { postReaction({ "id": reactButton.getAttribute("data-react-to-id"), "reaction": button.innerHTML }); emojiPicker.remove(); }); } e.preventDefault(); }); } });