@docusaurus/plugin-content-blog 0.0.0-6014 → 0.0.0-6016
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/atom.css +75 -0
- package/assets/atom.xsl +92 -0
- package/assets/rss.css +75 -0
- package/assets/rss.xsl +86 -0
- package/lib/feed.d.ts +3 -1
- package/lib/feed.js +67 -18
- package/lib/index.js +1 -0
- package/lib/options.d.ts +4 -0
- package/lib/options.js +79 -25
- package/package.json +12 -11
- package/src/feed.ts +140 -17
- package/src/index.ts +1 -0
- package/src/options.ts +100 -32
- package/src/plugin-content-blog.d.ts +26 -0
package/assets/atom.css
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
main {
|
|
9
|
+
flex: 1 0 auto;
|
|
10
|
+
width: 100%;
|
|
11
|
+
margin: 2rem auto;
|
|
12
|
+
max-width: 800px;
|
|
13
|
+
/* stylelint-disable-next-line font-family-name-quotes */
|
|
14
|
+
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.info {
|
|
18
|
+
display: block;
|
|
19
|
+
margin: 2rem 0;
|
|
20
|
+
padding: 1.6rem 2.4rem;
|
|
21
|
+
border: 1px solid dodgerblue;
|
|
22
|
+
border-left-width: 0.5rem;
|
|
23
|
+
border-radius: 0.4rem;
|
|
24
|
+
background-color: #edf5ff;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
a {
|
|
28
|
+
color: #005aff;
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h1 {
|
|
33
|
+
text-wrap: balance;
|
|
34
|
+
font-size: 3.4rem;
|
|
35
|
+
font-weight: 800;
|
|
36
|
+
margin-bottom: 2rem;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 .rss-icon {
|
|
42
|
+
height: 3.2rem;
|
|
43
|
+
width: 3.2rem;
|
|
44
|
+
margin-right: 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
h2 {
|
|
48
|
+
font-size: 2.2rem;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
margin-bottom: 0.2rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h3 {
|
|
54
|
+
font-size: 1.8rem;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
margin-bottom: 0.1rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.blog-description {
|
|
60
|
+
font-size: 1.4rem;
|
|
61
|
+
margin-bottom: 0.6rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.blog-post-date {
|
|
65
|
+
font-size: 1rem;
|
|
66
|
+
line-height: 1.4rem;
|
|
67
|
+
font-style: italic;
|
|
68
|
+
color: #797b7e;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.blog-post-description {
|
|
72
|
+
font-size: 1rem;
|
|
73
|
+
line-height: 1.4rem;
|
|
74
|
+
color: #434349;
|
|
75
|
+
}
|
package/assets/atom.xsl
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<xsl:stylesheet
|
|
3
|
+
version="3.0"
|
|
4
|
+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
|
5
|
+
xmlns:atom="http://www.w3.org/2005/Atom">
|
|
6
|
+
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
|
7
|
+
|
|
8
|
+
<xsl:template match="/">
|
|
9
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<title>Atom Feed | <xsl:value-of
|
|
12
|
+
select="atom:feed/atom:title"
|
|
13
|
+
/></title>
|
|
14
|
+
<link rel="stylesheet" href="atom.css" />
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<main>
|
|
18
|
+
<div class="description">
|
|
19
|
+
<div class="info">
|
|
20
|
+
<strong>This is an Atom feed</strong>. Subscribe by copying the URL
|
|
21
|
+
from the address bar into your newsreader. Visit
|
|
22
|
+
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
|
23
|
+
and get started. It’s free.
|
|
24
|
+
</div>
|
|
25
|
+
<h1>
|
|
26
|
+
<div class="rss-icon">
|
|
27
|
+
<svg
|
|
28
|
+
version="1.1"
|
|
29
|
+
id="Capa_1"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
32
|
+
viewBox="0 0 455.731 455.731"
|
|
33
|
+
xml:space="preserve">
|
|
34
|
+
<g>
|
|
35
|
+
<rect
|
|
36
|
+
x="0"
|
|
37
|
+
y="0"
|
|
38
|
+
style="fill: #f78422"
|
|
39
|
+
width="455.731"
|
|
40
|
+
height="455.731"
|
|
41
|
+
/>
|
|
42
|
+
<g>
|
|
43
|
+
<path
|
|
44
|
+
style="fill: #ffffff"
|
|
45
|
+
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
|
46
|
+
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
|
47
|
+
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
|
48
|
+
/>
|
|
49
|
+
<path
|
|
50
|
+
style="fill: #ffffff"
|
|
51
|
+
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
|
52
|
+
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
|
53
|
+
/>
|
|
54
|
+
<circle
|
|
55
|
+
style="fill: #ffffff"
|
|
56
|
+
cx="109.833"
|
|
57
|
+
cy="346.26"
|
|
58
|
+
r="46.088"
|
|
59
|
+
/>
|
|
60
|
+
</g>
|
|
61
|
+
</g>
|
|
62
|
+
</svg>
|
|
63
|
+
</div>
|
|
64
|
+
<xsl:value-of select="atom:feed/atom:title" />
|
|
65
|
+
</h1>
|
|
66
|
+
<p class="blog-description">
|
|
67
|
+
<xsl:value-of select="atom:feed/atom:subtitle" />
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
<h2>Recent Posts</h2>
|
|
71
|
+
<div class="blog-posts">
|
|
72
|
+
<xsl:for-each select="atom:feed/atom:entry">
|
|
73
|
+
<div class="blog-post">
|
|
74
|
+
<h3><a href="{atom:link[@rel='alternate']/@href}"><xsl:value-of
|
|
75
|
+
select="atom:title"
|
|
76
|
+
/></a></h3>
|
|
77
|
+
<div class="blog-post-date">
|
|
78
|
+
Published on <xsl:value-of
|
|
79
|
+
select="substring(atom:updated,0,11)"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="blog-post-description">
|
|
83
|
+
<xsl:value-of select="atom:summary" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</xsl:for-each>
|
|
87
|
+
</div>
|
|
88
|
+
</main>
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
91
|
+
</xsl:template>
|
|
92
|
+
</xsl:stylesheet>
|
package/assets/rss.css
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
main {
|
|
9
|
+
flex: 1 0 auto;
|
|
10
|
+
width: 100%;
|
|
11
|
+
margin: 2rem auto;
|
|
12
|
+
max-width: 800px;
|
|
13
|
+
/* stylelint-disable-next-line font-family-name-quotes */
|
|
14
|
+
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.info {
|
|
18
|
+
display: block;
|
|
19
|
+
margin: 2rem 0;
|
|
20
|
+
padding: 1.6rem 2.4rem;
|
|
21
|
+
border: 1px solid dodgerblue;
|
|
22
|
+
border-left-width: 0.5rem;
|
|
23
|
+
border-radius: 0.4rem;
|
|
24
|
+
background-color: #edf5ff;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
a {
|
|
28
|
+
color: #005aff;
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h1 {
|
|
33
|
+
text-wrap: balance;
|
|
34
|
+
font-size: 3.4rem;
|
|
35
|
+
font-weight: 800;
|
|
36
|
+
margin-bottom: 2rem;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 .rss-icon {
|
|
42
|
+
height: 3.2rem;
|
|
43
|
+
width: 3.2rem;
|
|
44
|
+
margin-right: 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
h2 {
|
|
48
|
+
font-size: 2.2rem;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
margin-bottom: 0.2rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h3 {
|
|
54
|
+
font-size: 1.8rem;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
margin-bottom: 0.1rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.blog-description {
|
|
60
|
+
font-size: 1.4rem;
|
|
61
|
+
margin-bottom: 0.6rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.blog-post-date {
|
|
65
|
+
font-size: 1rem;
|
|
66
|
+
line-height: 1.4rem;
|
|
67
|
+
font-style: italic;
|
|
68
|
+
color: #797b7e;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.blog-post-description {
|
|
72
|
+
font-size: 1rem;
|
|
73
|
+
line-height: 1.4rem;
|
|
74
|
+
color: #434349;
|
|
75
|
+
}
|
package/assets/rss.xsl
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<xsl:stylesheet
|
|
3
|
+
version="3.0"
|
|
4
|
+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
|
5
|
+
xmlns:atom="http://www.w3.org/2005/Atom">
|
|
6
|
+
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
|
|
7
|
+
|
|
8
|
+
<xsl:template match="/">
|
|
9
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<title>RSS Feed | <xsl:value-of select="rss/channel/title" /></title>
|
|
12
|
+
<link rel="stylesheet" href="rss.css" />
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<main>
|
|
16
|
+
<div class="description">
|
|
17
|
+
<div class="info">
|
|
18
|
+
<strong>This is an RSS feed</strong>. Subscribe by copying the URL
|
|
19
|
+
from the address bar into your newsreader. Visit
|
|
20
|
+
<a href="https://aboutfeeds.com/">About Feeds</a> to learn more
|
|
21
|
+
and get started. It’s free.
|
|
22
|
+
</div>
|
|
23
|
+
<h1>
|
|
24
|
+
<div class="rss-icon">
|
|
25
|
+
<svg
|
|
26
|
+
version="1.1"
|
|
27
|
+
id="Capa_1"
|
|
28
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
30
|
+
viewBox="0 0 455.731 455.731"
|
|
31
|
+
xml:space="preserve">
|
|
32
|
+
<g>
|
|
33
|
+
<rect
|
|
34
|
+
x="0"
|
|
35
|
+
y="0"
|
|
36
|
+
style="fill: #f78422"
|
|
37
|
+
width="455.731"
|
|
38
|
+
height="455.731"
|
|
39
|
+
/>
|
|
40
|
+
<g>
|
|
41
|
+
<path
|
|
42
|
+
style="fill: #ffffff"
|
|
43
|
+
d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
|
|
44
|
+
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
|
|
45
|
+
C391.986,303.103,357.971,220.923,296.208,159.16z"
|
|
46
|
+
/>
|
|
47
|
+
<path
|
|
48
|
+
style="fill: #ffffff"
|
|
49
|
+
d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
|
|
50
|
+
C282.429,270.196,184.507,172.273,64.143,172.273z"
|
|
51
|
+
/>
|
|
52
|
+
<circle
|
|
53
|
+
style="fill: #ffffff"
|
|
54
|
+
cx="109.833"
|
|
55
|
+
cy="346.26"
|
|
56
|
+
r="46.088"
|
|
57
|
+
/>
|
|
58
|
+
</g>
|
|
59
|
+
</g>
|
|
60
|
+
</svg>
|
|
61
|
+
</div>
|
|
62
|
+
<xsl:value-of select="rss/channel/title" />
|
|
63
|
+
</h1>
|
|
64
|
+
<p class="blog-description">
|
|
65
|
+
<xsl:value-of select="rss/channel/description" />
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<h2>Recent Posts</h2>
|
|
69
|
+
<div class="blog-posts">
|
|
70
|
+
<xsl:for-each select="rss/channel/item">
|
|
71
|
+
<div class="blog-post">
|
|
72
|
+
<h3><a href="{link}"><xsl:value-of select="title" /></a></h3>
|
|
73
|
+
<div class="blog-post-date">
|
|
74
|
+
Published on <xsl:value-of select="substring(pubDate,0,17)" />
|
|
75
|
+
</div>
|
|
76
|
+
<div class="blog-post-description">
|
|
77
|
+
<xsl:value-of select="description" />
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</xsl:for-each>
|
|
81
|
+
</div>
|
|
82
|
+
</main>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
85
|
+
</xsl:template>
|
|
86
|
+
</xsl:stylesheet>
|
package/lib/feed.d.ts
CHANGED
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
+
import type { BlogContentPaths } from './types';
|
|
7
8
|
import type { DocusaurusConfig, HtmlTags, LoadContext } from '@docusaurus/types';
|
|
8
9
|
import type { PluginOptions, BlogPost } from '@docusaurus/plugin-content-blog';
|
|
9
|
-
export declare function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }: {
|
|
10
|
+
export declare function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, contentPaths, }: {
|
|
10
11
|
blogPosts: BlogPost[];
|
|
11
12
|
options: PluginOptions;
|
|
12
13
|
siteConfig: DocusaurusConfig;
|
|
13
14
|
outDir: string;
|
|
14
15
|
locale: string;
|
|
16
|
+
contentPaths: BlogContentPaths;
|
|
15
17
|
}): Promise<void>;
|
|
16
18
|
export declare function createFeedHtmlHeadTags({ context, options, }: {
|
|
17
19
|
context: LoadContext;
|
package/lib/feed.js
CHANGED
|
@@ -11,12 +11,12 @@ exports.createFeedHtmlHeadTags = createFeedHtmlHeadTags;
|
|
|
11
11
|
const tslib_1 = require("tslib");
|
|
12
12
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
13
13
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
14
|
-
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
15
14
|
const feed_1 = require("feed");
|
|
16
15
|
const srcset = tslib_1.__importStar(require("srcset"));
|
|
17
16
|
const utils_1 = require("@docusaurus/utils");
|
|
18
17
|
const utils_common_1 = require("@docusaurus/utils-common");
|
|
19
18
|
const cheerio_1 = require("cheerio");
|
|
19
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
20
20
|
async function generateBlogFeed({ blogPosts, options, siteConfig, outDir, locale, }) {
|
|
21
21
|
if (!blogPosts.length) {
|
|
22
22
|
return null;
|
|
@@ -106,25 +106,72 @@ async function defaultCreateFeedItems({ blogPosts, siteConfig, outDir, }) {
|
|
|
106
106
|
return feedItem;
|
|
107
107
|
}));
|
|
108
108
|
}
|
|
109
|
-
async function
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
109
|
+
async function resolveXsltFilePaths({ xsltFilePath, contentPaths, }) {
|
|
110
|
+
const xsltAbsolutePath = path_1.default.isAbsolute(xsltFilePath)
|
|
111
|
+
? xsltFilePath
|
|
112
|
+
: (await (0, utils_1.getDataFilePath)({ filePath: xsltFilePath, contentPaths })) ??
|
|
113
|
+
path_1.default.resolve(contentPaths.contentPath, xsltFilePath);
|
|
114
|
+
if (!(await fs_extra_1.default.pathExists(xsltAbsolutePath))) {
|
|
115
|
+
throw new Error(logger_1.default.interpolate `Blog feed XSLT file not found at path=${path_1.default.relative(process.cwd(), xsltAbsolutePath)}`);
|
|
116
|
+
}
|
|
117
|
+
const parsedPath = path_1.default.parse(xsltAbsolutePath);
|
|
118
|
+
const cssAbsolutePath = path_1.default.resolve(parsedPath.dir, `${parsedPath.name}.css`);
|
|
119
|
+
if (!(await fs_extra_1.default.pathExists(xsltAbsolutePath))) {
|
|
120
|
+
throw new Error(logger_1.default.interpolate `Blog feed XSLT file was found at path=${path_1.default.relative(process.cwd(), xsltAbsolutePath)}
|
|
121
|
+
But its expected co-located CSS file could not be found at path=${path_1.default.relative(process.cwd(), cssAbsolutePath)}
|
|
122
|
+
If you want to provide a custom XSLT file, you must provide a CSS file with the exact same name.`);
|
|
123
|
+
}
|
|
124
|
+
return { xsltAbsolutePath, cssAbsolutePath };
|
|
125
|
+
}
|
|
126
|
+
async function generateXsltFiles({ xsltFilePath, generatePath, contentPaths, }) {
|
|
127
|
+
const { xsltAbsolutePath, cssAbsolutePath } = await resolveXsltFilePaths({
|
|
128
|
+
xsltFilePath,
|
|
129
|
+
contentPaths,
|
|
130
|
+
});
|
|
131
|
+
const xsltOutputPath = path_1.default.join(generatePath, path_1.default.basename(xsltAbsolutePath));
|
|
132
|
+
const cssOutputPath = path_1.default.join(generatePath, path_1.default.basename(cssAbsolutePath));
|
|
133
|
+
await fs_extra_1.default.copy(xsltAbsolutePath, xsltOutputPath);
|
|
134
|
+
await fs_extra_1.default.copy(cssAbsolutePath, cssOutputPath);
|
|
135
|
+
}
|
|
136
|
+
// This modifies the XML feed content to add a relative href to the XSLT file
|
|
137
|
+
// Good enough for now: we probably don't need a full XML parser just for that
|
|
138
|
+
// See also https://darekkay.com/blog/rss-styling/
|
|
139
|
+
function injectXslt({ feedContent, xsltFilePath, }) {
|
|
140
|
+
return feedContent.replace('<?xml version="1.0" encoding="utf-8"?>', `<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="${path_1.default.basename(xsltFilePath)}"?>`);
|
|
141
|
+
}
|
|
142
|
+
const FeedConfigs = {
|
|
143
|
+
rss: {
|
|
144
|
+
outputFileName: 'rss.xml',
|
|
145
|
+
getContent: (feed) => feed.rss2(),
|
|
146
|
+
getXsltFilePath: (xslt) => xslt.rss,
|
|
147
|
+
},
|
|
148
|
+
atom: {
|
|
149
|
+
outputFileName: 'atom.xml',
|
|
150
|
+
getContent: (feed) => feed.atom1(),
|
|
151
|
+
getXsltFilePath: (xslt) => xslt.atom,
|
|
152
|
+
},
|
|
153
|
+
json: {
|
|
154
|
+
outputFileName: 'feed.json',
|
|
155
|
+
getContent: (feed) => feed.json1(),
|
|
156
|
+
getXsltFilePath: () => null,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
async function createBlogFeedFile({ feed, feedType, generatePath, feedOptions, contentPaths, }) {
|
|
122
160
|
try {
|
|
123
|
-
|
|
161
|
+
const feedConfig = FeedConfigs[feedType];
|
|
162
|
+
let feedContent = feedConfig.getContent(feed);
|
|
163
|
+
const xsltFilePath = feedConfig.getXsltFilePath(feedOptions.xslt);
|
|
164
|
+
if (xsltFilePath) {
|
|
165
|
+
await generateXsltFiles({ xsltFilePath, contentPaths, generatePath });
|
|
166
|
+
feedContent = injectXslt({ feedContent, xsltFilePath });
|
|
167
|
+
}
|
|
168
|
+
const outputPath = path_1.default.join(generatePath, feedConfig.outputFileName);
|
|
169
|
+
await fs_extra_1.default.outputFile(outputPath, feedContent);
|
|
124
170
|
}
|
|
125
171
|
catch (err) {
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
throw new Error(`Generating ${feedType} feed failed.`, {
|
|
173
|
+
cause: err,
|
|
174
|
+
});
|
|
128
175
|
}
|
|
129
176
|
}
|
|
130
177
|
function shouldBeInFeed(blogPost) {
|
|
@@ -132,7 +179,7 @@ function shouldBeInFeed(blogPost) {
|
|
|
132
179
|
blogPost.metadata.frontMatter.unlisted;
|
|
133
180
|
return !excluded;
|
|
134
181
|
}
|
|
135
|
-
async function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }) {
|
|
182
|
+
async function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, contentPaths, }) {
|
|
136
183
|
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
|
137
184
|
const feed = await generateBlogFeed({
|
|
138
185
|
blogPosts,
|
|
@@ -149,6 +196,8 @@ async function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfi
|
|
|
149
196
|
feed,
|
|
150
197
|
feedType,
|
|
151
198
|
generatePath: path_1.default.join(outDir, options.routeBasePath),
|
|
199
|
+
feedOptions: options.feedOptions,
|
|
200
|
+
contentPaths,
|
|
152
201
|
})));
|
|
153
202
|
}
|
|
154
203
|
function createFeedHtmlHeadTags({ context, options, }) {
|
package/lib/index.js
CHANGED
package/lib/options.d.ts
CHANGED
|
@@ -7,4 +7,8 @@
|
|
|
7
7
|
import type { PluginOptions, Options } from '@docusaurus/plugin-content-blog';
|
|
8
8
|
import type { OptionValidationContext } from '@docusaurus/types';
|
|
9
9
|
export declare const DEFAULT_OPTIONS: PluginOptions;
|
|
10
|
+
export declare const XSLTBuiltInPaths: {
|
|
11
|
+
rss: string;
|
|
12
|
+
atom: string;
|
|
13
|
+
};
|
|
10
14
|
export declare function validateOptions({ validate, options, }: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions;
|
package/lib/options.js
CHANGED
|
@@ -6,12 +6,22 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.DEFAULT_OPTIONS = void 0;
|
|
9
|
+
exports.XSLTBuiltInPaths = exports.DEFAULT_OPTIONS = void 0;
|
|
10
10
|
exports.validateOptions = validateOptions;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
13
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
12
14
|
const utils_1 = require("@docusaurus/utils");
|
|
13
15
|
exports.DEFAULT_OPTIONS = {
|
|
14
|
-
feedOptions: {
|
|
16
|
+
feedOptions: {
|
|
17
|
+
type: ['rss', 'atom'],
|
|
18
|
+
copyright: '',
|
|
19
|
+
limit: 20,
|
|
20
|
+
xslt: {
|
|
21
|
+
rss: null,
|
|
22
|
+
atom: null,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
15
25
|
beforeDefaultRehypePlugins: [],
|
|
16
26
|
beforeDefaultRemarkPlugins: [],
|
|
17
27
|
admonitions: true,
|
|
@@ -51,6 +61,72 @@ exports.DEFAULT_OPTIONS = {
|
|
|
51
61
|
authorsBasePath: 'authors',
|
|
52
62
|
onInlineAuthors: 'warn',
|
|
53
63
|
};
|
|
64
|
+
exports.XSLTBuiltInPaths = {
|
|
65
|
+
rss: path_1.default.resolve(__dirname, '..', 'assets', 'rss.xsl'),
|
|
66
|
+
atom: path_1.default.resolve(__dirname, '..', 'assets', 'atom.xsl'),
|
|
67
|
+
};
|
|
68
|
+
function normalizeXsltOption(option, type) {
|
|
69
|
+
if (typeof option === 'string') {
|
|
70
|
+
return option;
|
|
71
|
+
}
|
|
72
|
+
if (option === true) {
|
|
73
|
+
return exports.XSLTBuiltInPaths[type];
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function createXSLTFilePathSchema(type) {
|
|
78
|
+
return utils_validation_1.Joi.alternatives()
|
|
79
|
+
.try(utils_validation_1.Joi.string().required(), utils_validation_1.Joi.boolean()
|
|
80
|
+
.allow(null, () => undefined)
|
|
81
|
+
.custom((val) => normalizeXsltOption(val, type)))
|
|
82
|
+
.optional()
|
|
83
|
+
.default(null);
|
|
84
|
+
}
|
|
85
|
+
const FeedXSLTOptionsSchema = utils_validation_1.Joi.alternatives()
|
|
86
|
+
.try(utils_validation_1.Joi.object({
|
|
87
|
+
rss: createXSLTFilePathSchema('rss'),
|
|
88
|
+
atom: createXSLTFilePathSchema('atom'),
|
|
89
|
+
}).required(), utils_validation_1.Joi.boolean()
|
|
90
|
+
.allow(null, () => undefined)
|
|
91
|
+
.custom((val) => ({
|
|
92
|
+
rss: normalizeXsltOption(val, 'rss'),
|
|
93
|
+
atom: normalizeXsltOption(val, 'atom'),
|
|
94
|
+
})))
|
|
95
|
+
.optional()
|
|
96
|
+
.custom((val) => {
|
|
97
|
+
if (val === null) {
|
|
98
|
+
return {
|
|
99
|
+
rss: null,
|
|
100
|
+
atom: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return val;
|
|
104
|
+
})
|
|
105
|
+
.default(exports.DEFAULT_OPTIONS.feedOptions.xslt);
|
|
106
|
+
const FeedOptionsSchema = utils_validation_1.Joi.object({
|
|
107
|
+
type: utils_validation_1.Joi.alternatives()
|
|
108
|
+
.try(utils_validation_1.Joi.array().items(utils_validation_1.Joi.string().equal('rss', 'atom', 'json')), utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.string().equal('all', 'rss', 'atom', 'json'), {
|
|
109
|
+
then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom', 'json'] : [val]),
|
|
110
|
+
}))
|
|
111
|
+
.allow(null)
|
|
112
|
+
.default(exports.DEFAULT_OPTIONS.feedOptions.type),
|
|
113
|
+
xslt: FeedXSLTOptionsSchema,
|
|
114
|
+
title: utils_validation_1.Joi.string().allow(''),
|
|
115
|
+
description: utils_validation_1.Joi.string().allow(''),
|
|
116
|
+
// Only add default value when user actually wants a feed (type is not null)
|
|
117
|
+
copyright: utils_validation_1.Joi.when('type', {
|
|
118
|
+
is: utils_validation_1.Joi.any().valid(null),
|
|
119
|
+
then: utils_validation_1.Joi.string().optional(),
|
|
120
|
+
otherwise: utils_validation_1.Joi.string()
|
|
121
|
+
.allow('')
|
|
122
|
+
.default(exports.DEFAULT_OPTIONS.feedOptions.copyright),
|
|
123
|
+
}),
|
|
124
|
+
language: utils_validation_1.Joi.string(),
|
|
125
|
+
createFeedItems: utils_validation_1.Joi.function(),
|
|
126
|
+
limit: utils_validation_1.Joi.alternatives()
|
|
127
|
+
.try(utils_validation_1.Joi.number(), utils_validation_1.Joi.valid(null), utils_validation_1.Joi.valid(false))
|
|
128
|
+
.default(exports.DEFAULT_OPTIONS.feedOptions.limit),
|
|
129
|
+
}).default(exports.DEFAULT_OPTIONS.feedOptions);
|
|
54
130
|
const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
55
131
|
path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
|
|
56
132
|
archiveBasePath: utils_validation_1.Joi.string()
|
|
@@ -89,29 +165,7 @@ const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
|
89
165
|
truncateMarker: utils_validation_1.Joi.object().default(exports.DEFAULT_OPTIONS.truncateMarker),
|
|
90
166
|
beforeDefaultRemarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRemarkPlugins),
|
|
91
167
|
beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
|
|
92
|
-
feedOptions:
|
|
93
|
-
type: utils_validation_1.Joi.alternatives()
|
|
94
|
-
.try(utils_validation_1.Joi.array().items(utils_validation_1.Joi.string().equal('rss', 'atom', 'json')), utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.string().equal('all', 'rss', 'atom', 'json'), {
|
|
95
|
-
then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom', 'json'] : [val]),
|
|
96
|
-
}))
|
|
97
|
-
.allow(null)
|
|
98
|
-
.default(exports.DEFAULT_OPTIONS.feedOptions.type),
|
|
99
|
-
title: utils_validation_1.Joi.string().allow(''),
|
|
100
|
-
description: utils_validation_1.Joi.string().allow(''),
|
|
101
|
-
// Only add default value when user actually wants a feed (type is not null)
|
|
102
|
-
copyright: utils_validation_1.Joi.when('type', {
|
|
103
|
-
is: utils_validation_1.Joi.any().valid(null),
|
|
104
|
-
then: utils_validation_1.Joi.string().optional(),
|
|
105
|
-
otherwise: utils_validation_1.Joi.string()
|
|
106
|
-
.allow('')
|
|
107
|
-
.default(exports.DEFAULT_OPTIONS.feedOptions.copyright),
|
|
108
|
-
}),
|
|
109
|
-
language: utils_validation_1.Joi.string(),
|
|
110
|
-
createFeedItems: utils_validation_1.Joi.function(),
|
|
111
|
-
limit: utils_validation_1.Joi.alternatives()
|
|
112
|
-
.try(utils_validation_1.Joi.number(), utils_validation_1.Joi.valid(null), utils_validation_1.Joi.valid(false))
|
|
113
|
-
.default(exports.DEFAULT_OPTIONS.feedOptions.limit),
|
|
114
|
-
}).default(exports.DEFAULT_OPTIONS.feedOptions),
|
|
168
|
+
feedOptions: FeedOptionsSchema,
|
|
115
169
|
authorsMapPath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.authorsMapPath),
|
|
116
170
|
readingTime: utils_validation_1.Joi.function().default(() => exports.DEFAULT_OPTIONS.readingTime),
|
|
117
171
|
sortPosts: utils_validation_1.Joi.string()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-content-blog",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-6016",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@docusaurus/core": "0.0.0-
|
|
35
|
-
"@docusaurus/logger": "0.0.0-
|
|
36
|
-
"@docusaurus/mdx-loader": "0.0.0-
|
|
37
|
-
"@docusaurus/theme-common": "0.0.0-
|
|
38
|
-
"@docusaurus/types": "0.0.0-
|
|
39
|
-
"@docusaurus/utils": "0.0.0-
|
|
40
|
-
"@docusaurus/utils-common": "0.0.0-
|
|
41
|
-
"@docusaurus/utils-validation": "0.0.0-
|
|
34
|
+
"@docusaurus/core": "0.0.0-6016",
|
|
35
|
+
"@docusaurus/logger": "0.0.0-6016",
|
|
36
|
+
"@docusaurus/mdx-loader": "0.0.0-6016",
|
|
37
|
+
"@docusaurus/theme-common": "0.0.0-6016",
|
|
38
|
+
"@docusaurus/types": "0.0.0-6016",
|
|
39
|
+
"@docusaurus/utils": "0.0.0-6016",
|
|
40
|
+
"@docusaurus/utils-common": "0.0.0-6016",
|
|
41
|
+
"@docusaurus/utils-validation": "0.0.0-6016",
|
|
42
42
|
"cheerio": "^1.0.0-rc.12",
|
|
43
43
|
"feed": "^4.2.2",
|
|
44
44
|
"fs-extra": "^11.1.1",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"node": ">=18.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@total-typescript/shoehorn": "^0.1.2"
|
|
62
|
+
"@total-typescript/shoehorn": "^0.1.2",
|
|
63
|
+
"tree-node-cli": "^1.6.0"
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "c51435db0c58b94667da1c745f1635de65240c63"
|
|
65
66
|
}
|
package/src/feed.ts
CHANGED
|
@@ -7,15 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs-extra';
|
|
10
|
-
import logger from '@docusaurus/logger';
|
|
11
10
|
import {Feed, type Author as FeedAuthor} from 'feed';
|
|
12
11
|
import * as srcset from 'srcset';
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getDataFilePath,
|
|
14
|
+
normalizeUrl,
|
|
15
|
+
readOutputHTMLFile,
|
|
16
|
+
} from '@docusaurus/utils';
|
|
14
17
|
import {
|
|
15
18
|
blogPostContainerID,
|
|
16
19
|
applyTrailingSlash,
|
|
17
20
|
} from '@docusaurus/utils-common';
|
|
18
21
|
import {load as cheerioLoad} from 'cheerio';
|
|
22
|
+
import logger from '@docusaurus/logger';
|
|
23
|
+
import type {BlogContentPaths} from './types';
|
|
19
24
|
import type {DocusaurusConfig, HtmlTags, LoadContext} from '@docusaurus/types';
|
|
20
25
|
import type {
|
|
21
26
|
FeedType,
|
|
@@ -23,6 +28,8 @@ import type {
|
|
|
23
28
|
Author,
|
|
24
29
|
BlogPost,
|
|
25
30
|
BlogFeedItem,
|
|
31
|
+
FeedOptions,
|
|
32
|
+
FeedXSLTOptions,
|
|
26
33
|
} from '@docusaurus/plugin-content-blog';
|
|
27
34
|
|
|
28
35
|
async function generateBlogFeed({
|
|
@@ -180,32 +187,144 @@ async function defaultCreateFeedItems({
|
|
|
180
187
|
);
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
async function resolveXsltFilePaths({
|
|
191
|
+
xsltFilePath,
|
|
192
|
+
contentPaths,
|
|
193
|
+
}: {
|
|
194
|
+
xsltFilePath: string;
|
|
195
|
+
contentPaths: BlogContentPaths;
|
|
196
|
+
}) {
|
|
197
|
+
const xsltAbsolutePath: string = path.isAbsolute(xsltFilePath)
|
|
198
|
+
? xsltFilePath
|
|
199
|
+
: (await getDataFilePath({filePath: xsltFilePath, contentPaths})) ??
|
|
200
|
+
path.resolve(contentPaths.contentPath, xsltFilePath);
|
|
201
|
+
|
|
202
|
+
if (!(await fs.pathExists(xsltAbsolutePath))) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
logger.interpolate`Blog feed XSLT file not found at path=${path.relative(
|
|
205
|
+
process.cwd(),
|
|
206
|
+
xsltAbsolutePath,
|
|
207
|
+
)}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const parsedPath = path.parse(xsltAbsolutePath);
|
|
212
|
+
const cssAbsolutePath = path.resolve(
|
|
213
|
+
parsedPath.dir,
|
|
214
|
+
`${parsedPath.name}.css`,
|
|
215
|
+
);
|
|
216
|
+
if (!(await fs.pathExists(xsltAbsolutePath))) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
logger.interpolate`Blog feed XSLT file was found at path=${path.relative(
|
|
219
|
+
process.cwd(),
|
|
220
|
+
xsltAbsolutePath,
|
|
221
|
+
)}
|
|
222
|
+
But its expected co-located CSS file could not be found at path=${path.relative(
|
|
223
|
+
process.cwd(),
|
|
224
|
+
cssAbsolutePath,
|
|
225
|
+
)}
|
|
226
|
+
If you want to provide a custom XSLT file, you must provide a CSS file with the exact same name.`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {xsltAbsolutePath, cssAbsolutePath};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function generateXsltFiles({
|
|
234
|
+
xsltFilePath,
|
|
235
|
+
generatePath,
|
|
236
|
+
contentPaths,
|
|
237
|
+
}: {
|
|
238
|
+
xsltFilePath: string;
|
|
239
|
+
generatePath: string;
|
|
240
|
+
contentPaths: BlogContentPaths;
|
|
241
|
+
}) {
|
|
242
|
+
const {xsltAbsolutePath, cssAbsolutePath} = await resolveXsltFilePaths({
|
|
243
|
+
xsltFilePath,
|
|
244
|
+
contentPaths,
|
|
245
|
+
});
|
|
246
|
+
const xsltOutputPath = path.join(
|
|
247
|
+
generatePath,
|
|
248
|
+
path.basename(xsltAbsolutePath),
|
|
249
|
+
);
|
|
250
|
+
const cssOutputPath = path.join(generatePath, path.basename(cssAbsolutePath));
|
|
251
|
+
await fs.copy(xsltAbsolutePath, xsltOutputPath);
|
|
252
|
+
await fs.copy(cssAbsolutePath, cssOutputPath);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// This modifies the XML feed content to add a relative href to the XSLT file
|
|
256
|
+
// Good enough for now: we probably don't need a full XML parser just for that
|
|
257
|
+
// See also https://darekkay.com/blog/rss-styling/
|
|
258
|
+
function injectXslt({
|
|
259
|
+
feedContent,
|
|
260
|
+
xsltFilePath,
|
|
261
|
+
}: {
|
|
262
|
+
feedContent: string;
|
|
263
|
+
xsltFilePath: string;
|
|
264
|
+
}) {
|
|
265
|
+
return feedContent.replace(
|
|
266
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
267
|
+
`<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="${path.basename(
|
|
268
|
+
xsltFilePath,
|
|
269
|
+
)}"?>`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const FeedConfigs: Record<
|
|
274
|
+
FeedType,
|
|
275
|
+
{
|
|
276
|
+
outputFileName: string;
|
|
277
|
+
getContent: (feed: Feed) => string;
|
|
278
|
+
getXsltFilePath: (xslt: FeedXSLTOptions) => string | null;
|
|
279
|
+
}
|
|
280
|
+
> = {
|
|
281
|
+
rss: {
|
|
282
|
+
outputFileName: 'rss.xml',
|
|
283
|
+
getContent: (feed) => feed.rss2(),
|
|
284
|
+
getXsltFilePath: (xslt) => xslt.rss,
|
|
285
|
+
},
|
|
286
|
+
atom: {
|
|
287
|
+
outputFileName: 'atom.xml',
|
|
288
|
+
getContent: (feed) => feed.atom1(),
|
|
289
|
+
getXsltFilePath: (xslt) => xslt.atom,
|
|
290
|
+
},
|
|
291
|
+
json: {
|
|
292
|
+
outputFileName: 'feed.json',
|
|
293
|
+
getContent: (feed) => feed.json1(),
|
|
294
|
+
getXsltFilePath: () => null,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
183
298
|
async function createBlogFeedFile({
|
|
184
299
|
feed,
|
|
185
300
|
feedType,
|
|
186
301
|
generatePath,
|
|
302
|
+
feedOptions,
|
|
303
|
+
contentPaths,
|
|
187
304
|
}: {
|
|
188
305
|
feed: Feed;
|
|
189
306
|
feedType: FeedType;
|
|
190
307
|
generatePath: string;
|
|
308
|
+
feedOptions: FeedOptions;
|
|
309
|
+
contentPaths: BlogContentPaths;
|
|
191
310
|
}) {
|
|
192
|
-
const [feedContent, feedPath] = (() => {
|
|
193
|
-
switch (feedType) {
|
|
194
|
-
case 'rss':
|
|
195
|
-
return [feed.rss2(), 'rss.xml'];
|
|
196
|
-
case 'json':
|
|
197
|
-
return [feed.json1(), 'feed.json'];
|
|
198
|
-
case 'atom':
|
|
199
|
-
return [feed.atom1(), 'atom.xml'];
|
|
200
|
-
default:
|
|
201
|
-
throw new Error(`Feed type ${feedType} not supported.`);
|
|
202
|
-
}
|
|
203
|
-
})();
|
|
204
311
|
try {
|
|
205
|
-
|
|
312
|
+
const feedConfig = FeedConfigs[feedType];
|
|
313
|
+
|
|
314
|
+
let feedContent = feedConfig.getContent(feed);
|
|
315
|
+
|
|
316
|
+
const xsltFilePath = feedConfig.getXsltFilePath(feedOptions.xslt);
|
|
317
|
+
if (xsltFilePath) {
|
|
318
|
+
await generateXsltFiles({xsltFilePath, contentPaths, generatePath});
|
|
319
|
+
feedContent = injectXslt({feedContent, xsltFilePath});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const outputPath = path.join(generatePath, feedConfig.outputFileName);
|
|
323
|
+
await fs.outputFile(outputPath, feedContent);
|
|
206
324
|
} catch (err) {
|
|
207
|
-
|
|
208
|
-
|
|
325
|
+
throw new Error(`Generating ${feedType} feed failed.`, {
|
|
326
|
+
cause: err as Error,
|
|
327
|
+
});
|
|
209
328
|
}
|
|
210
329
|
}
|
|
211
330
|
|
|
@@ -222,12 +341,14 @@ export async function createBlogFeedFiles({
|
|
|
222
341
|
siteConfig,
|
|
223
342
|
outDir,
|
|
224
343
|
locale,
|
|
344
|
+
contentPaths,
|
|
225
345
|
}: {
|
|
226
346
|
blogPosts: BlogPost[];
|
|
227
347
|
options: PluginOptions;
|
|
228
348
|
siteConfig: DocusaurusConfig;
|
|
229
349
|
outDir: string;
|
|
230
350
|
locale: string;
|
|
351
|
+
contentPaths: BlogContentPaths;
|
|
231
352
|
}): Promise<void> {
|
|
232
353
|
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
|
233
354
|
|
|
@@ -250,6 +371,8 @@ export async function createBlogFeedFiles({
|
|
|
250
371
|
feed,
|
|
251
372
|
feedType,
|
|
252
373
|
generatePath: path.join(outDir, options.routeBasePath),
|
|
374
|
+
feedOptions: options.feedOptions,
|
|
375
|
+
contentPaths,
|
|
253
376
|
}),
|
|
254
377
|
),
|
|
255
378
|
);
|
package/src/index.ts
CHANGED
package/src/options.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import path from 'path';
|
|
8
9
|
import {
|
|
9
10
|
Joi,
|
|
10
11
|
RemarkPluginsSchema,
|
|
@@ -19,11 +20,20 @@ import type {
|
|
|
19
20
|
PluginOptions,
|
|
20
21
|
Options,
|
|
21
22
|
FeedType,
|
|
23
|
+
FeedXSLTOptions,
|
|
22
24
|
} from '@docusaurus/plugin-content-blog';
|
|
23
25
|
import type {OptionValidationContext} from '@docusaurus/types';
|
|
24
26
|
|
|
25
27
|
export const DEFAULT_OPTIONS: PluginOptions = {
|
|
26
|
-
feedOptions: {
|
|
28
|
+
feedOptions: {
|
|
29
|
+
type: ['rss', 'atom'],
|
|
30
|
+
copyright: '',
|
|
31
|
+
limit: 20,
|
|
32
|
+
xslt: {
|
|
33
|
+
rss: null,
|
|
34
|
+
atom: null,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
27
37
|
beforeDefaultRehypePlugins: [],
|
|
28
38
|
beforeDefaultRemarkPlugins: [],
|
|
29
39
|
admonitions: true,
|
|
@@ -64,6 +74,94 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
64
74
|
onInlineAuthors: 'warn',
|
|
65
75
|
};
|
|
66
76
|
|
|
77
|
+
export const XSLTBuiltInPaths = {
|
|
78
|
+
rss: path.resolve(__dirname, '..', 'assets', 'rss.xsl'),
|
|
79
|
+
atom: path.resolve(__dirname, '..', 'assets', 'atom.xsl'),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function normalizeXsltOption(
|
|
83
|
+
option: string | null | boolean,
|
|
84
|
+
type: 'rss' | 'atom',
|
|
85
|
+
): string | null {
|
|
86
|
+
if (typeof option === 'string') {
|
|
87
|
+
return option;
|
|
88
|
+
}
|
|
89
|
+
if (option === true) {
|
|
90
|
+
return XSLTBuiltInPaths[type];
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createXSLTFilePathSchema(type: 'atom' | 'rss') {
|
|
96
|
+
return Joi.alternatives()
|
|
97
|
+
.try(
|
|
98
|
+
Joi.string().required(),
|
|
99
|
+
Joi.boolean()
|
|
100
|
+
.allow(null, () => undefined)
|
|
101
|
+
.custom((val) => normalizeXsltOption(val, type)),
|
|
102
|
+
)
|
|
103
|
+
.optional()
|
|
104
|
+
.default(null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const FeedXSLTOptionsSchema = Joi.alternatives()
|
|
108
|
+
.try(
|
|
109
|
+
Joi.object<FeedXSLTOptions>({
|
|
110
|
+
rss: createXSLTFilePathSchema('rss'),
|
|
111
|
+
atom: createXSLTFilePathSchema('atom'),
|
|
112
|
+
}).required(),
|
|
113
|
+
Joi.boolean()
|
|
114
|
+
.allow(null, () => undefined)
|
|
115
|
+
.custom((val) => ({
|
|
116
|
+
rss: normalizeXsltOption(val, 'rss'),
|
|
117
|
+
atom: normalizeXsltOption(val, 'atom'),
|
|
118
|
+
})),
|
|
119
|
+
)
|
|
120
|
+
.optional()
|
|
121
|
+
.custom((val) => {
|
|
122
|
+
if (val === null) {
|
|
123
|
+
return {
|
|
124
|
+
rss: null,
|
|
125
|
+
atom: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return val;
|
|
129
|
+
})
|
|
130
|
+
.default(DEFAULT_OPTIONS.feedOptions.xslt);
|
|
131
|
+
|
|
132
|
+
const FeedOptionsSchema = Joi.object({
|
|
133
|
+
type: Joi.alternatives()
|
|
134
|
+
.try(
|
|
135
|
+
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
136
|
+
Joi.alternatives().conditional(
|
|
137
|
+
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
138
|
+
{
|
|
139
|
+
then: Joi.custom((val: FeedType | 'all') =>
|
|
140
|
+
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
141
|
+
),
|
|
142
|
+
},
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
.allow(null)
|
|
146
|
+
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
147
|
+
xslt: FeedXSLTOptionsSchema,
|
|
148
|
+
title: Joi.string().allow(''),
|
|
149
|
+
description: Joi.string().allow(''),
|
|
150
|
+
// Only add default value when user actually wants a feed (type is not null)
|
|
151
|
+
copyright: Joi.when('type', {
|
|
152
|
+
is: Joi.any().valid(null),
|
|
153
|
+
then: Joi.string().optional(),
|
|
154
|
+
otherwise: Joi.string()
|
|
155
|
+
.allow('')
|
|
156
|
+
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
157
|
+
}),
|
|
158
|
+
language: Joi.string(),
|
|
159
|
+
createFeedItems: Joi.function(),
|
|
160
|
+
limit: Joi.alternatives()
|
|
161
|
+
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
162
|
+
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
163
|
+
}).default(DEFAULT_OPTIONS.feedOptions);
|
|
164
|
+
|
|
67
165
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
68
166
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
|
69
167
|
archiveBasePath: Joi.string()
|
|
@@ -116,37 +214,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
116
214
|
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
|
117
215
|
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
|
118
216
|
),
|
|
119
|
-
feedOptions:
|
|
120
|
-
type: Joi.alternatives()
|
|
121
|
-
.try(
|
|
122
|
-
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
123
|
-
Joi.alternatives().conditional(
|
|
124
|
-
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
125
|
-
{
|
|
126
|
-
then: Joi.custom((val: FeedType | 'all') =>
|
|
127
|
-
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
128
|
-
),
|
|
129
|
-
},
|
|
130
|
-
),
|
|
131
|
-
)
|
|
132
|
-
.allow(null)
|
|
133
|
-
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
134
|
-
title: Joi.string().allow(''),
|
|
135
|
-
description: Joi.string().allow(''),
|
|
136
|
-
// Only add default value when user actually wants a feed (type is not null)
|
|
137
|
-
copyright: Joi.when('type', {
|
|
138
|
-
is: Joi.any().valid(null),
|
|
139
|
-
then: Joi.string().optional(),
|
|
140
|
-
otherwise: Joi.string()
|
|
141
|
-
.allow('')
|
|
142
|
-
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
143
|
-
}),
|
|
144
|
-
language: Joi.string(),
|
|
145
|
-
createFeedItems: Joi.function(),
|
|
146
|
-
limit: Joi.alternatives()
|
|
147
|
-
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
148
|
-
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
149
|
-
}).default(DEFAULT_OPTIONS.feedOptions),
|
|
217
|
+
feedOptions: FeedOptionsSchema,
|
|
150
218
|
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
|
151
219
|
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
|
152
220
|
sortPosts: Joi.string()
|
|
@@ -315,10 +315,26 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
315
315
|
}) => string | undefined;
|
|
316
316
|
|
|
317
317
|
export type FeedType = 'rss' | 'atom' | 'json';
|
|
318
|
+
|
|
319
|
+
export type FeedXSLTOptions = {
|
|
320
|
+
/**
|
|
321
|
+
* RSS XSLT file path, relative to the blog content folder.
|
|
322
|
+
* If null, no XSLT file is used and the feed will be displayed as raw XML.
|
|
323
|
+
*/
|
|
324
|
+
rss: string | null;
|
|
325
|
+
/**
|
|
326
|
+
* Atom XSLT file path, relative to the blog content folder.
|
|
327
|
+
* If null, no XSLT file is used and the feed will be displayed as raw XML.
|
|
328
|
+
*/
|
|
329
|
+
atom: string | null;
|
|
330
|
+
};
|
|
331
|
+
|
|
318
332
|
/**
|
|
319
333
|
* Normalized feed options used within code.
|
|
320
334
|
*/
|
|
321
335
|
export type FeedOptions = {
|
|
336
|
+
/** Enable feeds xslt stylesheets */
|
|
337
|
+
xslt: FeedXSLTOptions;
|
|
322
338
|
/** If `null`, no feed is generated. */
|
|
323
339
|
type?: FeedType[] | null;
|
|
324
340
|
/** Title of generated feed. */
|
|
@@ -507,6 +523,14 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
507
523
|
onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw';
|
|
508
524
|
};
|
|
509
525
|
|
|
526
|
+
export type UserFeedXSLTOptions =
|
|
527
|
+
| boolean
|
|
528
|
+
| null
|
|
529
|
+
| {
|
|
530
|
+
rss?: string | boolean | null;
|
|
531
|
+
atom?: string | boolean | null;
|
|
532
|
+
};
|
|
533
|
+
|
|
510
534
|
/**
|
|
511
535
|
* Feed options, as provided by user config. `type` accepts `all` as shortcut
|
|
512
536
|
*/
|
|
@@ -515,6 +539,8 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
515
539
|
{
|
|
516
540
|
/** Type of feed to be generated. Use `null` to disable generation. */
|
|
517
541
|
type?: FeedOptions['type'] | 'all' | FeedType;
|
|
542
|
+
/** User-provided XSLT config for feeds, un-normalized */
|
|
543
|
+
xslt?: UserFeedXSLTOptions;
|
|
518
544
|
}
|
|
519
545
|
>;
|
|
520
546
|
/**
|