summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2023-03-11 19:32:05 -0500
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2023-03-11 19:32:05 -0500
commit32e642f3789d163fae18824ec04e7e833713b26c (patch)
treeec2fe39a92d57be80fdc86746891614e65713e0a
parent4d07077daedc30271be69c86c855795affdea860 (diff)
Reaction UI
-rw-r--r--haunt/jakob/builder/blog.scm1
-rw-r--r--haunt/static/css/style.css15
-rw-r--r--haunt/static/js/comment-reaction.js103
3 files changed, 119 insertions, 0 deletions
diff --git a/haunt/jakob/builder/blog.scm b/haunt/jakob/builder/blog.scm
index de1c788..3285a51 100644
--- a/haunt/jakob/builder/blog.scm
+++ b/haunt/jakob/builder/blog.scm
@@ -89,6 +89,7 @@
(input (@ (name "source") (type "url")))
(input (@ (value "Send Webmention") (type "submit"))))
,(script "section-folds.js")
+ ,(script "comment-reaction.js")
;; ,(script "webmention.js")
)))
diff --git a/haunt/static/css/style.css b/haunt/static/css/style.css
index b48108b..2672f7c 100644
--- a/haunt/static/css/style.css
+++ b/haunt/static/css/style.css
@@ -233,6 +233,21 @@ ul.webmention-container .comment .comment-source-identifier {
max-width: 16px;
}
+/* Widget for comment reactions. */
+
+.emoji-picker {
+ width: 16em;
+ height: 2.75em;
+ border: solid 1px;
+ overflow: scroll;
+}
+
+.emoji-picker > span {
+ margin: 2px;
+ font-size: 2em;
+}
+
+
/* Webmention form. */
#webmention-form form {
diff --git a/haunt/static/js/comment-reaction.js b/haunt/static/js/comment-reaction.js
new file mode 100644
index 0000000..530abf0
--- /dev/null
+++ b/haunt/static/js/comment-reaction.js
@@ -0,0 +1,103 @@
+/*
+ * comment-reaction.js -- Glue for Nolan Lawson's JavaScript emoji picker
+ * Copyright © 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org>
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ */
+
+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) {
+ if (xhr.status != 200) {
+ alert("Failed to react.");
+ } else {
+ }
+ }
+ 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.appendElement(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();
+ });
+ }
+});