Now, I'm in IndieWeb?

Since I saw the concept of IndieWeb last year, I’ve been wanting to support it on my Zola blog. Before that my blog had never had a commenting system, and this time with Webmention I was finally able to get a static blog to display responses to articles on the web in plain html. I will document how I did it in this article.

The IndieWeb IndieWeb is a community of independent & personal websites connected by simple standards, based on the principles of: owning your domain & using it as your primary identity, publishing on your own site (optionally syndicating elsewhere), and owning your data.

1. Support IndieWeb Auth🔗

IndieAuth is a federated login protocol for Web sign-in, so by enabling this, we can use our own domain to sign in to other sites and services which support IndieAuth. Learn more about why IndieAuth and how to IndieAuth, here’s how I enabled it:

  1. By editing my index template file, add this to the head:
<link rel="me" href="https://twitter.com/TheOwenYoung" />
<link rel="me" href="https://github.com/theowenyoung" />
<link rel="me" href="mailto:owen@owenyoung.com" />
  1. Editing Twitter/GitHub profile bio, add my website URL https://www.owenyoung.com

  2. Test it at https://indielogin.com/

2. Add Profile Info🔗

When you sign in with IndieAuth or using web mention, some sites will try to get your profile info, like name, image, and bio. So we can add this to our homepage, you can learn more about h-card here. Here’s how I added it:

By editing my index template file, add this in the aside (Cause I don’t want this to affect the layout of my site, so use css display: none to hide it for human, but the bot will see it):

<div class="display-none h-card pt">
  <img class="u-photo icon" alt="Owen"
  src="{{get_url(path="site/images/favicon-96x96.png",cachebust=true)}}" />
  <a class="p-name u-url" href="{{ config.base_url }}"
    >{{ config.extra.author }}</a
  >
  <p class="p-note">{{ config.extra.bio }}</p>
</div>

3. Joining IndieWeb Webring🔗

A webring (or web ring) is a collection of websites linked together in a circular structure, and usually organized around a specific theme, often educational or social. They were popular in the 1990s and early 2000s, particularly among amateur websites.

Now, IndieWeb has one IndieWeb Webring! by adding webring in our sites, so people can find (and be found by) other folks with IndieWeb building blocks on their sites!

So, I’m in it! By joining this, my blog has been listed in the IndieWeb Webring Directory, you can see I already have a profile there.

It’s easy to join the webring, just click the link, and login with my domain www.owenyoung.com, then I can get my webring code, my webring code like this:

<a href="https://xn--sr8hvo.ws/%F0%9F%93%AE%F0%9F%86%99%F0%9F%93%A9/previous"
  ></a
>
An IndieWeb Webring 🕸💍
<a href="https://xn--sr8hvo.ws/%F0%9F%93%AE%F0%9F%86%99%F0%9F%93%A9/next"></a>

Then, I add this code to my homepage aside, you can see it on the aside footer

4. Adding Webmention Response to articles🔗

Webmention is a web standard for mentions and conversations across the web, a powerful building block that is used for a growing federated network of comments, likes, reposts, and other rich interactions across the decentralized social web.

“An @ mention that works across websites; so that you don’t feel immovable from Twitter or Fb.” — Rony Ngala

Learn more about Webmention and How to support webmention

Basically, I use Webmention.io to collect all webmentions about this blog, and then I use Denoflow to cache them to my blog repo, and then I use Zola load_data function to load them, and render them.

  1. First, go webmention.io and create a new account with my domain www.owenyoung.com, then I can get my webmention endpoint, and I connected my twitter and github account to the service.

  2. Second, let other services know your webmention endpoint. Add this to the head:

<link
  rel="webmention"
  href="https://webmention.io/www.owenyoung.com/webmention"
/>
  1. Use Denoflow to cache all webmentions to my blog repo

Workflow file(workflows/fetch-webmention.yml):

Fetch webmention API to get updates, then save to webmentions directory.

sources:
  - use: fetch
    args:
      - https://webmention.io/api/mentions.jf2?domain=www.owenyoung.com&per-page=999&token=${{ctx.env.WEBMENTION_TOKEN}}
    run: return ctx.result.json()
    itemsPath: children
    key: "wm-id"
