Browse files

New post: GitHub Powered Comments

  • Loading branch information...
p3lim committed May 4, 2018
1 parent 4de4845 commit 43eca2893479069b5f561af9847061a42d396342
Showing with 158 additions and 0 deletions.
  1. +158 −0 _posts/
@@ -0,0 +1,158 @@
layout: post
title: GitHub Powered Comments
tags: javascript
I'm a big fan of [GitHub Pages](, which this website runs on. So far I haven't done much with it, but I figured I'd mess about and get a comment section for it, just in case :)
Adding a comments section leaves me with a few options, but I figured I'd try to be a bit creative.
Posts on a GitHub Pages website is done through markdown files pushed to a Git repository, which means it will have an accompanying commit page on GitHub (like [this one]() for this very post you're reading).
GitHub also has a nice feature that allows users to comment on pretty much anything they'd like, e.g. lines in code, issues, pull requests, _and even commits!_
An added bonus is that there are moderating capabilities, and even full Markdown support, which is just icing on the cake!
And adding to this, they expose a bit of [repository metadata]( to [Jekyll](, which means that we could easily get enough data through the markup directly without any modifications to the website code (just adding a script and some stylesheets), query the [GitHub API](, and we'd be able to present the comments on the commit as the comments section on a post.
Now, we _could_ get all the data to kind of guess which commit relates to the current post using the API, but there's [rate limiting](, and it's much slower to do so.
The alternative is adding a single piece of [front matter]( to the post, specifically the commit sha for the commit of the post, and then use that instead as the commit sha is unique within a repository.
### Implementation
So let's put this to the test, example time!
On my website I have two layouts, one that serves as the base for everything, aptly named `base`, and one named `post`. The latter is used for every article published, and is where we'll need to add our modifications.
I have this layout defined as such (simplified):
<header><h1>{{ page.title }}</h1></header>
<section>{{ content }}</section>
Simple enough, an `article` element that contains a `header` with the title of the page (or post in this specific context), as well as a `section` containing the post content itself.
Here we'll want to add the metadata from Jekyll, so we can reach it with JavaScript later. Just edit the first line like so:
<article data-nwo='{{ site.github.repository_nwo }}' data-sha='{{ page.sha }}'>
`site.github.repository_nwo` is basically `username/repository` for the website repository on GitHub, in my example it's `p3lim/`. `page.sha` is custom frontmatter we place in the top of the posts we want to enable comments on, which represents the commit for the post, added like so:
layout: post
title: GitHub Powered Comments
_The main downside of this is that you'll need to commit the post, then edit it again just to add the frontmatter to it for the `sha` reference._
Lastly, since we'll be using JavaScript for this, we'll create that file and load it at the end of the `post` layout file, like so:
{% if page.sha %}
<script src='/assets/js/comments.js' async></script>
{% endif %}
Being extra fancy here, we only load the JavaScript file _if_ we've added the sha to the frontmatter, which makes the comments opt-in for every post!
### Fetching the comments
First, lets get the two attributes we added to the article element:
const article = document.querySelector('article');
const nwo = article.getAttribute('data-nwo');
const sha = article.getAttribute('data-sha');
You'll notice I'm using ES6 here, which all modern browsers today support. Feel free to adjust to ES5, but the rest of this implementation is going to heavily rely on ES6 features.
In any case, we've now gotten the necessary metadata from the HTML to fetch the comments.
So let's do just that:
let handleComments = function(){};
let xhr = new XMLHttpRequest();
xhr.onload = handleComments;'GET', `${nwo}/commits/${sha}/comments`);
With that we've queried the [API for the commit comments](, ready to parse. Let's expand on the handling:
let handleComments = function(){
// We'll have to make sure that the request responded successfully, so let's check for the return status
if (this.status === 200){
// Success! We've received the data we wanted
} else {
// Boo! Something went wrong
Web request, such amaze. Let's handle the successful scenario first:
let handleComments = function(){
if (this.status === 200){
// We'll need a section to place our comments in, so we'll create that first
article.insertAdjacentHTML('beforeend', '<section id="comments"></section>');
// Let's assign a variable for that section we just created
let section = document.querySelector('article #comments');
// All of the data for the comments lies in the response data, so let's apply some ES6 magic here
section.insertAdjacentHTML('beforeend', JSON.parse(this.response).map(template).join(''));
// Lastly, we'll add a link to the comment section on the commit on GitHub so people know where to post
section.insertAdjacentHTML('beforeend', `
<div class='gfm'>
<a href='${nwo}/commit/${sha}#comments'>Leave a comment on GitHub</a>
_Most_ of that should be rather self-explanatory, but in essence we're just adding elements to a `section`-element at the bottom of the article. The most obfuscated line in there is the line that parses the comments. What it does is the following: it parses the response from the web request, as it's a JSON string, which it then maps all the comments using a function named `template` that we haven't defined yet. The mapping will return an array, so we join that array into a single string, which we then append to the end of the comments section.
Let's create the `template` function:
let template = data => {
// We're aiming to replicate the GitHub look for comments, so this is mostly just markup for that.
// The data variable contains everything from the individual comments fetched earlier
// (see the API docs for info), which we'll use heavily.
return `
<div class='avatar'>
<a href='${data.user.html_url}'>
<img alt='@${data.user.login}' src='${data.user.avatar_url}'>
<div class='comment'>
<div class='header'>
<a href='${data.user.html_url'>${data.user.login}</a>
<div class='body'>
Aaaaand... we're done! Well, pretty much. This is just the essence of it. In addition to the above code, I've added (relative) timestamps using the [Moment.js]( library, as well parsed the comment body using [Marked.js]( (which supports GitHub Flavored Markdown!).
I've also added error handling, and some stylesheets to make it look pretty.
Here's links to the relevant files in all their glory:
<> (see the #comments section)
<> (I honestly don't remember where I found this, but I've modified it some to look more "correct" vs GitHub's look)
This post has comments enabled using the final code, feel free to test it out and let me know what you think.

1 comment on commit 43eca28


This comment has been minimized.


p3lim commented on 43eca28 May 4, 2018

Huzzah! Markdown support is also very cool 👍

Please sign in to comment.