Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Staticman nested comments support from Huginn #69

Merged
merged 9 commits into from
Aug 22, 2019

Conversation

VincentTam
Copy link
Collaborator

@VincentTam VincentTam commented Aug 17, 2019

Description

It is in turn a port from halogenica/beautifulhugo#222. (typo edited) Introduced true nested comments with reply functionality.

  1. Reply target anchors to easily jump between replies.
  2. Interactive reply target display with Gravatar and name.
  3. Improved input type for "website" field.
  4. Implemented FR FEATURE: Hide Staticman POST URL in Go-HTML template #63 to construct POST URL in JS.
  5. Clearer instructions for Google reCAPTCHA v2.
  6. Introduced some SCSS variables for layout control
  7. Fixed missing "id" attribute in each comment.
  8. Fixed missing translation UI text for Staticman forms.
  9. Changed default API endpoint to the one serving @staticmanlab due to
    503 error using v3 endpoint eduardoboucas/staticman#307.

Motivation and Context

Resolving #63 and adding missng i18n strings to Staticman form requires changing the template, so I adapted my Staticman templates for Huginn, Introduction, Hugo Swift Theme and my blog into this theme.

Screenshots (if appropriate):

Screenshot from 2019-08-16 23-59-41

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

which is in turn a port from halogenical/beautifulhugo#222.  Introduced true
nested comments with reply functionality.

1. Reply target anchors to easily jump between replies.
2. Interactive reply target display with Gravatar and name.
3. Improved input type for "website" field.
4. Implemented FR pacollins#63 to construct POST URL in JS.
5. Clearer instructions for Google reCAPTCHA v2.
6. Introduced some SCSS variables for layout control
7. Fixed missing "id" attribute in each comment.
8. Fixed missing translation UI text for Staticman forms.
9. Changed default API endpoint to the one serving @staticmanlab due to
eduardoboucas/staticman#307.
@VincentTam VincentTam self-assigned this Aug 17, 2019
Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are accessibility issues in these changes.

layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Show resolved Hide resolved
layouts/post/staticman.html Outdated Show resolved Hide resolved
layouts/post/staticman.html Outdated Show resolved Hide resolved
Forgotten to update master branch before branching
Gravatar alt text wasn't there in the original code, but I agree with the bot.
@pacollins
Copy link
Owner

Marginally related: I have removed the Accesslint App for the time being. I may reintroduce it once things settle and we can focus on accessibility.

@VincentTam
Copy link
Collaborator Author

VincentTam commented Aug 19, 2019

@pacollins Please review my PR. I prefer porting from my existing nested comments templatesest rather than re-inventing the wheel. Here's some (possible) bugs in the current Staticman template.

For background and motivations about nested comments, please see https://github.com/onweru/hugo-swift-theme/projects.

Test site short link: https://lstu.fr/fish-demo, https://vincenttam.frama.io/fish-demo
Source: https://framagit.org/VincentTam/fish-demo

{{ with .Site.Params.staticman }}
<form class="post-new-comment" method="POST" action="https://{{ .api | default "api.staticman.net" }}/v3/entry/{{ .gitprovider }}/{{ .username }}/{{ .repo }}/{{ .branch }}/comments">
{{ end }}
'

Even though this PR is not as important as #47 and #71, the possible spamming problem mentioned in #63 can be quite annoying. For example, you may see https://github.com/hashtafak/hashtafak.github.io/commits/master. I confess that I didn't know Node.JS when I created my Staticman instance for testing. I've tried ways like reCAPTCHA v2 and allowedOrigins, but these aren't really effective. Note that Akismet for official instances has probably been disabled: eduardoboucas/staticman#83 (comment). Moving the form action URL to JS is the next way that I've found from https://discourse.gohugo.io/t/disqus-alternatives/2948/42?u=vincenttam.

