Mastodon comments

I just added support for comments on this blog. It's all powered by the Fediverse (Mastodon in this case).

People usually think primarily of Mastodon, but Mastodon is just the most well-known Fediverse software. There are many, many other platforms, all compatible between them all. Check the link above.

I really like Mastodon, though, although the community itself is not as developed as Twitter, it really feels like Twitter at its beginnings: An open and very interconnectable world, with no ads, no sorting algorithms, and a plethora of open-source client applications.

There is a long time that I wanted to integrate this blog to the Fediverse. But to turn the blog into a proper Fediverse instance always seemed like a gigantic task for, thinking a bit better, I wasn't quite sure what for. And in any case there was no plugin for Pico, the blog engine I use, so that meant coding a plugin, and well...

It turned out that what I was doing until now, that is, simply posting each new article I write to my Mastodon account, actually did most of the work already. I recently stumbled upon this article explaining how to pull comments from Mastodon, and here we are, this blog is now pretty much Fediverse-enabled!

How it works:

The pages of this blog are written in markdown. On top of the markdown page, there is a yaml header, where you can add data to the page, that is not rendered to HTML. However, Pico themes can read that data and create pieces of HTML from it. So when I add a yaml line like:

Mastodon: https://fosstodon.org/@yorik/109437765431326373

Then in my theme I look if there is such a line, and if yes, a "reply with Mastodon" link is added at the bottom. So far so good.

However, the Mastodon API also allows to fetch replies to a certain post. So what we can do is to add an empty div element, fetch the replies from Mastodon using the API, and format the results (which come in json format, which is essentially already made of javascript objects, ready to use) and fill the div with it.

I chose, like in the original article, not to fetch the replies automatically, but rather to add a "show replies" button, that the user must click to load and see the replies. This to not create unnecessary load on the Mastodon servers, which are often managed by volunteers and resources can be scarce.

So the HTML code that is inserted in every page looks like this:

<a id="replies">Show replies</a>
<div id="mastdon-comments-list"></div>
<a href="{{ meta.mastodon }}">Reply with Mastodon</a>

The {{ meta.mastodon }} is specific to Pico, it's how you get elements from the YAML into your HTML code.

Then, we have this, which does the real job:

<script src="{{ theme_url }}/js/purify.min.js"></script>
<script type="text/javascript">
    url = "{{ meta.mastodon }}";
    mastohost = url.split("/")[2];
    postid = url.split("/").pop();
    document.getElementById("replies").addEventListener("click", function() {
        document.getElementById("replies").innerHTML = "Loading...";
        fetch('https://'+mastohost+'/api/v1/statuses/'+postid+'/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 = "<h3>Replies</h3>";
                document.getElementById('mastodon-comments-list').classList.add("container");
                document.getElementById('mastodon-comments-list').classList.add("mastodon-comments");
                data['descendants'].forEach(function(reply) {
                    reply.account.display_name = reply.account.display_name;
                    reply.account.emojis.forEach(emoji => {
                        reply.account.display_name = reply.account.display_name.replace(`:${emoji.shortcode}:`,
                        `<img src="${emoji.static_url}" alt="Emoji ${emoji.shortcode}" height="20" width="20" />`);
                    });
                    mastodonComment =
                    `<div class="mastodon-comment">
                        <div class="avatar">
                            <img src="${reply.account.avatar_static}" height=60 width=60 alt="">
                        </div>
                        <div class="content">
                        <div class="author">
                            <a href="${reply.account.url}" rel="nofollow">
                                <span>${reply.account.display_name}</span>
                                <span class="disabled">${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}));
                });
                mastodonComment = `<div class="mastodon-comment">Use the Mastodon link below to add a comment. If you are using a different instance, paste the link in the search box of your instance.</div>`;
                document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
            } else {
                document.getElementById('mastodon-comments-list').innerHTML = "No replies found. <a href={{meta.mastodon}}>Add yours!</a>";
            }
        });
    });
</script>

What is does basically is fetch posts from Mastodon, and replace the contents of our div with that data (which gets nicely formatted along the way). Simple and neat!

Later on of course I could think of setting up a cache system, where fetched data is written to disk so we can display replies without querying the Mastodon server each time. But for now I think it works wonderfully, specially given the simplicity of the method.

Comment on this post on Twitter Mastodon - Show replies