mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-30 18:58:36 +02:00
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 <lorber.sebastien@gmail.com>
This commit is contained in:
parent
08a893a2eb
commit
7be1feaa0a
37 changed files with 3224 additions and 113 deletions
|
@ -32,6 +32,8 @@
|
||||||
"website/_dogfooding/_pages tests/diagrams.mdx",
|
"website/_dogfooding/_pages tests/diagrams.mdx",
|
||||||
"*.xyz",
|
"*.xyz",
|
||||||
"*.docx",
|
"*.docx",
|
||||||
|
"*.xsl",
|
||||||
|
"*.xslt",
|
||||||
"*.gitignore",
|
"*.gitignore",
|
||||||
"versioned_docs",
|
"versioned_docs",
|
||||||
"*.min.*",
|
"*.min.*",
|
||||||
|
|
|
@ -42,6 +42,10 @@ const config: Config = {
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
showReadingTime: true,
|
showReadingTime: true,
|
||||||
|
feedOptions: {
|
||||||
|
type: ['rss', 'atom'],
|
||||||
|
xslt: true,
|
||||||
|
},
|
||||||
// Please change this to your repo.
|
// Please change this to your repo.
|
||||||
// Remove this to remove the "edit this page" links.
|
// Remove this to remove the "edit this page" links.
|
||||||
editUrl:
|
editUrl:
|
||||||
|
|
|
@ -48,6 +48,10 @@ const config = {
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
showReadingTime: true,
|
showReadingTime: true,
|
||||||
|
feedOptions: {
|
||||||
|
type: ['rss', 'atom'],
|
||||||
|
xslt: true,
|
||||||
|
},
|
||||||
// Please change this to your repo.
|
// Please change this to your repo.
|
||||||
// Remove this to remove the "edit this page" links.
|
// Remove this to remove the "edit this page" links.
|
||||||
editUrl:
|
editUrl:
|
||||||
|
|
75
packages/docusaurus-plugin-content-blog/assets/atom.css
Normal file
75
packages/docusaurus-plugin-content-blog/assets/atom.css
Normal file
|
@ -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;
|
||||||
|
}
|
92
packages/docusaurus-plugin-content-blog/assets/atom.xsl
Normal file
92
packages/docusaurus-plugin-content-blog/assets/atom.xsl
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Atom Feed | <xsl:value-of
|
||||||
|
select="atom:feed/atom:title"
|
||||||
|
/></title>
|
||||||
|
<link rel="stylesheet" href="atom.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an Atom feed</strong>. Subscribe by copying the URL
|
||||||
|
from the address bar into your newsreader. Visit
|
||||||
|
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free.
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill: #f78422"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill: #ffffff"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<xsl:value-of select="atom:feed/atom:title" />
|
||||||
|
</h1>
|
||||||
|
<p class="blog-description">
|
||||||
|
<xsl:value-of select="atom:feed/atom:subtitle" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="blog-posts">
|
||||||
|
<xsl:for-each select="atom:feed/atom:entry">
|
||||||
|
<div class="blog-post">
|
||||||
|
<h3><a href="{atom:link[@rel='alternate']/@href}"><xsl:value-of
|
||||||
|
select="atom:title"
|
||||||
|
/></a></h3>
|
||||||
|
<div class="blog-post-date">
|
||||||
|
Published on <xsl:value-of
|
||||||
|
select="substring(atom:updated,0,11)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-description">
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
75
packages/docusaurus-plugin-content-blog/assets/rss.css
Normal file
75
packages/docusaurus-plugin-content-blog/assets/rss.css
Normal file
|
@ -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;
|
||||||
|
}
|
86
packages/docusaurus-plugin-content-blog/assets/rss.xsl
Normal file
86
packages/docusaurus-plugin-content-blog/assets/rss.xsl
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
||||||
|
<link rel="stylesheet" href="rss.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an RSS feed</strong>. Subscribe by copying the URL
|
||||||
|
from the address bar into your newsreader. Visit
|
||||||
|
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free.
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill: #f78422"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill: #ffffff"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<xsl:value-of select="rss/channel/title" />
|
||||||
|
</h1>
|
||||||
|
<p class="blog-description">
|
||||||
|
<xsl:value-of select="rss/channel/description" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="blog-posts">
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class="blog-post">
|
||||||
|
<h3><a href="{link}"><xsl:value-of select="title" /></a></h3>
|
||||||
|
<div class="blog-post-date">
|
||||||
|
Published on <xsl:value-of select="substring(pubDate,0,17)" />
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-description">
|
||||||
|
<xsl:value-of select="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
|
@ -59,6 +59,7 @@
|
||||||
"node": ">=18.0"
|
"node": ">=18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/shoehorn": "^0.1.2"
|
"@total-typescript/shoehorn": "^0.1.2",
|
||||||
|
"tree-node-cli": "^1.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css
generated
Normal file
76
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css
generated
Normal file
|
@ -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;
|
||||||
|
}
|
65
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl
generated
Normal file
65
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl
generated
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Atom Feed | <xsl:value-of select="atom:feed/atom:title" /></title>
|
||||||
|
<link rel="stylesheet" href="custom-atom.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an Atom feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"/>
|
||||||
|
<circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom Atom Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="atom:feed/atom:title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of select="atom:feed/atom:subtitle" /></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="atom:feed/atom:entry">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{atom:link[@rel='alternate']/@href}">
|
||||||
|
<xsl:value-of select="atom:title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(atom:updated, 0, 17)" />
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
76
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css
generated
Normal file
76
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css
generated
Normal file
|
@ -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;
|
||||||
|
}
|
66
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl
generated
Normal file
66
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl
generated
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
||||||
|
<link rel="stylesheet" href="custom-rss.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an RSS feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"/>
|
||||||
|
<circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom RSS Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="rss/channel/title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of select="rss/channel/description" /></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{link}">
|
||||||
|
<xsl:value-of select="title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(pubDate,0,17)" />
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of
|
||||||
|
select="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
75
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css
generated
Normal file
75
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css
generated
Normal file
|
@ -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;
|
||||||
|
}
|
92
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl
generated
Normal file
92
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl
generated
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Atom Feed | <xsl:value-of
|
||||||
|
select="atom:feed/atom:title"
|
||||||
|
/></title>
|
||||||
|
<link rel="stylesheet" href="atom.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an Atom feed</strong>. Subscribe by copying the URL
|
||||||
|
from the address bar into your newsreader. Visit
|
||||||
|
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free.
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill: #f78422"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill: #ffffff"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<xsl:value-of select="atom:feed/atom:title" />
|
||||||
|
</h1>
|
||||||
|
<p class="blog-description">
|
||||||
|
<xsl:value-of select="atom:feed/atom:subtitle" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="blog-posts">
|
||||||
|
<xsl:for-each select="atom:feed/atom:entry">
|
||||||
|
<div class="blog-post">
|
||||||
|
<h3><a href="{atom:link[@rel='alternate']/@href}"><xsl:value-of
|
||||||
|
select="atom:title"
|
||||||
|
/></a></h3>
|
||||||
|
<div class="blog-post-date">
|
||||||
|
Published on <xsl:value-of
|
||||||
|
select="substring(atom:updated,0,11)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-description">
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Atom Feed | <xsl:value-of select="atom:feed/atom:title" /></title>
|
||||||
|
<link rel="stylesheet" href="custom-atom.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an Atom feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"/>
|
||||||
|
<circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom Atom Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="atom:feed/atom:title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of select="atom:feed/atom:subtitle" /></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="atom:feed/atom:entry">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{atom:link[@rel='alternate']/@href}">
|
||||||
|
<xsl:value-of select="atom:title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(atom:updated, 0, 17)" />
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
||||||
|
<link rel="stylesheet" href="custom-rss.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an RSS feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"/>
|
||||||
|
<circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom RSS Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="rss/channel/title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of select="rss/channel/description" /></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{link}">
|
||||||
|
<xsl:value-of select="title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(pubDate,0,17)" />
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of
|
||||||
|
select="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
75
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css
generated
Normal file
75
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css
generated
Normal file
|
@ -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;
|
||||||
|
}
|
86
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl
generated
Normal file
86
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl
generated
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
||||||
|
<link rel="stylesheet" href="rss.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an RSS feed</strong>. Subscribe by copying the URL
|
||||||
|
from the address bar into your newsreader. Visit
|
||||||
|
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free.
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill: #f78422"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill: #ffffff"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill: #ffffff"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<xsl:value-of select="rss/channel/title" />
|
||||||
|
</h1>
|
||||||
|
<p class="blog-description">
|
||||||
|
<xsl:value-of select="rss/channel/description" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="blog-posts">
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class="blog-post">
|
||||||
|
<h3><a href="{link}"><xsl:value-of select="title" /></a></h3>
|
||||||
|
<div class="blog-post-date">
|
||||||
|
Published on <xsl:value-of select="substring(pubDate,0,17)" />
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-description">
|
||||||
|
<xsl:value-of select="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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"`;
|
exports[`validateOptions throws Error in case of invalid options 1`] = `""postsPerPage" must be greater than or equal to 1"`;
|
||||||
|
|
|
@ -10,13 +10,15 @@ import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
|
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
|
||||||
import {fromPartial} from '@total-typescript/shoehorn';
|
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 {generateBlogPosts} from '../blogUtils';
|
||||||
import {createBlogFeedFiles} from '../feed';
|
import {createBlogFeedFiles} from '../feed';
|
||||||
import {getAuthorsMap} from '../authorsMap';
|
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 {BlogContentPaths} from '../types';
|
||||||
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
|
import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
const DefaultI18N: I18n = {
|
const DefaultI18N: I18n = {
|
||||||
currentLocale: 'en',
|
currentLocale: 'en',
|
||||||
|
@ -50,8 +52,16 @@ function getBlogContentPaths(siteDir: string): BlogContentPaths {
|
||||||
|
|
||||||
async function testGenerateFeeds(
|
async function testGenerateFeeds(
|
||||||
context: LoadContext,
|
context: LoadContext,
|
||||||
options: PluginOptions,
|
optionsInput: Options,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const options = validateOptions({
|
||||||
|
validate: normalizePluginOptions as Validate<
|
||||||
|
Options | undefined,
|
||||||
|
PluginOptions
|
||||||
|
>,
|
||||||
|
options: optionsInput,
|
||||||
|
});
|
||||||
|
|
||||||
const contentPaths = getBlogContentPaths(context.siteDir);
|
const contentPaths = getBlogContentPaths(context.siteDir);
|
||||||
const authorsMap = await getAuthorsMap({
|
const authorsMap = await getAuthorsMap({
|
||||||
contentPaths,
|
contentPaths,
|
||||||
|
@ -72,10 +82,11 @@ async function testGenerateFeeds(
|
||||||
siteConfig: context.siteConfig,
|
siteConfig: context.siteConfig,
|
||||||
outDir: context.outDir,
|
outDir: context.outDir,
|
||||||
locale: 'en',
|
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(() => {});
|
const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {});
|
||||||
|
|
||||||
it('does not get generated without posts', async () => {
|
it('does not get generated without posts', async () => {
|
||||||
|
@ -105,13 +116,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
feedOptions: {
|
feedOptions: {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
|
xslt: {atom: null, rss: null},
|
||||||
},
|
},
|
||||||
readingTime: ({content, defaultReadingTime}) =>
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
defaultReadingTime({content}),
|
defaultReadingTime({content}),
|
||||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||||
onInlineTags: 'ignore',
|
onInlineTags: 'ignore',
|
||||||
onInlineAuthors: 'ignore',
|
onInlineAuthors: 'ignore',
|
||||||
} as PluginOptions,
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fsMock).toHaveBeenCalledTimes(0);
|
expect(fsMock).toHaveBeenCalledTimes(0);
|
||||||
|
@ -148,13 +160,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
feedOptions: {
|
feedOptions: {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
|
xslt: {atom: null, rss: null},
|
||||||
},
|
},
|
||||||
readingTime: ({content, defaultReadingTime}) =>
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
defaultReadingTime({content}),
|
defaultReadingTime({content}),
|
||||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||||
onInlineTags: 'ignore',
|
onInlineTags: 'ignore',
|
||||||
onInlineAuthors: 'ignore',
|
onInlineAuthors: 'ignore',
|
||||||
} as PluginOptions,
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -203,13 +216,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
xslt: {atom: null, rss: null},
|
||||||
},
|
},
|
||||||
readingTime: ({content, defaultReadingTime}) =>
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
defaultReadingTime({content}),
|
defaultReadingTime({content}),
|
||||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||||
onInlineTags: 'ignore',
|
onInlineTags: 'ignore',
|
||||||
onInlineAuthors: 'ignore',
|
onInlineAuthors: 'ignore',
|
||||||
} as PluginOptions,
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -249,13 +263,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
limit: 2,
|
limit: 2,
|
||||||
|
xslt: {atom: null, rss: null},
|
||||||
},
|
},
|
||||||
readingTime: ({content, defaultReadingTime}) =>
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
defaultReadingTime({content}),
|
defaultReadingTime({content}),
|
||||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||||
onInlineTags: 'ignore',
|
onInlineTags: 'ignore',
|
||||||
onInlineAuthors: 'ignore',
|
onInlineAuthors: 'ignore',
|
||||||
} as PluginOptions,
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -295,13 +310,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
feedOptions: {
|
feedOptions: {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
|
xslt: {atom: null, rss: null},
|
||||||
},
|
},
|
||||||
readingTime: ({content, defaultReadingTime}) =>
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
defaultReadingTime({content}),
|
defaultReadingTime({content}),
|
||||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||||
onInlineTags: 'ignore',
|
onInlineTags: 'ignore',
|
||||||
onInlineAuthors: 'ignore',
|
onInlineAuthors: 'ignore',
|
||||||
} as PluginOptions,
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -309,4 +325,99 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
fsMock.mockClear();
|
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: /<!--\s*truncate\s*-->/,
|
||||||
|
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: /<!--\s*truncate\s*-->/,
|
||||||
|
onInlineTags: 'ignore',
|
||||||
|
onInlineAuthors: 'ignore',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tree(path.join(outDir, 'blog'))).toMatchSnapshot('blog tree');
|
||||||
|
|
||||||
|
expect(fsMock.mock.calls).toMatchSnapshot();
|
||||||
|
fsMock.mockClear();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import {validateOptions, DEFAULT_OPTIONS} from '../options';
|
import {validateOptions, DEFAULT_OPTIONS, XSLTBuiltInPaths} from '../options';
|
||||||
import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog';
|
import type {
|
||||||
|
Options,
|
||||||
|
PluginOptions,
|
||||||
|
UserFeedOptions,
|
||||||
|
} from '@docusaurus/plugin-content-blog';
|
||||||
import type {Validate} from '@docusaurus/types';
|
import type {Validate} from '@docusaurus/types';
|
||||||
|
|
||||||
function testValidate(options?: Options) {
|
function testValidate(options?: Options) {
|
||||||
|
@ -38,7 +42,10 @@ describe('validateOptions', () => {
|
||||||
it('accepts correctly defined user options', () => {
|
it('accepts correctly defined user options', () => {
|
||||||
const userOptions: Options = {
|
const userOptions: Options = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
feedOptions: {type: 'rss' as const, title: 'myTitle'},
|
feedOptions: {
|
||||||
|
type: 'rss' as const,
|
||||||
|
title: 'myTitle',
|
||||||
|
},
|
||||||
path: 'not_blog',
|
path: 'not_blog',
|
||||||
routeBasePath: '/myBlog',
|
routeBasePath: '/myBlog',
|
||||||
postsPerPage: 5,
|
postsPerPage: 5,
|
||||||
|
@ -48,7 +55,13 @@ describe('validateOptions', () => {
|
||||||
};
|
};
|
||||||
expect(testValidate(userOptions)).toEqual({
|
expect(testValidate(userOptions)).toEqual({
|
||||||
...userOptions,
|
...userOptions,
|
||||||
feedOptions: {type: ['rss'], title: 'myTitle', copyright: '', limit: 20},
|
feedOptions: {
|
||||||
|
type: ['rss'],
|
||||||
|
title: 'myTitle',
|
||||||
|
copyright: '',
|
||||||
|
limit: 20,
|
||||||
|
xslt: {rss: null, atom: null},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,6 +92,7 @@ describe('validateOptions', () => {
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('feed', () => {
|
||||||
it('throws Error in case of invalid feed type', () => {
|
it('throws Error in case of invalid feed type', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
testValidate({
|
testValidate({
|
||||||
|
@ -97,18 +111,27 @@ describe('validateOptions', () => {
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
feedOptions: {type: ['rss', 'atom', 'json'], copyright: '', limit: 20},
|
feedOptions: {
|
||||||
|
type: ['rss', 'atom', 'json'],
|
||||||
|
copyright: '',
|
||||||
|
limit: 20,
|
||||||
|
xslt: {rss: null, atom: null},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts null type and return same', () => {
|
it('accepts null feed type and return same', () => {
|
||||||
expect(
|
expect(
|
||||||
testValidate({
|
testValidate({
|
||||||
feedOptions: {type: null},
|
feedOptions: {type: null},
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
feedOptions: {type: null, limit: 20},
|
feedOptions: {
|
||||||
|
type: null,
|
||||||
|
limit: 20,
|
||||||
|
xslt: {rss: null, atom: null},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -132,10 +155,122 @@ describe('validateOptions', () => {
|
||||||
title: 'title',
|
title: 'title',
|
||||||
copyright: '',
|
copyright: '',
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
xslt: {rss: null, atom: null},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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]"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('accepts 0 sidebar count', () => {
|
it('accepts 0 sidebar count', () => {
|
||||||
const userOptions = {blogSidebarCount: 0};
|
const userOptions = {blogSidebarCount: 0};
|
||||||
expect(testValidate(userOptions)).toEqual({
|
expect(testValidate(userOptions)).toEqual({
|
||||||
|
|
|
@ -7,15 +7,20 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import logger from '@docusaurus/logger';
|
|
||||||
import {Feed, type Author as FeedAuthor} from 'feed';
|
import {Feed, type Author as FeedAuthor} from 'feed';
|
||||||
import * as srcset from 'srcset';
|
import * as srcset from 'srcset';
|
||||||
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
|
import {
|
||||||
|
getDataFilePath,
|
||||||
|
normalizeUrl,
|
||||||
|
readOutputHTMLFile,
|
||||||
|
} from '@docusaurus/utils';
|
||||||
import {
|
import {
|
||||||
blogPostContainerID,
|
blogPostContainerID,
|
||||||
applyTrailingSlash,
|
applyTrailingSlash,
|
||||||
} from '@docusaurus/utils-common';
|
} from '@docusaurus/utils-common';
|
||||||
import {load as cheerioLoad} from 'cheerio';
|
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 {DocusaurusConfig, HtmlTags, LoadContext} from '@docusaurus/types';
|
||||||
import type {
|
import type {
|
||||||
FeedType,
|
FeedType,
|
||||||
|
@ -23,6 +28,8 @@ import type {
|
||||||
Author,
|
Author,
|
||||||
BlogPost,
|
BlogPost,
|
||||||
BlogFeedItem,
|
BlogFeedItem,
|
||||||
|
FeedOptions,
|
||||||
|
FeedXSLTOptions,
|
||||||
} from '@docusaurus/plugin-content-blog';
|
} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
async function generateBlogFeed({
|
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(
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>',
|
||||||
|
`<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="${path.basename(
|
||||||
|
xsltFilePath,
|
||||||
|
)}"?>`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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({
|
async function createBlogFeedFile({
|
||||||
feed,
|
feed,
|
||||||
feedType,
|
feedType,
|
||||||
generatePath,
|
generatePath,
|
||||||
|
feedOptions,
|
||||||
|
contentPaths,
|
||||||
}: {
|
}: {
|
||||||
feed: Feed;
|
feed: Feed;
|
||||||
feedType: FeedType;
|
feedType: FeedType;
|
||||||
generatePath: string;
|
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 {
|
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) {
|
} catch (err) {
|
||||||
logger.error(`Generating ${feedType} feed failed.`);
|
throw new Error(`Generating ${feedType} feed failed.`, {
|
||||||
throw err;
|
cause: err as Error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,12 +341,14 @@ export async function createBlogFeedFiles({
|
||||||
siteConfig,
|
siteConfig,
|
||||||
outDir,
|
outDir,
|
||||||
locale,
|
locale,
|
||||||
|
contentPaths,
|
||||||
}: {
|
}: {
|
||||||
blogPosts: BlogPost[];
|
blogPosts: BlogPost[];
|
||||||
options: PluginOptions;
|
options: PluginOptions;
|
||||||
siteConfig: DocusaurusConfig;
|
siteConfig: DocusaurusConfig;
|
||||||
outDir: string;
|
outDir: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
|
contentPaths: BlogContentPaths;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
||||||
|
|
||||||
|
@ -250,6 +371,8 @@ export async function createBlogFeedFiles({
|
||||||
feed,
|
feed,
|
||||||
feedType,
|
feedType,
|
||||||
generatePath: path.join(outDir, options.routeBasePath),
|
generatePath: path.join(outDir, options.routeBasePath),
|
||||||
|
feedOptions: options.feedOptions,
|
||||||
|
contentPaths,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -388,6 +388,7 @@ export default async function pluginContentBlog(
|
||||||
outDir,
|
outDir,
|
||||||
siteConfig,
|
siteConfig,
|
||||||
locale: currentLocale,
|
locale: currentLocale,
|
||||||
|
contentPaths,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
Joi,
|
Joi,
|
||||||
RemarkPluginsSchema,
|
RemarkPluginsSchema,
|
||||||
|
@ -19,11 +20,20 @@ import type {
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
Options,
|
Options,
|
||||||
FeedType,
|
FeedType,
|
||||||
|
FeedXSLTOptions,
|
||||||
} from '@docusaurus/plugin-content-blog';
|
} from '@docusaurus/plugin-content-blog';
|
||||||
import type {OptionValidationContext} from '@docusaurus/types';
|
import type {OptionValidationContext} from '@docusaurus/types';
|
||||||
|
|
||||||
export const DEFAULT_OPTIONS: PluginOptions = {
|
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: [],
|
beforeDefaultRehypePlugins: [],
|
||||||
beforeDefaultRemarkPlugins: [],
|
beforeDefaultRemarkPlugins: [],
|
||||||
admonitions: true,
|
admonitions: true,
|
||||||
|
@ -64,6 +74,94 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
||||||
onInlineAuthors: 'warn',
|
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<FeedXSLTOptions>({
|
||||||
|
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<PluginOptions>({
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||||
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
||||||
archiveBasePath: Joi.string()
|
archiveBasePath: Joi.string()
|
||||||
|
@ -116,37 +214,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||||
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
||||||
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
||||||
),
|
),
|
||||||
feedOptions: Joi.object({
|
feedOptions: FeedOptionsSchema,
|
||||||
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),
|
|
||||||
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
||||||
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
||||||
sortPosts: Joi.string()
|
sortPosts: Joi.string()
|
||||||
|
|
|
@ -315,10 +315,26 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
}) => string | undefined;
|
}) => string | undefined;
|
||||||
|
|
||||||
export type FeedType = 'rss' | 'atom' | 'json';
|
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.
|
* Normalized feed options used within code.
|
||||||
*/
|
*/
|
||||||
export type FeedOptions = {
|
export type FeedOptions = {
|
||||||
|
/** Enable feeds xslt stylesheets */
|
||||||
|
xslt: FeedXSLTOptions;
|
||||||
/** If `null`, no feed is generated. */
|
/** If `null`, no feed is generated. */
|
||||||
type?: FeedType[] | null;
|
type?: FeedType[] | null;
|
||||||
/** Title of generated feed. */
|
/** Title of generated feed. */
|
||||||
|
@ -507,6 +523,14 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw';
|
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
|
* 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 of feed to be generated. Use `null` to disable generation. */
|
||||||
type?: FeedOptions['type'] | 'all' | FeedType;
|
type?: FeedOptions['type'] | 'all' | FeedType;
|
||||||
|
/** User-provided XSLT config for feeds, un-normalized */
|
||||||
|
xslt?: UserFeedXSLTOptions;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -412,6 +412,8 @@ webpackbar
|
||||||
webstorm
|
webstorm
|
||||||
Wolcott
|
Wolcott
|
||||||
Xplorer
|
Xplorer
|
||||||
|
xslt
|
||||||
|
XSLT
|
||||||
XSOAR
|
XSOAR
|
||||||
Yacop
|
Yacop
|
||||||
yangshun
|
yangshun
|
||||||
|
|
76
website/_dogfooding/_blog tests/custom-atom.css
Normal file
76
website/_dogfooding/_blog tests/custom-atom.css
Normal file
|
@ -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;
|
||||||
|
}
|
94
website/_dogfooding/_blog tests/custom-atom.xsl
Normal file
94
website/_dogfooding/_blog tests/custom-atom.xsl
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Atom Feed | <xsl:value-of
|
||||||
|
select="atom:feed/atom:title"
|
||||||
|
/></title>
|
||||||
|
<link rel="stylesheet" href="custom-atom.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an Atom feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a
|
||||||
|
href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill:#F78422;"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom Atom Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="atom:feed/atom:title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of
|
||||||
|
select="atom:feed/atom:subtitle"
|
||||||
|
/></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="atom:feed/atom:entry">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{atom:link[@rel='alternate']/@href}">
|
||||||
|
<xsl:value-of select="atom:title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(atom:updated, 0, 17)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
76
website/_dogfooding/_blog tests/custom-rss.css
Normal file
76
website/_dogfooding/_blog tests/custom-rss.css
Normal file
|
@ -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;
|
||||||
|
}
|
92
website/_dogfooding/_blog tests/custom-rss.xsl
Normal file
92
website/_dogfooding/_blog tests/custom-rss.xsl
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
||||||
|
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
||||||
|
<link rel="stylesheet" href="custom-rss.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="description">
|
||||||
|
<div class="info">
|
||||||
|
<strong>This is an RSS feed</strong>. Subscribe by copying the URL from the address
|
||||||
|
bar into your newsreader. Visit <a
|
||||||
|
href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
||||||
|
and get started. It’s free. </div>
|
||||||
|
<h1 class="flex items-start">
|
||||||
|
<div class="rss-icon">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 455.731 455.731"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="fill:#F78422;"
|
||||||
|
width="455.731"
|
||||||
|
height="455.731"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
||||||
|
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
||||||
|
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
||||||
|
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
style="fill:#FFFFFF;"
|
||||||
|
cx="109.833"
|
||||||
|
cy="346.26"
|
||||||
|
r="46.088"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Custom RSS Feed Preview </h1>
|
||||||
|
<h2>
|
||||||
|
<xsl:value-of select="rss/channel/title" />
|
||||||
|
</h2>
|
||||||
|
<p>Description: <xsl:value-of
|
||||||
|
select="rss/channel/description"
|
||||||
|
/></p>
|
||||||
|
</div>
|
||||||
|
<h2>Recent Posts</h2>
|
||||||
|
<div class="postsList">
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class="pb-7">
|
||||||
|
<a href="{link}">
|
||||||
|
<xsl:value-of select="title" />
|
||||||
|
</a>
|
||||||
|
<div class="text-2 text-offset"> Published on <xsl:value-of
|
||||||
|
select="substring(pubDate,0,17)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-2 text-offset italic">
|
||||||
|
<xsl:value-of select="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -88,6 +88,10 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
|
||||||
type: 'all',
|
type: 'all',
|
||||||
title: 'Docusaurus Tests Blog',
|
title: 'Docusaurus Tests Blog',
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
||||||
|
xslt: {
|
||||||
|
rss: 'custom-rss.xsl',
|
||||||
|
atom: 'custom-atom.xsl',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
readingTime: ({content, frontMatter, defaultReadingTime}) =>
|
readingTime: ({content, frontMatter, defaultReadingTime}) =>
|
||||||
frontMatter.hide_reading_time
|
frontMatter.hide_reading_time
|
||||||
|
|
|
@ -77,6 +77,7 @@ Accepted fields:
|
||||||
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |
|
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |
|
||||||
| `feedOptions.description` | `string` | <code>\`$\{siteConfig.title} Blog\`</code> | Description of the feed. |
|
| `feedOptions.description` | `string` | <code>\`$\{siteConfig.title} Blog\`</code> | Description of the feed. |
|
||||||
| `feedOptions.copyright` | `string` | `undefined` | Copyright message. |
|
| `feedOptions.copyright` | `string` | `undefined` | Copyright message. |
|
||||||
|
| `feedOptions.xslt` | <code>boolean \| [FeedXSLTOptions](#FeedXSLTOptions)</code> | `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. |
|
| `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` | <code>'descending' \| 'ascending' </code> | `'descending'` | Governs the direction of blog post sorting. |
|
| `sortPosts` | <code>'descending' \| 'ascending' </code> | `'descending'` | Governs the direction of blog post sorting. |
|
||||||
| `processBlogPosts` | <code>[ProcessBlogPostsFn](#ProcessBlogPostsFn)</code> | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). |
|
| `processBlogPosts` | <code>[ProcessBlogPostsFn](#ProcessBlogPostsFn)</code> | `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';
|
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}
|
#### `CreateFeedItemsFn` {#CreateFeedItemsFn}
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
|
@ -602,9 +602,18 @@ type BlogOptions = {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
copyright: string;
|
copyright: string;
|
||||||
|
|
||||||
language?: string; // possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
language?: string; // possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||||
limit?: number | false | null; // defaults to 20
|
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: {
|
createFeedItems?: (params: {
|
||||||
blogPosts: BlogPost[];
|
blogPosts: BlogPost[];
|
||||||
siteConfig: DocusaurusConfig;
|
siteConfig: DocusaurusConfig;
|
||||||
|
|
|
@ -487,7 +487,10 @@ export default async function createConfigAsync() {
|
||||||
postsPerPage: 5,
|
postsPerPage: 5,
|
||||||
feedOptions: {
|
feedOptions: {
|
||||||
type: 'all',
|
type: 'all',
|
||||||
|
description:
|
||||||
|
'Keep up to date with upcoming Docusaurus releases and articles by following our feed!',
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
||||||
|
xslt: true,
|
||||||
},
|
},
|
||||||
blogTitle: 'Docusaurus blog',
|
blogTitle: 'Docusaurus blog',
|
||||||
blogDescription: 'Read blog posts about Docusaurus from the team',
|
blogDescription: 'Read blog posts about Docusaurus from the team',
|
||||||
|
|
Loading…
Add table
Reference in a new issue