filter:
  run: |
    const {ensureDir} = await import("https://deno.land/std@0.121.0/fs/mod.ts");
    const { dirname } = await import("https://deno.land/std@0.121.0/path/mod.ts");
    for(const item of ctx.items){
      const id = item["wm-id"];
      const target = new URL(item["wm-target"]);
      const pathname = target.pathname;
      const filename = pathname.slice(1).replace(/\/$/, "");
      const filepath = "webmentions/"+filename+".json";
      await ensureDir(dirname(filepath));
      let webmentionData = {};
      try {
        const dataString = await Deno.readTextFile(filepath);
        webmentionData = JSON.parse(dataString);
      } catch (_e) {
        // ignore
      }
      webmentionData[id] = item;
      console.log("write file:", filepath);
      await Deno.writeTextFile(filepath, JSON.stringify(webmentionData,null,2));
    }
    return ctx.items.map(()=>true);

Github Workflow file(.github/workflows/denoflow.yml):

Run denoflow every day at midnight, if there are any new updates, it will create a new pull request.

name: Denoflow
on:
  repository_dispatch:
  workflow_dispatch:
  # push:
  #   branches:
  #     - main
  schedule:
    - cron: "1 0 * * *"
jobs:
  denoflow:
    runs-on: ubuntu-latest
    concurrency: denoflow
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x
      - run: make webmention
        env:
          WEBMENTION_TOKEN: ${{secrets.WEBMENTION_TOKEN}}
        continue-on-error: true
      - name: chown
        run: sudo chown -R $USER:$USER ./
      - name: git config
        run: git config --global user.name "github-actions[bot]" && git config --global user.email github-actions-bot@users.noreply.github.com
      - name: git add
        run: git add data && git add webmentions
      - run: git status
      - id: isChanged
        run: git diff-index --cached --quiet HEAD || echo '::set-output name=changed::true'
      - name: Create pull request
        uses: peter-evans/create-pull-request@v3
        if: ${{ steps.isChanged.outputs.changed == 'true' }}
        with:
          token: ${{ secrets.PERSONAL_TOKEN }}
          labels: automerge
          add-paths: data,webmentions
          commit-message: "chore: new item"
          committer: "github-actions[bot] <github-actions-bot@users.noreply.github.com>"
          author: "github-actions[bot] <github-actions-bot@users.noreply.github.com>"
          branch: new-item
          delete-branch: true
          title: New item update

Github auto merge workflow file(.github/workflows/auto-merge.yml):

When a pull request is created, the workflow will automatically merge it if the pull request is from the same author

name: Auto merge
on:
  workflow_dispatch:
  pull_request_target:
jobs:
  auto-approve:
    runs-on: ubuntu-latest
    steps:
      - name: Merge
        if: (github.actor=='theowenyoung') && (startsWith(github.head_ref,'new-item'))
        uses: "pascalgn/automerge-action@v0.14.3"
        env:
          GITHUB_TOKEN: "${{ secrets.PERSONAL_TOKEN }}"
          MERGE_DELETE_BRANCH: true
          MERGE_LABELS: ""
  1. Add response block to page tempalte

This part may take a while to finish, here is the main page template code, I’ll only show the main part.

Use Zola load_data function to load the webmention data JSON file.

{% set current_webmention_file_name = current_path | trim_end_matches(pat="/")
%} {% set webmention_data =
load_data(path="webmentions"~current_webmention_file_name~".json",required=false)
%}

Show response:

{% if webmention_data %} {% set_global mentions = [] %} {% for ignored, item in
webmention_data %} {% set_global mentions = mentions | concat(with=item) %} {%
endfor %}
<p class="muted text-sm">
  {{trans(key="label_response_description",lang=lang)| markdown(inline=true) |
  safe}}
</p>
{% for type, items in mentions | group_by(attribute="wm-property") %}
<h3>
  {{trans(key="label_"~type,lang=lang)}} ({{items | length}})<a
    href="#{{type}}"
    id="{{type}}"
    class="zola-anchor"
    >🔗</a
  >
</h3>
{% if type == 'like-of' or type == "repost-of" or type == "bookmark-of" or type
== "follow-of" %}
<ul class="list-none flex items-center flex-wrap">
  {% for item in items %} {{ macro::webmention(type=type, item=item) }} {%
  endfor %}
</ul>
{% else %}
<ul class="list-none">
  {% for item in items %} {{ macro::webmention(type=type, item=item) }} {%
  endfor %}
