Skip to content

Instantly share code, notes, and snippets.

@deepakness
Created February 3, 2026 19:10
Show Gist options
  • Select an option

  • Save deepakness/782a1ff050ed52084eb5461bf8a7618a to your computer and use it in GitHub Desktop.

Select an option

Save deepakness/782a1ff050ed52084eb5461bf8a7618a to your computer and use it in GitHub Desktop.
Webmentions implementation for DeepakNess, built using 11ty
{% set target = page.url | absoluteUrl(metadata.url) %}
{% set replies = webmentions.children | filterWebmentions(target, "in-reply-to") %}
{% set likes = webmentions.children | filterWebmentions(target, "like-of") %}
{% set reposts = webmentions.children | filterWebmentions(target, "repost-of") %}
{% set hasReplies = replies.length %}
{% set hasReactions = likes.length or reposts.length %}
{% set hasWebmentions = hasReplies or hasReactions %}
<style>
.webmentions-wrap {
margin-top: 2.25rem;
padding-top: 1.25rem;
border-top: 1px solid var(--border-color, #e6e6e6);
text-align: left;
}
.webmentions-actions {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: center;
margin: 0 0 2rem 0;
}
.webmentions-action {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: var(--text-color-link, #3364ff);
text-decoration: none;
font-size: 0.825rem;
font-weight: 400;
margin: 0;
}
.webmentions-action:hover {
color: var(--text-color-link-active, #2b55d6);
text-decoration: none;
}
.webmentions-action-icon {
display: inline-flex;
align-items: center;
justify-content: center;
color: currentColor;
margin-bottom: 3px;
}
.webmentions-action-icon svg {
width: 16px;
height: 16px;
}
main .webmentions-actions a,
main .webmentions-actions a:hover,
main .webmentions-actions a:active,
main .webmentions-actions a:focus {
border-bottom: none !important;
padding-bottom: 0 !important;
text-decoration: none !important;
}
.webmentions-wrap[data-has-webmentions="false"] .webmentions-header,
.webmentions-wrap[data-has-webmentions="false"] .webmentions-reactions,
.webmentions-wrap[data-has-webmentions="false"] .webmentions-list,
.webmentions-wrap[data-has-webmentions="false"] .webmentions-actions--full {
display: none;
}
.webmentions-wrap[data-has-webmentions="true"] .webmentions-actions--empty {
display: none;
}
.webmentions-wrap[data-has-replies="false"] .webmentions-list {
display: none;
}
.webmentions-wrap[data-has-reactions="false"] .webmentions-reactions {
display: none;
}
.webmentions-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-bottom: 0.85rem;
margin-bottom: 1.35rem;
}
.webmentions-title-row {
display: inline-flex;
align-items: center;
gap: 0.8rem;
}
.webmentions-title {
font-size: 0.95rem;
font-weight: 700;
color: #767f89;
margin: 0;
text-transform: uppercase;
letter-spacing: 3px;
}
.webmentions-help {
display: inline-flex;
align-items: center;
gap: 0.45rem;
color: #8a949f;
text-decoration: none;
font-size: 0.8rem;
font-weight: 400;
}
.webmentions-help:hover {
color: #5b6672;
text-decoration: none;
}
.webmentions-reactions {
display: inline-flex;
align-items: center;
gap: 0.6rem;
font-size: 0.8rem;
color: #8a949f;
}
.webmentions-reaction {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
main a.webmentions-help,
main a.webmentions-help:hover,
main a.webmentions-help:active,
main a.webmentions-help:focus {
border-bottom: none !important;
padding-bottom: 0 !important;
text-decoration: none !important;
}
.webmentions-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.webmention-item {
position: relative;
display: grid;
grid-template-columns: 56px 1fr;
gap: 0.15rem;
align-items: start;
}
.webmention-avatar-wrap {
position: relative;
width: 48px;
height: 48px;
}
.webmention-avatar {
width: 100%;
height: 100%;
border-radius: 999px;
overflow: hidden;
background: #f1f3f5;
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid #eef2f7;
}
.webmention-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border: none !important;
border-radius: 999px !important;
}
.webmention-avatar picture {
display: block;
width: 100%;
height: 100%;
}
.webmention-avatar-fallback {
width: 100%;
height: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 0.75rem;
color: #9aa3ad;
}
.webmention-avatar[data-has-img="true"] .webmention-avatar-fallback {
display: none;
}
.webmention-body {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.webmention-meta {
display: flex;
align-items: baseline;
gap: 0.35rem;
flex-wrap: wrap;
}
.webmention-author {
font-weight: 400;
color: var(--text-color, #222);
text-decoration: none;
font-size: 0.9rem;
}
main a.webmention-author,
main a.webmention-author:hover,
main a.webmention-author:active,
main a.webmention-author:focus {
border-bottom: none !important;
padding-bottom: 0 !important;
text-decoration: none !important;
}
.webmention-date {
font-size: 0.8rem;
color: #8a949f;
}
.webmention-bubble {
background: rgba(51, 100, 255, 0.04);
border: 1px solid rgba(51, 100, 255, 0.12);
border-radius: 2px 10px 10px 10px;
padding: 0.475rem 0.65rem;
}
.webmention-text {
margin: 0;
color: var(--text-color, #222);
line-height: 1.6;
font-size: 0.85rem;
}
.webmention-text a {
color: var(--text-color-link, #3364ff);
}
@media (max-width: 600px) {
.webmentions-actions {
gap: 0.85rem;
justify-content: space-between;
}
.webmention-item {
grid-template-columns: 40px 1fr;
}
.webmention-avatar-wrap,
.webmention-avatar {
width: 34px;
height: 34px;
}
.webmention-item::before {
left: 16px;
top: 40px;
}
}
</style>
<section
class="webmentions-wrap"
data-webmentions-root
data-webmention-target="{{ target }}"
data-has-webmentions="{% if hasWebmentions %}true{% else %}false{% endif %}"
data-has-replies="{% if hasReplies %}true{% else %}false{% endif %}"
data-has-reactions="{% if hasReactions %}true{% else %}false{% endif %}"
>
<div class="webmentions-actions webmentions-actions--empty">
<a class="webmentions-action" href="mailto:{{ metadata.author.email }}?subject=Comment on: {{ title | urlencode }}">
<span class="webmentions-action-icon" aria-hidden="true">{% phicon "envelope-simple" %}</span>
Comment via Email
</a>
</div>
<div class="webmentions-actions webmentions-actions--full">
{%- if mastodon %}
<a class="webmentions-action" href="{{ mastodon }}" target="_blank" rel="noopener noreferrer">
<span class="webmentions-action-icon" aria-hidden="true">{% phicon "chat-text" %}</span>
Comment on Mastodon
</a>
{%- endif %}
<a class="webmentions-action" href="mailto:{{ metadata.author.email }}?subject=Comment on: {{ title | urlencode }}">
<span class="webmentions-action-icon" aria-hidden="true">{% phicon "envelope-simple" %}</span>
Comment via Email
</a>
</div>
<div class="webmentions-header">
<div class="webmentions-title-row">
<h3 class="webmentions-title">Webmentions</h3>
<div class="webmentions-reactions">
{% if likes.length %}
<span class="webmentions-reaction">❤️ {{ likes.length }}</span>
{% endif %}
{% if reposts.length %}
<span class="webmentions-reaction">🔁 {{ reposts.length }}</span>
{% endif %}
</div>
</div>
<a class="webmentions-help" href="/webmention/" aria-label="What’s this?">
<span class="webmentions-help-text">What’s this?</span>
</a>
</div>
<ul class="webmentions-list">
{% for m in replies %}
<li class="webmention-item">
<div class="webmention-avatar-wrap">
{% set authorLabel = m.author.name or (m.url | replace("https://", "") | replace("http://", "")) %}
<div class="webmention-avatar" role="img" aria-label="{{ authorLabel }}" data-has-img="false">
{% if m.author and m.author.photo %}
<img
src="{{ m.author.photo }}"
alt=""
data-no-picture
loading="lazy"
decoding="async"
onload="var a=this.closest('.webmention-avatar'); if (a) a.dataset.hasImg='true';"
onerror="var a=this.closest('.webmention-avatar'); if (a) a.dataset.hasImg='false'; this.remove();"
>
{% endif %}
<span class="webmention-avatar-fallback" aria-hidden="true">?</span>
</div>
</div>
<div class="webmention-body">
<div class="webmention-meta">
<a href="{{ m.url }}" class="webmention-author" target="_blank" rel="noopener">
{{ m.author.name or (m.url | replace("https://", "") | replace("http://", "")) }}
</a>
{% if m.published %}
<span class="webmention-date">· {{ m.published | webmentionDate }}</span>
{% endif %}
</div>
{% if m.content and (m.content.text or m.content.html) %}
{% set replyText = m.content.text or (m.content.html | striptags) %}
<div class="webmention-bubble">
<p class="webmention-text">{{ replyText | truncate(260) }}</p>
</div>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
</section>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment