summaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2020-07-28 12:56:02 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2020-07-28 12:57:18 -0400
commitaa0f62411452ab7d0f4ed4595937acb2ef8eac0c (patch)
treeffc3e05c60937494c08a24415a66f0dddcd8fd52 /static
parent03860ebc2e0624ecd0cf094498ac3bfc2df18183 (diff)
[kona-web] Implement mass selection and tagging.
Diffstat (limited to 'static')
-rw-r--r--static/css/style.css6
-rw-r--r--static/js/mass_editor.js116
2 files changed, 122 insertions, 0 deletions
diff --git a/static/css/style.css b/static/css/style.css
index 017622a..6c2f508 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -28,3 +28,9 @@ main {
min-width: 100%;
margin-bottom: 16px;
}
+
+.image-container {
+ display: inline-block;
+ padding: 5px;
+ margin: 5px;
+}
diff --git a/static/js/mass_editor.js b/static/js/mass_editor.js
new file mode 100644
index 0000000..949e130
--- /dev/null
+++ b/static/js/mass_editor.js
@@ -0,0 +1,116 @@
+// Copyright © 2020 Jakob L. Kreuze <zerodaysfordays@sdf.org>
+//
+// This file is part of Kona.
+//
+// Kona is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or (at
+// your option) any later version.
+//
+// Kona 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 Affero General Public
+// License for more details.
+//
+// You should have received a copy of the GNU Affero General Public
+// License along with Kona. If not, see <http://www.gnu.org/licenses/>.
+
+let selectedImages = [];
+
+window.addEventListener("load", () => {
+ // Enable visual queues that make sense only when edit mode is available.
+ document.styleSheets[0].insertRule('.image-container { border: none; }', 0);
+ document.styleSheets[0].insertRule('.image-container:hover { border: solid 1px; }', 0);
+
+ // Similarly, add a tag dialog that makes sense only when edit mode is available.
+ let form = document.createElement("form");
+ form.id = "update-form";
+
+ let input = document.createElement("input");
+ input.id = "update-prompt";
+ input.type = "text";
+
+ let updateButton = document.createElement("button");
+ updateButton.id = "update-form-submit";
+ updateButton.textContent = "Update Selected";
+
+ form.appendChild(input);
+ form.appendChild(updateButton);
+ document.getElementById("tag-view").appendChild(form);
+
+ updateButton.addEventListener("click", (e) => {
+ let queryString = input.value.replace(" ", ",");
+
+ // Send a post for each ID.
+ selectedImages.forEach((id) => {
+ let xhr = new XMLHttpRequest();
+
+ xhr.open("POST", `/api/posts/${id}`);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+
+ // Update the tag counts when the update hits the database.
+ xhr.onload = () => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "/api/tags");
+ xhr.onload = () => {
+ let topTags = document.getElementById("top-tags");
+ topTags.textContent = "";
+ JSON.parse(xhr.response).forEach((pair) => {
+ let count = pair[0];
+ let tag = pair[1];
+
+ let a = document.createElement("a");
+ a.href = `/posts?query=${tag}`;
+ a.textContent = tag;
+
+ let li = document.createElement("li");
+ li.appendChild(a);
+ li.innerHTML += " " + count;
+
+ topTags.appendChild(li);
+ });
+ };
+ xhr.send();
+ }
+
+ xhr.send(encodeURI(`tags=${queryString}`));
+ });
+
+ e.preventDefault();
+ });
+
+ // Add event listeners for enabling "select" mode.
+ let selectingImages = false;
+ document.body.addEventListener("keydown", (e) => { if (e.keyCode == 17) selectingImages = true; });
+ document.body.addEventListener("keyup", (e) => { if (e.keyCode == 17) selectingImages = false; });
+
+ // Handle potential selection.
+ document.getElementById("main").addEventListener("click", (e) => {
+ // Select the containing DIV, if the click event occurred on the IMG child.
+ let target = e.target.nodeName === "IMG" ?
+ e.target.parentElement.parentElement :
+ e.target;
+
+ // Ignore clicks that don't land on any image container.
+ if (target.nodeName === "MAIN") {
+ return false;
+ }
+
+ if (selectingImages) {
+ // Obtain the image's ID from the element ID.
+ let id = parseInt(/image-([\d]*)/.exec(target.id)[1]);
+
+ // Either deselect, or select the image.
+ if (selectedImages.includes(id)) {
+ selectedImages = selectedImages.filter((n) => n !== id );
+ target.style.border = "none";
+ } else {
+ selectedImages.push(id);
+ target.style.border = "solid 2px red";
+ }
+
+ // Don't follow the link if we're holding the "select" hotkey.
+ e.preventDefault();
+ }
+ })
+});