Display webmentions

This commit is contained in:
Devon Campbell 2024-06-20 14:25:29 -04:00
commit 91326c03fc
11 changed files with 314 additions and 15 deletions

View file

@ -7,6 +7,15 @@ const markdownItAttrs = require("markdown-it-attrs");
const markdownItEleventyImg = require("markdown-it-eleventy-img");
const pluginRss = require("@11ty/eleventy-plugin-rss");
const embedYouTube = require("eleventy-plugin-youtube-embed");
const sanitizeHtml = require("sanitize-html");
const metadata = require("./src/_data/metadata.json");
const sanitize = (html) => {
return sanitizeHtml(html, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
allowedAttributes: { 'a': ['href'] }
});
};
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(embedYouTube);
@ -57,7 +66,49 @@ module.exports = function(eleventyConfig) {
return '<iframe width="560" height="315" src="https://www.youtube.com/embed/' + videoId + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
});
});
eleventyConfig.addFilter('webmentionsByUrl', function(webmentions, url) {
const allowedTypes = ['in-reply-to', 'like-of', 'repost-of'];
const hasRequiredFields = mention => {
const { author, published, content } = mention;
return author.name && published && content;
};
const matchesUrl = mention => {
return mention['wm-target'] === url;
};
const filteredMentions = webmentions
.filter(mention => {
const isAllowedType = allowedTypes.includes(mention['wm-property']);
if (isAllowedType) {
const isReply = mention['wm-property'] === 'in-reply-to';
if (isReply) {
return matchesUrl(mention) && hasRequiredFields(mention);
}
return matchesUrl(mention);
}
}
);
const sanitizedMentions = filteredMentions.map((mention) => {
if (mention?.content?.html) {
mention.content.html = sanitize(mention.content.html);
const mastodonInstance = metadata.author.mastodon.instance;
const mastodonUsername = metadata.author.mastodon.username;
const mentionLink = `<a href="https://${mastodonInstance}/@${mastodonUsername}">@${metadata.author.mastodon.username}</a> `;
if (mention.content.html.startsWith(mentionLink)) {
mention.content.html = mention.content.html.substring(mentionLink.length)
}
}
return mention;
});
const sortedMentions = sanitizedMentions.reduce((accumulator, mention) => {
const key = mention['wm-property'];
const curGroup = accumulator[key] ?? [];
return { ...accumulator, [key]: [...curGroup, mention] };
}, {});
console.log(sortedMentions);
return sortedMentions;
});
const markdownLibrary = markdownIt({ html: true })
.use(markdownItAnchor, { slugify })

View file

@ -325,6 +325,81 @@ p:first-of-type {
justify-self: end;
}
.webmentions-likes, .webmentions-boosts, .webmentions-replies {
list-style-type: none;
padding: 0;
margin: 0;
}
.webmentions-replies li {
display: flex;
align-items: flex-start;
}
.webmentions-replies li:not(:last-child) {
margin-bottom: 1.25rem;
}
.webmentions-replies li > a,
.webmentions-likes li > a,
.webmentions-boosts li > a {
display: block;
position: relative;
}
.webmentions-likes li > a,
.webmentions-boosts li > a {
width: 4rem;
height: 4rem;
}
.webmentions-replies li > a {
width: 6rem;
height: 6rem;
margin-right: 1rem;
}
.webmentions-replies .from-mastodon > a::before,
.webmentions-likes .from-mastodon > a::before,
.webmentions-boosts .from-mastodon > a::before {
content: "";
position: absolute;
background: url('/images/mastodon-logo.png') no-repeat center center;
background-size: contain;
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5));
}
.webmentions-replies .from-mastodon > a::before {
bottom: 0.25rem;
left: 0.25rem;
width: 1.25rem;
height: 1.25rem;
}
.webmentions-likes .from-mastodon > a::before,
.webmentions-boosts .from-mastodon > a::before {
bottom: 0.15rem;
left: 0.15rem;
width: 1rem;
height: 1rem;
}
.webmentions-replies li img {
width: 6rem;
}
.webmentions-replies li .content {
flex: 1;
}
.webmentions-replies li .content .published-datetime {
font-size: 0.8em;
opacity: 0.8;
}
.webmentions-replies li .content > a {
color: var(--text);
}
.webmentions-likes, .webmentions-boosts {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.webmentions-likes li img, .webmentions-boosts li img {
width: 4rem;
}
.mention-heading {
margin-bottom: 1.2rem;
}
.hidden {
display: none;
}

100
package-lock.json generated
View file

@ -1621,6 +1621,12 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"dev": true
},
"is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
@ -2173,6 +2179,12 @@
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"dev": true
},
"nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
@ -2396,6 +2408,12 @@
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
"dev": true
},
"picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -2417,6 +2435,17 @@
"semver-compare": "^1.0.0"
}
},
"postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true,
"requires": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.2.0"
}
},
"posthtml": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz",
@ -2765,6 +2794,71 @@
"truncate-utf8-bytes": "^1.0.0"
}
},
"sanitize-html": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz",
"integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==",
"dev": true,
"requires": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
},
"dependencies": {
"dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
}
},
"domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0"
}
},
"domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"requires": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
}
},
"entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true
},
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
}
}
},
"sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@ -2894,6 +2988,12 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",

View file

@ -25,6 +25,7 @@
"markdown-it-attrs": "^4.1.4",
"markdown-it-eleventy-img": "^0.9.0",
"markdown-it-obsidian": "github:raddevon/markdown-it-obsidian",
"sanitize-html": "^2.13.0",
"simpledotcss": "^2.1.0"
}
}

