new listings daily roundups rss

This commit is contained in:
2026-05-03 08:11:23 -07:00
parent d3441c94b0
commit 7a7e977270
28 changed files with 611 additions and 4 deletions

20
node_modules/@11ty/eleventy-plugin-rss/.eleventy.js generated vendored Normal file
View File

@ -0,0 +1,20 @@
import rssPlugin from "./src/rssPlugin.js";
import dateRfc3339 from "./src/dateRfc3339.js";
import dateRfc822 from "./src/dateRfc822.js";
import getNewestCollectionItemDate from "./src/getNewestCollectionItemDate.js";
import virtualTemplate from "./src/virtualTemplate.js";
import absoluteUrl from "./src/absoluteUrl.js";
import convertHtmlToAbsoluteUrls from "./src/htmlToAbsoluteUrls.js";
export default rssPlugin;
export {
rssPlugin,
virtualTemplate as feedPlugin,
dateRfc3339 as dateToRfc3339,
dateRfc822 as dateToRfc822,
getNewestCollectionItemDate as getNewestCollectionItemDate,
absoluteUrl as absoluteUrl,
convertHtmlToAbsoluteUrls as convertHtmlToAbsoluteUrls
};

21
node_modules/@11ty/eleventy-plugin-rss/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Zach Leatherman @zachleat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
node_modules/@11ty/eleventy-plugin-rss/README.md generated vendored Normal file
View File

