diff options
| -rw-r--r-- | haunt/jakob/builder/blog.scm | 9 | ||||
| -rw-r--r-- | haunt/jakob/dynamic/capabilities/comment-form.scm | 95 | ||||
| -rw-r--r-- | haunt/static/css/style.css | 4 | ||||
| -rw-r--r-- | haunt/static/js/dynamic-comment-form.js | 81 | ||||
| -rw-r--r-- | haunt/static/js/proof-of-work.js | 47 |
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(); + }) +}); |