From 7be1feaa0a6fbfd18faec8fe0488d24a61b15596 Mon Sep 17 00:00:00 2001
From: Rohan Thakur
Date: Fri, 2 Aug 2024 22:20:48 +0530
Subject: [PATCH] feat(blog): add feed xlst options to render beautiful RSS and
Atom feeds (#9252)
Co-authored-by: ozakione <29860391+OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien
---
.cspell.json | 2 +
.../classic-typescript/docusaurus.config.ts | 4 +
.../templates/classic/docusaurus.config.js | 4 +
.../assets/atom.css | 75 ++
.../assets/atom.xsl | 92 ++
.../assets/rss.css | 75 ++
.../assets/rss.xsl | 86 ++
.../package.json | 3 +-
.../__fixtures__/website/blog/custom-atom.css | 76 ++
.../__fixtures__/website/blog/custom-atom.xsl | 65 +
.../__fixtures__/website/blog/custom-rss.css | 76 ++
.../__fixtures__/website/blog/custom-rss.xsl | 66 ++
.../website/build-snap/blog/atom.css | 75 ++
.../website/build-snap/blog/atom.xsl | 92 ++
.../website/build-snap/blog/custom-atom.css | 76 ++
.../website/build-snap/blog/custom-atom.xsl | 65 +
.../website/build-snap/blog/custom-rss.css | 76 ++
.../website/build-snap/blog/custom-rss.xsl | 66 ++
.../website/build-snap/blog/rss.css | 75 ++
.../website/build-snap/blog/rss.xsl | 86 ++
.../__tests__/__snapshots__/feed.test.ts.snap | 1050 ++++++++++++++++-
.../__snapshots__/options.test.ts.snap | 2 +-
.../src/__tests__/feed.test.ts | 131 +-
.../src/__tests__/options.test.ts | 235 +++-
.../src/feed.ts | 157 ++-
.../src/index.ts | 1 +
.../src/options.ts | 132 ++-
.../src/plugin-content-blog.d.ts | 26 +
project-words.txt | 2 +
.../_dogfooding/_blog tests/custom-atom.css | 76 ++
.../_dogfooding/_blog tests/custom-atom.xsl | 94 ++
.../_dogfooding/_blog tests/custom-rss.css | 76 ++
.../_dogfooding/_blog tests/custom-rss.xsl | 92 ++
website/_dogfooding/dogfooding.config.ts | 4 +
.../docs/api/plugins/plugin-content-blog.mdx | 20 +
website/docs/blog.mdx | 11 +-
website/docusaurus.config.ts | 3 +
37 files changed, 3229 insertions(+), 118 deletions(-)
create mode 100644 packages/docusaurus-plugin-content-blog/assets/atom.css
create mode 100644 packages/docusaurus-plugin-content-blog/assets/atom.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/assets/rss.css
create mode 100644 packages/docusaurus-plugin-content-blog/assets/rss.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl
create mode 100644 website/_dogfooding/_blog tests/custom-atom.css
create mode 100644 website/_dogfooding/_blog tests/custom-atom.xsl
create mode 100644 website/_dogfooding/_blog tests/custom-rss.css
create mode 100644 website/_dogfooding/_blog tests/custom-rss.xsl
diff --git a/.cspell.json b/.cspell.json
index ed3031458ccf..82ebc2c41a41 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -32,6 +32,8 @@
"website/_dogfooding/_pages tests/diagrams.mdx",
"*.xyz",
"*.docx",
+ "*.xsl",
+ "*.xslt",
"*.gitignore",
"versioned_docs",
"*.min.*",
diff --git a/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts
index 4058eac59e23..ae7cbf502e05 100644
--- a/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts
+++ b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts
@@ -42,6 +42,10 @@ const config: Config = {
},
blog: {
showReadingTime: true,
+ feedOptions: {
+ type: ['rss', 'atom'],
+ xslt: true,
+ },
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
diff --git a/packages/create-docusaurus/templates/classic/docusaurus.config.js b/packages/create-docusaurus/templates/classic/docusaurus.config.js
index 7b8d5b1c7f11..d3b51858f497 100644
--- a/packages/create-docusaurus/templates/classic/docusaurus.config.js
+++ b/packages/create-docusaurus/templates/classic/docusaurus.config.js
@@ -48,6 +48,10 @@ const config = {
},
blog: {
showReadingTime: true,
+ feedOptions: {
+ type: ['rss', 'atom'],
+ xslt: true,
+ },
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
diff --git a/packages/docusaurus-plugin-content-blog/assets/atom.css b/packages/docusaurus-plugin-content-blog/assets/atom.css
new file mode 100644
index 000000000000..d2fc20b27416
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/assets/atom.css
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 2rem auto;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 2rem 0;
+ padding: 1.6rem 2.4rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #edf5ff;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.4rem;
+ font-weight: 800;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+}
+
+h1 .rss-icon {
+ height: 3.2rem;
+ width: 3.2rem;
+ margin-right: 1rem;
+}
+
+h2 {
+ font-size: 2.2rem;
+ font-weight: 700;
+ margin-bottom: 0.2rem;
+}
+
+h3 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 0.1rem;
+}
+
+.blog-description {
+ font-size: 1.4rem;
+ margin-bottom: 0.6rem;
+}
+
+.blog-post-date {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ font-style: italic;
+ color: #797b7e;
+}
+
+.blog-post-description {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ color: #434349;
+}
diff --git a/packages/docusaurus-plugin-content-blog/assets/atom.xsl b/packages/docusaurus-plugin-content-blog/assets/atom.xsl
new file mode 100644
index 000000000000..271895cf7775
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/assets/atom.xsl
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+ Atom Feed |
+
+
+
+
+
+
+
This is an Atom feed . Subscribe by copying the URL
+ from the address bar into your newsreader. Visit
+
About Feeds to learn more
+ and get started. It’s free.
+
+
+
+
+
+
+
+
+
+ Recent Posts
+
+
+
+
+
+ Published on
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/assets/rss.css b/packages/docusaurus-plugin-content-blog/assets/rss.css
new file mode 100644
index 000000000000..d2fc20b27416
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/assets/rss.css
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 2rem auto;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 2rem 0;
+ padding: 1.6rem 2.4rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #edf5ff;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.4rem;
+ font-weight: 800;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+}
+
+h1 .rss-icon {
+ height: 3.2rem;
+ width: 3.2rem;
+ margin-right: 1rem;
+}
+
+h2 {
+ font-size: 2.2rem;
+ font-weight: 700;
+ margin-bottom: 0.2rem;
+}
+
+h3 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 0.1rem;
+}
+
+.blog-description {
+ font-size: 1.4rem;
+ margin-bottom: 0.6rem;
+}
+
+.blog-post-date {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ font-style: italic;
+ color: #797b7e;
+}
+
+.blog-post-description {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ color: #434349;
+}
diff --git a/packages/docusaurus-plugin-content-blog/assets/rss.xsl b/packages/docusaurus-plugin-content-blog/assets/rss.xsl
new file mode 100644
index 000000000000..e9695b298468
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/assets/rss.xsl
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ RSS Feed |
+
+
+
+
+
+
+
This is an RSS feed . Subscribe by copying the URL
+ from the address bar into your newsreader. Visit
+
About Feeds to learn more
+ and get started. It’s free.
+
+
+
+
+
+
+
+
+
+ Recent Posts
+
+
+
+
+
+ Published on
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json
index 815413ff2cfd..ff88860d2ae4 100644
--- a/packages/docusaurus-plugin-content-blog/package.json
+++ b/packages/docusaurus-plugin-content-blog/package.json
@@ -59,6 +59,7 @@
"node": ">=18.0"
},
"devDependencies": {
- "@total-typescript/shoehorn": "^0.1.2"
+ "@total-typescript/shoehorn": "^0.1.2",
+ "tree-node-cli": "^1.6.0"
}
}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl
new file mode 100644
index 000000000000..80406b153124
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+ Atom Feed |
+
+
+
+
+
+
+
This is an Atom feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom Atom Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl
new file mode 100644
index 000000000000..4d793963cba1
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ RSS Feed |
+
+
+
+
+
+
+
This is an RSS feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom RSS Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css
new file mode 100644
index 000000000000..d2fc20b27416
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 2rem auto;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 2rem 0;
+ padding: 1.6rem 2.4rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #edf5ff;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.4rem;
+ font-weight: 800;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+}
+
+h1 .rss-icon {
+ height: 3.2rem;
+ width: 3.2rem;
+ margin-right: 1rem;
+}
+
+h2 {
+ font-size: 2.2rem;
+ font-weight: 700;
+ margin-bottom: 0.2rem;
+}
+
+h3 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 0.1rem;
+}
+
+.blog-description {
+ font-size: 1.4rem;
+ margin-bottom: 0.6rem;
+}
+
+.blog-post-date {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ font-style: italic;
+ color: #797b7e;
+}
+
+.blog-post-description {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ color: #434349;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl
new file mode 100644
index 000000000000..271895cf7775
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+ Atom Feed |
+
+
+
+
+
+
+
This is an Atom feed . Subscribe by copying the URL
+ from the address bar into your newsreader. Visit
+
About Feeds to learn more
+ and get started. It’s free.
+
+
+
+
+
+
+
+
+
+ Recent Posts
+
+
+
+
+
+ Published on
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl
new file mode 100644
index 000000000000..80406b153124
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+ Atom Feed |
+
+
+
+
+
+
+
This is an Atom feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom Atom Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl
new file mode 100644
index 000000000000..4d793963cba1
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ RSS Feed |
+
+
+
+
+
+
+
This is an RSS feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom RSS Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css
new file mode 100644
index 000000000000..d2fc20b27416
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 2rem auto;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 2rem 0;
+ padding: 1.6rem 2.4rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #edf5ff;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.4rem;
+ font-weight: 800;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+}
+
+h1 .rss-icon {
+ height: 3.2rem;
+ width: 3.2rem;
+ margin-right: 1rem;
+}
+
+h2 {
+ font-size: 2.2rem;
+ font-weight: 700;
+ margin-bottom: 0.2rem;
+}
+
+h3 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 0.1rem;
+}
+
+.blog-description {
+ font-size: 1.4rem;
+ margin-bottom: 0.6rem;
+}
+
+.blog-post-date {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ font-style: italic;
+ color: #797b7e;
+}
+
+.blog-post-description {
+ font-size: 1rem;
+ line-height: 1.4rem;
+ color: #434349;
+}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl
new file mode 100644
index 000000000000..e9695b298468
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ RSS Feed |
+
+
+
+
+
+
+
This is an RSS feed . Subscribe by copying the URL
+ from the address bar into your newsreader. Visit
+
About Feeds to learn more
+ and get started. It’s free.
+
+
+
+
+
+
+
+
+
+ Recent Posts
+
+
+
+
+
+ Published on
+
+
+
+
+
+
+
+
+
+
+
+
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 6b693d83fb2e..2c90688e2c56 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
@@ -92,6 +92,192 @@ exports[`atom filters to the first two entries using limit 1`] = `
]
`;
+exports[`atom has custom xslt files for feed 1`] = `
+[
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xml",
+ "
+
+ 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[`atom has custom xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
+
exports[`atom has feed item for each post - with trailing slash 1`] = `
[
"
@@ -352,6 +538,192 @@ exports[`atom has feed item for each post 1`] = `
]
`;
+exports[`atom has xslt files for feed 1`] = `
+[
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xml",
+ "
+
+ 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[`atom has xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
+
exports[`json filters to the first two entries 1`] = `
[
"{
@@ -414,6 +786,161 @@ exports[`json filters to the first two entries using limit 1`] = `
]
`;
+exports[`json has custom xslt files for feed 1`] = `
+[
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/feed.json",
+ "{
+ "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": [
+ "inlineTag",
+ "Global Tag label (en)"
+ ]
+ }
+ ]
+}",
+ ],
+]
+`;
+
+exports[`json has custom xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
+
exports[`json has feed item for each post - with trailing slash 1`] = `
[
"{
@@ -483,9 +1010,108 @@ exports[`json has feed item for each post - with trailing slash 1`] = `
"tags": []
},
{
- "id": "https://docusaurus.io/myBaseUrl/blog/date-matter/",
+ "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": [
+ "inlineTag",
+ "Global Tag label (en)"
+ ]
+ }
+ ]
+}",
+]
+`;
+
+exports[`json has feed item for each post 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/",
+ "url": "https://docusaurus.io/myBaseUrl/blog/date-matter",
"title": "date-matter",
"summary": "date inside front matter",
"date_modified": "2019-01-01T00:00:00.000Z",
@@ -494,9 +1120,9 @@ exports[`json has feed item for each post - with trailing slash 1`] = `
]
},
{
- "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/",
+ "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/",
+ "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",
@@ -513,9 +1139,11 @@ exports[`json has feed item for each post - with trailing slash 1`] = `
]
`;
-exports[`json has feed item for each post 1`] = `
+exports[`json has xslt files for feed 1`] = `
[
- "{
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/feed.json",
+ "{
"version": "https://jsonfeed.org/version/1",
"title": "Hello Blog",
"home_page_url": "https://docusaurus.io/myBaseUrl/blog",
@@ -609,9 +1237,63 @@ exports[`json has feed item for each post 1`] = `
}
]
}",
+ ],
]
`;
+exports[`json has xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
+
exports[`rss filters to the first two entries 1`] = `
[
"
@@ -708,6 +1390,184 @@ exports[`rss filters to the first two entries using limit 1`] = `
]
`;
+exports[`rss has custom xslt files for feed 1`] = `
+[
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xml",
+ "
+
+
+ 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))
+ inlineTag
+ Global Tag label (en)
+
+
+ ",
+ ],
+]
+`;
+
+exports[`rss has custom xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
+
exports[`rss has feed item for each post - with trailing slash 1`] = `
[
"
@@ -951,3 +1811,181 @@ exports[`rss has feed item for each post 1`] = `
",
]
`;
+
+exports[`rss has xslt files for feed 1`] = `
+[
+ [
+ "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xml",
+ "
+
+
+ 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))
+ inlineTag
+ Global Tag label (en)
+
+
+ ",
+ ],
+]
+`;
+
+exports[`rss has xslt files for feed: blog tree 1`] = `
+"blog
+├── 2018
+│ └── 12
+│ └── 14
+│ └── Happy-First-Birthday-Slash
+│ └── index.html
+├── archive
+│ └── index.html
+├── atom.css
+├── atom.xml
+├── atom.xsl
+├── blog-with-links
+│ └── index.html
+├── custom-atom.css
+├── custom-atom.xsl
+├── custom-rss.css
+├── custom-rss.xsl
+├── date-matter
+│ └── index.html
+├── feed.json
+├── heading-as-title
+│ └── index.html
+├── hey
+│ └── my super path
+│ └── héllô
+│ └── index.html
+├── index.html
+├── mdx-blog-post
+│ └── index.html
+├── mdx-require-blog-post
+│ └── index.html
+├── page
+│ ├── 2
+│ │ └── index.html
+│ └── 3
+│ └── index.html
+├── rss.css
+├── rss.xml
+├── rss.xsl
+├── simple
+│ └── slug
+│ └── index.html
+├── tags
+│ ├── complex
+│ │ └── index.html
+│ ├── date
+│ │ └── index.html
+│ └── index.html
+└── unlisted
+ └── index.html"
+`;
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap
index 4406ef7b72eb..ce4af321ff02 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`validateOptions throws Error in case of invalid feed type 1`] = `""feedOptions.type" does not match any of the allowed types"`;
+exports[`validateOptions feed throws Error in case of invalid feed type 1`] = `""feedOptions.type" does not match any of the allowed types"`;
exports[`validateOptions throws Error in case of invalid options 1`] = `""postsPerPage" must be greater than or equal to 1"`;
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 ef57b4c7d63e..3cca6f94aaf9 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -10,13 +10,15 @@ 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 {normalizePluginOptions} from '@docusaurus/utils-validation';
+import tree from 'tree-node-cli';
+import {DEFAULT_OPTIONS, validateOptions} from '../options';
import {generateBlogPosts} from '../blogUtils';
import {createBlogFeedFiles} from '../feed';
import {getAuthorsMap} from '../authorsMap';
-import type {LoadContext, I18n} from '@docusaurus/types';
+import type {LoadContext, I18n, Validate} from '@docusaurus/types';
import type {BlogContentPaths} from '../types';
-import type {PluginOptions} from '@docusaurus/plugin-content-blog';
+import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog';
const DefaultI18N: I18n = {
currentLocale: 'en',
@@ -50,8 +52,16 @@ function getBlogContentPaths(siteDir: string): BlogContentPaths {
async function testGenerateFeeds(
context: LoadContext,
- options: PluginOptions,
+ optionsInput: Options,
): Promise {
+ const options = validateOptions({
+ validate: normalizePluginOptions as Validate<
+ Options | undefined,
+ PluginOptions
+ >,
+ options: optionsInput,
+ });
+
const contentPaths = getBlogContentPaths(context.siteDir);
const authorsMap = await getAuthorsMap({
contentPaths,
@@ -72,10 +82,11 @@ async function testGenerateFeeds(
siteConfig: context.siteConfig,
outDir: context.outDir,
locale: 'en',
+ contentPaths,
});
}
-describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
+describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {});
it('does not get generated without posts', async () => {
@@ -105,13 +116,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
feedOptions: {
type: [feedType],
copyright: 'Copyright',
+ xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
- } as PluginOptions,
+ },
);
expect(fsMock).toHaveBeenCalledTimes(0);
@@ -148,13 +160,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
feedOptions: {
type: [feedType],
copyright: 'Copyright',
+ xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
- } as PluginOptions,
+ },
);
expect(
@@ -203,13 +216,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
...rest,
});
},
+ xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
- } as PluginOptions,
+ },
);
expect(
@@ -249,13 +263,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
type: [feedType],
copyright: 'Copyright',
limit: 2,
+ xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
- } as PluginOptions,
+ },
);
expect(
@@ -295,13 +310,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
feedOptions: {
type: [feedType],
copyright: 'Copyright',
+ xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
- } as PluginOptions,
+ },
);
expect(
@@ -309,4 +325,99 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
).toMatchSnapshot();
fsMock.mockClear();
});
+
+ it('has xslt files for feed', 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',
+ 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',
+ xslt: true,
+ },
+ readingTime: ({content, defaultReadingTime}) =>
+ defaultReadingTime({content}),
+ truncateMarker: //,
+ onInlineTags: 'ignore',
+ onInlineAuthors: 'ignore',
+ },
+ );
+
+ expect(tree(path.join(outDir, 'blog'))).toMatchSnapshot('blog tree');
+
+ expect(fsMock.mock.calls).toMatchSnapshot();
+ fsMock.mockClear();
+ });
+
+ it('has custom xslt files for feed', 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',
+ 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',
+ xslt: {
+ rss: 'custom-rss.xsl',
+ atom: 'custom-atom.xsl',
+ },
+ },
+ readingTime: ({content, defaultReadingTime}) =>
+ defaultReadingTime({content}),
+ truncateMarker: //,
+ onInlineTags: 'ignore',
+ onInlineAuthors: 'ignore',
+ },
+ );
+
+ expect(tree(path.join(outDir, 'blog'))).toMatchSnapshot('blog tree');
+
+ expect(fsMock.mock.calls).toMatchSnapshot();
+ fsMock.mockClear();
+ });
});
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts
index 050e2a016023..254d56b96b2a 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts
@@ -6,8 +6,12 @@
*/
import {normalizePluginOptions} from '@docusaurus/utils-validation';
-import {validateOptions, DEFAULT_OPTIONS} from '../options';
-import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog';
+import {validateOptions, DEFAULT_OPTIONS, XSLTBuiltInPaths} from '../options';
+import type {
+ Options,
+ PluginOptions,
+ UserFeedOptions,
+} from '@docusaurus/plugin-content-blog';
import type {Validate} from '@docusaurus/types';
function testValidate(options?: Options) {
@@ -38,7 +42,10 @@ describe('validateOptions', () => {
it('accepts correctly defined user options', () => {
const userOptions: Options = {
...defaultOptions,
- feedOptions: {type: 'rss' as const, title: 'myTitle'},
+ feedOptions: {
+ type: 'rss' as const,
+ title: 'myTitle',
+ },
path: 'not_blog',
routeBasePath: '/myBlog',
postsPerPage: 5,
@@ -48,7 +55,13 @@ describe('validateOptions', () => {
};
expect(testValidate(userOptions)).toEqual({
...userOptions,
- feedOptions: {type: ['rss'], title: 'myTitle', copyright: '', limit: 20},
+ feedOptions: {
+ type: ['rss'],
+ title: 'myTitle',
+ copyright: '',
+ limit: 20,
+ xslt: {rss: null, atom: null},
+ },
});
});
@@ -79,60 +92,182 @@ describe('validateOptions', () => {
).toThrowErrorMatchingSnapshot();
});
- it('throws Error in case of invalid feed type', () => {
- expect(() =>
- testValidate({
+ describe('feed', () => {
+ it('throws Error in case of invalid feed type', () => {
+ expect(() =>
+ testValidate({
+ feedOptions: {
+ // @ts-expect-error: test
+ type: 'none',
+ },
+ }),
+ ).toThrowErrorMatchingSnapshot();
+ });
+
+ it('converts all feed type to array with other feed type', () => {
+ expect(
+ testValidate({
+ feedOptions: {type: 'all'},
+ }),
+ ).toEqual({
+ ...defaultOptions,
feedOptions: {
- // @ts-expect-error: test
- type: 'none',
+ type: ['rss', 'atom', 'json'],
+ copyright: '',
+ limit: 20,
+ xslt: {rss: null, atom: null},
},
- }),
- ).toThrowErrorMatchingSnapshot();
- });
+ });
+ });
- it('converts all feed type to array with other feed type', () => {
- expect(
- testValidate({
- feedOptions: {type: 'all'},
- }),
- ).toEqual({
- ...defaultOptions,
- feedOptions: {type: ['rss', 'atom', 'json'], copyright: '', limit: 20},
+ it('accepts null feed type and return same', () => {
+ expect(
+ testValidate({
+ feedOptions: {type: null},
+ }),
+ ).toEqual({
+ ...defaultOptions,
+ feedOptions: {
+ type: null,
+ limit: 20,
+ xslt: {rss: null, atom: null},
+ },
+ });
});
- });
- it('accepts null type and return same', () => {
- expect(
- testValidate({
- feedOptions: {type: null},
- }),
- ).toEqual({
- ...defaultOptions,
- feedOptions: {type: null, limit: 20},
+ it('contains array with rss + atom for missing feed type', () => {
+ expect(
+ testValidate({
+ feedOptions: {},
+ }),
+ ).toEqual(defaultOptions);
});
- });
- it('contains array with rss + atom for missing feed type', () => {
- expect(
- testValidate({
- feedOptions: {},
- }),
- ).toEqual(defaultOptions);
- });
+ it('has array with rss + atom, title for missing feed type', () => {
+ expect(
+ testValidate({
+ feedOptions: {title: 'title'},
+ }),
+ ).toEqual({
+ ...defaultOptions,
+ feedOptions: {
+ type: ['rss', 'atom'],
+ title: 'title',
+ copyright: '',
+ limit: 20,
+ xslt: {rss: null, atom: null},
+ },
+ });
+ });
- it('has array with rss + atom, title for missing feed type', () => {
- expect(
- testValidate({
- feedOptions: {title: 'title'},
- }),
- ).toEqual({
- ...defaultOptions,
- feedOptions: {
- type: ['rss', 'atom'],
- title: 'title',
- copyright: '',
- limit: 20,
- },
+ describe('feed xslt', () => {
+ function testXSLT(xslt: UserFeedOptions['xslt']) {
+ return testValidate({feedOptions: {xslt}}).feedOptions.xslt;
+ }
+
+ it('accepts xslt: true', () => {
+ expect(testXSLT(true)).toEqual({
+ rss: XSLTBuiltInPaths.rss,
+ atom: XSLTBuiltInPaths.atom,
+ });
+ });
+
+ it('accepts xslt: false', () => {
+ expect(testXSLT(false)).toEqual({
+ rss: null,
+ atom: null,
+ });
+ });
+
+ it('accepts xslt: null', () => {
+ expect(testXSLT(null)).toEqual({
+ rss: null,
+ atom: null,
+ });
+ });
+
+ it('accepts xslt: undefined', () => {
+ expect(testXSLT(undefined)).toEqual({
+ rss: null,
+ atom: null,
+ });
+ });
+
+ it('accepts xslt: {rss: true}', () => {
+ expect(testXSLT({rss: true})).toEqual({
+ rss: XSLTBuiltInPaths.rss,
+ atom: null,
+ });
+ });
+
+ it('accepts xslt: {atom: true}', () => {
+ expect(testXSLT({atom: true})).toEqual({
+ rss: null,
+ atom: XSLTBuiltInPaths.atom,
+ });
+ });
+
+ it('accepts xslt: {rss: true, atom: true}', () => {
+ expect(testXSLT({rss: true, atom: true})).toEqual({
+ rss: XSLTBuiltInPaths.rss,
+ atom: XSLTBuiltInPaths.atom,
+ });
+ });
+
+ it('accepts xslt: {rss: "custom-path"}', () => {
+ expect(testXSLT({rss: 'custom-path'})).toEqual({
+ rss: 'custom-path',
+ atom: null,
+ });
+ });
+
+ it('accepts xslt: {rss: true, atom: "custom-path"}', () => {
+ expect(testXSLT({rss: true, atom: 'custom-path'})).toEqual({
+ rss: XSLTBuiltInPaths.rss,
+ atom: 'custom-path',
+ });
+ });
+
+ it('accepts xslt: {rss: null, atom: true}', () => {
+ expect(testXSLT({rss: null, atom: true})).toEqual({
+ rss: null,
+ atom: XSLTBuiltInPaths.atom,
+ });
+ });
+
+ it('accepts xslt: {rss: false, atom: null}', () => {
+ expect(testXSLT({rss: false, atom: null})).toEqual({
+ rss: null,
+ atom: null,
+ });
+ });
+
+ it('rejects xslt: 42', () => {
+ // @ts-expect-error: bad type
+ expect(() => testXSLT(42)).toThrowErrorMatchingInlineSnapshot(
+ `""feedOptions.xslt" must be one of [object, boolean]"`,
+ );
+ });
+ it('rejects xslt: []', () => {
+ // @ts-expect-error: bad type
+ expect(() => testXSLT([])).toThrowErrorMatchingInlineSnapshot(
+ `""feedOptions.xslt" must be one of [object, boolean]"`,
+ );
+ });
+
+ it('rejects xslt: {rss: 42}', () => {
+ // @ts-expect-error: bad type
+ expect(() => testXSLT({rss: 42})).toThrowErrorMatchingInlineSnapshot(
+ `""feedOptions.xslt.rss" must be one of [string, boolean]"`,
+ );
+ });
+
+ it('rejects xslt: {rss: []}', () => {
+ // @ts-expect-error: bad type
+ expect(() => testXSLT({rss: 42})).toThrowErrorMatchingInlineSnapshot(
+ `""feedOptions.xslt.rss" must be one of [string, boolean]"`,
+ );
+ });
});
});
diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts
index b8bc8c1481c9..96f252f23070 100644
--- a/packages/docusaurus-plugin-content-blog/src/feed.ts
+++ b/packages/docusaurus-plugin-content-blog/src/feed.ts
@@ -7,15 +7,20 @@
import path from 'path';
import fs from 'fs-extra';
-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 {
+ getDataFilePath,
+ normalizeUrl,
+ readOutputHTMLFile,
+} from '@docusaurus/utils';
import {
blogPostContainerID,
applyTrailingSlash,
} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
+import logger from '@docusaurus/logger';
+import type {BlogContentPaths} from './types';
import type {DocusaurusConfig, HtmlTags, LoadContext} from '@docusaurus/types';
import type {
FeedType,
@@ -23,6 +28,8 @@ import type {
Author,
BlogPost,
BlogFeedItem,
+ FeedOptions,
+ FeedXSLTOptions,
} from '@docusaurus/plugin-content-blog';
async function generateBlogFeed({
@@ -180,32 +187,144 @@ async function defaultCreateFeedItems({
);
}
+async function resolveXsltFilePaths({
+ xsltFilePath,
+ contentPaths,
+}: {
+ xsltFilePath: string;
+ contentPaths: BlogContentPaths;
+}) {
+ const xsltAbsolutePath: string = path.isAbsolute(xsltFilePath)
+ ? xsltFilePath
+ : (await getDataFilePath({filePath: xsltFilePath, contentPaths})) ??
+ path.resolve(contentPaths.contentPath, xsltFilePath);
+
+ if (!(await fs.pathExists(xsltAbsolutePath))) {
+ throw new Error(
+ logger.interpolate`Blog feed XSLT file not found at path=${path.relative(
+ process.cwd(),
+ xsltAbsolutePath,
+ )}`,
+ );
+ }
+
+ const parsedPath = path.parse(xsltAbsolutePath);
+ const cssAbsolutePath = path.resolve(
+ parsedPath.dir,
+ `${parsedPath.name}.css`,
+ );
+ if (!(await fs.pathExists(xsltAbsolutePath))) {
+ throw new Error(
+ logger.interpolate`Blog feed XSLT file was found at path=${path.relative(
+ process.cwd(),
+ xsltAbsolutePath,
+ )}
+But its expected co-located CSS file could not be found at path=${path.relative(
+ process.cwd(),
+ cssAbsolutePath,
+ )}
+If you want to provide a custom XSLT file, you must provide a CSS file with the exact same name.`,
+ );
+ }
+
+ return {xsltAbsolutePath, cssAbsolutePath};
+}
+
+async function generateXsltFiles({
+ xsltFilePath,
+ generatePath,
+ contentPaths,
+}: {
+ xsltFilePath: string;
+ generatePath: string;
+ contentPaths: BlogContentPaths;
+}) {
+ const {xsltAbsolutePath, cssAbsolutePath} = await resolveXsltFilePaths({
+ xsltFilePath,
+ contentPaths,
+ });
+ const xsltOutputPath = path.join(
+ generatePath,
+ path.basename(xsltAbsolutePath),
+ );
+ const cssOutputPath = path.join(generatePath, path.basename(cssAbsolutePath));
+ await fs.copy(xsltAbsolutePath, xsltOutputPath);
+ await fs.copy(cssAbsolutePath, cssOutputPath);
+}
+
+// This modifies the XML feed content to add a relative href to the XSLT file
+// Good enough for now: we probably don't need a full XML parser just for that
+// See also https://darekkay.com/blog/rss-styling/
+function injectXslt({
+ feedContent,
+ xsltFilePath,
+}: {
+ feedContent: string;
+ xsltFilePath: string;
+}) {
+ return feedContent.replace(
+ '',
+ ``,
+ );
+}
+
+const FeedConfigs: Record<
+ FeedType,
+ {
+ outputFileName: string;
+ getContent: (feed: Feed) => string;
+ getXsltFilePath: (xslt: FeedXSLTOptions) => string | null;
+ }
+> = {
+ rss: {
+ outputFileName: 'rss.xml',
+ getContent: (feed) => feed.rss2(),
+ getXsltFilePath: (xslt) => xslt.rss,
+ },
+ atom: {
+ outputFileName: 'atom.xml',
+ getContent: (feed) => feed.atom1(),
+ getXsltFilePath: (xslt) => xslt.atom,
+ },
+ json: {
+ outputFileName: 'feed.json',
+ getContent: (feed) => feed.json1(),
+ getXsltFilePath: () => null,
+ },
+};
+
async function createBlogFeedFile({
feed,
feedType,
generatePath,
+ feedOptions,
+ contentPaths,
}: {
feed: Feed;
feedType: FeedType;
generatePath: string;
+ feedOptions: FeedOptions;
+ contentPaths: BlogContentPaths;
}) {
- const [feedContent, feedPath] = (() => {
- switch (feedType) {
- case 'rss':
- return [feed.rss2(), 'rss.xml'];
- case 'json':
- return [feed.json1(), 'feed.json'];
- case 'atom':
- return [feed.atom1(), 'atom.xml'];
- default:
- throw new Error(`Feed type ${feedType} not supported.`);
- }
- })();
try {
- await fs.outputFile(path.join(generatePath, feedPath), feedContent);
+ const feedConfig = FeedConfigs[feedType];
+
+ let feedContent = feedConfig.getContent(feed);
+
+ const xsltFilePath = feedConfig.getXsltFilePath(feedOptions.xslt);
+ if (xsltFilePath) {
+ await generateXsltFiles({xsltFilePath, contentPaths, generatePath});
+ feedContent = injectXslt({feedContent, xsltFilePath});
+ }
+
+ const outputPath = path.join(generatePath, feedConfig.outputFileName);
+ await fs.outputFile(outputPath, feedContent);
} catch (err) {
- logger.error(`Generating ${feedType} feed failed.`);
- throw err;
+ throw new Error(`Generating ${feedType} feed failed.`, {
+ cause: err as Error,
+ });
}
}
@@ -222,12 +341,14 @@ export async function createBlogFeedFiles({
siteConfig,
outDir,
locale,
+ contentPaths,
}: {
blogPosts: BlogPost[];
options: PluginOptions;
siteConfig: DocusaurusConfig;
outDir: string;
locale: string;
+ contentPaths: BlogContentPaths;
}): Promise {
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
@@ -250,6 +371,8 @@ export async function createBlogFeedFiles({
feed,
feedType,
generatePath: path.join(outDir, options.routeBasePath),
+ feedOptions: options.feedOptions,
+ contentPaths,
}),
),
);
diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts
index 679924729e5f..ea2652c57fe8 100644
--- a/packages/docusaurus-plugin-content-blog/src/index.ts
+++ b/packages/docusaurus-plugin-content-blog/src/index.ts
@@ -388,6 +388,7 @@ export default async function pluginContentBlog(
outDir,
siteConfig,
locale: currentLocale,
+ contentPaths,
});
},
diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts
index 20e0c3427948..e9d91d3bf449 100644
--- a/packages/docusaurus-plugin-content-blog/src/options.ts
+++ b/packages/docusaurus-plugin-content-blog/src/options.ts
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
+import path from 'path';
import {
Joi,
RemarkPluginsSchema,
@@ -19,11 +20,20 @@ import type {
PluginOptions,
Options,
FeedType,
+ FeedXSLTOptions,
} from '@docusaurus/plugin-content-blog';
import type {OptionValidationContext} from '@docusaurus/types';
export const DEFAULT_OPTIONS: PluginOptions = {
- feedOptions: {type: ['rss', 'atom'], copyright: '', limit: 20},
+ feedOptions: {
+ type: ['rss', 'atom'],
+ copyright: '',
+ limit: 20,
+ xslt: {
+ rss: null,
+ atom: null,
+ },
+ },
beforeDefaultRehypePlugins: [],
beforeDefaultRemarkPlugins: [],
admonitions: true,
@@ -64,6 +74,94 @@ export const DEFAULT_OPTIONS: PluginOptions = {
onInlineAuthors: 'warn',
};
+export const XSLTBuiltInPaths = {
+ rss: path.resolve(__dirname, '..', 'assets', 'rss.xsl'),
+ atom: path.resolve(__dirname, '..', 'assets', 'atom.xsl'),
+};
+
+function normalizeXsltOption(
+ option: string | null | boolean,
+ type: 'rss' | 'atom',
+): string | null {
+ if (typeof option === 'string') {
+ return option;
+ }
+ if (option === true) {
+ return XSLTBuiltInPaths[type];
+ }
+ return null;
+}
+
+function createXSLTFilePathSchema(type: 'atom' | 'rss') {
+ return Joi.alternatives()
+ .try(
+ Joi.string().required(),
+ Joi.boolean()
+ .allow(null, () => undefined)
+ .custom((val) => normalizeXsltOption(val, type)),
+ )
+ .optional()
+ .default(null);
+}
+
+const FeedXSLTOptionsSchema = Joi.alternatives()
+ .try(
+ Joi.object({
+ rss: createXSLTFilePathSchema('rss'),
+ atom: createXSLTFilePathSchema('atom'),
+ }).required(),
+ Joi.boolean()
+ .allow(null, () => undefined)
+ .custom((val) => ({
+ rss: normalizeXsltOption(val, 'rss'),
+ atom: normalizeXsltOption(val, 'atom'),
+ })),
+ )
+ .optional()
+ .custom((val) => {
+ if (val === null) {
+ return {
+ rss: null,
+ atom: null,
+ };
+ }
+ return val;
+ })
+ .default(DEFAULT_OPTIONS.feedOptions.xslt);
+
+const FeedOptionsSchema = Joi.object({
+ type: Joi.alternatives()
+ .try(
+ Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
+ Joi.alternatives().conditional(
+ Joi.string().equal('all', 'rss', 'atom', 'json'),
+ {
+ then: Joi.custom((val: FeedType | 'all') =>
+ val === 'all' ? ['rss', 'atom', 'json'] : [val],
+ ),
+ },
+ ),
+ )
+ .allow(null)
+ .default(DEFAULT_OPTIONS.feedOptions.type),
+ xslt: FeedXSLTOptionsSchema,
+ title: Joi.string().allow(''),
+ description: Joi.string().allow(''),
+ // Only add default value when user actually wants a feed (type is not null)
+ copyright: Joi.when('type', {
+ is: Joi.any().valid(null),
+ then: Joi.string().optional(),
+ otherwise: Joi.string()
+ .allow('')
+ .default(DEFAULT_OPTIONS.feedOptions.copyright),
+ }),
+ language: Joi.string(),
+ createFeedItems: Joi.function(),
+ limit: Joi.alternatives()
+ .try(Joi.number(), Joi.valid(null), Joi.valid(false))
+ .default(DEFAULT_OPTIONS.feedOptions.limit),
+}).default(DEFAULT_OPTIONS.feedOptions);
+
const PluginOptionSchema = Joi.object({
path: Joi.string().default(DEFAULT_OPTIONS.path),
archiveBasePath: Joi.string()
@@ -116,37 +214,7 @@ const PluginOptionSchema = Joi.object({
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
),
- feedOptions: Joi.object({
- type: Joi.alternatives()
- .try(
- Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
- Joi.alternatives().conditional(
- Joi.string().equal('all', 'rss', 'atom', 'json'),
- {
- then: Joi.custom((val: FeedType | 'all') =>
- val === 'all' ? ['rss', 'atom', 'json'] : [val],
- ),
- },
- ),
- )
- .allow(null)
- .default(DEFAULT_OPTIONS.feedOptions.type),
- title: Joi.string().allow(''),
- description: Joi.string().allow(''),
- // Only add default value when user actually wants a feed (type is not null)
- copyright: Joi.when('type', {
- is: Joi.any().valid(null),
- then: Joi.string().optional(),
- otherwise: Joi.string()
- .allow('')
- .default(DEFAULT_OPTIONS.feedOptions.copyright),
- }),
- language: Joi.string(),
- createFeedItems: Joi.function(),
- limit: Joi.alternatives()
- .try(Joi.number(), Joi.valid(null), Joi.valid(false))
- .default(DEFAULT_OPTIONS.feedOptions.limit),
- }).default(DEFAULT_OPTIONS.feedOptions),
+ feedOptions: FeedOptionsSchema,
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
sortPosts: Joi.string()
diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
index e0e5617ce792..02e98f0b1e3b 100644
--- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
+++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
@@ -315,10 +315,26 @@ declare module '@docusaurus/plugin-content-blog' {
}) => string | undefined;
export type FeedType = 'rss' | 'atom' | 'json';
+
+ export type FeedXSLTOptions = {
+ /**
+ * RSS XSLT file path, relative to the blog content folder.
+ * If null, no XSLT file is used and the feed will be displayed as raw XML.
+ */
+ rss: string | null;
+ /**
+ * Atom XSLT file path, relative to the blog content folder.
+ * If null, no XSLT file is used and the feed will be displayed as raw XML.
+ */
+ atom: string | null;
+ };
+
/**
* Normalized feed options used within code.
*/
export type FeedOptions = {
+ /** Enable feeds xslt stylesheets */
+ xslt: FeedXSLTOptions;
/** If `null`, no feed is generated. */
type?: FeedType[] | null;
/** Title of generated feed. */
@@ -507,6 +523,14 @@ declare module '@docusaurus/plugin-content-blog' {
onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw';
};
+ export type UserFeedXSLTOptions =
+ | boolean
+ | null
+ | {
+ rss?: string | boolean | null;
+ atom?: string | boolean | null;
+ };
+
/**
* Feed options, as provided by user config. `type` accepts `all` as shortcut
*/
@@ -515,6 +539,8 @@ declare module '@docusaurus/plugin-content-blog' {
{
/** Type of feed to be generated. Use `null` to disable generation. */
type?: FeedOptions['type'] | 'all' | FeedType;
+ /** User-provided XSLT config for feeds, un-normalized */
+ xslt?: UserFeedXSLTOptions;
}
>;
/**
diff --git a/project-words.txt b/project-words.txt
index 528d635318ee..8ff86be90d0e 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -412,6 +412,8 @@ webpackbar
webstorm
Wolcott
Xplorer
+xslt
+XSLT
XSOAR
Yacop
yangshun
diff --git a/website/_dogfooding/_blog tests/custom-atom.css b/website/_dogfooding/_blog tests/custom-atom.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/website/_dogfooding/_blog tests/custom-atom.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/website/_dogfooding/_blog tests/custom-atom.xsl b/website/_dogfooding/_blog tests/custom-atom.xsl
new file mode 100644
index 000000000000..560d535302b5
--- /dev/null
+++ b/website/_dogfooding/_blog tests/custom-atom.xsl
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+ Atom Feed |
+
+
+
+
+
+
+
This is an Atom feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom Atom Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/_dogfooding/_blog tests/custom-rss.css b/website/_dogfooding/_blog tests/custom-rss.css
new file mode 100644
index 000000000000..c016178d9007
--- /dev/null
+++ b/website/_dogfooding/_blog tests/custom-rss.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+* {
+ color: #0d1137;
+}
+
+main {
+ flex: 1 0 auto;
+ width: 100%;
+ margin: 4rem auto;
+ padding: 1.5 rem;
+ max-width: 800px;
+ /* stylelint-disable-next-line font-family-name-quotes */
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
+}
+
+.info {
+ display: block;
+ margin: 3rem 0;
+ padding: 2rem 3rem;
+ border: 1px solid dodgerblue;
+ border-left-width: 0.5rem;
+ border-radius: 0.4rem;
+ background-color: #e52165;
+}
+
+.rss-icon {
+ height: 3.8rem;
+ width: 3.8rem;
+ margin-right: 1rem;
+}
+
+.flex {
+ display: flex;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.pb-7 {
+ padding-bottom: 3rem;
+}
+
+a {
+ color: #005aff;
+ text-decoration: none;
+}
+
+h1 {
+ text-wrap: balance;
+ font-size: 3.8rem;
+ line-height: 1;
+ font-weight: 800;
+ margin-bottom: 4rem;
+}
+
+h2 {
+ font-size: 3rem;
+ line-height: 1.2;
+ font-weight: 700;
+ margin-bottom: 3rem;
+}
+
+h2:not(:first-child) {
+ margin-top: 5.8rem;
+}
+
+.italic {
+ font-style: italic;
+}
diff --git a/website/_dogfooding/_blog tests/custom-rss.xsl b/website/_dogfooding/_blog tests/custom-rss.xsl
new file mode 100644
index 000000000000..dd868860c2d6
--- /dev/null
+++ b/website/_dogfooding/_blog tests/custom-rss.xsl
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+ RSS Feed |
+
+
+
+
+
+
+
This is an RSS feed . Subscribe by copying the URL from the address
+ bar into your newsreader. Visit
About Feeds to learn more
+ and get started. It’s free.
+
+
+ Custom RSS Feed Preview
+
+
+
+
Description:
+
+ Recent Posts
+
+
+
+
+
+
+
Published on
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts
index f68020dca475..d31bce0776d7 100644
--- a/website/_dogfooding/dogfooding.config.ts
+++ b/website/_dogfooding/dogfooding.config.ts
@@ -88,6 +88,10 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
type: 'all',
title: 'Docusaurus Tests Blog',
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
+ xslt: {
+ rss: 'custom-rss.xsl',
+ atom: 'custom-atom.xsl',
+ },
},
readingTime: ({content, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time
diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx
index 3ad31c590296..9b2d50d96d2e 100644
--- a/website/docs/api/plugins/plugin-content-blog.mdx
+++ b/website/docs/api/plugins/plugin-content-blog.mdx
@@ -77,6 +77,7 @@ Accepted fields:
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |
| `feedOptions.description` | `string` | \`$\{siteConfig.title} Blog\`
| Description of the feed. |
| `feedOptions.copyright` | `string` | `undefined` | Copyright message. |
+| `feedOptions.xslt` | boolean \| [FeedXSLTOptions](#FeedXSLTOptions)
| `undefined` | Copyright message. |
| `feedOptions.language` | `string` (See [documentation](http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes) for possible values) | `undefined` | Language metadata of the feed. |
| `sortPosts` | 'descending' \| 'ascending'
| `'descending'` | Governs the direction of blog post sorting. |
| `processBlogPosts` | [ProcessBlogPostsFn](#ProcessBlogPostsFn)
| `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). |
@@ -129,6 +130,25 @@ type ReadingTimeFn = (params: {
type FeedType = 'rss' | 'atom' | 'json';
```
+#### `FeedXSLTOptions` {#FeedXSLTOptions}
+
+Permits to style the blog XML feeds so that browsers render them nicely with [XSLT](https://developer.mozilla.org/en-US/docs/Web/XSLT).
+
+- Use `true` to let the blog use its built-in `.xsl` and `.css` files to style the blog feed
+- Use a falsy value (`undefined | null | false`) to disable the feature
+- Use a `string` to provide a file path to a custom `.xsl` file relative to the blog content folder. By convention, you must provide a `.css` file with the exact same name.
+
+```ts
+type FeedXSLTOptions =
+ | boolean
+ | undefined
+ | null
+ | {
+ rss?: string | boolean | null | undefined;
+ atom?: string | boolean | null | undefined;
+ };
+```
+
#### `CreateFeedItemsFn` {#CreateFeedItemsFn}
```ts
diff --git a/website/docs/blog.mdx b/website/docs/blog.mdx
index e6cf18644e98..468e42f3b929 100644
--- a/website/docs/blog.mdx
+++ b/website/docs/blog.mdx
@@ -602,9 +602,18 @@ type BlogOptions = {
title?: string;
description?: string;
copyright: string;
+
language?: string; // possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
limit?: number | false | null; // defaults to 20
- /** Allow control over the construction of BlogFeedItems */
+ // XSLT permits browsers to style and render nicely the feed XML files
+ xslt?:
+ | boolean
+ | {
+ //
+ rss?: string | boolean;
+ atom?: string | boolean;
+ };
+ // Allow control over the construction of BlogFeedItems
createFeedItems?: (params: {
blogPosts: BlogPost[];
siteConfig: DocusaurusConfig;
diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts
index 70d7fd33b78f..26a3e265f0af 100644
--- a/website/docusaurus.config.ts
+++ b/website/docusaurus.config.ts
@@ -487,7 +487,10 @@ export default async function createConfigAsync() {
postsPerPage: 5,
feedOptions: {
type: 'all',
+ description:
+ 'Keep up to date with upcoming Docusaurus releases and articles by following our feed!',
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
+ xslt: true,
},
blogTitle: 'Docusaurus blog',
blogDescription: 'Read blog posts about Docusaurus from the team',