punkfairie.net

Marley Rae punkfairie mar email website

indieweb comments with webmentions


You may have noticed a small section at the bottom of diary pages for webmentions. If you've ever heard of pingbacks during the Wordpress days, they are the same concept!

Webmentions can be set up for any website, and displayed however you please. You can even pull fediverse comments as webmentions and display them on your site! I am not an expert, but following is a little tutorial on how I set up webmentions for this site.

I use 11ty to build my site, and you will often see tutorials that use 11ty data files to pull mentions at build time. The downside to this is that you have to rebuild your site every time you want to update your mentions. The solution to this is usually webhooks that tell netlify/vercel/what-have-you to re-deploy the site when new mentions are received, or scheduling a rebuild once a day. To me, this is unnecessarily complicated for the (imo) small gain of less client side javascript. Especially considering I self host my site using Docker on a server in my own home, and therefore can't as easily use webhooks/scheduled rebuilds.

So I have instead taken the route of using client-side JS for this. Webmentions are updated on every page refresh, and if the user has JS turned off, no big loss for them. Comments are a progressive enhancement for this sort of website anyway. Another plus is that this tutorial should be applicable no matter how you make your website, as long as you have access to JS :)

Step 1: Webmention.io & Bridgy

I do not love using third party services for this, but the DIY way is way more complicated then I want to maintain, so I had to give in here.

  • Webmention.io - the service that allows us to receive webmentions. You will need to have a website already published with web sign-in enabled (more on that below). Don't forget to click the link to add a site after you are logged in!
  • Bridgy - this is optional, it allows for pulling in fedi/reddit/bluesky comments as webmentions.

Step 1.1: Webmention.io & Web Sign-In

If you don't have web sign-in already enabled, here's how to do so:

First - you need to have a website you can publish BEFORE setting up webmentions, as you will need at least a published index page with the html explained below in order to log into webmention.io. It doesn't need to have content or be pretty, just accessible.

Put at least one of the following link tags between <head> and <⁄head> on your index page. It MUST be on your index for sign-in to work!

<!-- sign in using OTP sent to your email: -->
<link rel="me" href="mailto:YOUR@EMAIL.COM">

<!-- or use your github account: -->
<link rel="me" href="https://github.com/YOUR_USERNAME">

It is possible to use your phone number as well, but I do not recommend this as <head> content is still visible to the public via the browser inspector, and to any web scrapers.

Publish your website with those changes, and then you should be able to go to webmention.io and log in with your email'd OTP or GitHub. Once you are on the dashboard, click the link to add a site and add in your website.

Step 2: Add your webmention link

We will add two things in this section: a <link> tag for bots, and a visible link for humans, letting them know how to send you webmentions.

On webmention.io, click "Sites" in the top bar, then click "Get Setup Code" next to your site (I somehow ended up with duplicate punkfairie.net entries - if that happens to you just pick the first one). Copy the code to somewhere between <head> and <⁄head> on every page someone might want to send mentions for. If your site uses a global layout file, that's a great place for it, otherwise you'll have to paste it on every page you want mentions for.

Next, we will put a link on every page you want to receive mentions for, so that people can send you mentions. This part is not strictly necessary, as users could still send you mentions by using inspect element and finding the <link> element we just added, or using other automated tools. But it's a better user experience to explicitly let people know you can receive mentions!

There are many ways to do this, but what I've done is probably the easiest. Going back to that code snippet we copied from webmention.io earlier, grab just the URL in the href="" part of that. If you go to that link in your browser, you'll notice that it actually links to a form to send the mention with. Go ahead and put a link to that somewhere on each page you want to receive mentions for. I've done it like this (see it in action at the bottom of this page!):

<p>
  Have you linked to this page on your website?
  <a
    href="https://webmention.io/punkfairie.net/webmention"
    target="_blank"
    >Send me a webmention!</a
  >
</p>

Step 3: Display Webmentions

Now for the slightly more complicated bit. Receiving webmentions is fine and all, but they are useless unless displayed somehow!

I use Alpine.js for all my JS - if you've ever used jQuery it's sort of trying to achieve the same goal. It makes it really easy to incorporate small amounts of interactivity. The actual fetch mentions logic is vanilla JS, so you can use that and figure out the displaying bit on your own! It isn't much more complicated, but since I use Alpine that's the method I'll show here.

The easiest way to show this is by showing the full code (stripped of my own CSS classes) with comments explaining each piece. I'm not going to fully explain the Alpine.js bits, as that's a whole other tutorial, but the docs are pretty easy to understand :)

This is the HTML that goes where you want the mentions to show:

