Merge remote-tracking branch 'origin/main'

This commit is contained in:
Dan 2026-01-16 11:12:23 +00:00
commit c80f39db49
5 changed files with 225 additions and 5 deletions

203
assets/js/webmentions.js Normal file
View file

@ -0,0 +1,203 @@
/**
* Webmention utilities for ritual.sh
* Fetches and formats webmentions from the API
*/
class WebmentionUtils {
constructor() {
this.apiUrl = "https://api.ritual.sh";
}
/**
* Fetch webmentions for a given target URL
* @param {string} targetUrl - The page URL to get webmentions for
* @param {number} limit - Maximum number of webmentions to fetch (default: 100)
* @returns {Promise<Array>} Array of webmention objects
*/
async fetch(targetUrl, limit = 100) {
try {
const params = new URLSearchParams({
target: targetUrl,
limit: limit.toString(),
});
const response = await fetch(`${this.apiUrl}/webmentions?${params}`);
if (!response.ok) {
console.error(`Webmention API error: ${response.status}`);
return [];
}
const data = await response.json();
return data.mentions || [];
} catch (error) {
console.error("Error fetching webmentions:", error);
return [];
}
}
/**
* Format webmentions as a comma-separated list of links
* @param {Array} mentions - Array of webmention objects
* @returns {string} HTML string of comma-separated links
*/
asCommaSeparatedList(mentions) {
if (!mentions || mentions.length === 0) {
return "";
}
return mentions
.map((mention) => {
const url = mention.author_url || mention.source;
const domain = this.formatDomain(url);
const escapedUrl = this.escapeHtml(url);
const escapedDomain = this.escapeHtml(domain);
return `<a href="${escapedUrl}" target="_blank">${escapedDomain}</a>`;
})
.join(", ");
}
/**
* Format a URL to display just the domain (no protocol, www, or path)
* @param {string} url - The URL to format
* @returns {string} Clean domain name
*/
formatDomain(url) {
if (!url) return "";
try {
const urlObj = new URL(url);
let domain = urlObj.hostname;
// Remove www. prefix if present
if (domain.startsWith("www.")) {
domain = domain.substring(4);
}
return domain;
} catch (e) {
// Fallback: strip protocol and www manually
return url
.replace(/^https?:\/\//, "")
.replace(/^www\./, "")
.split("/")[0];
}
}
/**
* Escape HTML to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped HTML string
*/
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
/**
* Auto-initialize all webmention elements
*/
async autoInit() {
// Group elements by target URL to avoid duplicate API calls
const targetGroups = new Map();
const selectors = [
"[data-webmention-list]",
"[data-webmention-show]",
"[data-webmention-hide]",
"[data-webmention-text]",
];
selectors.forEach((selector) => {
document.querySelectorAll(selector).forEach((element) => {
const targetUrl = this.getTargetUrl(element) || window.location.href;
if (!targetGroups.has(targetUrl)) {
targetGroups.set(targetUrl, []);
}
targetGroups.get(targetUrl).push(element);
});
});
// Fetch and process each target URL
for (const [targetUrl, elements] of targetGroups) {
const mentions = await this.fetch(targetUrl);
const hasMentions = mentions.length > 0;
elements.forEach((element) => {
this.processElement(element, mentions, hasMentions);
});
}
}
/**
* Get the target URL from an element's data attributes or parent container
*/
getTargetUrl(element) {
// First check the element's own attributes for a non-empty value
const ownUrl =
element.dataset.webmentionList ||
element.dataset.webmentionShow ||
element.dataset.webmentionHide ||
element.dataset.webmentionText ||
"";
if (ownUrl) {
return ownUrl;
}
// Check for parent container with data-webmention-target
const container = element.closest("[data-webmention-target]");
if (container) {
return container.dataset.webmentionTarget;
}
return "";
}
/**
* Process a single element based on its data attributes
*/
processElement(element, mentions, hasMentions) {
// Handle data-webmention-list
if (element.hasAttribute("data-webmention-list")) {
element.innerHTML = this.asCommaSeparatedList(mentions);
}
// Handle data-webmention-show (visible only if mentions exist)
if (element.hasAttribute("data-webmention-show")) {
element.style.display = hasMentions ? "" : "none";
}
// Handle data-webmention-hide (hidden if mentions exist)
if (element.hasAttribute("data-webmention-hide")) {
element.style.display = hasMentions ? "none" : "";
}
// Handle data-webmention-text (different text based on count)
if (element.hasAttribute("data-webmention-text")) {
const textConfig = element.dataset.webmentionText;
// Format: "none text|single text|plural text" or just check for pipe
if (textConfig.includes("|")) {
const parts = textConfig.split("|");
if (mentions.length === 0) {
element.textContent = parts[0] || "";
} else if (mentions.length === 1) {
element.textContent = parts[1] || parts[0] || "";
} else {
element.textContent = parts[2] || parts[1] || parts[0] || "";
}
}
}
}
}
// Create global instance
window.WebmentionUtils = new WebmentionUtils();
// Auto-initialize on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
window.WebmentionUtils.autoInit();
});
} else {
window.WebmentionUtils.autoInit();
}

