IndieBlog

Pure Blog IndieWeb Boilerplate

Footer HTML

<script>
if (document.querySelector("article")?.classList.contains("h-entry")) {

async function fetchInteractions() {
  try {
    const res = await fetch(
      "https://webmention.io/api/mentions.jf2?target=" +
      encodeURIComponent(location.href)
    );

    const data = await res.json();
    if (!data?.children?.length) return;

    const likes = [];
    const reposts = [];
    const replies = [];
    const mentions = [];

    data.children.forEach(child => {
      const authorUrl = child.author?.url || child["wm-source"] || "#";
      const authorPhoto = child.author?.photo || "https://via.placeholder.com/50";

      function createAvatar(emoji = null) {
        const wrapper = document.createElement("div");
        wrapper.className = "avatar-container";

        const avatarLink = document.createElement("a");
        avatarLink.href = authorUrl;
        avatarLink.target = "_blank";
        avatarLink.rel = "nofollow noopener";

        const img = document.createElement("img");
        img.src = authorPhoto;
        img.className = "webmention-avatar";
        avatarLink.appendChild(img);
        wrapper.appendChild(avatarLink);

        if (emoji) {
          const badge = document.createElement("span");
          badge.className = "avatar-emoji";
          badge.textContent = emoji;
          wrapper.appendChild(badge);
        }

        return wrapper;
      }

      switch (child["wm-property"]) {
        case "like-of":
          likes.push(createAvatar("❤️"));
          break;
        case "repost-of":
          reposts.push(createAvatar("🔁"));
          break;
        case "in-reply-to":
          const reply = document.createElement("div");
          reply.className = "webmention-reply";
          reply.appendChild(createAvatar("💬"));
          if (child.content?.text) {
            const quote = document.createElement("blockquote");
            quote.textContent = child.content.text;
            reply.appendChild(quote);
          }
          replies.push(reply);
          break;
        case "mention-of":
          const link = document.createElement("a");
          link.href = child["wm-source"];
          link.target = "_blank";
          link.rel = "nofollow noopener";
          link.textContent = child["wm-source"];
          mentions.push(link);
          break;
      }
    });

    const wrapper = document.createElement("div");
    wrapper.className = "webmentions";

    const details = document.createElement("details");
    if (replies.length) details.open = true;

    const summary = document.createElement("summary");
    // wrap the emoji + counts in a span
    summary.innerHTML = `IndieWeb Interactions <span class="counts">❤️ ${likes.length} 🔁 ${reposts.length} 💬 ${replies.length} ✍️ ${mentions.length}</span>`;
    details.appendChild(summary);

    const reactionsRow = document.createElement("div");
    reactionsRow.className = "reactions-row";
    likes.forEach(a => reactionsRow.appendChild(a));
    reposts.forEach(a => reactionsRow.appendChild(a));
    details.appendChild(reactionsRow);

    if (replies.length) {
      const repliesContainer = document.createElement("div");
      repliesContainer.className = "replies-section";
      replies.forEach(r => repliesContainer.appendChild(r));
      details.appendChild(repliesContainer);
    }

    if (mentions.length) {
      const mentionsHeading = document.createElement("p");
      mentionsHeading.textContent = "✍️ Mentions";
      details.appendChild(mentionsHeading);

      const ul = document.createElement("ul");
      mentions.forEach(m => {
        const li = document.createElement("li");
        li.appendChild(m);
        ul.appendChild(li);
      });
      details.appendChild(ul);
    }

    wrapper.appendChild(details);

    const postNav = document.querySelector("div.post-nav");
    if (postNav) postNav.parentNode.insertBefore(wrapper, postNav.nextSibling);

  } catch (err) {
    console.warn("Webmentions failed", err);
  }
}

fetchInteractions();
}
</script>