</ul>
{% endif %} {% endfor %} {% endif %}

Then, I add a webmention form, so if people want to submit their mentions, they can directly submit it.

<form
  class="webmention-form"
  action="https://webmention.io/www.owenyoung.com/webmention"
  method="post"
>
  <div class="flex items-center flex-wrap pb">
    <input type="url" name="source" class="flex-3 mr-sm w-full py-sm px-sm" />
    <input type="submit" class="px py-sm" value="Send Webmention" />
  </div>
  <input type="hidden" name="target" value="{{current_url}}" />
</form>

Optional: I also added an activity page for aggregating all response, the activity template page is here

Cause I don’t have too many mentions, so I use sebastiandedeyne’s mention data as this article’s webmention data.

5. Send webmention when you publish a new article🔗

When we publish a new article, we want to send a webmention to the mentioned links. We can do this by using Denoflow and the Webmention.app API. Webmention.app can check all the mentioned links in the new article and send all webmentions to them. Before Webmention.app can recognize the mentioned links, we need to add some extra microformats2 to our article html. Basically, it’s some html tag class names. These took me quite a few time to update my templates, so now I have supported h-card, h-entry and h-feed. It looks like this:

<article class="h-entry">
  <h1 class="p-name">Microformats are amazing</h1>
  <p>
    Published by
    <a class="p-author h-card" href="http://example.com">W. Developer</a> on
    <time class="dt-published" datetime="2013-06-13 12:00:00"
      >13<sup>th</sup> June 2013</time
    >
  </p>
  <p class="p-summary">In which I extoll the virtues of using microformats.</p>
  <div class="e-content">
    <p>Blah blah blah</p>
  </div>
</article>

I have updated page.html, index.html, taxonomy_single.html and section.html to support these new microformats. I have to say this is the most demanding job, good luck!

Once finished, I went to indiewebify to test if it can recognize my new microformats. It works!

Next, I went to webmention.app to apply for a token. Then I can add a denoflow workflow file to fetch it’s service every day.

Here is the workflow file(workflows/send-webmention.yml):

sources:
  - from: https://deno.land/x/denoflow@0.0.35/sources/rss.ts
    args:
      - https://www.owenyoung.com/blog/atom.xml
  - from: https://deno.land/x/denoflow@0.0.35/sources/rss.ts
    args:
      - https://www.owenyoung.com/en/blog/atom.xml
steps:
  - use: fetch
    args:
      - https://webmention.app/check?token=${{ctx.env.WEBMENTION_APP_TOKEN}}&url=${{encodeURIComponent(ctx.item.links[0].href)}}
      - method: GET
        headers:
          Content-Type: application/json
    run: |
      console.log(ctx.item.links[0].href);
      const json = await ctx.result.json();
      console.log(json);

Don’t forget to add enviroment variables WEBMENTION_APP_TOKEN to your denoflow workflow file(.github/workflows/denoflow.yml).

- run: deno run -A https://deno.land/x/denoflow/cli.ts run
  env:
    WEBMENTION_TOKEN: ${{secrets.WEBMENTION_TOKEN}}
    WEBMENTION_APP_TOKEN: ${{secrets.WEBMENTION_APP_TOKEN}}

Conclusion🔗

I really like the concept of IndieWeb and their API design philosophy, it took some time but I still think it was worth it. It gave me renewed confidence in the Internet.

You can find the all source code of this blog on Github

Resources🔗

Category: Random 
TagsIndieWebZolaWebmention
Published:   📝 Edit this page

Response🔗

Have you written a response to this? Let me know the article or tweet URL (Article or tweet content needs to include the URL of this article, learn more about webmention here).

Or, you can simply reply to this tweet:

用推特回复作为静态博客的评论系统,既可以过滤spam,又可以增加推特互动,webmention.io 也支持抓取推特的响应,然后每天自动同步一次webmention的数据到repo里就能显示了,和我之前的indieweb完美结合。刚给我博客加了一个仿推特的卡片,博客本身依然是静态的! owenyoung.com/en/blog/indiew…

— Owen on 2022.07.25   Reply

The response will be automatically collected by webmention.io bot, and another bot will automatically post it here within 24 hours, I wrote an article about how to achieve this. You can also send your response via mail owen@owenyoung.com or Twitter DM

Replies (19)🔗

Likes (40)🔗

Reposts (9)🔗

Mentions (19)🔗