Update 20190819: The public instance of the Staticman API is broken with no clear line-of-sight on when or if it will be repaired. Reference issue #307. I am now running my own Staticman instance in Heroku using the suggestions from VincentTam. I am using the master branch and integrating PR #285. VincentTam did a good write up on all of these things in a few places: Issue #296 & VincentTam’s blog on hosting your own instance of Staticman.. This all takes some piecing together, so I may write an update post on how I am hosting my instance and link it here. If there are any questions feel free to hit me in the comments.
In this post I want to cover the steps I went through to get Staticman nested comments and e-mail notifications working in Hugo.
Disclaimer: I am new to Hugo, Go Templating, JavaScript, and all the bits and pieces used in this write-up. I am sure there are more efficient methods to achieve these results. Please provide any feedback in the comments or feel free to issue a pull request. Thanks!
Below is a list of the technology I use for this blog:
- Hugo - Version 0.31.1
- Beautiful Hugo Theme
- Staticman
- Netlify
- Network Hobo GitHub Repo
I do not go through all of the steps for basic Staticman setup and API registration. This is covered in the great Staticman Documentation.
In the beginning…
After years of hosting this blog on Wordpress I decided I wanted to make a move to a more flexible option. I started looking at static site generation and all of the various options. I finally decided to take a crack at Hugo.
I went in search of a clean template for my new Hugo site. After much searching I decided to go with Beautiful Hugo. At the time, I started using Beautiful Hugo it only supported Disqus for commenting. I knew I wanted to take this opportunity to gain control of all of my data, so I started adapting the Beautiful Hugo theme to use Staticman for commenting.
- Note: As of November 21, 2017, the Beautiful Hugo theme now natively supports Staticman comments (PR#99). This is basic commenting without replies or e-mail notification. I plan to work these features into the theme. Just wanted to get my logic down here before I lose it :-).
I started my work with Staticman by using the Hugo example site from Staticman themselves. Eduardo Bouças (creator of Staticman) did a great job putting this small example site together. I used the post-comments.html partial from this site as the base to begin exploring Staticman. I broke this out into two partials, one for parsing the comments to show and one for the comment form itself. I did this so that later I could add a feature to lock comments on a post if needed.
Now I need to investigate how to handle e-mail notifications. That is when I stumbled upon this gold mine of information from Michael Rose. This article and Michael’s GitHub repo really helped me walk through the logic of e-mail replies as well as how he handled nesting replies. What a GREAT source of information!
Putting it all together:
The first thing I wanted to be sure of was that I did not statically configure anything. I wanted Staticman to act just like another available comment module within the template. Therefor I added the following pieces of information to the Params
section of my config.toml
file:
staticman_api = "https://api.staticman.net/v2/entry/dancwilliams/networkhobo/master/comments" #Add staticman API URL to enable staticman comments
Then I added some logic to the layouts/_default/single.html
file to look for the staticman_api Site.Param, and if existed to add the post-comments.html
partial:
{{ if (.Params.comments) | or (and (or (not (isset .Params "comments")) (eq .Params.comments nil)) (.Site.Params.comments)) }}
{{ if .Site.Config.Services.Disqus.Shortname }}
<div class="disqus-comments">
{{ template "_internal/disqus.html" . }}
</div>
{{ end }}
{{ if (.Site.Params.staticman_api) }}
{{ partial "post-comments" . }}
{{ end }}
{{ end }}
I then moved on to the creation of the layouts/partials/post-comments.html
partial:
<section class="post-comments">
<h3>Comments</h3>
{{ $.Scratch.Add "hasComments" 0 }}
{{ $entryId := .File.BaseFileName }}
{{ range $index, $comments := (index $.Site.Data.comments $entryId ) }}
{{ $.Scratch.Add "hasComments" 1 }}
{{ if not .reply_to }}
<div class="post-comment">
<div class="post-comment-header">
<img class="post-comment-avatar" src="https://www.gravatar.com/avatar/{{ .email }}?s=100">
<p class="post-comment-info"><strong>{{ .name }}</strong><br>{{ dateFormat "Monday, Jan 2, 2006" .date }}</p>
</div>
{{ .body | markdownify }}
</div>
<div class="comment__reply">
<a id="{{ ._id }}" class="btn-info" href="#comment-form" onclick="changeValue('fields[reply_to]', '{{ ._id }}')">Reply to {{ .name }}</a>
</div>
{{ partial "comment-replies" (dict "entryId_parent" $entryId "SiteDataComments_parent" $.Site.Data.comments "parentId" ._id "parentName" .name "context" .) }}
{{ end }}
{{ end }}
{{ if eq ($.Scratch.Get "hasComments") 0 }}
<p>Nothing yet.</p>
{{ end }}
{{ partial "comment_form" . }}
</section>
You can see that most of this is pulled from the Staticman Hugo example. I did remove some of the looping and dataset reading that I found to be excessive. You will also see that I added some logic to only present comments that were not replies and to add a button for replies to those comments.
I had to add a piece of JavaScript to the reply button to set the value of the fields[reply_to]
hidden input. This field is set to the ._id
value of the current parent comment. This allows for the fields to be properly populated by Staticman:
Located here
// Added function to change value onclick
function changeValue(elementName, newValue){
document.getElementsByName(elementName)[0].value=newValue;
};
Here are examples of the two types of comment YAML files that are created:
Parent Comment:
In the parent comments you will see that the reply_to
field is blank.
_id: 74ea2730-ed18-11e7-96e3-b9aaffd0f2aa
_parent: >-
2013-12-23-cisco-unified-communications-manager-unity-connection-sftp-emergency-backup-to-mac-os-x-over-the-internet
reply_to: ''
name: Dan
email: 9162d0c5aca33e7e4c8ec6fc3d44f541
body: Test comment 1
date: '2017-12-30T04:18:15.955Z'
Child Comment:
In the reply comments you will see that the reply_to
field is populated with the ._id
value from the parent comment.
_id: f7be83c0-ed1a-11e7-96e3-b9aaffd0f2aa
_parent: >-
2013-12-23-cisco-unified-communications-manager-unity-connection-sftp-emergency-backup-to-mac-os-x-over-the-internet
reply_to: 74ea2730-ed18-11e7-96e3-b9aaffd0f2aa
name: Reply Tester
email: 53d8e4904144b75f9ada3862b6ebafae
body: Testing a reply to Dan’s comment
date: '2017-12-30T04:36:14.421Z'
This reply_to
field is what is used to differentiate parent and child comments.
After a “parent” comment is printed the layouts/partials/comment-replies.html
partial is called. This partial looks much like the post-comments
partial:
{{ range $index, $comments := (index $.SiteDataComments_parent $.entryId_parent ) }}
{{ if eq .reply_to $.parentId }}
<div class="post-comment-reply">
<div class="post-comment-header">
<img class="post-comment-avatar" src="https://www.gravatar.com/avatar/{{ .email }}?s=100">
<p class="post-comment-info"><strong>{{ .name }}</strong><br><i><small>In reply to {{ $.parentName }}</i></small><br>{{ dateFormat "Monday, Jan 2, 2006" .date }}</p>
</div>
{{ .body | markdownify }}
</div>
{{ end }}
{{ end }}
When this partial is called a dictionary of variables are passed:
{{ partial "comment-replies" (dict "entryId_parent" $entryId "SiteDataComments_parent" $.Site.Data.comments "parentId" ._id "parentName" .name "context" .) }}
These variables allow the replies partial to match the reply_to
field against the parent comment ._id
field (variable parentId
) within this partial. Once a match it hit the same process is used for presenting the comment, with the slight addition of adding a text blurb to say this is a reply to the name of the parent comment author.
After all of the comments and replies for a particular post have been processed, the layouts/partials/comment_form.html
partial is called:
<section class="comment_form">
<a id="comment-form"></a>
<h3>Say something</h3>
<form class="post-new-comment" method="POST" action="{{ .Site.Params.staticman_api }}">
<input type="hidden" name="options[redirect]" value="{{ .Permalink }}#post-submitted">
<input type="hidden" name="options[redirectError]" value="{{ .Permalink }}#post-error">
<input type="hidden" name="options[entryId]" value="{{ .File.BaseFileName }}">
<input type="hidden" name="options[slug]" value="{{ .Permalink }}">
<input type="hidden" name="options[origin]" value="{{ .Permalink }}">
<input type="hidden" name="options[parent]" value="{{ .File.BaseFileName }}">
<input type="hidden" name="fields[reply_to]" value="">
<input type="hidden" name="options[reCaptcha][siteKey]" value="{{ .Site.Params.recaptcha_siteKey }}">
<input type="hidden" name="options[reCaptcha][secret]" value="{{ .Site.Params.recaptcha_secret }}">
<fieldset>
<input name="fields[name]" type="text" class="post-comment-field" placeholder="Your name">
</fieldset>
<fieldset>
<input name="fields[email]" type="email" class="post-comment-field" placeholder="Your email address">
</fieldset>
<fieldset>
<textarea name="fields[body]" class="post-comment-field" placeholder="Your message. Feel free to use Markdown." rows="10"></textarea>
</fieldset>
<fieldset>
<div class="notify-me">
<input type="checkbox" id="comment-form-reply" name="options[subscribe]" value="email">
Send me an email when someone comments on this post.
</div>
</fieldset>
<fieldset>
<div class="g-recaptcha" data-sitekey="{{ .Site.Params.recaptcha_siteKey }}" data-callback="enableBtn"></div>
<input type="submit" class="post-comment-field btn" value="Submit" id="submit_button">
</fieldset>
</form>
<script async src='https://www.google.com/recaptcha/api.js' ></script>
<script type="text/javascript">
document.getElementById("submit_button").disabled = true;
</script>
<script type="text/javascript">
function enableBtn(){
document.getElementById("submit_button").disabled = false;
}
</script>
<div id="post-submitted" class="dialog">
<h3>Thank you</h3>
<p>Your post has been submitted and will be published once it has been approved.</p>
{{ if (.Site.Params.githubPullURL) }}
<p><a href="{{ .Site.Params.githubPullURL }}">Click here</a> to see the pull request you generated.</p>
{{ end }}
<p><a href="#" class="btn">OK</a></p>
</div>
<div id="post-error" class="dialog">
<h3>OOPS!</h3>
<p>Your post has not been submitted. Please return to the page and try again. Thank You!</p>
<p><a href="#" class="btn">OK</a></p>
</div>
</section>
This form was also taken from the Staticman Hugo example site and modified in a few ways. Multiple hidden input fields were added to support replies as well as subscribing to comment e-mail notifications.
To support e-mail replies we had to add a checkbox to the comment form:
<fieldset>
<div class="notify-me">
<input type="checkbox" id="comment-form-reply" name="options[subscribe]" value="email">
Send me an email when someone comments on this post.
</div>
</fieldset>
When this checkbox is selected it sets the value of the input options[subscribe]
to the given e-mail address. This option is used by Staticman for creating and/or populating the mailing lists in Mailgun.
There is also logic to support reCaptcha. The reCaptcha authorizationis used in two ways:
- It is used by the backend Staticman server (setting in
staticman.yml
) - It is used by the submit button on the comment form. The submit button remains disabled until the reCaptcha test is passed.
Once the form is filled out and the comment is submitted one of two things could happen. Within the comment_form
partial there is a dialog for submission success and one for failure.
Success Dialog:
<div id="post-submitted" class="dialog">
<h3>Thank you</h3>
<p>Your post has been submitted and will be published once it has been approved.</p>
{{ if (.Site.Params.githubPullURL) }}
<p><a href="{{ .Site.Params.githubPullURL }}">Click here</a> to see the pull request you generated.</p>
{{ end }}
<p><a href="#" class="btn">OK</a></p>
</div>
If the Staticman API call comes back as successful, this dialog is presented. If the configuration parameter githubPullURL
is set, a link will be presented to view the pull request created by Staticman. The OK button will take the user back to the beginning of the post.
Failure Dialog:
<div id="post-error" class="dialog">
<h3>OOPS!</h3>
<p>Your post has not been submitted. Please return to the page and try again. Thank You!</p>
<p><a href="#" class="btn">OK</a></p>
</div>
If the Staticman API call reports a failure, this dialog is presented. The OK button will take the user back to the beginning of the post.
Summary
This has been a fun project that I have been working on for some time. I have learned a lot about all of the components of Hugo and hope that some people will show me better/cleaner/more efficient ways of handling this.
In this post I tried to cover everything from my notes, but if I forgot something I will be sure to update.
Thanks for taking the time to read this and I hope you find it helpful. Feel free to comment here and/or reach out to me on Twitter (@dancwilliams) if you would like to discuss any of this.
Thanks again!
Lagniappe
When I was migrating from Wordpress to Hugo + Staticman I wanted to be sure to migrate all of my existing comments. There was a lot of good stuff in there and I didn’t want to lose it. Since Wordpress provides a full export of your site in an XML document, I wrote a little Python script to extract the comments and create the YAML files. It even preserves the nesting. Here is a link to the script in GitHub.
117 comments
Dan
Sunday, Dec 31, 2017
Dan
In reply to Dan
Sunday, Dec 31, 2017
Daryn
In reply to Dan
Sunday, Dec 31, 2017
Tiago
In reply to Dan
Tuesday, Jan 30, 2018
test
In reply to Dan
Wednesday, May 16, 2018
B
In reply to Dan
Friday, Jun 1, 2018
Donald
In reply to Dan
Tuesday, Jun 5, 2018
Donald
In reply to Dan
Tuesday, Jun 5, 2018
Joseph
In reply to Dan
Saturday, Sep 15, 2018
Dan
In reply to Dan
Saturday, Sep 15, 2018
Hugo 0.48
. This format still works, but I want to clean up some of the variable activity.test
In reply to Dan
Saturday, Feb 22, 2020
b
In reply to Dan
Tuesday, Feb 25, 2020
ldkjfs
In reply to Dan
Friday, Apr 3, 2020
test
In reply to Dan
Tuesday, Apr 21, 2020
Lin
In reply to Dan
Wednesday, Jan 20, 2021
test1
In reply to Dan
Wednesday, Sep 8, 2021
a
In reply to Dan
Wednesday, Sep 21, 2022
Juan
In reply to Dan
Wednesday, May 17, 2023
Apurva
Thursday, Mar 15, 2018
Dan
In reply to Apurva
Thursday, Mar 15, 2018
Apurva,
You should be able to submit a comment without providing an email address. The email address is only used to populate a users Gravatar (if one is available) or provide notifications if the user selects to be notified by email.
Did you try to submit your comment without providing an email and received an error? If it is throwing an error I will definitely look into the code.
Thanks!
Dan
Apurva
In reply to Apurva
Friday, Mar 16, 2018
I have been using only snippets from your code. I had some errors which I mistakenly thought were because of the blank fields.
Thanks for the blog post. It’s very helpful.
snowman
In reply to Apurva
Tuesday, May 17, 2022
Johann
Monday, Apr 30, 2018
Erik
In reply to Johann
Monday, May 7, 2018
Boo back to you!
Dan
Monday, May 7, 2018
Isaac
Wednesday, May 16, 2018
Smithc663
Saturday, May 19, 2018
Thank you my good man
In reply to Smithc663
Monday, Jun 11, 2018
JS
Monday, May 21, 2018
anima
In reply to JS
Monday, Dec 16, 2019
Testing
Sunday, May 27, 2018
Dan
In reply to Testing
Friday, Jun 1, 2018
Test
Monday, Jun 4, 2018
Test
Monday, Jun 4, 2018
testitnow
In reply to Test
Tuesday, Jun 5, 2018
adf
Tuesday, Jun 12, 2018
Smithd948
Wednesday, Jun 27, 2018
Mooash
Sunday, Jul 1, 2018
Paolino Paperino
In reply to Mooash
Saturday, Sep 29, 2018
Paperino
In reply to Mooash
Tuesday, Oct 2, 2018
Scotty
Tuesday, Jul 10, 2018
HowNowBrownCow
Dan
In reply to Scotty
Tuesday, Jul 10, 2018
saj
In reply to Scotty
Saturday, Dec 8, 2018
Dan
In reply to Scotty
Monday, Jan 14, 2019
Mike jordan
Friday, Aug 3, 2018
Smithf528
Thursday, Sep 6, 2018
test guy
Sunday, Oct 7, 2018
Test Guy Reply
In reply to test guy
Thursday, Nov 15, 2018
Test reply again
In reply to test guy
Monday, Jan 14, 2019
Vincent Tam
Thursday, Nov 15, 2018
I think the “In reply to
<name>
” contains a logic error. When the “reply to thread button is clicked, thechangeValue
function only setsreply_to
to_id
of the head of the thread, and theparentName
is tied withparentId
. In one single thread, there’s only one head (i.e.parentId
), but it may have multiple participants (i.e.parentName
).Nonetheless, I’ve learnt a lot from your explanations. Thanks for this great article!
Irfan Gumelar
Thursday, Jan 24, 2019
Dan
In reply to Irfan Gumelar
Thursday, Jan 24, 2019
Rock
Friday, Jan 25, 2019
tomaja
Thursday, May 23, 2019
Xin Zhang
Monday, Jul 8, 2019
Thank you for your tutorial!
I got this error when building the site:
I don’t understand it. I just use the same
post-comments.html
as yours. Could you help me when you’re free?Dan
In reply to Xin Zhang
Monday, Jul 8, 2019
Xin Zhang
In reply to Xin Zhang
Monday, Jul 8, 2019
Dan
In reply to Xin Zhang
Monday, Jul 8, 2019
You are likely going to need a later version of
Hugo
. I know there were some changes in templating in the0.50
times that required some changes in the code.I am going on the assumption you are using Ubuntu…
It looks like Ubuntu doesn’t have the latest in the universe repo. Here is a link showing how to install the latest (or other newer) version of Hugo on Ubuntu using the
deb
from the Github repo.I hope this helps. Let me know if you have any other questions, get it working, or run into a new error. Thanks!
Xin Zhang
In reply to Xin Zhang
Tuesday, Jul 9, 2019
Thank you! I installed the latest hugo (0.55.6). But still got this error:
Is that related to my theme? I’m using AllinOne theme.
Xin Zhang
In reply to Xin Zhang
Tuesday, Jul 9, 2019
Dan
In reply to Xin Zhang
Tuesday, Jul 9, 2019
Xin Zhang
In reply to Xin Zhang
Wednesday, Aug 21, 2019
Hi, Dan!
As mentioned in this issue , staticmanapp doesn’t work anymore. I choose staticmanlab instead. But it doesn’t support mail notification.
Do you has any idea? Are you still using staticmanapp?
Regards, Xin
Dan
In reply to Xin Zhang
Wednesday, Aug 21, 2019
Xin Zhang
In reply to Xin Zhang
Wednesday, Aug 21, 2019
Kokako
In reply to Xin Zhang
Thursday, Jan 30, 2020
Kokako
In reply to Xin Zhang
Thursday, Jan 30, 2020
test
In reply to Xin Zhang
Sunday, Oct 18, 2020
Ewon
In reply to Xin Zhang
Wednesday, Jan 13, 2021
execute of template failed: template: partials/post-comments.html:7:35: executing "partials/post-comments.html" at <index $.Site.Data.comments $entryId>: error calling index: index of untyped nil
He solved in by “adding a test comment file”. I don’t quite get it. What is a test comment file?Zoltán
Monday, Aug 19, 2019
Mr Zuckeberg
Sunday, Sep 8, 2019
Teat
In reply to Mr Zuckeberg
Thursday, Sep 12, 2019
test
Friday, Jan 17, 2020
Nam
Sunday, Mar 15, 2020
Ron
Wednesday, Mar 18, 2020
Amazing
Friday, Apr 3, 2020
Sonia
Sunday, Apr 12, 2020
zeta
Tuesday, May 5, 2020
batman
In reply to zeta
Monday, Jun 15, 2020
Title
testuser12
In reply to zeta
Tuesday, Jul 14, 2020
Tom
Wednesday, Jul 29, 2020
Tom
Wednesday, Jul 29, 2020
Vipin
Wednesday, Aug 5, 2020
shlok
In reply to Vipin
Thursday, Aug 6, 2020
jff
Wednesday, Aug 12, 2020
Great article, this help me a lot to integrate the frontend section of staticman to the existing theme I’m using, I do like to add to display the comments you’ll need to add the following the counterpart of staticman “path” config in your config.yaml. https://j3ffrw.github.io/p504/#
Konstantinoos
Sunday, Aug 16, 2020
Mr B
In reply to Konstantinoos
Monday, Aug 17, 2020
Angel
Monday, Aug 24, 2020
Milovan
Wednesday, Aug 26, 2020
Kayle
In reply to Milovan
Sunday, Sep 6, 2020
Cool Tester
In reply to Milovan
Monday, Oct 10, 2022
Aditya
Monday, Oct 26, 2020
Somrat Sorkar
Saturday, Dec 19, 2020
Ahmad Muhardian
Tuesday, Apr 20, 2021
Thank you
vanomas
Sunday, May 16, 2021
Michael
Friday, Sep 10, 2021
Mike
Sunday, Oct 3, 2021
Matty Moo
Thursday, Nov 4, 2021
Hasan Hasanov
Saturday, Nov 6, 2021
Both this article and your github repo helped me a lot. Unfortunately the Staticman documentation is really far behind.
Thank you for your dedication!
cs
Friday, Nov 19, 2021
f9kqE4Ycfo
Sunday, Nov 21, 2021
static-tester
Monday, Dec 27, 2021
cow
Tuesday, Dec 28, 2021
yolo
In reply to cow
Sunday, Jan 2, 2022
Min
Tuesday, Feb 22, 2022
Remi
Wednesday, Apr 20, 2022
Test image
Azra
Friday, Jul 29, 2022
Azra
In reply to Azra
Friday, Jul 29, 2022
guillaume
Tuesday, Aug 30, 2022
thanks
ThisIsANuclearTest
Monday, Oct 24, 2022
a
Monday, Dec 12, 2022
I have a name
Wednesday, Jan 11, 2023
test
In reply to I have a name
Monday, Jul 17, 2023
harrison
Thursday, Jul 20, 2023
valentin
Tuesday, Jan 9, 2024
Dan
In reply to valentin
Tuesday, Jan 9, 2024
Albert
In reply to valentin
Wednesday, Oct 2, 2024
Henk
Thursday, Oct 31, 2024
Say something
Thank you
Your post has been submitted and will be published once it has been approved.
OK