Revisions for ⁨Mastodon-based comment system⁩

View the changes made to this paste.

public ⁨3⁩ ⁨files⁩ 2023-07-17 15:10:42 UTC

_README.md

@@ -34,4 +34,7 @@ Here are some previews

 ![no comments](https://user-images.githubusercontent.com/11576465/253985700-e14a66f8-714f-4aca-987e-e074f0b0e5a3.png "Message shown when no reply has been made yet")
 
 ### Comment
-![comment](https://user-images.githubusercontent.com/11576465/253999048-31f7b5ad-8adf-4866-8ee1-aa85a4757809.png "How the comment would look like")
\ No newline at end of file
+![comment](https://user-images.githubusercontent.com/11576465/253999048-31f7b5ad-8adf-4866-8ee1-aa85a4757809.png "How the comment would look like")
+
+## Credits
+Original concept and code-snippets: <https://danielpecos.com/2022/12/25/mastodon-as-comment-system-for-your-static-blog/>
\ No newline at end of file

comments.html

@@ -2,7 +2,7 @@

   <h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
   {% if page.meta.comment_id %}
     <noscript>
-      <div class="admonition warning">
+      <div class="admonition danger">
         <p class="admonition-title">
           Please enable Javascript to see comments from Mastodon.
         </p>

comments.html

@@ -156,7 +156,7 @@

   {% else %}
     <div class="admonition warning">
       <p class="admonition-title">
-        No Mastodon post configured for this page. Ask the post author to link a Mastodon post to comment here.
+        No Mastodon post configured for this page. Contact {{ config.site_author | default('the post author', true) }} if you want to comment here.
       </p>
     </div>
   {% endif %}

_README.md

@@ -22,4 +22,16 @@ This paste contains files to implement a comment system using a Mastodon instanc

       host: example.com # Replace with your Mastodon instance domain
       user: Example     # Replace with your username on the instance
   ```
-5. You're done! All you need to do is now create a Post on Mastodon, copy the ID from it and add it as `comment_id` YAML frontmatter to your page. Also, make sure to add `comments: true` to the front matter so that the comments.html content is used.
\ No newline at end of file
+5. You're done! All you need to do is now create a Post on Mastodon, copy the ID from it and add it as `comment_id` YAML frontmatter to your page. Also, make sure to add `comments: true` to the front matter so that the comments.html content is used.
+
+## Preview
+Here are some previews
+
+### No Post set
+![no post set](https://user-images.githubusercontent.com/11576465/253985888-7521a64f-5899-49e0-8797-6e7ee262e738.png "Message shown when no post has been set")
+
+### Post set, but no comments (Replies)
+![no comments](https://user-images.githubusercontent.com/11576465/253985700-e14a66f8-714f-4aca-987e-e074f0b0e5a3.png "Message shown when no reply has been made yet")
+
+### Comment
+![comment](https://user-images.githubusercontent.com/11576465/253999048-31f7b5ad-8adf-4866-8ee1-aa85a4757809.png "How the comment would look like")
\ No newline at end of file

comments.html

@@ -156,7 +156,7 @@

   {% else %}
     <div class="admonition warning">
       <p class="admonition-title">
-        No Mastodon post connected to this blog post. Please try again later.
+        No Mastodon post configured for this page. Ask the post author to link a Mastodon post to comment here.
       </p>
     </div>
   {% endif %}

_README.md

@@ -22,4 +22,4 @@ This paste contains files to implement a comment system using a Mastodon instanc

       host: example.com # Replace with your Mastodon instance domain
       user: Example     # Replace with your username on the instance
   ```
-5. You're done! All you need to do is now create a Post on Mastodon, copy the ID from it and add it as `comment_id` YAML frontmatter to your page.
\ No newline at end of file
+5. You're done! All you need to do is now create a Post on Mastodon, copy the ID from it and add it as `comment_id` YAML frontmatter to your page. Also, make sure to add `comments: true` to the front matter so that the comments.html content is used.
\ No newline at end of file

comments.html

@@ -1,161 +1,163 @@

-<h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
-{% if page.meta.comment_id %}
-  <noscript>
-    <div class="admonition warning">
-      <p class="admonition-title">
-        Please enable Javascript to see comments from Mastodon.
-      </p>
-    </div>
-  </noscript>
-  <p>
-    <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Comment on this blog post</a> using a Fediverse-compatible Account (Mastodon or alike).
-  </p>
-  
-  <p id="mastodon-comments-list"></p>
-  
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.1/purify.min.js" integrity="sha512-uHOKtSfJWScGmyyFr2O2+efpDx2nhwHU2v7MVeptzZoiC7bdF6Ny/CmZhN2AwIK1oCFiVQQ5DA/L9FSzyPNu6Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-  <script type="text/javascript">
-    var host = '{{ config.extra.mastodon.host }}';
-    var user = '{{ config.extra.mastodon.user }}';
-    var id = '{{ page.meta.comment_id }}'
-  
-    function escapeHtml(unsafe) {
-      return unsafe
-        .replace(/&/g, "&amp;")
-        .replace(/</g, "&lt;")
-        .replace(/>/g, "&gt;")
-        .replace(/"/g, "&quot;")
-        .replace(/'/g, "&#039;");
-    }
-  
-    var commentsLoaded = false;
-  
-    function toot_active(toot, what) {
-      var count = toot[what+'_count'];
-      return count > 0 ? 'active' : '';
-    }
-  
-    function toot_count(toot, what) {
-      var count = toot[what+'_count'];
-      return count > 0 ? count : '';
-    }
-  
-    function user_account(account) {
-      var result =`@${account.acct}`;
-      if (account.acct.indexOf('@') === -1) {
-        var domain = new URL(account.url)
-        result += `@${domain.hostname}`
+{% if page.meta.comments %}
+  <h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
+  {% if page.meta.comment_id %}
+    <noscript>
+      <div class="admonition warning">
+        <p class="admonition-title">
+          Please enable Javascript to see comments from Mastodon.
+        </p>
+      </div>
+    </noscript>
+    <p>
+      <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Comment on this blog post</a> using a Fediverse-compatible Account (Mastodon or alike).
+    </p>
+    
+    <p id="mastodon-comments-list"></p>
+    
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.1/purify.min.js" integrity="sha512-uHOKtSfJWScGmyyFr2O2+efpDx2nhwHU2v7MVeptzZoiC7bdF6Ny/CmZhN2AwIK1oCFiVQQ5DA/L9FSzyPNu6Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+    <script type="text/javascript">
+      var host = '{{ config.extra.mastodon.host }}';
+      var user = '{{ config.extra.mastodon.user }}';
+      var id = '{{ page.meta.comment_id }}'
+    
+      function escapeHtml(unsafe) {
+        return unsafe
+          .replace(/&/g, "&amp;")
+          .replace(/</g, "&lt;")
+          .replace(/>/g, "&gt;")
+          .replace(/"/g, "&quot;")
+          .replace(/'/g, "&#039;");
       }
-      return result;
-    }
-  
-    function render_toots(toots, in_reply_to, depth) {
-      var tootsToRender = toots
-        .filter(toot => toot.in_reply_to_id === in_reply_to)
-        .sort((a, b) => a.created_at.localeCompare(b.created_at));
-      tootsToRender.forEach(toot => render_toot(toots, toot, depth));
-    }
-  
-    function render_toot(toots, toot, depth) {
-      toot.account.display_name = escapeHtml(toot.account.display_name);
-      toot.account.emojis.forEach(emoji => {
-        toot.account.display_name = toot.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" style="margin-left: calc(var(--mastodon-comment-indent) * ${depth})">
-          <div class="author">
-            <div class="avatar">
-              <img src="${escapeHtml(toot.account.avatar_static)}" height=60 width=60 alt="">
-            </div>
-            <div class="details">
-              <a class="name" href="${toot.account.url}" rel="nofollow">${toot.account.display_name}</a>
-              <a class="user" href="${toot.account.url}" rel="nofollow">${user_account(toot.account)}</a>
-            </div>
-            <a class="date" href="${toot.url}" rel="nofollow">${toot.created_at.substr(0, 10)} ${toot.created_at.substr(11, 8)}</a>
-          </div>
-          <div class="content">${toot.content}</div>
-          <div class="attachments">
-            ${toot.media_attachments.map(attachment => {
-              if (attachment.type === 'image') {
-                return `<a href="${attachment.url}" rel="nofollow"><img src="${attachment.preview_url}" alt="${attachment.description}" /></a>`;
-              } else if (attachment.type === 'video') {
-                return `<video controls><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
-              } else if (attachment.type === 'gifv') {
-                return `<video autoplay loop muted playsinline><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
-              } else if (attachment.type === 'audio') {
-                return `<audio controls><source src="${attachment.url}" type="${attachment.mime_type}"></audio>`;
-              } else {
-                return `<a href="${attachment.url}" rel="nofollow">${attachment.type}</a>`;
-              }
-            }).join('')}
-          </div>
-          <div class="status">
-            <div class="twemoji replies ${toot_active(toot, 'replies')}">
-              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/reply.svg" %}${toot_count(toot, 'replies')}</a>
-            </div>
-            <div class="twemoji reblogs ${toot_active(toot, 'reblogs')}">
-              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/retweet.svg" %}${toot_count(toot, 'reblogs')}</a>
+    
+      var commentsLoaded = false;
+    
+      function toot_active(toot, what) {
+        var count = toot[what+'_count'];
+        return count > 0 ? 'active' : '';
+      }
+    
+      function toot_count(toot, what) {
+        var count = toot[what+'_count'];
+        return count > 0 ? count : '';
+      }
+    
+      function user_account(account) {
+        var result =`@${account.acct}`;
+        if (account.acct.indexOf('@') === -1) {
+          var domain = new URL(account.url)
+          result += `@${domain.hostname}`
+        }
+        return result;
+      }
+    
+      function render_toots(toots, in_reply_to, depth) {
+        var tootsToRender = toots
+          .filter(toot => toot.in_reply_to_id === in_reply_to)
+          .sort((a, b) => a.created_at.localeCompare(b.created_at));
+        tootsToRender.forEach(toot => render_toot(toots, toot, depth));
+      }
+    
+      function render_toot(toots, toot, depth) {
+        toot.account.display_name = escapeHtml(toot.account.display_name);
+        toot.account.emojis.forEach(emoji => {
+          toot.account.display_name = toot.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" style="margin-left: calc(var(--mastodon-comment-indent) * ${depth})">
+            <div class="author">
+              <div class="avatar">
+                <img src="${escapeHtml(toot.account.avatar_static)}" height=60 width=60 alt="">
+              </div>
+              <div class="details">
+                <a class="name" href="${toot.account.url}" rel="nofollow">${toot.account.display_name}</a>
+                <a class="user" href="${toot.account.url}" rel="nofollow">${user_account(toot.account)}</a>
+              </div>
+              <a class="date" href="${toot.url}" rel="nofollow">${toot.created_at.substr(0, 10)} ${toot.created_at.substr(11, 8)}</a>
             </div>
-            <div class="twemoji favourites ${toot_active(toot, 'favourites')}">
-              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/star.svg" %}${toot_count(toot, 'favourites')}</a>
+            <div class="content">${toot.content}</div>
+            <div class="attachments">
+              ${toot.media_attachments.map(attachment => {
+                if (attachment.type === 'image') {
+                  return `<a href="${attachment.url}" rel="nofollow"><img src="${attachment.preview_url}" alt="${attachment.description}" /></a>`;
+                } else if (attachment.type === 'video') {
+                  return `<video controls><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
+                } else if (attachment.type === 'gifv') {
+                  return `<video autoplay loop muted playsinline><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
+                } else if (attachment.type === 'audio') {
+                  return `<audio controls><source src="${attachment.url}" type="${attachment.mime_type}"></audio>`;
+                } else {
+                  return `<a href="${attachment.url}" rel="nofollow">${attachment.type}</a>`;
+                }
+              }).join('')}
             </div>
-          </div>
-        </div>`;
-      document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
-  
-      render_toots(toots, toot.id, depth + 1)
-    }
-  
-    function loadComments() {
-      if (commentsLoaded) return;
-  
-      document.getElementById("mastodon-comments-list").innerHTML = "Loading comments from the Fediverse...";
-  
-      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 = "";
-              render_toots(data['descendants'], id, 0)
-          } else {
-            document.getElementById('mastodon-comments-list').innerHTML = 
-            `<div class="admonition info">
-              <p class="admonition-title">
-                No comments found. <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Be the first!</a>
-              </p>
+            <div class="status">
+              <div class="twemoji replies ${toot_active(toot, 'replies')}">
+                <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/reply.svg" %}${toot_count(toot, 'replies')}</a>
+              </div>
+              <div class="twemoji reblogs ${toot_active(toot, 'reblogs')}">
+                <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/retweet.svg" %}${toot_count(toot, 'reblogs')}</a>
+              </div>
+              <div class="twemoji favourites ${toot_active(toot, 'favourites')}">
+                <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/star.svg" %}${toot_count(toot, 'favourites')}</a>
+              </div>
             </div>
-            `;
-          }
-  
-          commentsLoaded = true;
-        });
-    }
-  
-    function respondToVisibility(element, callback) {
-      var options = {
-        root: null,
-      };
-  
-      var observer = new IntersectionObserver((entries, observer) => {
-        entries.forEach(entry => {
-          if (entry.intersectionRatio > 0) {
-            callback();
-          }
-        });
-      }, options);
-  
-      observer.observe(element);
-    }
-  
-    var comments = document.getElementById("mastodon-comments-list");
-    respondToVisibility(comments, loadComments);
-  </script>
-{% else %}
-  <div class="admonition warning">
-    <p class="admonition-title">
-      No Mastodon post connected to this blog post. Please try again later.
-    </p>
-  </div>
+          </div>`;
+        document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
+    
+        render_toots(toots, toot.id, depth + 1)
+      }
+    
+      function loadComments() {
+        if (commentsLoaded) return;
+    
+        document.getElementById("mastodon-comments-list").innerHTML = "Loading comments from the Fediverse...";
+    
+        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 = "";
+                render_toots(data['descendants'], id, 0)
+            } else {
+              document.getElementById('mastodon-comments-list').innerHTML = 
+              `<div class="admonition info">
+                <p class="admonition-title">
+                  No comments found. <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Be the first!</a>
+                </p>
+              </div>
+              `;
+            }
+    
+            commentsLoaded = true;
+          });
+      }
+    
+      function respondToVisibility(element, callback) {
+        var options = {
+          root: null,
+        };
+    
+        var observer = new IntersectionObserver((entries, observer) => {
+          entries.forEach(entry => {
+            if (entry.intersectionRatio > 0) {
+              callback();
+            }
+          });
+        }, options);
+    
+        observer.observe(element);
+      }
+    
+      var comments = document.getElementById("mastodon-comments-list");
+      respondToVisibility(comments, loadComments);
+    </script>
+  {% else %}
+    <div class="admonition warning">
+      <p class="admonition-title">
+        No Mastodon post connected to this blog post. Please try again later.
+      </p>
+    </div>
+  {% endif %}
 {% endif %}
\ No newline at end of file

_README.md

@@ -0,0 +1,25 @@

+# Mastodon-based Comment system for Material for MkDocs
+This paste contains files to implement a comment system using a Mastodon instance and user.
+
+## Requirements
+
+- Mastodon account
+- Material for MkDocs theme
+- Knowledge in theme extension
+
+## Set up
+> Note: This setup was made using the dark theme of Material for MkDocs. You may need to edit colour values in the `comments.md` file to make it work with your design.
+
+1. Follow the Guide for [extending the theme](https://squidfunk.github.io/mkdocs-material/customization/#extending-the-theme) and create the necessary folders and files for [adding a comment system](https://squidfunk.github.io/mkdocs-material/setup/adding-a-comment-system/)
+    - Do NOT enable and/or create the Giscus `<script>` part. It's not needed.
+2. Copy and paste the content of `comments.html` into the file with the same name (`<your custom_dir folder>/partials/comments.html`)
+3. Create a new CSS file in your assets directory and copy the content of `comments.css` over. Don't forget to also add it as extra CSS in your mkdocs.yml file!
+4. Add a new section called `mastodon` in the `extra` section. If said section doesn't exist, create it. Add `user` and `host` to the `mastodon` section and add necessary values to it.
+  You should have a `extra` configuration that looks similar to this:  
+  ```yaml
+  extra:
+    mastodon:
+      host: example.com # Replace with your Mastodon instance domain
+      user: Example     # Replace with your username on the instance
+  ```
+5. You're done! All you need to do is now create a Post on Mastodon, copy the ID from it and add it as `comment_id` YAML frontmatter to your page.
\ No newline at end of file

comments.css

@@ -0,0 +1,116 @@

+:root{
+  --mastodon-comment-indent: 40px;
+  --mastodon-comment-border-radius: 3px;
+  
+  --mastodon-comment-bg-color: rgba(0, 0, 0, 0.2);
+  --mastodon-comment-border-color: rgba(0, 0, 0, 0.4);
+  --mastodon-comment-user-color: #939393;
+  
+  --mastodon-comment-status--inactive: #5d686f;
+  --mastodon-comment-status-replies--active: #448aff;
+  --mastodon-comment-status-favourite--active: #ff9100;
+  --mastodon-comment-status-reblog--active: #00c853;
+}
+
+@media only screen and (max-width: 1024px){
+  :root{
+    --mastodon-comment-indent: 20px;
+  }
+}
+
+@media only screen and (max-width: 640px){
+  :root{
+    --mastodon-comment-indent: 0px;
+  }
+}
+
+.mastodon-comment{
+  background-color: var(--mastodon-comment-bg-color);
+  border-radius: var(--mastodon-comment-border-radius);
+  border: 1px var(--mastodon-comment-border-color) solid;
+  padding: 20px;
+  margin-bottom: 1.5rem;
+  display: flex;
+  flex-direction: column;
+}
+
+.mastodon-comment p{
+  margin-bottom: 0px;
+}
+
+.mastodon-comment .content{
+  margin: 15px 20px;
+}
+
+.mastodon-comment .content p:first-child{
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.mastodon-comment .attachments{
+  max-width: 0px 10px;
+}
+
+.mastodon-comment .attachments > *{
+  max-width: 0px 10px;
+}
+
+.mastodon-comment .author{
+  padding-top: 0;
+  display: flex;
+}
+
+.mastodon-comment .author a{
+  text-decoration: none;
+}
+
+.mastodon-comment .author .avatar img{
+  margin-right: 1rem;
+  min-width: 60px;
+  border-radius: 5px;
+}
+
+.mastodon-comment .author .details{
+  display: flex;
+  flex-direction: column;
+}
+
+.mastodon-comment .author .details .name{
+  font-weight: bold;
+}
+
+.mastodon-comment .author .details .user{
+  color: var(--mastodon-comment-user-color);
+}
+
+.mastodon-comment .author .date{
+  margin-left: auto;
+  font-size: small;
+}
+
+.mastodon-comment .status > div{
+  display: inline-block;
+  margin-right: 15px;
+}
+
+.mastodon-comment .status a{
+  color: var(--mastodon-comment-status--inactive);
+  text-decoration: none;
+}
+
+.mastodon-comment .status .twemoji.replies.active a{
+  color: var(--mastodon-comment-status-replies--active);
+}
+
+.mastodon-comment .status .twemoji.reblogs.active a{
+  color: var(--mastodon-comment-status-reblog--active);
+}
+
+.mastodon-comment .status .twemoji.favourites.active a{
+  color: var(--mastodon-comment-status-favourite--active);
+}
+
+.mastodon-comment .status svg{
+  margin-right: 0.2rem;
+  vertical-align: middle;
+}
\ No newline at end of file

comments.html

@@ -0,0 +1,161 @@

+<h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
+{% if page.meta.comment_id %}
+  <noscript>
+    <div class="admonition warning">
+      <p class="admonition-title">
+        Please enable Javascript to see comments from Mastodon.
+      </p>
+    </div>
+  </noscript>
+  <p>
+    <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Comment on this blog post</a> using a Fediverse-compatible Account (Mastodon or alike).
+  </p>
+  
+  <p id="mastodon-comments-list"></p>
+  
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.1/purify.min.js" integrity="sha512-uHOKtSfJWScGmyyFr2O2+efpDx2nhwHU2v7MVeptzZoiC7bdF6Ny/CmZhN2AwIK1oCFiVQQ5DA/L9FSzyPNu6Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+  <script type="text/javascript">
+    var host = '{{ config.extra.mastodon.host }}';
+    var user = '{{ config.extra.mastodon.user }}';
+    var id = '{{ page.meta.comment_id }}'
+  
+    function escapeHtml(unsafe) {
+      return unsafe
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+        .replace(/"/g, "&quot;")
+        .replace(/'/g, "&#039;");
+    }
+  
+    var commentsLoaded = false;
+  
+    function toot_active(toot, what) {
+      var count = toot[what+'_count'];
+      return count > 0 ? 'active' : '';
+    }
+  
+    function toot_count(toot, what) {
+      var count = toot[what+'_count'];
+      return count > 0 ? count : '';
+    }
+  
+    function user_account(account) {
+      var result =`@${account.acct}`;
+      if (account.acct.indexOf('@') === -1) {
+        var domain = new URL(account.url)
+        result += `@${domain.hostname}`
+      }
+      return result;
+    }
+  
+    function render_toots(toots, in_reply_to, depth) {
+      var tootsToRender = toots
+        .filter(toot => toot.in_reply_to_id === in_reply_to)
+        .sort((a, b) => a.created_at.localeCompare(b.created_at));
+      tootsToRender.forEach(toot => render_toot(toots, toot, depth));
+    }
+  
+    function render_toot(toots, toot, depth) {
+      toot.account.display_name = escapeHtml(toot.account.display_name);
+      toot.account.emojis.forEach(emoji => {
+        toot.account.display_name = toot.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" style="margin-left: calc(var(--mastodon-comment-indent) * ${depth})">
+          <div class="author">
+            <div class="avatar">
+              <img src="${escapeHtml(toot.account.avatar_static)}" height=60 width=60 alt="">
+            </div>
+            <div class="details">
+              <a class="name" href="${toot.account.url}" rel="nofollow">${toot.account.display_name}</a>
+              <a class="user" href="${toot.account.url}" rel="nofollow">${user_account(toot.account)}</a>
+            </div>
+            <a class="date" href="${toot.url}" rel="nofollow">${toot.created_at.substr(0, 10)} ${toot.created_at.substr(11, 8)}</a>
+          </div>
+          <div class="content">${toot.content}</div>
+          <div class="attachments">
+            ${toot.media_attachments.map(attachment => {
+              if (attachment.type === 'image') {
+                return `<a href="${attachment.url}" rel="nofollow"><img src="${attachment.preview_url}" alt="${attachment.description}" /></a>`;
+              } else if (attachment.type === 'video') {
+                return `<video controls><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
+              } else if (attachment.type === 'gifv') {
+                return `<video autoplay loop muted playsinline><source src="${attachment.url}" type="${attachment.mime_type}"></video>`;
+              } else if (attachment.type === 'audio') {
+                return `<audio controls><source src="${attachment.url}" type="${attachment.mime_type}"></audio>`;
+              } else {
+                return `<a href="${attachment.url}" rel="nofollow">${attachment.type}</a>`;
+              }
+            }).join('')}
+          </div>
+          <div class="status">
+            <div class="twemoji replies ${toot_active(toot, 'replies')}">
+              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/reply.svg" %}${toot_count(toot, 'replies')}</a>
+            </div>
+            <div class="twemoji reblogs ${toot_active(toot, 'reblogs')}">
+              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/retweet.svg" %}${toot_count(toot, 'reblogs')}</a>
+            </div>
+            <div class="twemoji favourites ${toot_active(toot, 'favourites')}">
+              <a href="${toot.url}" rel="nofollow">{% include ".icons/fontawesome/solid/star.svg" %}${toot_count(toot, 'favourites')}</a>
+            </div>
+          </div>
+        </div>`;
+      document.getElementById('mastodon-comments-list').appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true}));
+  
+      render_toots(toots, toot.id, depth + 1)
+    }
+  
+    function loadComments() {
+      if (commentsLoaded) return;
+  
+      document.getElementById("mastodon-comments-list").innerHTML = "Loading comments from the Fediverse...";
+  
+      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 = "";
+              render_toots(data['descendants'], id, 0)
+          } else {
+            document.getElementById('mastodon-comments-list').innerHTML = 
+            `<div class="admonition info">
+              <p class="admonition-title">
+                No comments found. <a href="https://{{ config.extra.mastodon.host }}/@{{ config.extra.mastodon.user }}/{{ page.meta.comment_id }}">Be the first!</a>
+              </p>
+            </div>
+            `;
+          }
+  
+          commentsLoaded = true;
+        });
+    }
+  
+    function respondToVisibility(element, callback) {
+      var options = {
+        root: null,
+      };
+  
+      var observer = new IntersectionObserver((entries, observer) => {
+        entries.forEach(entry => {
+          if (entry.intersectionRatio > 0) {
+            callback();
+          }
+        });
+      }, options);
+  
+      observer.observe(element);
+    }
+  
+    var comments = document.getElementById("mastodon-comments-list");
+    respondToVisibility(comments, loadComments);
+  </script>
+{% else %}
+  <div class="admonition warning">
+    <p class="admonition-title">
+      No Mastodon post connected to this blog post. Please try again later.
+    </p>
+  </div>
+{% endif %}
\ No newline at end of file