#Now via Mastodon in 11ty
The “Now” page[1] of this site pulls it’s content from a Mastodon RSS feed at build time. Which lets me author the content by tooting a “#now” toot. Other people have done similar, but here is how I did that.
First to get started:
npm install rss-parser @11ty/eleventy-fetch
Then we’ll create a now.js
in our _data
folder (assuming you’re using the default folder configuration for 11ty).
The now.js
should look something like this:
const Parser = require("rss-parser");
const { AssetCache } = require("@11ty/eleventy-fetch");
module.exports = async function () {
const parser = new Parser();
// we're going to cache our response
// you'll want to add '.cache' to your .gitignore
let asset = new AssetCache("now_feed");
if (asset.isCacheValid("1d")) {
return asset.getCachedValue();
}
// this is the address of our desired source
// generally Mastodon feeds urls look like address.url/@user.rss
let url = "https://ansible.jshwlkr.info/@jshwlkr.rss";
let feed = await parser.parseURL(url);
let data = feed.items
.filter((item) => {
// we're going to filter our data
// so we only include toots with references
// to the now tag in the 'content'
return item.content.includes("https://ansible.jshwlkr.info/tags/now");
})
.map((item) => {
// a console.log(item)
// here will show you on build what's in the item
// this will give us access to a 'now' collection
// with now.content, now.date, and now.link
return {
content: item.contentSnippet,
date: item.pubDate,
link: item.link,
};
});
await asset.save(data, "json");
return data;
};
We’re only using @11ty/eleventy-fetch
for it’s cache. Is that the best way to do it? Probably not. But it works fine and it we really wanted we could probably write this whole file with @11ty/eleventy-fetch
, but that’s not what I did. We’re using rss-parser
to actually pull the feed and choose what information we want to pass into our collection. The cache code here is basically just a copy and paste from the Eleventy docs.
In the case of this particular flavor of Mastodon the feed item will contain links to the hashtag, so we’re using the filter()
method to only return items which include that particular URL. If you don’t understand how to use the filter()
method or don’t have something you can reliably filter on you can delete it and only use the map()
method.[2] In that case you’ve basically got a big piece of the RSS feed loaded into the now collection, which you can filter in the template instead.[3]
We’ll return the data by way of the map()
method. In this case item.contentSnippet
is a purely flat text representation of the toot. A somewhat more robust HTML representation exists at item.content
, but I chose to ignore it.
The liquid template for the page itself looks like this:
<div class="highlight">
{% for post in now limit: 1 %}
<p>{{ post.content | remove: "#now" }}</p>
<span class="meta">
&middot;
<a href="{{ post.link }}">
{{ post.date | date: "%b %d, %Y" }}
</a>
</span>
{% endfor %}
</div>
The two items of note here are liquid’s limit:1
, which limits the for loop to one iteration. In theory the now collection could include a large number of toots which reference the now tag, I only particularly cared about the first one. Next the remove: "#now"
filter removes all instances of the substring “#now” from the post.content
. In this case I didn’t care for a reference to the now hashtag so I’m removing it via this method.
A now page is about what you’re focused on in this particular time in your life. ↩︎
However, for those who have never extensively used
map()
, just note that you tend not to want to use any kind of conditional shenanigans with themap()
method. Not to make this a JavaScript lesson, but thefilter()
andreduce()
methods are the replacement for your standard conditionals here if you want this to be a frustration-free experience. So if you need to manipulate this feed beyond my choices here, note that you shouldn’t try throwing a lot ofif
at it. ↩︎Can’t say as I’d recommend this method. ↩︎