<!--
  x-data lets Alpine.js know that it should use the
  'mentionList' code defined below to populate the mentions
-->
<div x-data="mentionList">
  <!-- If we got an error, don't render this -->
  <template x-if="!error">
    <!--
      A for loop - for each mention, render
      the contents of this <template>
    -->
    <template x-for="mention in mentions">
      <!--
        If we have the author name, use
        that. Otherwise 'Someone'
      -->
      <span x-text="mention.author.name || 'Someone'"></span>

      <!--
        link to the URL that your page was mentioned
          on, and make the link text the type of mention,
        using the function defined in the JS below
      -->
      <a :href="mention.url" x-text="getType(mention)"></a>

      <!--
        Date the mention was sent, again
        using a function defined below
      -->
      this post on <span x-text="getDate(mention)"></span>

      <!--
        If the mention contains text
        (i.e. a mastondon comment), display that
      -->
      <template x-if="mention.content.text">
        <!--
          The getText function is defined below,
          it just truncates long text
        -->
        <blockquote x-text="getText(mention)"></blockquote>
      </template>
    </template>
  </template>
</div>

And this is the javascript that needs to be on the same page as the HTML code above:

<script>
  // The API endpoint to get mentions for this page.
  // Replace punkfairie.net/page-to-get with the URL
  // of the page you want to get mentions for.
  //
  // If you are using 11ty and a template language, you can
  // insert the link automatically like this (example
  // using liquid):
  // target=https://punkfairie.net/{{ permalink }}
  const url =
    'https://webmention.io/api/mentions.jf2?target=https://punkfairie.net/your-page&sort-by=published';

  document.addEventListener('alpine:init', () => {
    //
    // This is the bit that actually grabs the
    // mentions. 'mentionList' is what we put in the
    // x-data attribute in the HTML above.
    Alpine.data('mentionList', () => ({
      //
      // This runs on page load/refresh.
      async init() {
        //
        // try { ... } catch { ... } gracefully
        // handles errors.
        try {
          const response = await fetch(url);

          // If we got, say, a 404 error, set
          // error to true so that the HTML
          // above wouldn't try to render.
          if (!response.ok) {
            this.error = true;
          }

          // Set our 'mentions' variable to the
          // fetched mention list.
          const result = await response.json();
          this.mentions = result.children;
        } catch (err) {
          //
          // If fetch() threw an error, prevent the
          // HTML from loading.
          this.error = true;
        }
      },

      // Initial values for our variables.
      // Despite being below init(), these are
      // actually set first and available to init().
      mentions: [],
      error: false,

      // The webmention.io API returns the type of
      // mention in a way that isn't as nice to display,
      // so we transform that into something better.
      getType(mention) {
        switch (mention['wm-property']) {
          case 'like-of':
            return 'liked';
            break;
          case 'bookmark-of':
            return 'bookmarked';
            break;
          case 'repost-of':
            return 'reposted';
            break;
          case 'in-reply-to':
            return 'replied to';
            break;
          default:
            return 'mentioned';
        }
      },

      // This function just truncates long comment
      // text to a max of 200 chars.
      getText(mention) {
        const len = 200;
        const text = mention.content.text;

        if (text.length <= len) {
          return text;
        } else {
          return text.slice(0, len) + '...';
        }
      },

      // This function formats dates in a prettier way.
      getDate(mention) {
        //
        // Use the published date if it's available,
        // otherwise use the received date.
        const date = new Date(mention.published || mention['wm-received']);

        const formatter = new Intl.DateTimeFormat(undefined, {
          dateStyle: 'medium',
          timeStyle: 'short',
        });

        return formatter.format(date);
      },
    }));
  });
</script>

If you are using something like 11ty & a template language (such as liquid or nunjucks) that supports includes, you can put the displaying webmentions code in a separate file and include it on all your pages, to reduce copy pasting! Just make sure you use something like the {{ permalink }} shortcode to change the JS url variable on each page.

And that's all you need! The rest of this is optional, but goes over setting up fedi connections.

Step 4: Connecting Socials

This is where Bridgy will come in! Go ahead and click the social media option you want to connect (you can connect multiple!). If it asks "What do you want to do?" choose the first option: "Cross-post to a Mastodon account" (the rest of these instructions assume you are connecting Mastodon, as that's what I use).

You will also want to have a link to your website in your Mastodon profile. You can even "verify" yourself by adding a rel="me" link on your website that links to your Mastodon profile! Just add the following between <head> and <⁄head>:

<link rel="me" href="https://YOUR.INSTANCE/@YOUR_USERNAME">

Further reading: