Software engineer with a focus on game development and scalable backend development
Static sites are great, they allow you to build a website using nothing but client side tech, and let you host them easily in places like AWS S3, Github/Gitlab pages, or even on an arduino as they dont require any server side code. However they do have one big downside, user interaction.
This site for example is all client side, built using Hugo and hosted on Github Pages which makes it very easy to add new content and iterate but doesn’t really lend itself well to having user interaction, such as comments.
I wanted to add the ability to add comments as I want to start having more readers and conversations with people about the things I write, as such I evaluated a number of different alternatives to allow comments on to the site, including moving my posts to an external site.
First thing I looked at was Medium, it’s built for blogs and articles, has great analytics, and has built in comments even for specific sections of a post; however it also lacks in other aspects, you can’t style anything beyond what they provide, the code blocks don’t have syntax highlighting, and one of my bigger issues, it doesn’t let me keep control of my data (Sure, I can post and edit anything I write but I like the fact that with git I have full control and history of any of my posts).
Due to these issues, and the fact that medium is going to be tracking its users more than I like, I decided to not use medium for now.
Next up I looked at what is most likely the most widely used drop in solution for comments, Disqus, Disqus allows me to just drop a little bit of javascript into any post and instantly have a comments section that supports logging in via facebook, twitter, google… However much like medium, it also takes away control and injects a bunch of trackers, not to mention it loads a whole lot more content into any web page!
I also had a look at Staticman, Utterances and a couple others, but these all require you to create a github app and have your api key/secret in the javascript, something that just screams danger to me. That said I really liked the idea of hosting comments directly on github, it has an API that you can read from in json without authentication, it allows people to upvote/downvote comments, and because my posts are generally of a technical nature most readers will already have a github account; So after reading up a bit on how others had accomplished this (here and here), I decided to give it a go.
First thing was deciding how I would host comments, I thought about making each post a PR and then just having comments in these, but this seemed like a cumbersome workflow so I just opted for having an issue host a blogs comments.
The github api has an easy access to comments left on an issue, as described on their api docs and it only introduces the need for me to create an new issue before I post a new comment (I could even create the issue after as long as I remember what id the issue will have).
Next up is setting up my build system to include the js and css to show the comments, but I only want to load these if I have setup the post to support comments, I don’t want to have a reader have to download assets if they aren’t used.
Luckily hugo allows build-time code in the templates, so it was just a case of adding an if statement in a couple of places and comments would just work for any post that included a specific flag.
{{ if .Params.commentIssueId }}
<div id="comments">
<h6 class="docs-header">Comments</h6>
</div>
<script src="/js/github-comments.js" type="text/javascript"></script>
<noscript>
<p>Javascript is required to load post comments!</p>
</noscript>
<div id="comment-link">
<p>Want to comment? Just post to <a href="https://github.com/RichyHBM/richyhbm.github.io/issues/{{ .Params.commentIssueId }}">this github issue</a>!</p>
</div>
{{ end }}
Next up is polling the github api to get all comments related to an issue, currently this is fairly easy to do with a simple AJAX call, though that may change if the REST v3 endpoints get deprecated in favor of the authenticated GraphQL v4 API.
$(document).ready(function() {
$.ajax("https://api.github.com/repos/RichyHBM/richyhbm.github.io/issues/{{ .Params.commentIssueId }}/reactions", {
headers: { Accept: "application/vnd.github.squirrel-girl-preview" },
success: function(msg){
loadReactions(msg);
}
});
});
This will hit the github v3 api and request all comments for a particular issue, it’s worth noting that the “comment” creating the the issue (the first one that the repo owner creates) is excluded from the response data.
The response from that request is then sent to a function that goes through the response and extracts the comment text as well as a few other pieces of data.
function loadComments(data) {
for (var i = 0; i < data.length; i++) {
var commentDiv = document.createElement("div");
var headerDiv = document.createElement("div");
var userName = document.createElement("a");
var messageBody = document.createElement("div");
userName.classList.add("comment-username");
userName.innerText = data[i]['user']['login'];
headerDiv.append(userName);
messageBody.innerHTML = new showdown.Converter().makeHtml(data[i]['body']);
commentDiv.append(headerDiv);
commentDiv.append(messageBody);
$("#comments").append(commentDiv);
}
}
In the above example I process the response, extract some variables and populate html elements with this; in my actual comments I also run some additional javascript to turn the creation date into a github style “posted X days ago”. It is also worth noting that github returns the body of the comment as markdown, hence I use Showdown to render it.
Lastly is adding some CSS to make it look good, on my site I opted for going with a similar but more minimal style to github comments.
Another thing that github gives us are reactions, this allows me to emulate a “like” style feature or even a simple moderation system. For now I’m just displaying the reactions left on the original issue comment, but if I ever need to moderate comments I would either add an approve system (only comments I +1 show up) or a hide comment system (comments I -1 don’t show).
$.ajax("https://api.github.com/repos/RichyHBM/richyhbm.github.io/issues/{{ .Params.commentIssueId }}/comments", {
headers: { Accept: "application/vnd.github.v3+json" },
success: function(msg) {
loadComments(msg);
}
});
You may have noticed the weird header I send with the request, this is a required header by the github api as this endpoint is still in a preview stage. The response to that request is then sent to a javascript function to insert the reactions into the post page.
function loadReactions(data) {
var emotes = {
'+1' : 'thumbs-up',
'-1' : 'thumbs-down',
...
};
var emojiCount = [];
for (var i = 0; i < data.length; i++) {
var emote = data[i]['content'];
if(!emojiCount.hasOwnProperty(emote)) emojiCount[ emote ] = 1;
else emojiCount[ emote ] = emojiCount[ emote ] + 1;
}
Object.keys(emotes).forEach(function(key) {
if(emojiCount.hasOwnProperty(key)) {
var reaction = document.createElement("span");
reaction.classList.add("reaction");
reaction.innerText = emojiCount[key] + " ";
var reactionIcon = document.createElement("i");
reactionIcon.classList.add("icon-" + emotes[key]);
reaction.append(reactionIcon);
$("#reactions").append(reaction);
}
});
if(data.length > 0) {
$("#reactions").show();
}
}
And there you have it, a simple static site commenting system using no backend and with full control over the data. This could all break if github moved to the GraphQL endpoint but for now its fine, you could even implement this using tweets rather than a github issue, but that would require a bit more effort.