Comments from the Fediverse

When I got rid of my previous Django-based website and switched to Hugo, the idea was to have a low-maintenance website. I didn’t have much time and all the content was quite old. However, I always had the idea of bringing back some of the old content and even adding new articles at some point.

In the last few months I’ve been writing articles for Lutra Security and perhaps it’s time to revive this site as well. But before I really get to it, this site needs a few more features. Articles are not an end in themselves. Ideally a good article should make you think and maybe even start a discussion. For this, we need an option to comment on articles.

This blog post by Daniel Pecos Martínez inspired me to use Mastodon for this. I really like the API and that there are already some implementations that can be easily adapted.

However, I feel that copying code snippets from a few blog posts is not the way to go – especially when people might copy the code from my blog again. So I decided to take a more structured approach and put the relevant parts into a TypeScript module. While the module supports JavaScript string interpolation for the HTML output, I decided to take a different approach and use Alpine.js. This allowed me to use a <template> tag to define the HTML, which I found more convenient as I can use autocomplete and formatting in my IDE.

The code I ended up with, looked like this1:

<div id="mastodon-comments-list" x-data="{ comments: [], commentsLoaded: false }"
    x-on:comments-loaded="comments.push(...fedicomments.comments); commentsLoaded = true">
    <button class="button is-primary" id="mastodon-comments-load" x-show="!commentsLoaded"
        x-on:click="fedicomments.loadComments()">
        Load comments from {{ .Params.comments.host }}
    </button>
    <template x-for="comment in comments">
        <div class="card mt-2" x-bind:style="`margin-left: calc(2em * ${comment.auxiliary.depth})!important`">
            <div class="card-content">
                <div class="media">
                    <div class="media-left">
                        <img x-bind:src="comment.account.avatar_static" height=60 width=60 alt="">
                    </div>
                    <div class="media-content">
                        <a class="title is-4" x-bind:href="comment.account.url" rel="nofollow"
                            x-html="comment.account.display_name"></a>
                        <a class="title is-6" x-bind:href="comment.account.url" rel="nofollow"
                            x-text="comment.auxiliary.account"></a><br>
                        <a class="date" x-bind:href="comment.url" rel="nofollow"
                            x-text="`${comment.created_at.substr(0, 10)} ${comment.created_at.substr(11, 8)}`">
                        </a>
                    </div>
                </div>
                <div class="content" x-html="comment.content"></div>
                <div class="card-footer">
                    <div>
                        <a x-bind:href="comment.url" rel="nofollow"><i class="fa fa-reply fa-fw"></i>
                            <span x-text="comment.replies_count > 0 ? comment.replies_count : ''"></span></a>
                    </div>
                    <div>
                        <a x-bind:href="comment.url" rel="nofollow"><i class="fa fa-retweet fa-fw"></i>
                            <span x-text="comment.reblogs_count > 0 ? comment.reblogs_count : ''"></span></a>
                    </div>
                    <div>
                        <a x-bind:href="comment.url" rel="nofollow"><i class="fa fa-star fa-fw"></i>
                            <span x-text="comment.favourites_count > 0 ? comment.favourites_count : ''"></span></a>
                    </div>
                </div>
            </div>
        </div>
    </template>
</div>
{{ $fedicomments := resources.Get "/npm/fedicomments/dist/FediComments.js" | js.Build (dict "format" "esm") | fingerprint }}
{{ $alpinejs := resources.Get "/npm/alpinejs/dist/module.esm.js" | fingerprint }}
<script type="module" type="text/javascript">
    import FediComments from '{{ $fedicomments.RelPermalink }}'
    import Alpine from '{{ $alpinejs.RelPermalink }}'

    window.fedicomments = new FediComments(
        '{{ .Params.comments.host }}',
        '{{ .Params.comments.user }}',
        '{{ .Params.comments.id }}',
        'mastodon-comments-list'
    );

    Alpine.start();
</script>

There is a bit of Hugo in there for the configuration and loading of the JavaScript. Styling is done using Bulma and Font Awsome, but both could easily be replaced with custom CSS.

If you end up using the module and find any bugs or are missing features, feel free to create an issue on GitHub. I’ll also consider any pull requests.


  1. I’ll try to update this article if there are any major changes. If this happens, I will add a note to indicate this. ↩︎

Comments

You can use your Fediverse (i.e. Mastodon, among many others) account to reply to this post.