Skip to content

Commit 2e41008

Browse files
committed
Fixes #63
1 parent ed4cca8 commit 2e41008

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

‎src/virtualTemplate.js‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { createDebug } from "obug";
1+
// import { createDebug } from "obug";
22
import pkg from "../package.json" with {type: "json"};
33

44
import { DeepCopy } from "@11ty/eleventy-utils";
55

66
import rssPlugin from "./rssPlugin.js";
77

8-
const debug = createDebug("Eleventy:Rss:Feed");
8+
// const debug = createDebug("Eleventy:Rss:Feed");
99

1010
function getFeedContent({ type, stylesheet, collection, script }) {
1111
// Note: page.lang comes from the i18n plugin: https://www.11ty.dev/docs/plugins/i18n/#page.lang
1212

13+
// "auto" reverses the (oldest first) collection, "ascending" and "descending" sort by date
14+
let sort = collection.sort === "auto" ? " | reverse" : ` | eleventyFeedSortByDate("${collection.sort}")`;
15+
1316
if(type === "rss") {
1417
// Nunjucks template
1518
return `<?xml version="1.0" encoding="utf-8"?>
@@ -22,7 +25,7 @@ ${stylesheet ? `<?xml-stylesheet href="${stylesheet}" type="text/xsl"?>\n` : ""}
2225
<description>{{ metadata.subtitle }}</description>
2326
<language>{{ metadata.language or page.lang }}</language>
2427
{%- if metadata.icon %}<image>{{ metadata.icon }}</image>{%- endif %}
25-
{%- for post in collections.${collection.name} | reverse | eleventyFeedHead(${collection.limit}) %}
28+
{%- for post in collections.${collection.name}${sort} | eleventyFeedHead(${collection.limit}) %}
2629
{%- set absolutePostUrl = post.url | htmlBaseUrl(metadata.base) %}
2730
<item>
2831
<title>{{ post.data.title }}</title>
@@ -65,7 +68,7 @@ ${stylesheet ? `<?xml-stylesheet href="${stylesheet}" type="text/xsl"?>\n` : ""}
6568
<email>{{ metadata.author.email }}</email>
6669
{%- endif %}
6770
</author>
68-
{%- for post in collections['${collection.name}'] | reverse | eleventyFeedHead(${collection.limit}) %}
71+
{%- for post in collections['${collection.name}']${sort} | eleventyFeedHead(${collection.limit}) %}
6972
{%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.base) }}{% endset %}
7073
<entry>
7174
<title>{{ post.data.title }}</title>
@@ -97,7 +100,7 @@ ${stylesheet ? `<?xml-stylesheet href="${stylesheet}" type="text/xsl"?>\n` : ""}
97100
}
98101
],
99102
"items": [
100-
{%- for post in collections['${collection.name}'] | reverse | eleventyFeedHead(${collection.limit}) %}
103+
{%- for post in collections['${collection.name}']${sort} | eleventyFeedHead(${collection.limit}) %}
101104
{%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.base) }}{% endset %}
102105
{
103106
"id": "{{ absolutePostUrl }}",
@@ -134,6 +137,7 @@ export default function eleventyFeedPlugin(eleventyConfig, options = {}) {
134137
collection: {
135138
name: false, // required
136139
limit: 0, // limit number of entries, 0 means no limit
140+
sort: "auto", // "auto" reverses the collection, "ascending" or "descending" sorts by date
137141
},
138142
outputPath: "/feed.xml",
139143
inputPath: `eleventy-plugin-feed${inputPathSuffix}-${options.type || "atom"}.njk`, // TODO make this more unique
@@ -157,6 +161,10 @@ export default function eleventyFeedPlugin(eleventyConfig, options = {}) {
157161
throw new Error("Only string is supported in `collection.name` option in feedPlugin from @11ty/eleventy-plugin-rss. Received: " + typeof options.collection?.name);
158162
}
159163

164+
if(!["auto", "ascending", "descending"].includes(options.collection.sort)) {
165+
throw new Error("Invalid `collection.sort` option in feedPlugin from @11ty/eleventy-plugin-rss. Expected \"auto\", \"ascending\", or \"descending\", received: " + options.collection.sort);
166+
}
167+
160168
let eleventyExcludeFromCollections;
161169
let eleventyImport;
162170
if(options.collection.name === "all") {
@@ -178,6 +186,11 @@ export default function eleventyFeedPlugin(eleventyConfig, options = {}) {
178186
metadata: options.metadata,
179187
};
180188

189+
// Sort a copy of a collection by date.
190+
eleventyConfig.addFilter("eleventyFeedSortByDate", function(array, direction) {
191+
return [...array].sort((a, b) => direction === "ascending" ? a.date - b.date : b.date - a.date);
192+
});
193+
181194
// Get the first `n` elements of a collection.
182195
eleventyConfig.addFilter("eleventyFeedHead", function(array, n) {
183196
if(!n || n === 0) {

‎test/virtualTemplatesTest.js‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,61 @@ test("JSON virtual template uses `metadata.subtitle` for description", async (t)
7575
let [ feed ] = results.filter(entry => entry.outputPath.endsWith(".json"));
7676
t.is(JSON.parse(feed.content).description, "My subtitle");
7777
});
78+
79+
// https://github.com/11ty/plugin-rss/issues/63
80+
async function getFeedTitles(collectionOptions, reorder) {
81+
const { default: Eleventy } = await import("@11ty/eleventy");
82+
83+
let elev = new Eleventy("./test", "./test/_site", {
84+
config: function (eleventyConfig) {
85+
for(let j = 1; j <= 5; j++) {
86+
eleventyConfig.addTemplate(`post${j}.md`, `# Hello`, { title: `Post ${j}`, date: new Date(Date.UTC(2025, 0, j)), tags: ["blog"] });
87+
}
88+
eleventyConfig.addCollection("posts", (collectionApi) => {
89+
let posts = collectionApi.getFilteredByTag("blog");
90+
return reorder ? reorder(posts) : posts;
91+
});
92+
93+
eleventyConfig.addPlugin(feedPlugin, {
94+
type: "rss",
95+
outputPath: "/feed.xml",
96+
collection: {
97+
name: "posts",
98+
limit: 2,
99+
...collectionOptions,
100+
},
101+
});
102+
},
103+
});
104+
105+
let results = await elev.toJSON();
106+
let [ feed ] = results.filter(entry => entry.outputPath.endsWith(".xml"));
107+
return Array.from(feed.content.matchAll(/<title>(Post \d)<\/title>/g), match => match[1]);
108+
}
109+
110+
test("Feed shows newest entries from an ascending collection (default)", async (t) => {
111+
t.deepEqual(await getFeedTitles({}), ["Post 5", "Post 4"]);
112+
});
113+
114+
test("Feed shows oldest entries from a descending collection with `sort: \"auto\"`", async (t) => {
115+
t.deepEqual(await getFeedTitles({ sort: "auto" }, posts => posts.reverse()), ["Post 1", "Post 2"]);
116+
});
117+
118+
test("Feed shows newest entries from a descending collection with `sort: \"descending\"`", async (t) => {
119+
t.deepEqual(await getFeedTitles({ sort: "descending" }, posts => posts.reverse()), ["Post 5", "Post 4"]);
120+
});
121+
122+
test("Feed shows newest entries from an unordered collection with `sort: \"descending\"`", async (t) => {
123+
let order = [3, 5, 1, 4, 2];
124+
t.deepEqual(await getFeedTitles({ sort: "descending" }, posts => order.map(j => posts[j - 1])), ["Post 5", "Post 4"]);
125+
});
126+
127+
test("Feed shows oldest entries from an unordered collection with `sort: \"ascending\"`", async (t) => {
128+
let order = [3, 5, 1, 4, 2];
129+
t.deepEqual(await getFeedTitles({ sort: "ascending" }, posts => order.map(j => posts[j - 1])), ["Post 1", "Post 2"]);
130+
});
131+
132+
test("Invalid `collection.sort` throws", async (t) => {
133+
let error = await t.throwsAsync(() => getFeedTitles({ sort: "newest" }));
134+
t.regex(error.originalError.message, /collection\.sort/);
135+
});

0 commit comments

Comments
 (0)