14
src/_data/metadata.json Normal file
View file

@ -0,0 +1,14 @@
{
"title": "devon.lol",
"description": "I write essays to no one about video games, technology, the internet, and other things in the world.",
"language": "en-us",
"url": "https://devon.lol/",
"author": {
"name": "Devon Campbell",
"email": "devon@devon.lol",
"mastodon": {
"username": "RadDevon",
"instance": "techhub.social"
}
}
}

View file

@ -3,11 +3,10 @@ const EleventyFetch = require("@11ty/eleventy-fetch");
module.exports = async function fetchMentions() {
const url = `https://webmention.io/api/mentions.jf2?domain=devon.lol&token=${process.env.WEBMENTIONSIO_TOKEN}`;
const mentions = await EleventyFetch(url, {
const mentionsResponse = await EleventyFetch(url, {
duration: "1s",
type: "json",
});
console.log('mentions:', mentions);
console.log('mentions.children:', mentions.children);
return mentions.children;
const mentions = mentionsResponse.children;
return mentions;
};

View file

@ -22,10 +22,11 @@ layout: base.njk
{{ content | safe }}
</div>
<a class="u-url hidden" href="{{ page.url }}">Permalink</a>
{% include 'webmentions.njk' %}
</article>
<footer>
<p>Talk to me <a href="https://techhub.social/@RadDevon" rel="me">on Mastodon.</a></p>
<a href="https://techhub.social/@RadDevon" rel="me"><img src="/images/mastodon-logo.png" alt="Mastodon logo"></a>
<p>Talk to me <a href="https://{{ metadata.author.mastodon.instance }}/@{{ metadata.author.mastodon.username }}" rel="me">on Mastodon.</a></p>
<a href="https://{{ metadata.author.mastodon.instance }}/@{{ metadata.author.mastodon.username }}" rel="me"><img src="/images/mastodon-logo.png" alt="Mastodon logo"></a>
<a href="mailto:devon@devon.lol" rel="me" class="hidden">devon@devon.lol</a>
</footer>

View file

@ -16,7 +16,7 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="me" href="https://techhub.social/@RadDevon" />
<link rel="me" href="https://{{ metadata.author.mastodon.instance }}/@{{ metadata.author.mastodon.username }}" />
<link rel="me" href="https://github.com/raddevon" />
<link rel="webmention" href="https://webmention.io/devon.lol/webmention" />

View file

@ -0,0 +1,64 @@
{% set pageAbsoluteUrl = page.url | absoluteUrl(metadata.url) %}
{% set mentions = webmentions | webmentionsByUrl(pageAbsoluteUrl) %}
{% set mastodonSourcePrefix = r/^https:\/\/brid\.gy\/\w+\/mastodon\// %}
{% if mentions['in-reply-to'] | length %}
<h2 class="mention-heading">Replies</h2>
<ol class="webmentions-replies">
{%- for reply in mentions['in-reply-to'] %}
<li class="{% if mastodonSourcePrefix.test(reply['wm-source']) %}from-mastodon{% endif %}">
<a rel="noopener" href="{{ reply.author.url }}">
<img
src="{{ reply.author.photo }}"
title="{{ reply.author.name }}"
alt="{{ reply.author.name }}'s avatar"
>
</a>
<div class="content">
<a href="{{ reply.url }}">
<time
class="published-datetime"
datetime="{{ reply.published }}"
aria-label="Published on {{ reply.published | formatDatetimeForHumans }}"
>{{ reply.published | formatDatetimeForHumans }}</time>
</a>
<p>{{ reply.content.html | safe }}</p>
</div>
</li>
{% endfor %}
</ol>
{% endif %}
{% if mentions['repost-of'] | length %}
<h2 class="mention-heading">Boosts</h2>
<ol class="webmentions-boosts">
{%- for boost in mentions['repost-of'] %}
<li class="{% if mastodonSourcePrefix.test(boost['wm-source']) %}from-mastodon{% endif %}">
<a rel="noopener" href="{{boost.author.url}}">
<img
src="{{boost.author.photo}}"
title="{{boost.author.name}}"
alt="{{boost.author.name}}'s avatar"
>
</a>
</li>
{% endfor%}
</ol>
{% endif %}
{% if mentions['like-of'] | length %}
<h2 class="mention-heading">Likes</h2>
<ol class="webmentions-likes">
{%- for like in mentions['like-of'] %}
<li class="{% if mastodonSourcePrefix.test(like['wm-source']) %}from-mastodon{% endif %}">
<a rel="noopener" href="{{like.author.url}}">
<img
src="{{like.author.photo}}"
title="{{like.author.name}}"
alt="{{like.author.name}}'s avatar"
>
</a>
</li>
{% endfor%}
</ol>
{% endif %}

View file

@ -2,14 +2,6 @@
permalink: feed.xml
eleventyExcludeFromCollections: true
date: git Last Modified
metadata:
title: devon.lol
description: I write essays to no one about video games, technology, the internet, and other things in the world.
language: en-us
url: https://devon.lol/
author:
name: Devon Campbell
email: devon@devon.lol
---
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">

View file

@ -7,4 +7,6 @@ date: git Last Modified
**Last updated {{ page.date | formatDatetimeForHumans }}**
This site now both sends and receives WebMentions!
The Arc browser scares me. All the AI features and their insistence on referring to users as "members" just makes me feel like they're poised to make changes I couldn't live with. I decided to get ahead of them and [find an alternative browser](/blog/web-browser-review-06-2024).