/* * 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, callback) { 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: callback(); 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)); } function selectorWidgetName(id) { return `emoji-picker-${id}`; } // Simple Emoji picker widget. function selectorWidget(id) { function emojiButton(codepoint) { let span = document.createElement("span"); span.innerHTML = String.fromCodePoint(codepoint); return span; } let selector = document.createElement("div"); selector.setAttribute("id", selectorWidgetName(id)); 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", () => { // Unhide additional comment actions if Javascript is enabled. let additionalActionsRows = document.querySelectorAll(".comment-additional-actions"); for (let row of additionalActionsRows) { row.removeAttribute("hidden"); } let reactButtons = document.querySelectorAll(".comment-react-button"); for (let button of reactButtons) { let id = button.getAttribute("data-reply-to-id"); button.addEventListener('click', (e) => { // If we already made a selector widget for this comment, let's "hide" the // one that exists. let existingSelectorWidget = document.getElementById(selectorWidgetName(id)); if (existingSelectorWidget !== null) { existingSelectorWidget.remove(); e.preventDefault(); return; } let emojiPicker = selectorWidget(id); button.parentNode.parentNode.insertBefore(emojiPicker, button.parentNode); for (let button of emojiPicker.children) { button.addEventListener('click', (e) => { postReaction({ "id": button.getAttribute("data-react-to-id"), "reaction": button.innerHTML }, () => { let reactAcknowledgement = document.createElement("span"); reactAcknowledgement.innerHTML = "successfully reacted!" button.replaceWith(reactAcknowledgement); }); emojiPicker.remove(); }); } e.preventDefault(); }); } });