View file

@ -7,10 +7,14 @@ tags:
draft: false
---
- 🗨️ Added the ability to send and receive [webmentions](https://indieweb.org/Webmention) to the blog. Haven't automated displaying of received ones yet, but I'll get there.
- 🗨️ Added the ability to send and receive [webmentions](https://indieweb.org/Webmention) to the blog.
- ✉️ Received an actual real life email from [Dan](https://danq.me/) to let me know he had sent the first one and it wasn't showing up, so I actually had to rush out the code to display webmentions, too!
- 🧰 Setup a little personal API to handle the guestbook, visitor counter, and aforementioned webmentions.
- 📺 Setup a dashboard on my homelab for monitoring all of the above.
- 🛜 Added an RSS feed for the blog. I've made my full posts available via RSS so you can consume them however you please.
- 💏 Made some significant progress towards our plans for moving house, and life in general!
- 🎁 Found some awesome little bits of tech and my wife kindly agreed to get them for my birthday next month, they will absolutely be showing up on the blog some time after that.
- 🧑‍💻 Wrote a script that can take a video with subtitles and a string of text, and return the gif of the clip. It'll be online shortly on GitHub and my resources section.
## Links I Found Interesting
@ -18,6 +22,8 @@ draft: false
- [Zootopia 2](https://blog.yiningkarlli.com/2025/12/zootopia-2.html) - CG Engineer from Walt Disney Studios talks about some of the technical stuff from making Zootopia 2.
- [What I Have Learned Being on the IndieWeb for a Month](https://brennan.day/what-i-have-learned-being-on-the-indieweb-for-a-month/) - Brennan reviews what he has learnt from being on the IndieWeb for a month, and I definitely feel like I should post something similar very soon - I still have a few week or so!
## Music
- 📺 [Mortal Kombat x Rhythm is a Dancer](https://www.youtube.com/watch?v=vKxn6P947PE)

View file

@ -36,6 +36,6 @@ Big thanks to [neonaut's 88x31 archive](https://neonaut.neocities.org/cyber/88x3
[fyr.io](https://fyr.io/scrap/2026-01-09),
[brennan.day](https://brennan.day/resources-for-the-personal-web-a-follow-up-guide/),
[kuemmerle.name](https://kuemmerle.name/bastelglueck/),
[craney.uk](https://craney.uk/posts/stuff-this-week-74)
[craney.uk](https://craney.uk/posts/stuff-this-week-74), <span data-webmention-list></span>
Am I missing you? Email me or send a webmention!

View file

@ -22,7 +22,19 @@
{{ end }}
<div class="blog-summary">{{ .Content }}</div>
<div class="blog-footer">
🛜 <a href="/blog/feed.xml">Available via RSS</a>
🛜 <a href="/blog/feed.xml">Available via RSS</a> <br /><br />
You can respond to this post via
<a
href="https://indieweb.org/Webmention"
target="_blank"
rel="noopener noreferrer"
>webmentions</a
>. <br /><br />
<div data-webmention-target>
<span data-webmention-show>
Mentioned by: <span data-webmention-list></span
></span>
</div>
</div>
</article>

View file

@ -6,10 +6,9 @@ From: ritual.sh
/* THANKS */
Hugo - Static site generator
The IndieWeb community
Everyone still making personal websites in {{ now.Year }}
Everyone still making personal websites in 2026
/* SITE */
Last update: {{ .Lastmod.Format "2006/01/02" }}
Standards: HTML5, CSS3, RSS
Components: Hugo, YAML, Markdown
Software: Built with care