Radio wave icon F1RUM

Hugo blog with Mastodon comments

#Software

Inspired by Carl’s article, I’ve adapted its solution to embed Mastodon/Fediverse discussion into a static web site as a comments system.

Here, I share what I have done in my Hugo theme.

new file themes/my_theme/layouts/partials/comments.html

{{ with .Params.comments }}
<div class="article-content">
  {{ $icon := index $.Site.Data.fontawesome.icons "comments" }}
  {{ $svg := $icon.svg.solid | default $icon.svg.regular | default $icon.svg.brands }}
  <h2><span class="icons">{{ safeHTML $svg.raw }}</span> {{ i18n "Comments" . }}</h2>
  <p>
    {{ i18n "Use_fediverse_account" . | safeHTML }}
  </p>
  <p id="mastodon-comments-list"><button id="load-comment">{{ i18n "Load_comments" . }}</button></p>
  <noscript><p>You need JavaScript to view the comments.</p></noscript>
  <script src="/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" />`);
                });
                reply.media_previews='';
                if(typeof reply.media_attachments !== "undefined") {
                  reply.media_attachments.forEach(media => {
                    if(typeof media.preview_url !== "undefined" && typeof media.type !== "undefined" && media.type=="image")
                      reply.media_previews+='<img src="'+media.preview_url+'" /> ';
                  });
                }
                mastodonComment =
                  `<div class="mastodon-container" id="${reply.id}">
                   <div class="mastodon-comment">
                     <div class="avatar">
                       <img src="${escapeHtml(reply.account.avatar_static)}" height=60 width=60 alt="">
                     </div>
                     <div class="content">
                       <div class="author">
                         <a class="date" href="${reply.uri}" rel="nofollow">
                           ${reply.created_at.substr(0, 10)} ${reply.created_at.substr(11, 5)} ${reply.created_at.substr(-1)}
                         </a>
                         <a href="${reply.account.url}" rel="nofollow">
                           <span>${reply.account.display_name}</span>
                           <span class="disabled">/ @${escapeHtml(reply.account.acct)}</span>
                         </a>
                       </div>
                       <div class="mastodon-comment-content">${reply.content}</div> 
                       <div class="mastodon-comment-media">${reply.media_previews}</div> 
                     </div>
                   </div>
                   </div>`;
                reply_to_id = reply.in_reply_to_id;
                parentComment = document.getElementById(reply_to_id);
                if(parentComment !== null)
                  parentComment.appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
                else
                  document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
              });
          } else {
            document.getElementById('mastodon-comments-list').innerHTML = "<p>{{ i18n "No_comment" . }}</p>";
          }
        });
      });
  </script>
</div>
{{ end }}

new translation in themes/my_theme/i18n/en.toml

[Comments]
  other = "Comments"
[Use_fediverse_account]
  other = 'You can use your Mastodon or Fediverse account to <strong><a class="button" href="https://{{ .host }}/interact/{{ .id }}?type=reply">reply</a></strong> to this <strong><a class="link" href="https://{{ .host }}/@{{ .username }}/{{ .id }}">toot!</a></strong>'
[Load_comments]
  other = "Load comments"
[No_comment]
  other = "No comment, yet, be the first to react!"

add comment block near the end of themes/my_theme/layouts/_default/single.html

  </header>
  {{ .Content }}

  {{ partial "comments.html" . }}
</article>

{{ end }}

add a purifier library

Download DOMPurify lib purify.min.js to themes/my_theme/static/js/purify.min.js

This lib is released under the Apache license 2.0 and Mozilla Public License 2.0

Some css to put in your existing theme css files in themes/my_theme/static/css/something.css


.mastodon-container {
  margin-left: 1rem;
}

.mastodon-comment {
  border: 1px solid #999;
  border-radius: 6px;
  margin: 0.5rem 0;
  display: flex;
  padding: 0.5rem 1rem;
}

.mastodon-comment .avatar img {
  margin-right: 1rem;
  min-width: 60px;
}

.mastodon-comment .content {
  width: 100%;
}

.mastodon-comment .date {
  float: right;
}

.mastodon-comment .ellipsis::after {
  content: "\2026";
}

.mastodon-comment .invisible {
  font-size: 0;
  line-height: 0;
  display: inline-block;
  width: 0;
  height: 0;
}

Then you just have to add the announcement toot detail, in the header of your markdown article

---
title: "Hugo blog with Mastodon comments"
date: "2022-06-29"
tags: [software]
draft: false
comments:
  host: mastodon.radio
  username: F1RUM
  id: 108562734715879875
---

Comments

You can use your Mastodon or Fediverse account to reply to this toot!