@ -0,0 +1,34 @@
<p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="11ty Logo"></p>
# eleventy-plugin-rss 🕚⚡️🎈🐀
A pack of [Eleventy](https://github.com/11ty/eleventy) filters for generating Atom, JSON and RSS feeds using the Nunjucks templating engine.
See `sample/feed.njk` for an example Atom feed template, `sample/feed.json` for an example JSON feed template, or `sample/feed-rss.njk` for an example RSS feed template.
## [The full `eleventy-plugin-rss` documentation is on 11ty.dev](https://www.11ty.dev/docs/plugins/rss/).
* _This is a plugin for the [Eleventy static site generator](https://www.11ty.dev/)._
* Find more [Eleventy plugins](https://www.11ty.dev/docs/plugins/).
* Please star [Eleventy on GitHub](https://github.com/11ty/eleventy/), follow [@eleven_ty](https://twitter.com/eleven_ty) on Twitter, and support [11ty on Open Collective](https://opencollective.com/11ty)
[![npm Version](https://img.shields.io/npm/v/@11ty/eleventy-plugin-rss.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy-plugin-rss) [![GitHub issues](https://img.shields.io/github/issues/11ty/eleventy-plugin-rss.svg?style=for-the-badge)](https://github.com/11ty/eleventy-plugin-rss/issues)
## Installation
```
npm install @11ty/eleventy-plugin-rss
```
_[The full `eleventy-plugin-rss` documentation is on 11ty.dev](https://www.11ty.dev/docs/plugins/rss/)._
## Tests
```
npm run test
```
- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md))
- To keep tests fast, thou shalt try to avoid writing files in tests.

50
node_modules/@11ty/eleventy-plugin-rss/package.json generated vendored Normal file
View File

@ -0,0 +1,50 @@
{
"name": "@11ty/eleventy-plugin-rss",
"version": "3.0.0",
"type": "module",
"description": "Generate an Atom, RSS, or JSON feed.",
"publishConfig": {
"access": "public"
},
"main": ".eleventy.js",
"scripts": {
"test": "npx ava",
"sample": "cd sample && npx @11ty/eleventy --config=config-sample.js --pathprefix=pathprefix",
"clean": "rm -rf sample/_site"
},
"repository": {
"type": "git",
"url": "git+https://github.com/11ty/eleventy-plugin-rss.git"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/11ty"
},
"keywords": [
"eleventy",
"eleventy-plugin"
],
"author": {
"name": "Zach Leatherman",
"email": "zachleatherman@gmail.com",
"url": "https://zachleat.com/"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/11ty/eleventy-plugin-rss/issues"
},
"homepage": "https://www.11ty.dev/docs/plugins/rss/",
"11ty": {
"compatibility": ">=3.0.0-alpha.15"
},
"devDependencies": {
"@11ty/eleventy": "^3.1.5",
"ava": "^6.4.1"
},
"dependencies": {
"@11ty/eleventy-utils": "^2.0.7",
"@11ty/posthtml-urls": "^1.0.2",
"debug": "^4.4.3",
"posthtml": "^0.16.7"
}
}

View File

@ -0,0 +1,13 @@
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Rss");
// This is deprecated! Use the Eleventy HTML <base> plugin instead (2.0+)
export default function(url, base) {
try {
return (new URL(url, base)).toString()
} catch(e) {
debug("Trying to convert %o to be an absolute url with base %o and failed, returning: %o (invalid url)", url, base, url)
// TODO add debug output!
return url;
}
};

View File

@ -0,0 +1,11 @@
// Atom uses RFC 3339 dates
// https://tools.ietf.org/html/rfc3339#section-5.8
export default function(dateObj) {
let s = dateObj.toISOString();
// remove milliseconds
let split = s.split(".");
split.pop();
return split.join("") + "Z";
}

View File

@ -0,0 +1,23 @@
export default function pubDateRFC822(value, timeZone = undefined) {
const date = new Date(value);
const options = {
weekday: 'short',
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
timeZone: timeZone,
timeZoneName: 'longOffset',
};
const formattedDate = new Intl.DateTimeFormat('en-US', options).format(date);
const [wkd, mmm, dd, yyyy, time, z] = formattedDate.replace(/([,\s]+)/g, ' ').split(' ');
const tz = z.replace(/GMT(?<sign>\+|\-)(?<hour>\d\d):(?<minute>\d\d)/, '$<sign>$<hour>$<minute>');
return `${wkd}, ${dd} ${mmm} ${yyyy} ${time} ${tz}`;
}

View File

@ -0,0 +1,7 @@
export default function(collection, emptyFallbackDate) {
if( !collection || !collection.length ) {
return emptyFallbackDate || new Date();
}
return new Date(Math.max(...collection.map(item => {return item.date})));
}

View File

@ -0,0 +1,21 @@
import posthtml from 'posthtml';
import urls from '@11ty/posthtml-urls';
import absoluteUrl from "./absoluteUrl.js";
// This is deprecated! Use the Eleventy HTML <base> plugin instead (2.0+)
export default async function(htmlContent, base, processOptions = {}) {
if( !base ) {
throw new Error( "eleventy-plugin-rss: htmlToAbsoluteUrls(absolutePostUrl) was missing the full URL base `absolutePostUrl` argument.")
}
let options = {
eachURL: function(url) {
return absoluteUrl(url.trim(), base);
}
};
let modifier = posthtml().use(urls(options));
let result = await modifier.process(htmlContent, processOptions);
return result.html;
};

View File

@ -0,0 +1,60 @@
import pkg from "../package.json" with {type: "json"};
import dateRfc3339 from "./dateRfc3339.js";
import dateRfc822 from "./dateRfc822.js";
import getNewestCollectionItemDate from "./getNewestCollectionItemDate.js";
import absoluteUrl from "./absoluteUrl.js";
import convertHtmlToAbsoluteUrls from "./htmlToAbsoluteUrls.js";
export default function eleventyRssPlugin(eleventyConfig, options = {}) {
eleventyConfig.versionCheck(pkg["11ty"].compatibility);
// Guaranteed unique, first add wins
const pluginHtmlBase = eleventyConfig.resolvePlugin("@11ty/eleventy/html-base-plugin");
eleventyConfig.addPlugin(pluginHtmlBase, options.htmlBasePluginOptions || {});
// Dates
eleventyConfig.addNunjucksFilter("getNewestCollectionItemDate", getNewestCollectionItemDate);
eleventyConfig.addNunjucksFilter("dateToRfc3339", dateRfc3339);
eleventyConfig.addNunjucksFilter("dateToRfc822", dateRfc822);
// Deprecated in favor of the more efficient HTML <base> plugin bundled with Eleventy
eleventyConfig.addNunjucksFilter("absoluteUrl", absoluteUrl);
// Deprecated in favor of the more efficient HTML <base> plugin bundled with Eleventy
eleventyConfig.addNunjucksAsyncFilter("htmlToAbsoluteUrls", (htmlContent, base, callback) => {
if(!htmlContent) {
callback(null, "");
return;
}
let posthtmlOptions = Object.assign({
// default PostHTML render options
closingSingleTag: "slash"
}, options.posthtmlRenderOptions);
convertHtmlToAbsoluteUrls(htmlContent, base, posthtmlOptions).then(html => {
callback(null, html);
});
});
// These are removed, their names are incorrect! Issue #8, #21
eleventyConfig.addNunjucksFilter("rssLastUpdatedDate", () => {
throw new Error("The `rssLastUpdatedDate` filter was removed. Use `getNewestCollectionItemDate | dateToRfc3339` (for Atom) or `getNewestCollectionItemDate | dateToRfc822` (for RSS) instead.")
});
eleventyConfig.addNunjucksFilter("rssDate", () => {
throw new Error("The `rssDate` filter was removed. Use `dateToRfc3339` (for Atom) or `dateToRfc822` (for RSS) instead.");
});
};
Object.defineProperty(eleventyRssPlugin, "eleventyPackage", {
value: pkg.name
});
Object.defineProperty(eleventyRssPlugin, "eleventyPluginOptions", {
value: {
unique: true
}
});

View File

@ -0,0 +1,204 @@
import debugUtil from "debug";
import pkg from "../package.json" with {type: "json"};
import { DeepCopy } from "@11ty/eleventy-utils";
import rssPlugin from "./rssPlugin.js";
const debug = debugUtil("Eleventy:Rss:Feed");
function getFeedContent({ type, stylesheet, collection, script }) {
// Note: page.lang comes from the i18n plugin: https://www.11ty.dev/docs/plugins/i18n/#page.lang
if(type === "rss") {
// Nunjucks template
return `<?xml version="1.0" encoding="utf-8"?>
${stylesheet ? `<?xml-stylesheet href="${stylesheet}" type="text/xsl"?>\n` : ""}<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="{{ metadata.base | addPathPrefixToFullUrl }}" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
${script ? `<script src="${script}" xmlns="http://www.w3.org/1999/xhtml"></script>` : ""}
<title>{{ metadata.title }}</title>
<link>{{ metadata.base | addPathPrefixToFullUrl }}</link>
<atom:link href="{{ permalink | htmlBaseUrl(metadata.base) }}" rel="self" type="application/rss+xml" />
<description>{{ metadata.subtitle }}</description>
<language>{{ metadata.language or page.lang }}</language>
{%- if metadata.icon %}<image>{{ metadata.icon }}</image>{%- endif %}
{%- for post in collections.${collection.name} | reverse | eleventyFeedHead(${collection.limit}) %}
{%- set absolutePostUrl = post.url | htmlBaseUrl(metadata.base) %}
<item>
<title>{{ post.data.title }}</title>
<link>{{ absolutePostUrl }}</link>
{%- if (post.data.summary) -%}
<description>{{ post.data.summary }}</description>
<content:encoded>{{ post.content | renderTransforms(post.data.page, metadata.base) }}</content:encoded>
{%- else -%}
<description>{{ post.content | renderTransforms(post.data.page, metadata.base) }}</description>
{%- endif -%}
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
<dc:creator>{{ metadata.author.name }}</dc:creator>
<guid>{{ absolutePostUrl }}</guid>
</item>
{%- endfor %}
</channel>
</rss>`;
}
if(type === "atom") {
// Nunjucks template
return `<?xml version="1.0" encoding="utf-8"?>
${stylesheet ? `<?xml-stylesheet href="${stylesheet}" type="text/xsl"?>\n` : ""}<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ metadata.language or page.lang }}">
${script ? `<script src="${script}" xmlns="http://www.w3.org/1999/xhtml"></script>` : ""}
<title>{{ metadata.title }}</title>
<subtitle>{{ metadata.subtitle }}</subtitle>
<link href="{{ permalink | htmlBaseUrl(metadata.base) }}" rel="self" />
<link href="{{ metadata.base | addPathPrefixToFullUrl }}" />
<updated>{{ collections['${collection.name}'] | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
<id>{{ metadata.base | addPathPrefixToFullUrl }}</id>
{%- if metadata.icon %}
<icon>{{ metadata.icon }}</icon>
{%- endif %}
{%- if metadata.logo %}
<icon>{{ metadata.logo }}</icon>
{%- endif %}
<author>
<name>{{ metadata.author.name }}</name>
{%- if metadata.author.email %}
<email>{{ metadata.author.email }}</email>
{%- endif %}
</author>
{%- for post in collections['${collection.name}'] | reverse | eleventyFeedHead(${collection.limit}) %}
{%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.base) }}{% endset %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}" />
<updated>{{ post.date | dateToRfc3339 }}</updated>
<id>{{ absolutePostUrl }}</id>
{%- if post.data.summary %}
<summary>{{ post.data.summary }}</summary>
{%- endif %}
<content type="html">{{ post.content | renderTransforms(post.data.page, metadata.base) }}</content>
</entry>
{%- endfor %}
</feed>`;
}
if(type === "json") {
return `{
"version": "https://jsonfeed.org/version/1.1",
"title": "{{ metadata.title }}",
"language": "{{ metadata.language or page.lang }}",
"home_page_url": "{{ metadata.base | addPathPrefixToFullUrl }}",
"feed_url": "{{ permalink | htmlBaseUrl(metadata.base) }}",
"description": "{{ metadata.description }}",
"authors": [
{
"name": "{{ metadata.author.name }}"{% if metadata.author.email %},
"url": "mailto:{{ metadata.author.email }}"
{%- endif %}
}
],
"items": [
{%- for post in collections['${collection.name}'] | reverse | eleventyFeedHead(${collection.limit}) %}
{%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.base) }}{% endset %}
{
"id": "{{ absolutePostUrl }}",
"url": "{{ absolutePostUrl }}",
"title": "{{ post.data.title }}",
"content_html": {% if post.content %}{{ post.content | renderTransforms(post.data.page, metadata.base) | dump | safe }}{% else %}""{% endif %},
"date_published": "{{ post.date | dateToRfc3339 }}"
}
{% if not loop.last %},{% endif %}
{%- endfor %}
]
}`
}
throw new Error("Missing or invalid feed type. Received: " + type);
}
export default function eleventyFeedPlugin(eleventyConfig, options = {}) {
eleventyConfig.versionCheck(pkg["11ty"].compatibility);
// Guaranteed unique, first add wins
const pluginHtmlBase = eleventyConfig.resolvePlugin("@11ty/eleventy/html-base-plugin");
eleventyConfig.addPlugin(pluginHtmlBase, options.htmlBasePluginOptions || {});
// Guaranteed unique, first add wins
eleventyConfig.addPlugin(rssPlugin, options.rssPluginOptions || {});
let slugifyFilter = eleventyConfig.getFilter("slugify");
let inputPathSuffix = options?.metadata?.title ? `-${slugifyFilter(options?.metadata?.title)}` : "";
options = DeepCopy({
// rss and json also supported
type: "atom",
collection: {
name: false, // required
limit: 0, // limit number of entries, 0 means no limit
},
outputPath: "/feed.xml",
inputPath: `eleventy-plugin-feed${inputPathSuffix}-${options.type || "atom"}.njk`, // TODO make this more unique
templateData: {},
metadata: {
title: "Blog Title",
subtitle: "This is a longer description about your blog.",
language: "", // downstream templates use `page.lang` as fallback
base: "https://example.com/",
author: {
name: "Your Name",
email: "", // Optional
}
}
}, options);
if(!options.collection?.name) {
throw new Error("Missing `collection.name` option in feedPlugin from @11ty/eleventy-plugin-rss.");
}
if(typeof options.collection?.name !== "string") {
throw new Error("Only string is supported in `collection.name` option in feedPlugin from @11ty/eleventy-plugin-rss. Received: " + typeof options.collection?.name);
}
let eleventyExcludeFromCollections;
let eleventyImport;
if(options.collection.name === "all") {
eleventyExcludeFromCollections = true;
eleventyImport = {};
} else {
eleventyExcludeFromCollections = [ options.collection.name ]
eleventyImport = {
collections: [ options.collection.name ],
};
}
let templateData = {
...options?.templateData || {},
permalink: options.outputPath,
eleventyExcludeFromCollections,
eleventyImport,
layout: false,
metadata: options.metadata,
};
// Get the first `n` elements of a collection.
eleventyConfig.addFilter("eleventyFeedHead", function(array, n) {
if(!n || n === 0) {
return array;
}
if(n < 0) {
return array.slice(n);
}
return array.slice(0, n);
});
eleventyConfig.addTemplate(options.inputPath, getFeedContent(options), templateData);
};
Object.defineProperty(eleventyFeedPlugin, "eleventyPackage", {
value: `${pkg.name}/feed-plugin`
});
Object.defineProperty(eleventyFeedPlugin, "eleventyPluginOptions", {
value: {
// multiple adds of this one is OK
unique: false
}
});