I've overlooked issue eduardoboucas/staticman#307 when I proposed to use the official production Staticman instance at b75aa75#diff-b7cd599ec3be3f913bdf453cbd849f0d.
Changing this to my public GitLab instance @staticmanlab at 66f559b#diff-92cd1306607ed43482f2d42da3aeda93R12

{{ $comments := readDir "data/comments" }}

Use .Site.Data.comments instead. This would allow us to remove data/comments/.gitkeep. (I didn't realise that you put readDir when I proposed to remove data/comments/.gitkeep in one of my former PRs.) Not every user want to have comments.

<article id="{{ .uniqueID }}" class="post-comment">

{{ .uniqueID }} undefined.

<h3 class="post-comment-author">
<a rel="external nofollow" href="{{ .website }}">{{ .name }}</a>
</h3>

website isn't a required field. In case of empty website, a link to the current post head is created. That's not something desired.

@pacollins
Copy link
Owner

I will review now. I needed time to properly test (which I have at this moment).

@pacollins
Copy link
Owner

As of right now, using @staticmanlab:

  1. API set to staticman3.herokuapp.com
  2. Bot set as collaborator
  3. Bot linked via https://staticman3.herokuapp.com/v2/connect/
  4. Received OK
  5. Submit comment and receive Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again.

Form HTML is from the localhost:

<form id="post-js-form" class="post-new-comment" method="POST" _lpchecked="1">
      
      <h5 class="post-reply-notice hidden">
        <span class="post-reply-arrow"></span><span class="post-reply-name"></span>
      </h5>

      
      <input type="hidden" name="options[entryId]" value="6354f801456983b22c4b91ce42adfca7">
      <input type="hidden" name="fields[replyThread]" value="">
      <input type="hidden" name="fields[replyID]" value="">
      <input type="hidden" name="fields[replyName]" value="">

      
      <input required="" name="fields[name]" type="text" placeholder="Your name (Required)" style="background-image: url(&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHklEQVQ4EaVTO26DQBD1ohQWaS2lg9JybZ+AK7hNwx2oIoVf4UPQ0Lj1FdKktevIpel8AKNUkDcWMxpgSaIEaTVv3sx7uztiTdu2s/98DywOw3Dued4Who/M2aIx5lZV1aEsy0+qiwHELyi+Ytl0PQ69SxAxkWIA4RMRTdNsKE59juMcuZd6xIAFeZ6fGCdJ8kY4y7KAuTRNGd7jyEBXsdOPE3a0QGPsniOnnYMO67LgSQN9T41F2QGrQRRFCwyzoIF2qyBuKKbcOgPXdVeY9rMWgNsjf9ccYesJhk3f5dYT1HX9gR0LLQR30TnjkUEcx2uIuS4RnI+aj6sJR0AM8AaumPaM/rRehyWhXqbFAA9kh3/8/NvHxAYGAsZ/il8IalkCLBfNVAAAAABJRU5ErkJggg==&quot;); background-repeat: no-repeat; background-attachment: scroll; background-size: 16px 18px; background-position: 98% 50%; cursor: auto;">
      <input name="fields[website]" type="text" placeholder="Your website">
      <input required="" name="fields[email]" type="email" placeholder="Your email address (Required for Gravatar)">
      <textarea required="" name="fields[body]" placeholder="Your message. Feel free to use Markdown (Google 'Markdown Cheat Sheet')." rows="10"></textarea>

      
      

      
      <p class="post-submit-notice">
        <strong class="post-submit-notice-text submit-success hidden">Thanks for your comment! It will show on the site once it has been approved.</strong>
        <strong class="post-submit-notice-text submit-failed">Sorry, there was an error with your submission.  Please make sure all required fields have been completed and try again.</strong>
      </p>

      
      <input type="submit" value="Submit" class="button">
      <input type="submit" value="Submitted" class="button hidden" disabled="">
      <input type="reset" value="Reset" class="button">
    </form>

@VincentTam
Copy link
Collaborator Author

VincentTam commented Aug 21, 2019

Perhaps some images like

comment reply diagram

might help understanding my code.

To give a rough idea about the entire script, there're three parts in the Staticman script.

  1. AJAX post request: official JS script https://github.com/eduardoboucas/popcorn/blob/gh-pages/js/main.js and Minimal Mistakes' one

    1. Form submit event is detected
    2. Submit button is disabled
    3. Construct form action URL from several JS variables (to avoid search bots from obtaining the action URL by scanning HTML files)
    4. Form data converted to serialized URL by jQuery
    5. Submit form data
    6. Handle resutls
      • success: display success message for 3 sec + clear form
      • failed: display error message

    Remarks: the ajax method in this part allows us to remove the hidden options[redirect] field, since the page stays the same throughout the whole process.

  2. Nested comments: https://vincenttam.gitlab.io/post/2018-11-17-nested-comments-in-beautiful-hugo/

    1. In the Go-HTML template, use an <article> tag whose id attribute is set to _id of the YML/JSON data file to represent each comment.
    2. Use event delegation for setting the reply target: register the <article> tag, which listens to clicks on the reply button in each comment. The event delegate target is the <article> tag. I've used a trick of storing the thread ID (the _id of the eldest ancestor) in the reply buttons (which is an <a> internal reference in the title attribute). When a reply button is clicked, the browser script searches for attributes/text inside the delegate target (i.e. the comment containing the clicked button), set the values to the reply* input fields.
    // record reply target when "reply to this comment" is pressed
    $('article.static-comment').on('click', 'a.reply-btn', function (evt){
      var cmt = $(evt.delegateTarget);
      $('input[name="fields[replyThread]"]').val(this.title);
      $('input[name="fields[replyID]"]').val(cmt.attr("id"));
      authorTag = cmt.find('.comment-author');
      replyName = authorTag.text();
      $('input[name="fields[replyName]"]').val(replyName);
      $('.reply-notice-text').text('↷\xa0' + replyName);  // this line belongs to the 3rd part
    });

    👨‍💻 https://gitlab.com/VincentTam/huginn/blob/c9e48180845fc19f8c111d551ba8b3faa67aa927/assets/js/staticman.js#L47-56

  3. Reply target preview on top of the form: see GIF in https://framapiaf.org/@staticmanlab/102537205209643491

    1. Added a <p> element for containing the entire reply notice.
    2. When the reply button is clicked, reset reply target first by deleting the 'x' button and the Gravatar and clearing all user input and reply* fields.
    3. To display Gravatar, a deep clone of the <img> tag in the delegated comment is used so that the resetReplyTarget() will function as expected. Just like the previous part, read from the delegated comment and inject them into the reply notice.
    4. To easily reset reply target (from a blog visitor's point of view), a 'x' button (an <a> tag) is appended to the RHS of the reply notice. This is constructed in JS because a static <a class="btn><x></a> has unexpectedly triggered a form submit event, which isn't sth desired. Once this is clicked, the resetReplyTarget() method is triggered to delete/clear stuff. To avoid accumulation of Gravatars and 'x' buttons, they will be removed once

@pacollins
Copy link
Owner

Not gonna lie, Staticman goes over my ability. I still can't get a comment to post. Receiving the following from the console:

POST https://staticman3.herokuapp.com/v3/entry/github/pacollins/imperfect-test/master/comments 500 (Internal Server Error)

and

responseJSON:
errorCode: "GITHUB_READING_FILE"
rawError:
message: "{"message":"Not Found","documentation_url":"https://developer.github.com/v3/repos/contents/#get-contents"}"
statusCode: 404
__proto__: Object
success: false
__proto__: Object
responseText: "{"success":false,"rawError":{"message":"{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/repos/contents/#get-contents\"}","statusCode":404},"errorCode":"GITHUB_READING_FILE"}"

@VincentTam
Copy link
Collaborator Author

As of right now, using @staticmanlab:

  1. API set to staticman3.herokuapp.com
  2. Bot set as collaborator
  3. Bot linked via https://staticman3.herokuapp.com/v2/connect/
  4. Received OK
  5. Submit comment and receive Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again.

Form HTML is from the localhost:

<form id="post-js-form" class="post-new-comment" method="POST" _lpchecked="1">
      
      <h5 class="post-reply-notice hidden">
        <span class="post-reply-arrow"></span><span class="post-reply-name"></span>
      </h5>

      
      <input type="hidden" name="options[entryId]" value="6354f801456983b22c4b91ce42adfca7">
      <input type="hidden" name="fields[replyThread]" value="">
      <input type="hidden" name="fields[replyID]" value="">
      <input type="hidden" name="fields[replyName]" value="">

      
      <input required="" name="fields[name]" type="text" placeholder="Your name (Required)" style="background-image: url(&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHklEQVQ4EaVTO26DQBD1ohQWaS2lg9JybZ+AK7hNwx2oIoVf4UPQ0Lj1FdKktevIpel8AKNUkDcWMxpgSaIEaTVv3sx7uztiTdu2s/98DywOw3Dued4Who/M2aIx5lZV1aEsy0+qiwHELyi+Ytl0PQ69SxAxkWIA4RMRTdNsKE59juMcuZd6xIAFeZ6fGCdJ8kY4y7KAuTRNGd7jyEBXsdOPE3a0QGPsniOnnYMO67LgSQN9T41F2QGrQRRFCwyzoIF2qyBuKKbcOgPXdVeY9rMWgNsjf9ccYesJhk3f5dYT1HX9gR0LLQR30TnjkUEcx2uIuS4RnI+aj6sJR0AM8AaumPaM/rRehyWhXqbFAA9kh3/8/NvHxAYGAsZ/il8IalkCLBfNVAAAAABJRU5ErkJggg==&quot;); background-repeat: no-repeat; background-attachment: scroll; background-size: 16px 18px; background-position: 98% 50%; cursor: auto;">
      <input name="fields[website]" type="text" placeholder="Your website">
      <input required="" name="fields[email]" type="email" placeholder="Your email address (Required for Gravatar)">
      <textarea required="" name="fields[body]" placeholder="Your message. Feel free to use Markdown (Google 'Markdown Cheat Sheet')." rows="10"></textarea>

      
      

      
      <p class="post-submit-notice">
        <strong class="post-submit-notice-text submit-success hidden">Thanks for your comment! It will show on the site once it has been approved.</strong>
        <strong class="post-submit-notice-text submit-failed">Sorry, there was an error with your submission.  Please make sure all required fields have been completed and try again.</strong>
      </p>

      
      <input type="submit" value="Submit" class="button">
      <input type="submit" value="Submitted" class="button hidden" disabled="">
      <input type="reset" value="Reset" class="button">
    </form>

@pacollins To correctly test Staticmn integration for this blog theme,

  1. Please post the link to your source repo; https://github.com/pacollins/imperfect-test

  2. Please post the output (or at least, a screenshot) from the web browser's web developper tools. In case of errors, please include the error code and error message in the Staticman API's response, like

    {"success":false,"errorCode":"INVALID_VERSION"}

    This helps us figure out what goes wrong, sometimes.

  3. Your repo lacks a root-level Staticman repo config file (see step two in the docs by clicking the link). That should be copied from https://github.com/pacollins/hugo-future-imperfect-slim/pull/69/files#diff-b7432f3b1e01c9c18f2ba88cb9a82375.

  4. You have to place the staticman.yml on the source branch of your blog instead of the branch holding generated code. Let's take a closer look to your own Staticman config committed two years ago.

    # (*) REQUIRED
    #
    # Destination path (directory) for the data files. Accepts placeholders.
    path: "data/comments/{options.entryId}"

    data/comments is a file path on the source branch. On the branch holding the code generated by hugo, there's no data/.

@pacollins
Copy link
Owner

pacollins commented Aug 21, 2019

Fixed

Your repo lacks a root-level Staticman repo config file (see step two in the docs by clicking the link). That should be copied from https://github.com/pacollins/hugo-future-imperfect-slim/pull/69/files#diff-b7432f3b1e01c9c18f2ba88cb9a82375.

You have to place the staticman.yml on the source branch of your blog instead of the branch holding generated code. Let's take a closer look to your own Staticman config committed two years ago.

We need to move staticman.yml to static/staticman.yml for builds (or have a second one there.

EDIT: I say this as a concern, because there is the chance that a user will just push their public folder to their server.

EDIT: See comment below.

@VincentTam
Copy link
Collaborator Author

We need to move staticman.yml to static/staticman.yml for builds (or have a second one there.

I don't get this. It's clearly said that in the docs that this file has to be root-level.

For a self-contained setup guide, I've recently created a Wiki page for Staticman setup in Hugo Swift Theme.

I'm sorry that my hands type quite slow.

@pacollins
Copy link
Owner

Well, I take my statement back and clarify it. We need to add documenation somewhere that makes it clear you must put your entire Hugo folder (pre-built and public) to a repo. The issue I had was an edge case, but something someone might do. "Hugo builds my site to public, so I will just push all of the public files to my server." Since staticman.yml is at the root of the site, it does not get built into public, thus staticman doesn't work. If it were in static, it would get pulled into public and staticman could still function even if they didn't host the pre-build hugo files.

That all being said, it would cause more issues for them (having to individually download comments), so we (as a theme or as staticman) should probably make sure it is clear you need to push your whole pre-built site to the server and not just public when using staticman.

Does that edge-case thought process make sense? You did nothing wrong, I just represented someone who wasn't thinking and could have misinterpreted the instructions.

That all being said, I can now happily test your changes. 😄

@VincentTam
Copy link
Collaborator Author

Since staticman.yml is at the root of the site, it does not get built into public, thus staticman doesn't work. If it were in static, it would get pulled into public and staticman could still function even if they didn't host the pre-build hugo files.

I wonder what your deploy workflow is. My demo site (see my 2nd comment for the links) is using GitLab CI, which gives a publicly accessible file tree of exampleSite/public, in which staticman.yml is absent. However, my test site can still receive comments. Why it is so?

Note the difference between "root of the site" and "root of the repository". In the Staticman setup, it shows the later. The Staticman repo config file staticman.yml lies at the root of the source branch.

In eduardoboucas/staticman#264 (comment), @eduardobucas wrote

Staticman doesn't know (and shouldn't care) whether you use GitHub Pages or not. Its job is to get content into your repository. I'm not keen on adding logic that is specific to GitHub Pages.

Staticman is not related to the site. It communicates with the GitHub/GitLab repo. For a barebone example, you may see https://gitlab.com/ntsim/test-staticman/ (requires GitLab account). Another theoretically (but not practically) useful example at https://github.com/sdessus/comments_blog_tdm illustrates the functioning of Staticman (as a Node.JS app), even though what the owners claimed in their blog is apparently incorrect.

Le site est statique, c'est-à-dire que sont contenu est quelque peu figé. Ne vous attendez donc pas à voir apparaître votre commentaire directement après son envoi. Il me faut faire ma petite tambouille pour que vous ayez le bonheur de le voir apparaître sur l'article.

The site is static. That means its contents are less movable. Therefore, don't wait for the appearance of your comment after having sent it. I have to cook them a little bit so that you will happily see that your comments appears below the article.

This repo is not practically useful since the comments for all posts have been mixed. The above claim is wrong since JS's fetch() method can send asynchronous requests to remote servers and handle response.

That all being said, it would cause more issues for them (having to individually download comments), so we (as a theme or as staticman) should probably make sure it is clear you need to push your whole pre-built site to the server and not just public when using staticman.

I prefer using the word "source" over "pre-built". It has to be, for the moment and near future, on the source branch in a ( custom instance of) GitHub/GitLab repo, since only these two types of remote Git repo are supported.

@pacollins
Copy link
Owner

I wonder what your deploy workflow is. My demo site (see my 2nd comment for the links) is using GitLab CI, which gives a publicly accessible file tree of exampleSite/public, in which staticman.yml is absent. However, my test site can still receive comments. Why it is so?

The issue was I only pushed public to the repo, thus staticman.yml wasn't included. I personally use Netlify, but for this quick test site, I tried it that way. Again, an edge case. In my case, the root of the repo and site were one in the same. It wasn't a GitHub specific thing, it was how I uploaded (compiled vs source+compiled) - thanks for the better word there by the way. 😉 With staticman.yml in the compiled site folder, I could still take comments, but I would have had to download them individual and place them into my source files. Not practical, but an issue (or intent) someone might have.

I prefer using the word "source" over "pre-built". It has to be, for the moment and near future, on the source branch in a ( custom instance of) GitHub/GitLab repo, since only these two types of remote Git repo are supported.

Thanks for the word I couldn't think of when typing. 😄 Yes, it should be clear that it should exist within the source branch (not just the compiled site) on a repo.

Copy link
Owner

@pacollins pacollins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That all being said, is there anything in particular you want me looking for? Functionally, it seems to work. I am going to take a look at restyling the CSS over the next day or so. If you feel as though the spam protection is vital enough to merge now, I approve and I will do a separate PR for styling.

@VincentTam
Copy link
Collaborator Author

VincentTam commented Aug 22, 2019

Notes for commits after @pacollins's approval:

The clearForm() function had a bug: after successful form submission, the visible form fields weren't reset.

success: function (data) {
showAlert('success');
setTimeout(function(){ clearForm() }, 3000); // display success message for 3s
$(form).disabled = false;
},

This is due to the extra [type="hidden"] at line 52.
function clearForm() {
resetReplyTarget();
$('.post-new-comment input[type="hidden"]')
.filter(function() {
return this.name.match(/^fields\[.*\]$/);
})
.val(''); // empty all hidden fields but not options
$('.post-new-comment .submit-success').addClass('hidden'); // hide submission status
$('.post-new-comment .submit-failed').addClass('hidden'); // hide submission status
}

I had made such mistake after hours of programming, and I had forgotten that clearForm() is also called in caes of successful form submission. I had the wrong idea that the onclick listener of the "Reset" button were the only function that called clearForm().
// clear form when reset button is clicked
$('.post-new-comment input[type="reset"]').click(function (){
clearForm();
});

Now this has been corrected at ffa6711.

function clearForm() {
resetReplyTarget();
$('.post-new-comment input')
.filter(function() {
return this.name.match(/^fields\[.*\]$/);
})
.val(''); // empty all text & hidden fields but not options
$('.post-new-comment textarea').val(''); // empty text area
$('.post-new-comment .submit-success').addClass('hidden'); // hide submission status
$('.post-new-comment .submit-failed').addClass('hidden'); // hide submission status
}

In addition, since a Staticman instance running on a free Heroku dyno (say, @staticmanlab) might take time to respond, I've added a SCSS rule to decrease the opacity of the submitted form by half until the submission message appears. I've changed $(form).disable = true to $(form).addClass('loading') because the disabled attribute doesn't apply to <form> tags.

Visual results for my demo site: https://vincenttam.frama.io/fish-demo/blog/examples/
fish
Screenshot_2019-08-22 Markdown and Shortcodes
Btw, in the message for successful form submission in the above GIF, I've found an error: "It [the comment] will show be shown on the site once it has been approved".

@VincentTam VincentTam merged commit 118943a into pacollins:master Aug 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants