Web Notes
2016.08.20
Using Liquid in Jekyll - Live with Demos
Liquid is a simple template language that Jekyll uses to process pages for your site. With Liquid you can output complex contents without additional plugins.
It’s convenient to set up a comment system in Jekyll site with external social comment systems like Disqus or Duoshuo (多说). However, as you all know, Disqus was blocked in China and Duoshuo is going to shutdown. It’s the time to rethink about the comment system (although I didn’t get too many comments →_→), simple and controllable.
If you search for “Jekyll comments”, there are several plugins or solutions that can be used. Looked into those posts, Going Static: Episode II — Attack of the Comments using Staticman seemed most perfect, all the comments become static files in your git repository. Perfect Jekyll way! But, how about situations like mine, hosting site other than GitHub Pages.
Then, Creating a Firebase-Backed Commenting System attracted my attention. Using Firebase real-time database and a little bit JavaScript, it’s easy to set up your custom commenting system. Actually, I used Firebase a lot on this site, pageview counts, trending posts and a tiny “Like” button… Why not benefit more from it!
The following steps are adapted from the above post, and add the Markdown support for the comment system. And thanks to JiYou for the discussion.
Firebase is currently a part of Google Developers tool, its real-time database stores data as JSON objects. For example, we can construct the comments like this way:
{
"/tutorial/2016/01/02/title-tag.html": [
{
"name": "Bill",
"email": "bill@example.org",
"message": "Hi there, nice blog!",
"timestamp": 1452042357209
},
{
"name": "Bob",
"email": "bob@example.org",
"message": "Wow look at this blog.",
"timestamp": 145204235846
}
]
}
Using JavaScript, we can access the JSON database by Firebase references, that’s our comment system comes.
The first step is to create a new project in the Firebase console.
In the Overview
pannel, click the Add Firebase to your web app
to get the initialize parameters for later use:
// TODO: Replace with your project's customized code snippet
<script src="https://www.gstatic.com/firebasejs/3.4.0/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>'
};
firebase.initializeApp(config);
</script>
Here, I’m going to script in a separate JavaScript file like comment.js
together with jQuery. For initialising:
$.getScript('https://www.gstatic.com/firebasejs/3.4.0/firebase.js', function () {
// Initialize Firebase
var config = {
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>'
};
firebase.initializeApp(config);
// TO-DO
}
The following parts of JavaScript will fill into the TO-DO
above.
Add a reference to the Firebase database, that we can store new comments or read exist comments.
var rootRef = firebase.database().ref();
var postComments = rootRef.child('postComments');
Here, rootRef
is the root of the Firebase database and a child postComments
for all the comments.
Similar to Disqus, we need to setup an unique identity for each blog post.
var link = $("link[rel='canonical']").attr("href");
var pathkey = decodeURI(link.replace(new RegExp('\\/|\\.', 'g'),"_"));
var postRef = postComments.child(pathkey);
Here, I used the canonical
link for each post. You can replace it with window.location.pathname
if you prefer.
As Firebase doesn’t support certain characters for the node key, replace these characters and used it for post-identification. postRef
create a unique reference for each post under the postComments
reference.
If you create your own keys, they must be UTF-8 encoded, can be a maximum of 768 bytes, and cannot contain ., $, #, [, ], /, or ASCII control characters 0-31 or 127.
Now, look at your comment form in the Jekyll layout. If you don’t have one, just add it below the content of the post formatted in this way:
<h3>Leave a comment</h3>
<form id="comment">
<label for="message">Message</label>
<textarea id="message"></textarea>
<label for="name">Name</label>
<input type="text" id="name">
<label for="email">Email</label>
<input type="text" id="email">
<input type="submit" value="Post Comment">
</form>
Override the default submit action in JavaScript:
$("#comment").submit(function() {
postRef.push().set({
name: $("#name").val(),
message: $("#message").val(),
md5Email: md5($("#email").val()),
postedAt: firebase.database.ServerValue.TIMESTAMP
});
$("input[type=text], textarea").val("");
return false;
});
postRef.push()
creates an array in Firebase database if it doesn’t exist and returns a new reference to the first item. It looks like this in my test project:
Here, MD5 of the email address is stored and used for display profile images from Gravatar. Thus, include the JavaScript MD5 library before the comment.js
file. firebase.database.ServerValue.TIMESTAMP
is the timestamp from the Firebase that can avoid time zone issues.
Well, we can send new comments to the Firebase database now. Let’s take a further step to pull stored comments to the post page.
Before that, add a HTML container to hold the comments in the Jekyll layout:
<div id="comments-container"></div>
Then a new JavaScript function to trigger existing and new comments:
postRef.on("child_added", function(snapshot) {
var newComment = snapshot.val();
// Markdown into HTML
var converter = new showdown.Converter();
converter.setFlavor('github');
var markedMessage = converter.makeHtml(newComment.message);
// HTML format
var html = "<div class='comment'>";
html += "<h4>" + newComment.name + "</h4>";
html += "<div class='profile-image'><img src='https://www.gravatar.com/avatar/" + newComment.md5Email + "?s=100&d=retro'/></div>";
html += "<span class='date'>" + jQuery.timeago(newComment.postedAt) + "</span>"
html += "<p>" + markedMessage + "</p></div>";
$("#comments-container").prepend(html);
});
child_added
returns a snapshot of the comment data into snapshot.val()
. Then format it into HTML and prepend the result into the comments-container
.
For styling the comments, Showdown JS is used to convert markdown message into HTML. And timeago JS to convert the timestamp into readable string. Don’t forget to include these two libraries.
As I’m a newbie in this field, it’s my first time that encounters such issues as Cross-site Scripting (XSS) attack.
From Showdown’s wiki:
Cross-side scripting is a well known technique to gain access to private information of the users of a website. The attacker injects spurious HTML content (a script) on the web page which will read the user’s cookies and do something bad with it (like steal credentials). As a countermeasure, you should filter any suspicious content coming from user input. Showdown doesn’t include an XSS filter, so you must provide your own. But be careful in how you do it…
After a quick test, I found XSS is a problem on my site if scripts involved in the comments. Fortunately, there are several JavaScript implementation can filter XSS contents, showdown-xss-filter is such a accessible extension to filter XSS.
So, include the dependency to the XSS filter and update our JavaScript in previous section:
var converter = new showdown.Converter({ extensions: ['xssfilter'] });
That’s all for the XSS filter. But we still need to care about the data safety that stored in Firebase.
Since everyone is allowed to post comments without login, we have to set up some data rules to prevent deleting:
{
"rules": {
".read": true,
"$title": {
"$slug": {
".write": "!data.exists()",
"$message": {
".write": "!data.exists() || !newData.exists()"
}
}
}
}
}
For the !data.exists() || !newData.exists()
rule setting, we can write
as long as old data or new data does not exist.
Well, our Firebase comment system is ready to use. The JS libraries used are:
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.19.0/js/md5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/timeago@1.6.7/jquery.timeago.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/showdown@1.9.1/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/showdown-xss-filter@0.2.0/showdown-xss-filter.min.js"></script>
<script src="/assets/js/showdown-xss-filter.js"></script>
<script src="/assets/js/comment.js"></script>
In our comment.js
, the complete scripts are:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$.getScript("https://www.gstatic.com/firebasejs/3.4.0/firebase.js", function () {
var config = {
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>'
};
firebase.initializeApp(config);
var rootRef = firebase.database().ref();
var postComments = rootRef.child('postComments');
var link = $("link[rel='canonical']").attr("href");
var pathkey = decodeURI(link.replace(new RegExp('\\/|\\.', 'g'),"_"));
var postRef = postComments.child(pathkey);
$("#comment").submit(function() {
postRef.push().set({
name: $("#name").val(),
message: $("#message").val(),
md5Email: md5($("#email").val()),
postedAt: firebase.database.ServerValue.TIMESTAMP
});
$("input[type=text], textarea").val("");
return false;
});
postRef.on("child_added", function(snapshot) {
var newComment = snapshot.val();
var converter = new showdown.Converter({ extensions: ['xssfilter'] });
converter.setFlavor('github');
var markedMessage = converter.makeHtml(newComment.message);
var html = "<div class='comment'>";
html += "<h4>" + newComment.name + "</h4>";
html += "<div class='profile-image'><img src='https://www.gravatar.com/avatar/" + newComment.md5Email + "?s=100&d=retro'/></div>";
html += "<span class='date'>" + jQuery.timeago(newComment.postedAt) + "</span>"
html += "<p>" + markedMessage + "</p></div>";
$("#comments-container").prepend(html);
});
}
The above method might still working, but you should probably go though the updated Cloud Firestore documents that use the new Firestore instead of Firebase real-time database.
Frank Lin
Web Notes
2016.08.20
Liquid is a simple template language that Jekyll uses to process pages for your site. With Liquid you can output complex contents without additional plugins.
Tools
2020.10.20
IBM Cloud CLI allows complete management of the Cloud Functions system. You can use the Cloud Functions CLI plugin-in to manage your code snippets in actions, create triggers, and rules to enable your actions to respond to events, and bundle actions into packages.
Tutorials
2020.01.09
IKEv2, or Internet Key Exchange v2, is a protocol that allows for direct IPSec tunnelling between networks. It is developed by Microsoft and Cisco (primarily) for mobile users, and introduced as an updated version of IKEv1 in 2005. The IKEv2 MOBIKE (Mobility and Multihoming) protocol allows the client to main secure connection despite network switches, such as when leaving a WiFi area for a mobile data area. IKEv2 works on most platforms, and natively supported on some platforms (OS X 10.11+, iOS 9.1+, and Windows 10) with no additional applications necessary.