@docusaurus/plugin-content-blog 3.4.0 → 3.5.0
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/authors.d.ts +9 -11
- package/lib/authors.js +42 -64
- package/lib/authorsMap.d.ts +23 -0
- package/lib/authorsMap.js +116 -0
- package/lib/authorsProblems.d.ts +21 -0
- package/lib/authorsProblems.js +51 -0
- package/lib/authorsSocials.d.ts +10 -0
- package/lib/authorsSocials.js +48 -0
- package/lib/blogUtils.d.ts +6 -3
- package/lib/blogUtils.js +29 -14
- package/lib/client/contexts.d.ts +33 -0
- package/lib/client/contexts.js +54 -0
- package/lib/client/index.d.ts +3 -3
- package/lib/client/index.js +3 -9
- package/lib/client/sidebarUtils.d.ts +21 -0
- package/lib/client/sidebarUtils.js +49 -0
- package/lib/client/sidebarUtils.test.d.ts +7 -0
- package/lib/client/sidebarUtils.test.js +43 -0
- package/lib/client/structuredDataUtils.d.ts +10 -0
- package/lib/client/structuredDataUtils.js +122 -0
- package/lib/feed.d.ts +3 -2
- package/lib/feed.js +69 -21
- package/lib/frontMatter.d.ts +0 -1
- package/lib/frontMatter.js +3 -2
- package/lib/index.d.ts +0 -1
- package/lib/index.js +23 -4
- package/lib/markdownLoader.js +1 -1
- package/lib/options.d.ts +4 -1
- package/lib/options.js +98 -26
- package/lib/props.d.ts +9 -2
- package/lib/props.js +21 -3
- package/lib/remark/footnoteIDFixer.js +1 -1
- package/lib/routes.d.ts +0 -1
- package/lib/routes.js +82 -14
- package/lib/translations.d.ts +0 -1
- package/lib/translations.js +2 -3
- package/package.json +13 -10
- package/src/authors.ts +56 -93
- package/src/authorsMap.ts +171 -0
- package/src/authorsProblems.ts +72 -0
- package/src/authorsSocials.ts +64 -0
- package/src/blogUtils.ts +34 -7
- package/src/client/contexts.tsx +95 -0
- package/src/client/index.tsx +24 -0
- package/src/client/sidebarUtils.test.ts +52 -0
- package/src/client/sidebarUtils.tsx +85 -0
- package/src/client/structuredDataUtils.ts +178 -0
- package/src/feed.ts +140 -17
- package/src/frontMatter.ts +2 -0
- package/src/index.ts +31 -1
- package/src/options.ts +123 -32
- package/src/plugin-content-blog.d.ts +150 -12
- package/src/props.ts +39 -1
- package/src/routes.ts +102 -12
- package/src/client/index.ts +0 -20
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/frontMatter.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
URISchema,
|
|
14
14
|
validateFrontMatter,
|
|
15
15
|
} from '@docusaurus/utils-validation';
|
|
16
|
+
import {AuthorSocialsSchema} from './authorsSocials';
|
|
16
17
|
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
|
17
18
|
|
|
18
19
|
const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|
@@ -21,6 +22,7 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|
|
21
22
|
title: Joi.string(),
|
|
22
23
|
url: URISchema,
|
|
23
24
|
imageURL: Joi.string(),
|
|
25
|
+
socials: AuthorSocialsSchema,
|
|
24
26
|
})
|
|
25
27
|
.or('key', 'name', 'imageURL')
|
|
26
28
|
.rename('image_url', 'imageURL', {alias: true});
|
package/src/index.ts
CHANGED
|
@@ -28,12 +28,14 @@ import {
|
|
|
28
28
|
shouldBeListed,
|
|
29
29
|
applyProcessBlogPosts,
|
|
30
30
|
generateBlogPosts,
|
|
31
|
+
reportUntruncatedBlogPosts,
|
|
31
32
|
} from './blogUtils';
|
|
32
33
|
import footnoteIDFixer from './remark/footnoteIDFixer';
|
|
33
34
|
import {translateContent, getTranslationFiles} from './translations';
|
|
34
35
|
import {createBlogFeedFiles, createFeedHtmlHeadTags} from './feed';
|
|
35
36
|
|
|
36
37
|
import {createAllRoutes} from './routes';
|
|
38
|
+
import {checkAuthorsMapPermalinkCollisions, getAuthorsMap} from './authorsMap';
|
|
37
39
|
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
|
38
40
|
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
39
41
|
import type {
|
|
@@ -160,15 +162,38 @@ export default async function pluginContentBlog(
|
|
|
160
162
|
blogTitle,
|
|
161
163
|
blogSidebarTitle,
|
|
162
164
|
pageBasePath,
|
|
165
|
+
authorsBasePath,
|
|
166
|
+
authorsMapPath,
|
|
163
167
|
} = options;
|
|
164
168
|
|
|
165
169
|
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
166
170
|
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
|
167
|
-
|
|
171
|
+
|
|
172
|
+
const authorsMap = await getAuthorsMap({
|
|
173
|
+
contentPaths,
|
|
174
|
+
authorsMapPath,
|
|
175
|
+
authorsBaseRoutePath: normalizeUrl([
|
|
176
|
+
baseUrl,
|
|
177
|
+
routeBasePath,
|
|
178
|
+
authorsBasePath,
|
|
179
|
+
]),
|
|
180
|
+
});
|
|
181
|
+
checkAuthorsMapPermalinkCollisions(authorsMap);
|
|
182
|
+
|
|
183
|
+
let blogPosts = await generateBlogPosts(
|
|
184
|
+
contentPaths,
|
|
185
|
+
context,
|
|
186
|
+
options,
|
|
187
|
+
authorsMap,
|
|
188
|
+
);
|
|
168
189
|
blogPosts = await applyProcessBlogPosts({
|
|
169
190
|
blogPosts,
|
|
170
191
|
processBlogPosts: options.processBlogPosts,
|
|
171
192
|
});
|
|
193
|
+
reportUntruncatedBlogPosts({
|
|
194
|
+
blogPosts,
|
|
195
|
+
onUntruncatedBlogPosts: options.onUntruncatedBlogPosts,
|
|
196
|
+
});
|
|
172
197
|
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
|
173
198
|
|
|
174
199
|
if (!blogPosts.length) {
|
|
@@ -178,6 +203,7 @@ export default async function pluginContentBlog(
|
|
|
178
203
|
blogListPaginated: [],
|
|
179
204
|
blogTags: {},
|
|
180
205
|
blogTagsListPath,
|
|
206
|
+
authorsMap,
|
|
181
207
|
};
|
|
182
208
|
}
|
|
183
209
|
|
|
@@ -226,6 +252,7 @@ export default async function pluginContentBlog(
|
|
|
226
252
|
blogListPaginated,
|
|
227
253
|
blogTags,
|
|
228
254
|
blogTagsListPath,
|
|
255
|
+
authorsMap,
|
|
229
256
|
};
|
|
230
257
|
},
|
|
231
258
|
|
|
@@ -250,6 +277,7 @@ export default async function pluginContentBlog(
|
|
|
250
277
|
admonitions,
|
|
251
278
|
rehypePlugins,
|
|
252
279
|
remarkPlugins,
|
|
280
|
+
recmaPlugins,
|
|
253
281
|
truncateMarker,
|
|
254
282
|
beforeDefaultRemarkPlugins,
|
|
255
283
|
beforeDefaultRehypePlugins,
|
|
@@ -262,6 +290,7 @@ export default async function pluginContentBlog(
|
|
|
262
290
|
admonitions,
|
|
263
291
|
remarkPlugins,
|
|
264
292
|
rehypePlugins,
|
|
293
|
+
recmaPlugins,
|
|
265
294
|
beforeDefaultRemarkPlugins: [
|
|
266
295
|
footnoteIDFixer,
|
|
267
296
|
...beforeDefaultRemarkPlugins,
|
|
@@ -364,6 +393,7 @@ export default async function pluginContentBlog(
|
|
|
364
393
|
outDir,
|
|
365
394
|
siteConfig,
|
|
366
395
|
locale: currentLocale,
|
|
396
|
+
contentPaths,
|
|
367
397
|
});
|
|
368
398
|
},
|
|
369
399
|
|
package/src/options.ts
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
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,
|
|
11
12
|
RehypePluginsSchema,
|
|
13
|
+
RecmaPluginsSchema,
|
|
12
14
|
AdmonitionsSchema,
|
|
13
15
|
RouteBasePathSchema,
|
|
14
16
|
URISchema,
|
|
@@ -18,20 +20,32 @@ import type {
|
|
|
18
20
|
PluginOptions,
|
|
19
21
|
Options,
|
|
20
22
|
FeedType,
|
|
23
|
+
FeedXSLTOptions,
|
|
21
24
|
} from '@docusaurus/plugin-content-blog';
|
|
22
25
|
import type {OptionValidationContext} from '@docusaurus/types';
|
|
23
26
|
|
|
24
27
|
export const DEFAULT_OPTIONS: PluginOptions = {
|
|
25
|
-
feedOptions: {
|
|
28
|
+
feedOptions: {
|
|
29
|
+
type: ['rss', 'atom'],
|
|
30
|
+
copyright: '',
|
|
31
|
+
limit: 20,
|
|
32
|
+
xslt: {
|
|
33
|
+
rss: null,
|
|
34
|
+
atom: null,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
26
37
|
beforeDefaultRehypePlugins: [],
|
|
27
38
|
beforeDefaultRemarkPlugins: [],
|
|
28
39
|
admonitions: true,
|
|
29
40
|
truncateMarker: /<!--\s*truncate\s*-->|\{\/\*\s*truncate\s*\*\/\}/,
|
|
30
41
|
rehypePlugins: [],
|
|
31
42
|
remarkPlugins: [],
|
|
43
|
+
recmaPlugins: [],
|
|
32
44
|
showReadingTime: true,
|
|
33
45
|
blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
|
|
34
46
|
blogTagsListComponent: '@theme/BlogTagsListPage',
|
|
47
|
+
blogAuthorsPostsComponent: '@theme/Blog/Pages/BlogAuthorsPostsPage',
|
|
48
|
+
blogAuthorsListComponent: '@theme/Blog/Pages/BlogAuthorsListPage',
|
|
35
49
|
blogPostComponent: '@theme/BlogPostPage',
|
|
36
50
|
blogListComponent: '@theme/BlogListPage',
|
|
37
51
|
blogArchiveComponent: '@theme/BlogArchivePage',
|
|
@@ -56,8 +70,99 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
56
70
|
processBlogPosts: async () => undefined,
|
|
57
71
|
onInlineTags: 'warn',
|
|
58
72
|
tags: undefined,
|
|
73
|
+
authorsBasePath: 'authors',
|
|
74
|
+
onInlineAuthors: 'warn',
|
|
75
|
+
onUntruncatedBlogPosts: 'warn',
|
|
59
76
|
};
|
|
60
77
|
|
|
78
|
+
export const XSLTBuiltInPaths = {
|
|
79
|
+
rss: path.resolve(__dirname, '..', 'assets', 'rss.xsl'),
|
|
80
|
+
atom: path.resolve(__dirname, '..', 'assets', 'atom.xsl'),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function normalizeXsltOption(
|
|
84
|
+
option: string | null | boolean,
|
|
85
|
+
type: 'rss' | 'atom',
|
|
86
|
+
): string | null {
|
|
87
|
+
if (typeof option === 'string') {
|
|
88
|
+
return option;
|
|
89
|
+
}
|
|
90
|
+
if (option === true) {
|
|
91
|
+
return XSLTBuiltInPaths[type];
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createXSLTFilePathSchema(type: 'atom' | 'rss') {
|
|
97
|
+
return Joi.alternatives()
|
|
98
|
+
.try(
|
|
99
|
+
Joi.string().required(),
|
|
100
|
+
Joi.boolean()
|
|
101
|
+
.allow(null, () => undefined)
|
|
102
|
+
.custom((val) => normalizeXsltOption(val, type)),
|
|
103
|
+
)
|
|
104
|
+
.optional()
|
|
105
|
+
.default(null);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const FeedXSLTOptionsSchema = Joi.alternatives()
|
|
109
|
+
.try(
|
|
110
|
+
Joi.object<FeedXSLTOptions>({
|
|
111
|
+
rss: createXSLTFilePathSchema('rss'),
|
|
112
|
+
atom: createXSLTFilePathSchema('atom'),
|
|
113
|
+
}).required(),
|
|
114
|
+
Joi.boolean()
|
|
115
|
+
.allow(null, () => undefined)
|
|
116
|
+
.custom((val) => ({
|
|
117
|
+
rss: normalizeXsltOption(val, 'rss'),
|
|
118
|
+
atom: normalizeXsltOption(val, 'atom'),
|
|
119
|
+
})),
|
|
120
|
+
)
|
|
121
|
+
.optional()
|
|
122
|
+
.custom((val) => {
|
|
123
|
+
if (val === null) {
|
|
124
|
+
return {
|
|
125
|
+
rss: null,
|
|
126
|
+
atom: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return val;
|
|
130
|
+
})
|
|
131
|
+
.default(DEFAULT_OPTIONS.feedOptions.xslt);
|
|
132
|
+
|
|
133
|
+
const FeedOptionsSchema = Joi.object({
|
|
134
|
+
type: Joi.alternatives()
|
|
135
|
+
.try(
|
|
136
|
+
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
137
|
+
Joi.alternatives().conditional(
|
|
138
|
+
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
139
|
+
{
|
|
140
|
+
then: Joi.custom((val: FeedType | 'all') =>
|
|
141
|
+
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
.allow(null)
|
|
147
|
+
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
148
|
+
xslt: FeedXSLTOptionsSchema,
|
|
149
|
+
title: Joi.string().allow(''),
|
|
150
|
+
description: Joi.string().allow(''),
|
|
151
|
+
// Only add default value when user actually wants a feed (type is not null)
|
|
152
|
+
copyright: Joi.when('type', {
|
|
153
|
+
is: Joi.any().valid(null),
|
|
154
|
+
then: Joi.string().optional(),
|
|
155
|
+
otherwise: Joi.string()
|
|
156
|
+
.allow('')
|
|
157
|
+
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
158
|
+
}),
|
|
159
|
+
language: Joi.string(),
|
|
160
|
+
createFeedItems: Joi.function(),
|
|
161
|
+
limit: Joi.alternatives()
|
|
162
|
+
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
163
|
+
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
164
|
+
}).default(DEFAULT_OPTIONS.feedOptions);
|
|
165
|
+
|
|
61
166
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
62
167
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
|
63
168
|
archiveBasePath: Joi.string()
|
|
@@ -79,6 +184,12 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
79
184
|
blogTagsPostsComponent: Joi.string().default(
|
|
80
185
|
DEFAULT_OPTIONS.blogTagsPostsComponent,
|
|
81
186
|
),
|
|
187
|
+
blogAuthorsPostsComponent: Joi.string().default(
|
|
188
|
+
DEFAULT_OPTIONS.blogAuthorsPostsComponent,
|
|
189
|
+
),
|
|
190
|
+
blogAuthorsListComponent: Joi.string().default(
|
|
191
|
+
DEFAULT_OPTIONS.blogAuthorsListComponent,
|
|
192
|
+
),
|
|
82
193
|
blogArchiveComponent: Joi.string().default(
|
|
83
194
|
DEFAULT_OPTIONS.blogArchiveComponent,
|
|
84
195
|
),
|
|
@@ -93,6 +204,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
93
204
|
showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime),
|
|
94
205
|
remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins),
|
|
95
206
|
rehypePlugins: RehypePluginsSchema.default(DEFAULT_OPTIONS.rehypePlugins),
|
|
207
|
+
recmaPlugins: RecmaPluginsSchema.default(DEFAULT_OPTIONS.recmaPlugins),
|
|
96
208
|
admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions),
|
|
97
209
|
editUrl: Joi.alternatives().try(URISchema, Joi.function()),
|
|
98
210
|
editLocalizedFiles: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedFiles),
|
|
@@ -103,37 +215,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
103
215
|
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
|
104
216
|
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
|
105
217
|
),
|
|
106
|
-
feedOptions:
|
|
107
|
-
type: Joi.alternatives()
|
|
108
|
-
.try(
|
|
109
|
-
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
110
|
-
Joi.alternatives().conditional(
|
|
111
|
-
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
112
|
-
{
|
|
113
|
-
then: Joi.custom((val: FeedType | 'all') =>
|
|
114
|
-
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
115
|
-
),
|
|
116
|
-
},
|
|
117
|
-
),
|
|
118
|
-
)
|
|
119
|
-
.allow(null)
|
|
120
|
-
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
121
|
-
title: Joi.string().allow(''),
|
|
122
|
-
description: Joi.string().allow(''),
|
|
123
|
-
// Only add default value when user actually wants a feed (type is not null)
|
|
124
|
-
copyright: Joi.when('type', {
|
|
125
|
-
is: Joi.any().valid(null),
|
|
126
|
-
then: Joi.string().optional(),
|
|
127
|
-
otherwise: Joi.string()
|
|
128
|
-
.allow('')
|
|
129
|
-
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
130
|
-
}),
|
|
131
|
-
language: Joi.string(),
|
|
132
|
-
createFeedItems: Joi.function(),
|
|
133
|
-
limit: Joi.alternatives()
|
|
134
|
-
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
135
|
-
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
136
|
-
}).default(DEFAULT_OPTIONS.feedOptions),
|
|
218
|
+
feedOptions: FeedOptionsSchema,
|
|
137
219
|
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
|
138
220
|
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
|
139
221
|
sortPosts: Joi.string()
|
|
@@ -153,6 +235,15 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
153
235
|
.disallow('')
|
|
154
236
|
.allow(null, false)
|
|
155
237
|
.default(() => DEFAULT_OPTIONS.tags),
|
|
238
|
+
authorsBasePath: Joi.string()
|
|
239
|
+
.default(DEFAULT_OPTIONS.authorsBasePath)
|
|
240
|
+
.disallow(''),
|
|
241
|
+
onInlineAuthors: Joi.string()
|
|
242
|
+
.equal('ignore', 'log', 'warn', 'throw')
|
|
243
|
+
.default(DEFAULT_OPTIONS.onInlineAuthors),
|
|
244
|
+
onUntruncatedBlogPosts: Joi.string()
|
|
245
|
+
.equal('ignore', 'log', 'warn', 'throw')
|
|
246
|
+
.default(DEFAULT_OPTIONS.onUntruncatedBlogPosts),
|
|
156
247
|
}).default(DEFAULT_OPTIONS);
|
|
157
248
|
|
|
158
249
|
export function validateOptions({
|