Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
OUTDATED : See the file here instead: Version of comment-section.html that allows for scripts to be loaded after the page loads. This ensures scripts aren't loaded for users when they aren't even going to view the comments
{% if site.comment-read %}
<!-- Our own JS starts here -->
/* Initial load of the comments section contnet. This ensures that we are only loading
JQuery and other scripts once we actually need them */
async function initialCommentLoad() {
/* Load JQuery */
var loadJquery = document.createElement("script");
loadJquery.type = "text/javascript";
loadJquery.src = "";
/* Load Validator (to ensure we don't run code from comments for example) */
var loadValidator = document.createElement("script");
loadValidator.src = "";
/* Await Validator & JQuery Loaded */
await loadValidator.onload;
loadJquery.onload = function() {
/* We need to ensure that jQuery is ready before we can load JQuery CSV (used for .csv parsing) */
async function checkJQuery() {
if (typeof jQuery === "undefined") {
return false;
} else {
/* Load JQuery CSV (need regular JQuery first) */
var loadJqueryCsv = document.createElement("script");
loadJqueryCsv.src = "";
/* Wait for JQuery CSV to finish loading */
await loadJqueryCsv.onload;
/* Now, load the comments */
/* check if JQuery is ready every 250 MS
this timer will be cleared after the check passes */
var interval = setInterval(checkJQuery, 250);
var thisPageUrl = "{{ include.url }}";
var visibleComments = [];
/* Format the comment's date to our desired format */
function formatDate(stringDate) {
var date = new Date(stringDate);
var dateOptions = { year: 'numeric', month: 'long', day: 'numeric'};
var timeOptions = { hour: 'numeric', minute: 'numeric' };
return `${date.toLocaleDateString("en-us", dateOptions)} at ${date.toLocaleTimeString("en-us", timeOptions)}`;
/* Called when the comments have been loaded or relaoded.
Comments that we have already displayed on the page will be ignored */
async function displayComments(comments) {
/* if we have no comments, show the 'no comments available' section */
if (comments == null || comments === "") {
if (visibleComments.length == 0) {
/* Add headers so json objects are named properly */
comments = `timestamp,name,comment,isAuthor\n${comments}`;
commentList = $.csv.toObjects(comments);
/* iterate over each comment that came from Google Sheets */
commentList.forEach(function(element) {
/* don't re-add existing comments */
if (visibleComments.includes(JSON.stringify(element))) {
/* If the author was the one who commented back, add this 'verified' logic */
var verifiedAuthorTag = "";
if (element.isAuthor === "TRUE") {
verifiedAuthorTag = "&nbsp;<strong>(Author)</strong>";
/* Create the new item using HTML and append it to the user-visible comment section */
var newItem = $(`
<div class="comment-item">
<p class="commenter-name">${validator.escape(}${verifiedAuthorTag}<small>${formatDate(element.timestamp)}</small></p>
<p>${validator.escape(element.comment).replace(new RegExp('\r?\n','g'), '<br />')}</p>
/* Hide the 'no comments available' section since we have added a comment */
/* Calls to the Google Sheets gviz API to load the comment data
Since we are storing comments for all of our blog articles in one sheet, we filter
by page URL. */
function reloadComments() {
/* Use SQL in the gviz URL to load the correct csv.
In this implementation, columns are mapped to the following:
A - timestamp, the time the comment was left
B - page url, the page the comment was left on
C - name, the text left in the comment name field
D - comment, the text left in the comment field
E - isAuthor, boolean indicating if the comment was left by the actual author */
var sqlStatement = encodeURIComponent(`SELECT A, C, D, E WHERE B = '${thisPageUrl}'`);
fetch(`{{ site.comment-read }}/gviz/tq?tqx=out:csv&sheet=comments&tq=${sqlStatement}&headers=0`)
.then(response => response.text())
.then(response => displayComments(response));
/* encodes the form data for http transport */
const encodeFormData = (data) => {
return Object.keys(data)
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
/* Posts a comment */
function postComment() {
var username = $("#comment-name").val();
var comment = $("#comment-comment").val();
fetch(`{{ site.comment-post }}/formResponse`, {
method: 'POST',
mode: 'no-cors',
headers: {
"Content-Type": "application/x-www-form-urlencoded"
body: encodeFormData({
"{{ site.comment-post-fields[0] }}" : thisPageUrl,
"{{ site.comment-post-fields[1] }}" : username,
"{{ site.comment-post-fields[2] }}" : comment
}).then(response => {
.catch(error => {console.log(error);});
return false;
HTML for displaying the actual comment section.
It has a state for oth when the article has comments and when it does not.
<h4 class="post-subtitle">Comments</h4>
<div id="load-comments" class="comments">
<button class="comment-button" onclick="initialCommentLoad()">Load Comments</button>
<div id="comments-and-input">
<div id="commentSection" class="comments">
<div id="no-comments" class="post-info">
<p>There are currently no comments on this article, be the first to add one below</p>
<h4 class="post-subtitle">Add a Comment</h4>
<div class="comments">
<div class="post-info">
<p>Note that I may remove comments for any reason, so try to be civil. If you are looking for a response to your comment, either leave your email address or check back on this page periodically.</p>
<div class="comments-form">
<form onsubmit="return postComment()" id="comment-form" autocomplete="off">
<li><input id="comment-name" name="username" type="text" placeholder="Name" required="" value=""></li>
<li><textarea id="comment-comment" name="comment" placeholder="Comment" required=""></textarea></li>
<li><input id="comment-submit" type="submit" value="Post" class="comment-button"></li>
Styling information.
This might typically be in an scss file but is added here so only a single
file needs to be added to a project trying to implement this feature.
@import url("");
#comments-and-input {
display: none;
.comment-item {
position: relative;
padding: 0.5em 2.5em;
background: white;
font-family: "Source Sans Pro", sans-serif;
width: 95%;
margin: 1em auto;
box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
.comment-item:after {
content: "";
position: relative;
display: block;
clear: both;
.comment-item p {
line-height: 1.6;
.comment-item small {
padding-left: 0.7em;
color: #999;
font-size: 0.8em;
.commenter-name {
color: #367588;
text-decoration: none;
font-size: 1em;
font-weight: 700;
.commenter-name strong {
color: #e91e63;
text-decoration: none;
font-size: 1em;
font-weight: 700;
.comments {
border-top: 0.5px solid #e5e5e5;
padding-top: 1rem;
position: relative;
.comments-form {
margin-left: 1em;
margin-right: 1em;
width: 95%;
font-family: "Source Sans Pro", sans-serif;
.comments-form ul {
list-style: none;
margin-left: -40px;
.comments-form ul li {
margin-bottom: 15px;
.comment-button {
border: 0;
border-radius: 8px;
padding: 10px 10px;
background: #9ccc65;
color: #fff;
font-family: "Source Sans Pro", sans-serif;
font-weight: bold;
font-size: 0.9em;
text-transform: uppercase;
margin-bottom: 20px;
width: 25%;
outline-style: none;
.comments-form ul li input[type='text'], .comments-form ul li textarea {
width: 50%;
padding: 10px;
border: 0;
border-radius: 8px;
background: #f7f7f7;
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']::-webkit-input-placeholder, .comments-form ul li textarea::-webkit-input-placeholder {
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']::-moz-placeholder, .comments-form ul li textarea::-moz-placeholder {
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']:-ms-input-placeholder, .comments-form ul li textarea:-ms-input-placeholder {
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']::-ms-input-placeholder, .comments-form ul li textarea::-ms-input-placeholder {
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']::placeholder, .comments-form ul li textarea::placeholder {
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li input[type='text']:focus, .comments-form ul li textarea:focus {
background: #fff;
font-family: "Source Sans Pro", sans-serif;
.comments-form ul li textarea {
width: 100%;
height: 80px;
resize: vertical;
min-height: 80px;
{% endif %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment