Skip to main content

Adding Mastodon comments to your static Nikola blog

Static and comments

The neverending story. The arch enemies. Beauty and the Beast. If you want to enable comments in a static blog article you will have to come back to third party services.

Nikola already supports several comment systems like Disqus or Isso. As a frequent Mastodon user it has long been my intent to integrate Mastodon threads as a detour comment system.

I stumbled across this JavaScript solution that does the job on any static page: Adding comments to your static blog with Mastodon.

The comments listed are the replies to the corresponding toot:

Potential and restrictions

Unlike embedded Mastodon toots the loaded comments fit neatly to the blog (some CSS theme adaptation required). Adding a comment is still done via the Mastodon web UI.

If you announce an article to Mastodon and want to refer to that toot as your comment thread you will have to either edit the page because you don't know the toot id in advance and therefore build and deploy your site a second time or be quick and announce the article before deploying and then edit the toot id.

A classic comment moderation is not possible though. I personally expect spam and inappropriate contents quite unlikly as you need a Mastodon account in the first place and instance admins are usually very accurate when it comes to enforce their instance rules. This might change with time but right now I don't see a substantial danger.

Preparing Nikola

The main question is "Where put the code?" and the answer is:

/images/shortcodes.jpg

A shortcode provides a code snippet that you can call via

{{% name parameters %}}

just save the file in the shortcodes folder.

mastodon-comments.tmpl (Source)

{#
Usage:

    {{% mastodon-comments host=instance.domain username=username id=tootnr %}}

#}

<h2>Comments</h2>
<div class="article-content">
  <p>You can use your Mastodon account to reply to this <a class="link" href="https://{{ host }}/@{{ username }}/{{ id }}">post</a>.</p>
  <p><a class="button" href="https://{{ host }}/interact/{{ id }}?type=reply">Reply</a></p>
  <p id="mastodon-comments-list"><button id="load-comment">Load comments</button></p>
  <noscript><p>You need JavaScript to view the comments.</p></noscript>
  <script src="/assets/js/purify.min.js"></script>
  <script type="text/javascript">
    function escapeHtml(unsafe) {
      return unsafe
           .replace(/&/g, "&amp;")
           .replace(/</g, "&lt;")
           .replace(/>/g, "&gt;")
           .replace(/"/g, "&quot;")
           .replace(/'/g, "&#039;");
   }

    document.getElementById("load-comment").addEventListener("click", function() {
      document.getElementById("load-comment").innerHTML = "Loading";
      fetch('https://{{ host }}/api/v1/statuses/{{ id }}/context')
        .then(function(response) {
          return response.json();
        })
        .then(function(data) {
          if(data['descendants'] &&
             Array.isArray(data['descendants']) &&
            data['descendants'].length > 0) {
              document.getElementById('mastodon-comments-list').innerHTML = "";
              data['descendants'].forEach(function(reply) {
                reply.account.display_name = escapeHtml(reply.account.display_name);
                reply.account.emojis.forEach(emoji => {
                  reply.account.display_name = reply.account.display_name.replace(`:${emoji.shortcode}:`,
                    `<img src="${escapeHtml(emoji.static_url)}" alt="Emoji ${emoji.shortcode}" height="20" width="20" />`);
                });
                mastodonComment =
                  `<div class="mastodon-comment">
                     <div class="avatar">
                       <img src="${escapeHtml(reply.account.avatar_static)}" height=60 width=60 alt="">
                     </div>
                     <div class="comment">
                       <div class="author">
                         <a href="${reply.account.url}" rel="nofollow">
                           <span>${reply.account.display_name}</span>
                           <span class="disabled">${escapeHtml(reply.account.acct)}</span>
                         </a>
                         <a class="date" href="${reply.uri}" rel="nofollow">
                           ${reply.created_at.substr(0, 10)}
                         </a>
                       </div>
                       <div class="mastodon-comment-content">${reply.content}</div>
                     </div>
                   </div>`;
                document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
              });
          } else {
            document.getElementById('mastodon-comments-list').innerHTML = "<p>Not comments found</p>";
          }
        });
      });
  </script>
</div>

The script uses DOMPurify so you have to put the purify.min.js file into the $THEME/assets/js/ folder.

There are some div classes used in the code and may require some adaptation to your theme. This should be done in the custom.css. I stole the CSS from Carl's site and this is what I use here:

custom.css (Source)

.mastodon-comment {
 background-color: azure;
 border-radius: 10px;
 padding: 5px;
 margin-bottom: 1rem;
 display: flex;
 font-size: 80%;
}
.mastodon-comment .content {
 flex-grow:2
}
.mastodon-comment .avatar img {
 margin-right:1rem;
 min-width:60px
}
.mastodon-comment .author {
 padding-top:0;
 display:flex
}
.mastodon-comment .author .date {
 margin-left:auto
}
.mastodon-comment .disabled {
 color:var(--accent-color)
}
.mastodon-comment-content p:first-child {
 margin-top:0
}

That's it.

Just add a shortcode to any article in your blog. You can even provide a regular comment system like Disqus parallely.

Comments

You can use your Mastodon account to reply to this post.

Reply