From c00ace03d64001c6fb49d7ba7e25d75386eb6ae9 Mon Sep 17 00:00:00 2001 From: "Jakob L. Kreuze" Date: Mon, 16 Feb 2026 12:26:20 -0500 Subject: Fix comment form --- jakob/builder/blog.scm | 21 ++---- jakob/dynamic/capabilities/comment-form.scm | 111 +++++++++++++++------------- pages/changelog.org | 5 -- static/css/style.css | 65 ++++++++++++++-- static/js/dynamic-comment-form.js | 25 +++---- static/js/proof-of-work.js | 7 +- 6 files changed, 141 insertions(+), 93 deletions(-) diff --git a/jakob/builder/blog.scm b/jakob/builder/blog.scm index bb5567c..00b7ddc 100644 --- a/jakob/builder/blog.scm +++ b/jakob/builder/blog.scm @@ -102,7 +102,6 @@ ,(first-paragraph post) (p ,(hyperlink (or crosspost-uri local-uri) "read more →"))))) -;; TODO: Should dynamically pull comments. (define (comments-section post) `(section (@ (class "comments-section") (aria-labelledby "comments-heading")) @@ -110,17 +109,11 @@ (fetch-webmentions (post-identifier post))) (div (@ (class "comment-form-module")) (h4 (@ (class "module-title")) - "Leave a Comment") - (form (@ (class "comment-form")) - (div (@ (class "form-row")) - (input (@ (type "text") (placeholder "Name") (required "") (aria-label "Name"))) - (input (@ (type "text") (placeholder "Subject (Optional)") (aria-label "Subject")))) - (div (@ (class "form-row")) - (input (@ (type "url") (placeholder "Website (https://...)") (aria-label "Website URL")))) - (textarea (@ (placeholder "Write your comment here...") - (rows "4") - (required "") - (aria-label "Comment body"))))))) + "Leave a Comment") + (noscript + (p (@ (style "font-size: 10px; color: #666; text-align: center; padding: 10px;")) + "Click " ,(hyperlink (build-comment-url post) "here") " to write a comment on this post.")) + ,(render-dynamic-comment-form (post-slug post))))) ;;; @@ -154,8 +147,8 @@ #:scripts (if (not (eof-object? scripts)) scripts '()) #:content `(,(render-article post) ,(comments-section post) - ,(script "section-folds.js") - ,(script "comment-reaction.js"))) + ,(script "comment-reaction.js") + ,(script "section-folds.js"))) sxml->html)) diff --git a/jakob/dynamic/capabilities/comment-form.scm b/jakob/dynamic/capabilities/comment-form.scm index 958e9dc..5ce4008 100644 --- a/jakob/dynamic/capabilities/comment-form.scm +++ b/jakob/dynamic/capabilities/comment-form.scm @@ -34,40 +34,57 @@ get-comment-form)) (define (render-comment-field) - `(fieldset (@ (id "comment-content")) - (legend "Comment") - (label (@ (for "name") (class "required")) "Name:") - (input (@ (type "text") (id "name") (name "name") (required #t))) - (label (@ (for "email")) "Email:") - (input (@ (type "text") (id "email") (name "email"))) - (label (@ (for "url")) "Webpage URL:") - (input (@ (type "text") (id "url") (name "url"))) - (label (@ (for "subject")) "Subject:") - (input (@ (type "text") (id "subject") (name "subject"))) - (label (@ (for "comment") (class "required")) "Comment :") - (textarea (@ (id "coment") (name "comment"))) - (p "(*) Indicates a required field."))) + `((div (@ (class "form-row")) + (input (@ (type "text") + (id "name") + (name "name") + (required #t) + (aria-label "Name") + (placeholder "Name"))) + (input (@ (type "text") + (id "subject") + (name "subject") + (aria-label "Subject") + (placeholder "Subject (Optional)")))) + (div (@ (class "form-row")) + (input (@ (type "text") + (id "url") + (name "url") + (aria-label "Website URL") + (placeholder "Website (https://...)")))) + (textarea (@ (id "coment") + (name "comment") + (placeholder "Write your comment here...") + (rows "4") + (required "") + (aria-label "Comment body"))))) (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"))))) + `((div (@ (id "captcha-container")) + (div (@ (id "captcha-image-wrapper"))) + (div (@ (id "captcha-input-wrapper")) + (input (@ (type "text") + (id "captcha-solution") + (name "captcha") + (required "true"))) + (button (@ (id "captcha-challenge-trigger") + (type "button")) + "Fetch Captcha")) + (input (@ (autocomplete "off") (type "text") (hidden #t) + (id "captcha-id") (name "captcha-id") (value ,captcha-id))) + (input (@ (autocomplete "off") (type "text") (hidden #t) + (id "captcha-alt") (name "captcha-alt"))) + (input (@ (autocomplete "off") (type "text") (hidden #t) + (id "captcha-alt-id") (name "captcha-alt-id"))) + (div (@ (id "captcha-alternative-wrapper") + (hidden "true")) + (p "If you can't solve this integral, click this button to run an +alternative proof-of-work captcha.") + (button (@ (type "button") + (id "proof-of-work-trigger") + (aria-label "Automatic Captcha Button")) + "Calculate Proof-of-Work For Me"))))) (define (render-static-comment-form slug captcha-id captcha-image) `(div (@ (id "comment-form")) @@ -79,27 +96,17 @@ ,(script "proof-of-work.js"))) (define (render-dynamic-comment-form slug) - `(div (@ (id "comment-form")) - (h3 (@ (id "comment-form-header")) "Comment form") - (form (@ (id "comment-input") (action "/api/comment") (method "post")) - (input (@ (autocomplete "off") - (type "text") - (name "slug") - (hidden #t) - (value ,slug))) - (input (@ (autocomplete "off") - (type "text") - (name "reply-to") - (id "reply-to") - (hidden #t) - (value ""))) - ,(render-comment-field) - (fieldset (@ (id "captcha-trigger-block")) - (legend "Captcha") - (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)) + `(form (@ (id "comment-form") (action "/api/comment") + (method "post") (hidden "true")) + ;; Hidden fields. + (input (@ (autocomplete "off") (type "text") (hidden #t) + (name "slug") (value ,slug))) + (input (@ (autocomplete "off") (type "text") (hidden #t) + (name "reply-to") (id "reply-to") (value ""))) + ,@(render-comment-field) + ,@(render-comment-captcha-field) + (button (@ (type "submit") (style "margin-top: 10px;")) + "Submit Comment") ,(script "dynamic-comment-form.js") ,(script "proof-of-work.js"))) diff --git a/pages/changelog.org b/pages/changelog.org index 6a779da..cb15388 100644 --- a/pages/changelog.org +++ b/pages/changelog.org @@ -10,11 +10,6 @@ A record of any notable user-facing changes made to this website. For more detai - Comments - Comment replies - Comment reactions -- The following features are planned for this new theme: - - [[https://danilafe.com/blog/blog_microfeatures/][Grouping series of posts]] - - [[https://danilafe.com/blog/blog_microfeatures/][Origin for code blocks]] - - [[https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images][Responsive images]] - - Feed for new comments ** Wednesday, August 20, 2025 diff --git a/static/css/style.css b/static/css/style.css index 3328f8c..442af67 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -278,7 +278,7 @@ a:hover { border-color: #2e8b57 } -.comment-form textarea,.form-row input,.search-module input { +#comment-form textarea,.form-row input,.search-module input { flex: 1; font-size: 9px; border: 1px solid #ccc; @@ -286,7 +286,7 @@ a:hover { font-family: inherit } -.comment-form button,.search-module button { +#comment-form button,.search-module button { text-transform: uppercase; font-size: 9px; cursor: pointer @@ -392,20 +392,21 @@ hr { border: 1px solid #ccc } -.comment-form { +#comment-form { + display: flex; flex-direction: column } -.comment-form,.form-row,.legacy-warning { +#comment-form,.form-row,.legacy-warning { display: flex; gap: 8px } -.comment-form textarea,.form-row input { +#comment-form textarea,.form-row input { padding: 5px } -.comment-form button { +#comment-form button { align-self: flex-start; background: #333; color: #fff; @@ -413,10 +414,60 @@ hr { padding: 5px 15px } -.comment-form button:hover { +#comment-form button:hover { background: #2e8b57 } +#captcha-image-wrapper img { + border: 1px solid #ccc; + background: #fff; + display: block; + max-width: 100% +} + +#captcha-input-wrapper { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 8px; + margin-top: 12px +} + +#captcha-input-wrapper input { + flex: 1; + font-size: 9px; + border: 1px solid #ccc; + padding: 5px +} + +#captcha-challenge-trigger { + height: 23px; + padding: 0 10px; + font-size: 9px; + text-transform: uppercase; + cursor: pointer; + background: #eee; + border: 1px solid #ccc +} + +#captcha-alternative-wrapper { + align-items: center; + gap: 10px; +} + +#captcha-alternative-wrapper button { + background: #eee; + border: 1px solid #ccc; + color: #333; + font-size: 9px +} + +#captcha-alternative-wrapper p { + font-size: 9px; + margin: 0; + color: #999 +} + .legacy-warning { background: linear-gradient(to bottom,#fff3e0 0,#ffe0b2 100%); border: 1px dotted #fb8c00; diff --git a/static/js/dynamic-comment-form.js b/static/js/dynamic-comment-form.js index ff5f875..809a755 100644 --- a/static/js/dynamic-comment-form.js +++ b/static/js/dynamic-comment-form.js @@ -42,19 +42,15 @@ function makeRequest (method, url) { } window.addEventListener("load", () => { - let primaryDisplay = document.getElementById("comment-form-primary"); + let primaryDisplay = document.getElementById("comment-form"); primaryDisplay.removeAttribute("hidden"); - let altDisplay = document.getElementById("comment-form-alt"); - altDisplay.remove(); + const altDisplay = document.getElementById("captcha-alternative-wrapper"); 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"); + let wrapper = document.getElementById("captcha-image-wrapper"); trigger.innerHTML = "Please wait..."; trigger.disabled = true; @@ -65,13 +61,17 @@ window.addEventListener("load", () => { // Update form with parsed values. let challengeData = JSON.parse(data); captchaId.value = challengeData["challenge-id"]; - captchaImage.src = challengeData["image"]; - // Unhide fieldset. - captchaField.removeAttribute("hidden"); + const img = document.createElement('img'); + img.src = challengeData["image"]; + // img.src = `data:image/png;base64,${base64Data}`; + wrapper.innerHTML = ''; + wrapper.appendChild(img); + + trigger.hidden = true; - // Hide initial trigger. - triggerBlock.remove(); + // Unhide fieldset. + altDisplay.removeAttribute("hidden"); }) .catch(console.err); } @@ -80,7 +80,6 @@ window.addEventListener("load", () => { }); }); - window.addEventListener("load", () => { // Unhide additional comment actions if Javascript is enabled. let additionalActionsRows = document.querySelectorAll(".comment-additional-actions"); diff --git a/static/js/proof-of-work.js b/static/js/proof-of-work.js index b4b5c57..f2f0484 100644 --- a/static/js/proof-of-work.js +++ b/static/js/proof-of-work.js @@ -81,7 +81,7 @@ function raceEndpoint() { } window.addEventListener("load", () => { - let trigger = document.getElementById("pow-trigger"); + let trigger = document.getElementById("proof-of-work-trigger"); trigger.removeAttribute("hidden"); trigger.addEventListener("click", (e) => { trigger.innerHTML = "Please wait..."; @@ -96,8 +96,11 @@ window.addEventListener("load", () => { 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"); + let primaryChallenge = document.getElementById("captcha-image-wrapper"); primaryChallenge.hidden = true; + let solutionInput = document.getElementById("captcha-solution"); + solutionInput.hidden = true; + solutionInput.removeAttribute("required"); }) .catch((err) => { console.log(err); -- cgit v1.3