diff options
| author | Jakob L. Kreuze | 2022-11-20 13:29:29 -0500 |
|---|---|---|
| committer | Jakob L. Kreuze | 2022-11-20 13:29:29 -0500 |
| commit | 7092a1977d075001320470d81cc740d33409cc64 (patch) | |
| tree | d61003453c681f8d732d73255e12b0916f3aa1ad | |
| parent | ebeeb7518241bea945b6c372fe07fb20abf020f3 (diff) | |
[dynamic] Initial dynamically-loaded comment form
To curb constraints surrounding where and when dynamic content can appear, the
comment form _embedded into the page for a blog post_ is dynamically loaded by
some JavaScript code. This is the initial implementation of that.
There are a few parts of this implementation that need to be improved:
- Make it clear that the captcha is the only part that's being loaded by XHR.
i.e., display the form with a "loading..." placeholder where the captcha image
should be.
- Have this triggered by the user, so the API doesn't get flooded with requests
that aren't going to be completed. (The ratio of readers to commenters is likely
to be very high.)
| -rw-r--r-- | haunt/api.scm | 3 | ||||
| -rw-r--r-- | haunt/jakob/builder/blog.scm | 12 | ||||
| -rw-r--r-- | haunt/jakob/dynamic/capabilities/comment-form.scm | 88 | ||||
| -rw-r--r-- | haunt/jakob/dynamic/captcha.scm | 14 | ||||
| -rw-r--r-- | haunt/static/js/proof-of-work.js | 3 |
5 files changed, 71 insertions, 49 deletions
diff --git a/haunt/api.scm b/haunt/api.scm index 0103400..1d275f8 100644 --- a/haunt/api.scm +++ b/haunt/api.scm @@ -41,7 +41,8 @@ (log-append! 'info (format #f "~a ~a (~a) (~a)" method endpoint args originating-ip))) ((match (cons (request-method request) endpoint) (('GET "comment-form" _) get-comment-form) - ('(GET "challenge") make-pow-challenge!) + ('(GET "challenge" "proof-of-work") make-pow-challenge!) + ('(GET "challenge" "captcha") make-captcha-challenge!) ;; ('(GET "comments") get-comments) (('POST "comment") put-comment) (('GET "gallery") get-gallery) diff --git a/haunt/jakob/builder/blog.scm b/haunt/jakob/builder/blog.scm index 531804b..ba8a5ee 100644 --- a/haunt/jakob/builder/blog.scm +++ b/haunt/jakob/builder/blog.scm @@ -21,6 +21,7 @@ #:use-module (haunt utils) #:use-module (ice-9 format) #:use-module (ice-9 match) + #:use-module (jakob dynamic capabilities comment-form) #:use-module (jakob theme) #:use-module (jakob utils) #:use-module (jakob utils pagination) @@ -72,8 +73,12 @@ (article ,(post-sxml post)) (section (@ (id "webmention")) - (h2 ,(hyperlink "https://indieweb.org/Webmention" "Webmentions") - " for this Page") + (h2 "Comments for this page") + (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.") (form (@ (id "webmention-form") @@ -84,9 +89,6 @@ ", you can let me know the URL:") (input (@ (name "source") (type "url"))) (input (@ (value "Send Webmention") (type "submit")))) - (ul (@ (id "webmention-container")) - ;; ,@(render-comment-view (fetch-webmentions (post-identifier post)))) - ,@(render-comment-view (fetch-comments (post-identifier post)))) ,(script "section-folds.js") ;; ,(script "webmention.js") ))) diff --git a/haunt/jakob/dynamic/capabilities/comment-form.scm b/haunt/jakob/dynamic/capabilities/comment-form.scm index 6937d15..29e00ec 100644 --- a/haunt/jakob/dynamic/capabilities/comment-form.scm +++ b/haunt/jakob/dynamic/capabilities/comment-form.scm @@ -29,44 +29,49 @@ #:use-module (web request) #:use-module (web response) #:use-module (web uri) - #:export (render-comment-form-success + #:export (render-comment-form + + render-comment-form-success render-comment-form-failure get-comment-form)) -(define (render-comment-form slug) - (let-values (((captcha-id captcha-image) (new-captcha!))) - `((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))) - (input (@ (type "text") (name "captcha-id") (hidden #t) (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")) - (legend "Captcha") - (label (@ (for "captcha")) "Please evaluate the following definite integral:") - (img (@ (src ,(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"))))) - ,(script "proof-of-work.js"))))) +(define* (render-comment-form slug #:key (header-level 'h1) (captcha-id "") captcha-image) + `(div (@ (id "comment-form")) + (,header-level "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")) + (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"))))) + ,(script "dynamic-comment-form.js") + ,(script "proof-of-work.js"))) (define (render-comment-form-success slug) `((div (h1 "Success!") @@ -77,8 +82,13 @@ "API endpoint handler for querying for the comments on a particular post 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))) - (values '((content-type . (text/html))) - (sxml->html-string (theme #:content (render-comment-form slug) #:title "Comment prompt"))))) + (let-values (((captcha-id captcha-image) (new-captcha!))) + (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))) + (values '((content-type . (text/html))) + (sxml->html-string + (theme #:content form #:title "Comment prompt")))))) diff --git a/haunt/jakob/dynamic/captcha.scm b/haunt/jakob/dynamic/captcha.scm index 0f6e0ba..05426d1 100644 --- a/haunt/jakob/dynamic/captcha.scm +++ b/haunt/jakob/dynamic/captcha.scm @@ -44,9 +44,10 @@ new-captcha! validate-captcha! + make-captcha-challenge! - make-pow-challenge! - validate-proof-of-work!)) + validate-proof-of-work! + make-pow-challenge!)) (define-record-type <id-queue> (make-id-queue mutex min-free-threshold free-ids allocated-ids) @@ -239,6 +240,15 @@ internally-defined `time-to-live-seconds'." (<= (abs (- solution (string->number user-answer))) epsilon)))) +(define (make-captcha-challenge! request body) + "API endpoint handler for requesting a captcha challenge" + (let-values (((challenge-id image) (new-captcha!))) + (values '((content-type . (application/json))) + (scm->json-string + `((challenge-id . ,challenge-id) + (image . ,(format #f "data:image/jpeg;charset=utf-8;base64,~a" + (base64-encode image)))))))) + (define pow-challenge-id-queue (make-queue 1024)) diff --git a/haunt/static/js/proof-of-work.js b/haunt/static/js/proof-of-work.js index 1a57c2a..5dedb66 100644 --- a/haunt/static/js/proof-of-work.js +++ b/haunt/static/js/proof-of-work.js @@ -68,10 +68,9 @@ function makeRequest (method, url) { }); } - function raceEndpoint() { return new Promise(function (resolve, reject) { - makeRequest("GET", "/api/challenge") + makeRequest("GET", "/api/challenge/proof-of-work") .then(function (data) { let challengeData = JSON.parse(data); findPrefix(challengeData.hardness, challengeData.nonce) |