summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2022-11-20 14:03:31 -0500
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2022-11-20 14:09:03 -0500
commit0d7dbb395c3bc166f0a6db6d3167531facef62fc (patch)
treed04f2dd4e416a1911be67f8b4d80d7e74e0be6b1
parent7092a1977d075001320470d81cc740d33409cc64 (diff)
[dynamic] Resolve grievances with dynamic comment form
-rw-r--r--haunt/jakob/builder/blog.scm9
-rw-r--r--haunt/jakob/dynamic/capabilities/comment-form.scm95
-rw-r--r--haunt/static/css/style.css4
-rw-r--r--haunt/static/js/dynamic-comment-form.js81
-rw-r--r--haunt/static/js/proof-of-work.js47
5 files changed, 178 insertions, 58 deletions
diff --git a/haunt/jakob/builder/blog.scm b/haunt/jakob/builder/blog.scm
index ba8a5ee..cef3b69 100644
--- a/haunt/jakob/builder/blog.scm
+++ b/haunt/jakob/builder/blog.scm
@@ -77,9 +77,12 @@
(ul (@ (id "webmention-container"))
;; ,@(render-comment-view (fetch-webmentions (post-identifier post))))
,@(render-comment-view (fetch-comments (post-identifier post))))
- (div (@ (id "dynamic-comment-form-insert") (hidden #t))
- ,(render-comment-form (post-identifier post) #:header-level 'h3))
- (p "Click " ,(hyperlink (build-comment-url post) "here") " to write a comment on this post.")
+
+ (div (@ (id "comment-form-primary") (hidden #t))
+ ,(render-dynamic-comment-form (post-identifier post)))
+ (p (@ (id "comment-form-alt"))
+ "Click " ,(hyperlink (build-comment-url post) "here") " to write a comment on this post.")
+
(form
(@ (id "webmention-form")
(action "https://webmention.io/jakob.space/webmention")
diff --git a/haunt/jakob/dynamic/capabilities/comment-form.scm b/haunt/jakob/dynamic/capabilities/comment-form.scm
index 29e00ec..23d669d 100644
--- a/haunt/jakob/dynamic/capabilities/comment-form.scm
+++ b/haunt/jakob/dynamic/capabilities/comment-form.scm
@@ -29,47 +29,76 @@
#:use-module (web request)
#:use-module (web response)
#:use-module (web uri)
- #:export (render-comment-form
+ #:export (render-static-comment-form
+ render-dynamic-comment-form
render-comment-form-success
render-comment-form-failure
get-comment-form))
-(define* (render-comment-form slug #:key (header-level 'h1) (captcha-id "") captcha-image)
+(define (render-commenter-info-field)
+ `(fieldset (@ (id "commenter-info"))
+ (legend "Commenter Info")
+ (label (@ (for "name")) "Name:")
+ (input (@ (type "text") (id "name") (name "name") (required #t) (size 24)))
+ (label (@ (for "email")) "Email (optional, used for Gravatar):")
+ (input (@ (type "text") (id "email") (name "email") (size 24)))
+ (label (@ (for "url")) "Webpage URL (optional):")
+ (input (@ (type "text") (id "url") (name "url") (size 24)))))
+
+(define (render-comment-content-field)
+ `(fieldset (@ (id "comment-content"))
+ (legend "Comment")
+ (label (@ (for "subject")) "Subject (optional):")
+ (input (@ (type "text") (id "subject") (name "subject") (size 24)))
+ (label (@ (for "comment")) "Comment :")
+ (textarea (@ (id "coment") (name "comment") (rows 4) (cols 50)))))
+
+(define* (render-comment-captcha-field #:optional (captcha-id "") captcha-image
+ #:key hidden)
+ `(fieldset ,(if hidden
+ '(@ (id "comment-captcha") (hidden "#t"))
+ '(@ (id "comment-captcha")))
+ (legend "Captcha")
+ (div (@ (id "captcha-challenge-primary"))
+ (label (@ (for "captcha")) "Please evaluate the following definite integral:")
+ (img (@ (id "captcha-image")
+ (src ,(if captcha-image
+ (format #f "data:image/jpeg;charset=utf-8;base64,~a"
+ (base64-encode captcha-image))
+ ""))))
+ (input (@ (type "text") (id "captcha") (name "captcha") (size 24))))
+ (button (@ (id "pow-trigger") (hidden #t))
+ "Too hard? (Or unable to see the challenge?) Click here.")
+ (input (@ (autocomplete "off") (type "text") (id "captcha-id") (name "captcha-id") (hidden #t) (value ,captcha-id)))
+ (input (@ (autocomplete "off") (type "text") (id "captcha-alt") (name "captcha-alt") (hidden #t)))
+ (input (@ (autocomplete "off") (type "text") (id "captcha-alt-id") (name "captcha-alt-id") (hidden #t)))
+ (input (@ (type "submit") (id "submit-form") (value "Submit")))))
+
+(define (render-static-comment-form slug captcha-id captcha-image)
+ `(div (@ (id "comment-form"))
+ (h1 "Comment form")
+ (form (@ (id "comment-input") (action "/api/comment") (method "post"))
+ (input (@ (type "text") (name "slug") (hidden #t) (value ,slug)))
+ ,(render-commenter-info-field)
+ ,(render-comment-content-field)
+ ,(render-comment-captcha-field captcha-id captcha-image))
+ ,(script "proof-of-work.js")))
+
+(define* (render-dynamic-comment-form slug)
`(div (@ (id "comment-form"))
- (,header-level "Comment form")
+ (h3 "Comment form")
(form (@ (id "comment-input") (action "/api/comment") (method "post"))
(input (@ (type "text") (name "slug") (hidden #t) (value ,slug)))
- (input (@ (type "text") (id "captcha-id") (name "captcha-id") (hidden #t) (autocomplete "off") (value ,captcha-id)))
- (fieldset (@ (id "commenter-info"))
- (legend "Commenter Info")
- (label (@ (for "name")) "Name:")
- (input (@ (type "text") (id "name") (name "name") (required #t) (size 24)))
- (label (@ (for "email")) "Email (optional, used for Gravatar):")
- (input (@ (type "text") (id "email") (name "email") (size 24)))
- (label (@ (for "url")) "Webpage URL (optional):")
- (input (@ (type "text") (id "url") (name "url") (size 24))))
- (fieldset (@ (id "comment-content"))
- (legend "Comment")
- (label (@ (for "subject")) "Subject (optional):")
- (input (@ (type "text") (id "subject") (name "subject") (size 24)))
- (label (@ (for "comment")) "Comment :")
- (textarea (@ (id "coment") (name "comment") (rows 4) (cols 50))))
- (fieldset (@ (id "comment-captcha"))
+ ,(render-commenter-info-field)
+ ,(render-comment-content-field)
+ (fieldset (@ (id "captcha-trigger-block"))
(legend "Captcha")
- (label (@ (for "captcha")) "Please evaluate the following definite integral:")
- (img (@ (id "captcha-image")
- (src ,(if captcha-image
- (format #f "data:image/jpeg;charset=utf-8;base64,~a"
- (base64-encode captcha-image))
- ""))))
- (input (@ (type "text") (id "captcha") (name "captcha") (size 24)))
- (button (@ (id "pow-trigger") (hidden #t))
- "Too hard? (Or unable to see the challenge?) Click here.")
- (input (@ (type "text") (id "captcha-alt") (name "captcha-alt") (hidden #t)))
- (input (@ (type "text") (id "captcha-alt-id") (name "captcha-alt-id") (hidden #t)))
- (input (@ (type "submit") (id "submit-form") (value "Submit")))))
+ (label "You need to complete a captcha to write a comment.")
+ (button (@ (id "captcha-challenge-trigger"))
+ "Click here to generate a captcha challenge"))
+ ,(render-comment-captcha-field #:hidden #t))
,(script "dynamic-comment-form.js")
,(script "proof-of-work.js")))
@@ -86,9 +115,7 @@ This is a wrapper around `get-comments-by-slug'."
(let* ((path-encoded (uri-path (request-uri request)))
(path (split-and-decode-uri-path path-encoded))
(slug (last path))
- (form (render-comment-form slug
- #:captcha-id captcha-id
- #:captcha-image captcha-image)))
+ (form (render-staticcomment-form slug #f captcha-id captcha-image)))
(values '((content-type . (text/html)))
(sxml->html-string
(theme #:content form #:title "Comment prompt"))))))
diff --git a/haunt/static/css/style.css b/haunt/static/css/style.css
index 82bd8da..15af437 100644
--- a/haunt/static/css/style.css
+++ b/haunt/static/css/style.css
@@ -259,6 +259,10 @@ ul#webmention-container .comment .comment-source-identifier {
clear: left;
}
+#comment-form #captcha-trigger-block label {
+ display: block;
+}
+
#comment-form label,
#comment-form input,
#comment-form button {
diff --git a/haunt/static/js/dynamic-comment-form.js b/haunt/static/js/dynamic-comment-form.js
new file mode 100644
index 0000000..30253bb
--- /dev/null
+++ b/haunt/static/js/dynamic-comment-form.js
@@ -0,0 +1,81 @@
+/*
+ * dynamic-comment-form.js -- Captcha challenge loader
+ * Copyright © 2022 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 makeRequest (method, url) {
+ return new Promise(function (resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url);
+ xhr.onload = function () {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ resolve(xhr.response);
+ } else {
+ reject({
+ status: xhr.status,
+ statusText: xhr.statusText
+ });
+ }
+ };
+ xhr.onerror = function () {
+ reject({
+ status: xhr.status,
+ statusText: xhr.statusText
+ });
+ };
+ xhr.send();
+ });
+}
+
+window.addEventListener("load", () => {
+ let primaryDisplay = document.getElementById("comment-form-primary");
+ primaryDisplay.removeAttribute("hidden");
+
+ let altDisplay = document.getElementById("comment-form-alt");
+ altDisplay.remove();
+
+ let trigger = document.getElementById("captcha-challenge-trigger");
+ let triggerBlock = document.getElementById("captcha-trigger-block");
+ trigger.addEventListener("click", (event) => {
+ let captchaField = document.getElementById("comment-captcha");
+
+ let captchaId = document.getElementById("captcha-id");
+ let captchaImage = document.getElementById("captcha-image");
+
+ trigger.innerHTML = "Please wait...";
+ trigger.disabled = true;
+
+ if (captchaId.value === "") {
+ makeRequest("GET", "/api/challenge/captcha")
+ .then(function (data) {
+ // Update form with parsed values.
+ let challengeData = JSON.parse(data);
+ captchaId.value = challengeData["challenge-id"];
+ captchaImage.src = challengeData["image"];
+
+ // Unhide fieldset.
+ captchaField.removeAttribute("hidden");
+
+ // Hide initial trigger.
+ triggerBlock.remove();
+ })
+ .catch(console.err);
+ }
+
+ event.preventDefault();
+ });
+});
diff --git a/haunt/static/js/proof-of-work.js b/haunt/static/js/proof-of-work.js
index 5dedb66..f2439dd 100644
--- a/haunt/static/js/proof-of-work.js
+++ b/haunt/static/js/proof-of-work.js
@@ -80,25 +80,30 @@ function raceEndpoint() {
});
}
-let trigger = document.getElementById("pow-trigger");
-trigger.hidden = false;
-trigger.addEventListener("click", (e) => {
- trigger.innerHTML = "Please wait...";
- trigger.disabled = true;
- raceEndpoint()
- .then((result) => {
- let resultField = document.getElementById("captcha-alt");
- let challengeIdField = document.getElementById("captcha-alt-id");
- resultField.value = result[0];
- challengeIdField.value = result[1];
- trigger.innerHTML = "Proof-of-Work completed successfully!";
- trigger.disabled = true;
- })
- .catch((err) => {
- console.log(err);
- trigger.innerHTML = "Proof-of-Work failed!";
- trigger.disabled = true;
- });
- e.preventDefault();
-})
+window.addEventListener("load", () => {
+ let trigger = document.getElementById("pow-trigger");
+ trigger.removeAttribute("hidden");
+ trigger.addEventListener("click", (e) => {
+ trigger.innerHTML = "Please wait...";
+ trigger.disabled = true;
+ raceEndpoint()
+ .then((result) => {
+ let resultField = document.getElementById("captcha-alt");
+ let challengeIdField = document.getElementById("captcha-alt-id");
+ resultField.value = result[0];
+ challengeIdField.value = result[1];
+ trigger.innerHTML = "Proof-of-Work completed successfully!";
+ trigger.disabled = true;
+ // Hide the primary captcha, too, to make it clear that it isn't necessary to complete.
+ let primaryChallenge = document.getElementById("captcha-challenge-primary");
+ primaryChallenge.hidden = true;
+ })
+ .catch((err) => {
+ console.log(err);
+ trigger.innerHTML = "Proof-of-Work failed!";
+ trigger.disabled = true;
+ });
+ e.preventDefault();
+ })
+});