diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
index 8af04cf9ae22..b207a23623b9 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
@@ -220,6 +220,134 @@ exports[`atom has feed item for each post 1`] = `
]
`;
+exports[`atom has feed item for each post - with trailing slash 1`] = `
+[
+ "
+
+ https://docusaurus.io/myBaseUrl/blog/
+ Hello Blog
+ 2023-07-23T00:00:00.000Z
+ https://github.com/jpmonette/feed
+
+ Hello Blog
+ https://docusaurus.io/myBaseUrl/image/favicon.ico
+ Copyright
+
+
+ https://docusaurus.io/myBaseUrl/blog/blog-with-links/
+
+ 2023-07-23T00:00:00.000Z
+
+ absolute full url
+absolute pathname
+relative pathname
+md link
+anchor
+relative pathname + anchor
+
+
+
+]]>
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/
+
+ 2021-03-06T00:00:00.000Z
+
+ Test MDX with require calls
+
+
+
+
+]]>
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/
+
+ 2021-03-05T00:00:00.000Z
+
+ HTML Heading 1
+HTML Heading 2
+HTML Paragraph
+
+
+Import DOM
+Heading 1
+
+
+
+
+
+
+Normal Text Italics Text Bold Text
+link
]]>
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/
+
+ 2020-08-16T00:00:00.000Z
+
+ complex url slug]]>
+
+
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/simple/slug/
+
+ 2020-08-15T00:00:00.000Z
+
+ simple url slug]]>
+
+ Sébastien Lorber
+ https://sebastienlorber.com
+
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/heading-as-title/
+
+ 2019-01-02T00:00:00.000Z
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/date-matter/
+
+ 2019-01-01T00:00:00.000Z
+
+ date inside front matter]]>
+
+
+
+
+ https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/
+
+ 2018-12-14T00:00:00.000Z
+
+ Happy birthday! (translated)]]>
+
+ Yangshun Tay (translated)
+
+
+ Sébastien Lorber (translated)
+ lorber.sebastien@gmail.com
+
+
+",
+]
+`;
+
exports[`json filters to the first two entries 1`] = `
[
"{
@@ -378,6 +506,102 @@ exports[`json has feed item for each post 1`] = `
]
`;
+exports[`json has feed item for each post - with trailing slash 1`] = `
+[
+ "{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "Hello Blog",
+ "home_page_url": "https://docusaurus.io/myBaseUrl/blog/",
+ "description": "Hello Blog",
+ "items": [
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/blog-with-links/",
+ "content_html": "absolute full url
/nabsolute pathname
/nrelative pathname
/nmd link
/nanchor
/nrelative pathname + anchor
/n/n/n/n",
+ "url": "https://docusaurus.io/myBaseUrl/blog/blog-with-links/",
+ "title": "test links",
+ "summary": "absolute full url",
+ "date_modified": "2023-07-23T00:00:00.000Z",
+ "tags": []
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/",
+ "content_html": "Test MDX with require calls
/n/n/n/n/n",
+ "url": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/",
+ "title": "MDX Blog Sample with require calls",
+ "summary": "Test MDX with require calls",
+ "date_modified": "2021-03-06T00:00:00.000Z",
+ "tags": []
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/",
+ "content_html": "HTML Heading 1
/nHTML Heading 2
/nHTML Paragraph
/n/n/nImport DOM
/nHeading 1
/nHeading 2
/nHeading 3
/nHeading 4
/nHeading 5
/n/n/nNormal Text Italics Text Bold Text
/nlink
",
+ "url": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/",
+ "title": "Full Blog Sample",
+ "summary": "HTML Heading 1",
+ "date_modified": "2021-03-05T00:00:00.000Z",
+ "tags": []
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/",
+ "content_html": "complex url slug
",
+ "url": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/",
+ "title": "Complex Slug",
+ "summary": "complex url slug",
+ "date_modified": "2020-08-16T00:00:00.000Z",
+ "tags": [
+ "date",
+ "complex"
+ ]
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/simple/slug/",
+ "content_html": "simple url slug
",
+ "url": "https://docusaurus.io/myBaseUrl/blog/simple/slug/",
+ "title": "Simple Slug",
+ "summary": "simple url slug",
+ "date_modified": "2020-08-15T00:00:00.000Z",
+ "author": {
+ "name": "Sébastien Lorber",
+ "url": "https://sebastienlorber.com"
+ },
+ "tags": []
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/heading-as-title/",
+ "content_html": "",
+ "url": "https://docusaurus.io/myBaseUrl/blog/heading-as-title/",
+ "title": "some heading",
+ "date_modified": "2019-01-02T00:00:00.000Z",
+ "tags": []
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/date-matter/",
+ "content_html": "date inside front matter
",
+ "url": "https://docusaurus.io/myBaseUrl/blog/date-matter/",
+ "title": "date-matter",
+ "summary": "date inside front matter",
+ "date_modified": "2019-01-01T00:00:00.000Z",
+ "tags": [
+ "date"
+ ]
+ },
+ {
+ "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/",
+ "content_html": "Happy birthday! (translated)
",
+ "url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/",
+ "title": "Happy 1st Birthday Slash! (translated)",
+ "summary": "Happy birthday! (translated)",
+ "date_modified": "2018-12-14T00:00:00.000Z",
+ "author": {
+ "name": "Yangshun Tay (translated)"
+ },
+ "tags": []
+ }
+ ]
+}",
+]
+`;
+
exports[`rss filters to the first two entries 1`] = `
[
"
@@ -593,3 +817,123 @@ exports[`rss has feed item for each post 1`] = `
",
]
`;
+
+exports[`rss has feed item for each post - with trailing slash 1`] = `
+[
+ "
+
+
+ Hello Blog
+ https://docusaurus.io/myBaseUrl/blog/
+ Hello Blog
+ Sun, 23 Jul 2023 00:00:00 GMT
+ https://validator.w3.org/feed/docs/rss2.html
+ https://github.com/jpmonette/feed
+ en
+ Copyright
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/blog-with-links/
+ https://docusaurus.io/myBaseUrl/blog/blog-with-links/
+ Sun, 23 Jul 2023 00:00:00 GMT
+
+ absolute full url
+
absolute pathname
+relative pathname
+md link
+anchor
+relative pathname + anchor
+
+
+
+]]>
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/
+ https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/
+ Sat, 06 Mar 2021 00:00:00 GMT
+
+ Test MDX with require calls
+
+
+
+
+]]>
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/
+ https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/
+ Fri, 05 Mar 2021 00:00:00 GMT
+
+ HTML Heading 1
+
HTML Heading 2
+HTML Paragraph
+
+
+Import DOM
+Heading 1
+
+
+
+
+
+
+Normal Text Italics Text Bold Text
+link
]]>
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/
+ https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/
+ Sun, 16 Aug 2020 00:00:00 GMT
+
+ complex url slug]]>
+ date
+ complex
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/simple/slug/
+ https://docusaurus.io/myBaseUrl/blog/simple/slug/
+ Sat, 15 Aug 2020 00:00:00 GMT
+
+ simple url slug]]>
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/heading-as-title/
+ https://docusaurus.io/myBaseUrl/blog/heading-as-title/
+ Wed, 02 Jan 2019 00:00:00 GMT
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/date-matter/
+ https://docusaurus.io/myBaseUrl/blog/date-matter/
+ Tue, 01 Jan 2019 00:00:00 GMT
+
+ date inside front matter]]>
+ date
+
+ -
+
+ https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/
+ https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/
+ Fri, 14 Dec 2018 00:00:00 GMT
+
+ Happy birthday! (translated)]]>
+ lorber.sebastien@gmail.com (Sébastien Lorber (translated))
+
+
+",
+]
+`;
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
index 022d4ce749a9..b900129c9bdc 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -9,6 +9,7 @@ import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
+import {fromPartial} from '@total-typescript/shoehorn';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import {createBlogFeedFiles} from '../feed';
@@ -80,12 +81,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
const outDir = path.join(siteDir, 'build-snap');
await testGenerateFeeds(
- {
+ fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
- } as LoadContext,
+ }),
{
path: 'invalid-blog-path',
routeBasePath: 'blog',
@@ -120,12 +121,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
- {
+ fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
- } as LoadContext,
+ }),
{
path: 'blog',
routeBasePath: 'blog',
@@ -163,12 +164,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
- {
+ fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
- } as LoadContext,
+ }),
{
path: 'blog',
routeBasePath: 'blog',
@@ -216,12 +217,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
- {
+ fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
- } as LoadContext,
+ }),
{
path: 'blog',
routeBasePath: 'blog',
@@ -245,4 +246,48 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
).toMatchSnapshot();
fsMock.mockClear();
});
+
+ it('has feed item for each post - with trailing slash', async () => {
+ const siteDir = path.join(__dirname, '__fixtures__', 'website');
+ const outDir = path.join(siteDir, 'build-snap');
+ const siteConfig = {
+ title: 'Hello',
+ baseUrl: '/myBaseUrl/',
+ url: 'https://docusaurus.io',
+ favicon: 'image/favicon.ico',
+ trailingSlash: true,
+ markdown,
+ };
+
+ // Build is quite difficult to mock, so we built the blog beforehand and
+ // copied the output to the fixture...
+ await testGenerateFeeds(
+ fromPartial({
+ siteDir,
+ siteConfig,
+ i18n: DefaultI18N,
+ outDir,
+ }),
+ {
+ path: 'blog',
+ routeBasePath: 'blog',
+ tagsBasePath: 'tags',
+ authorsMapPath: 'authors.yml',
+ include: DEFAULT_OPTIONS.include,
+ exclude: DEFAULT_OPTIONS.exclude,
+ feedOptions: {
+ type: [feedType],
+ copyright: 'Copyright',
+ },
+ readingTime: ({content, defaultReadingTime}) =>
+ defaultReadingTime({content}),
+ truncateMarker: //,
+ } as PluginOptions,
+ );
+
+ expect(
+ fsMock.mock.calls.map((call) => call[1] as string),
+ ).toMatchSnapshot();
+ fsMock.mockClear();
+ });
});
diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts
index 22b7f53cbb80..95e63ac5af69 100644
--- a/packages/docusaurus-plugin-content-blog/src/feed.ts
+++ b/packages/docusaurus-plugin-content-blog/src/feed.ts
@@ -11,7 +11,10 @@ import logger from '@docusaurus/logger';
import {Feed, type Author as FeedAuthor} from 'feed';
import * as srcset from 'srcset';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
-import {blogPostContainerID} from '@docusaurus/utils-common';
+import {
+ blogPostContainerID,
+ applyTrailingSlash,
+} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
import type {DocusaurusConfig} from '@docusaurus/types';
import type {
@@ -40,8 +43,14 @@ async function generateBlogFeed({
}
const {feedOptions, routeBasePath} = options;
- const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
- const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
+ const {url: siteUrl, baseUrl, title, favicon, trailingSlash} = siteConfig;
+ const blogBaseUrl = applyTrailingSlash(
+ normalizeUrl([siteUrl, baseUrl, routeBasePath]),
+ {
+ trailingSlash,
+ baseUrl,
+ },
+ );
const blogPostsForFeed =
feedOptions.limit === false || feedOptions.limit === null
@@ -85,7 +94,7 @@ async function defaultCreateFeedItems({
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise {
- const {url: siteUrl} = siteConfig;
+ const {url: siteUrl, baseUrl, trailingSlash} = siteConfig;
function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email};
@@ -105,13 +114,19 @@ async function defaultCreateFeedItems({
} = post;
const content = await readOutputHTMLFile(
- permalink.replace(siteConfig.baseUrl, ''),
+ permalink.replace(baseUrl, ''),
outDir,
- siteConfig.trailingSlash,
+ trailingSlash,
);
const $ = cheerioLoad(content);
- const blogPostAbsoluteUrl = normalizeUrl([siteUrl, permalink]);
+ const blogPostAbsoluteUrl = applyTrailingSlash(
+ normalizeUrl([siteUrl, permalink]),
+ {
+ trailingSlash,
+ baseUrl,
+ },
+ );
const toAbsoluteUrl = (src: string) =>
String(new URL(src, blogPostAbsoluteUrl));