@docusaurus/plugin-content-blog 2.0.0-beta.8bda3b2db → 2.0.0-beta.9

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.
Files changed (65) hide show
  1. package/lib/.tsbuildinfo +1 -1
  2. package/lib/authors.d.ts +23 -0
  3. package/lib/authors.js +150 -0
  4. package/lib/blogFrontMatter.d.ts +19 -6
  5. package/lib/blogFrontMatter.js +31 -19
  6. package/lib/blogUtils.d.ts +10 -4
  7. package/lib/blogUtils.js +142 -136
  8. package/lib/feed.d.ts +20 -0
  9. package/lib/feed.js +90 -0
  10. package/lib/index.d.ts +1 -1
  11. package/lib/index.js +98 -98
  12. package/lib/markdownLoader.d.ts +3 -6
  13. package/lib/markdownLoader.js +5 -5
  14. package/lib/pluginOptionSchema.d.ts +3 -26
  15. package/lib/pluginOptionSchema.js +24 -7
  16. package/lib/translations.d.ts +10 -0
  17. package/lib/translations.js +53 -0
  18. package/lib/types.d.ts +53 -14
  19. package/package.json +15 -13
  20. package/src/__tests__/__fixtures__/authorsMapFiles/authors.json +29 -0
  21. package/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +27 -0
  22. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.json +5 -0
  23. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.yml +3 -0
  24. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.json +3 -0
  25. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.yml +2 -0
  26. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.json +8 -0
  27. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.yml +3 -0
  28. package/src/__tests__/__fixtures__/component/Typography.tsx +6 -0
  29. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathEmpty/empty +0 -0
  30. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson1/authors.json +0 -0
  31. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson2/authors.json +0 -0
  32. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathNestedYml/sub/folder/authors.yml +0 -0
  33. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml1/authors.yml +0 -0
  34. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml2/authors.yml +0 -0
  35. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  36. package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +3 -0
  37. package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +3 -0
  38. package/src/__tests__/__fixtures__/website/blog/_somePartial.md +3 -0
  39. package/src/__tests__/__fixtures__/website/blog/authors.yml +4 -0
  40. package/src/__tests__/__fixtures__/website/blog/mdx-blog-post.mdx +36 -0
  41. package/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx +14 -0
  42. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +4 -0
  43. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  44. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +5 -0
  45. package/src/__tests__/__fixtures__/website/static/img/docusaurus.png +0 -0
  46. package/src/__tests__/__snapshots__/feed.test.ts.snap +164 -0
  47. package/src/__tests__/__snapshots__/translations.test.ts.snap +64 -0
  48. package/src/__tests__/authors.test.ts +608 -0
  49. package/src/__tests__/blogFrontMatter.test.ts +93 -16
  50. package/src/__tests__/blogUtils.test.ts +94 -0
  51. package/src/__tests__/{generateBlogFeed.test.ts → feed.test.ts} +35 -9
  52. package/src/__tests__/index.test.ts +73 -12
  53. package/src/__tests__/pluginOptionSchema.test.ts +3 -3
  54. package/src/__tests__/translations.test.ts +92 -0
  55. package/src/authors.ts +202 -0
  56. package/src/blogFrontMatter.ts +73 -33
  57. package/src/blogUtils.ts +202 -179
  58. package/src/feed.ts +129 -0
  59. package/src/index.ts +124 -103
  60. package/src/markdownLoader.ts +8 -12
  61. package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
  62. package/src/pluginOptionSchema.ts +27 -9
  63. package/src/translations.ts +63 -0
  64. package/src/types.ts +68 -16
  65. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -76
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import fs from 'fs-extra';
9
8
  import path from 'path';
10
9
  import admonitions from 'remark-admonitions';
11
10
  import {
@@ -16,12 +15,13 @@ import {
16
15
  reportMessage,
17
16
  posixPath,
18
17
  addTrailingPathSeparator,
18
+ createAbsoluteFilePathMatcher,
19
19
  } from '@docusaurus/utils';
20
20
  import {
21
21
  STATIC_DIR_NAME,
22
22
  DEFAULT_PLUGIN_ID,
23
23
  } from '@docusaurus/core/lib/constants';
24
- import {flatten, take, kebabCase} from 'lodash';
24
+ import {translateContent, getTranslationFiles} from './translations';
25
25
 
26
26
  import {
27
27
  PluginOptions,
@@ -30,9 +30,10 @@ import {
30
30
  BlogItemsToMetadata,
31
31
  TagsModule,
32
32
  BlogPaginated,
33
- BlogPost,
34
33
  BlogContentPaths,
35
34
  BlogMarkdownLoaderOptions,
35
+ MetaData,
36
+ Assets,
36
37
  } from './types';
37
38
  import {PluginOptionSchema} from './pluginOptionSchema';
38
39
  import {
@@ -46,16 +47,18 @@ import {
46
47
  } from '@docusaurus/types';
47
48
  import {Configuration} from 'webpack';
48
49
  import {
49
- generateBlogFeed,
50
50
  generateBlogPosts,
51
51
  getContentPathList,
52
52
  getSourceToPermalink,
53
+ getBlogTags,
53
54
  } from './blogUtils';
55
+ import {BlogPostFrontMatter} from './blogFrontMatter';
56
+ import {createBlogFeedFiles} from './feed';
54
57
 
55
58
  export default function pluginContentBlog(
56
59
  context: LoadContext,
57
60
  options: PluginOptions,
58
- ): Plugin<BlogContent | null> {
61
+ ): Plugin<BlogContent> {
59
62
  if (options.admonitions) {
60
63
  options.remarkPlugins = options.remarkPlugins.concat([
61
64
  [admonitions, options.admonitions],
@@ -64,10 +67,11 @@ export default function pluginContentBlog(
64
67
 
65
68
  const {
66
69
  siteDir,
67
- siteConfig: {onBrokenMarkdownLinks},
70
+ siteConfig,
68
71
  generatedFilesDir,
69
72
  i18n: {currentLocale},
70
73
  } = context;
74
+ const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
71
75
 
72
76
  const contentPaths: BlogContentPaths = {
73
77
  contentPath: path.resolve(siteDir, options.path),
@@ -88,38 +92,51 @@ export default function pluginContentBlog(
88
92
  const aliasedSource = (source: string) =>
89
93
  `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
90
94
 
91
- let blogPosts: BlogPost[] = [];
92
-
93
95
  return {
94
96
  name: 'docusaurus-plugin-content-blog',
95
97
 
96
98
  getPathsToWatch() {
97
- const {include = []} = options;
98
- return flatten(
99
- getContentPathList(contentPaths).map((contentPath) => {
100
- return include.map((pattern) => `${contentPath}/${pattern}`);
101
- }),
99
+ const {include, authorsMapPath} = options;
100
+ const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
101
+ (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
102
102
  );
103
- },
104
103
 
105
- getClientModules() {
106
- const modules = [];
104
+ // TODO: we should read this path in plugin! but plugins do not support async init for now :'(
105
+ // const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,});
106
+ // simplified impl, better than nothing for now:
107
+ const authorsMapFilePath = path.join(
108
+ contentPaths.contentPath,
109
+ authorsMapPath,
110
+ );
107
111
 
108
- if (options.admonitions) {
109
- modules.push(require.resolve('remark-admonitions/styles/infima.css'));
110
- }
112
+ return [authorsMapFilePath, ...contentMarkdownGlobs];
113
+ },
111
114
 
112
- return modules;
115
+ async getTranslationFiles() {
116
+ return getTranslationFiles(options);
113
117
  },
114
118
 
115
119
  // Fetches blog contents and returns metadata for the necessary routes.
116
120
  async loadContent() {
117
- const {postsPerPage, routeBasePath} = options;
121
+ const {
122
+ postsPerPage: postsPerPageOption,
123
+ routeBasePath,
124
+ tagsBasePath,
125
+ blogDescription,
126
+ blogTitle,
127
+ blogSidebarTitle,
128
+ } = options;
118
129
 
119
- blogPosts = await generateBlogPosts(contentPaths, context, options);
130
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
120
131
 
121
132
  if (!blogPosts.length) {
122
- return null;
133
+ return {
134
+ blogSidebarTitle,
135
+ blogPosts: [],
136
+ blogListPaginated: [],
137
+ blogTags: {},
138
+ blogTagsListPath: null,
139
+ };
123
140
  }
124
141
 
125
142
  // Colocate next and prev metadata.
@@ -145,18 +162,17 @@ export default function pluginContentBlog(
145
162
  // Blog pagination routes.
146
163
  // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
147
164
  const totalCount = blogPosts.length;
165
+ const postsPerPage =
166
+ postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
148
167
  const numberOfPages = Math.ceil(totalCount / postsPerPage);
149
- const {
150
- siteConfig: {baseUrl = ''},
151
- } = context;
152
- const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
168
+ const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
153
169
 
154
170
  const blogListPaginated: BlogPaginated[] = [];
155
171
 
156
172
  function blogPaginationPermalink(page: number) {
157
173
  return page > 0
158
- ? normalizeUrl([basePageUrl, `page/${page + 1}`])
159
- : basePageUrl;
174
+ ? normalizeUrl([baseBlogUrl, `page/${page + 1}`])
175
+ : baseBlogUrl;
160
176
  }
161
177
 
162
178
  for (let page = 0; page < numberOfPages; page += 1) {
@@ -172,8 +188,8 @@ export default function pluginContentBlog(
172
188
  page < numberOfPages - 1
173
189
  ? blogPaginationPermalink(page + 1)
174
190
  : null,
175
- blogDescription: options.blogDescription,
176
- blogTitle: options.blogTitle,
191
+ blogDescription,
192
+ blogTitle,
177
193
  },
178
194
  items: blogPosts
179
195
  .slice(page * postsPerPage, (page + 1) * postsPerPage)
@@ -181,46 +197,15 @@ export default function pluginContentBlog(
181
197
  });
182
198
  }
183
199
 
184
- const blogTags: BlogTags = {};
185
- const tagsPath = normalizeUrl([basePageUrl, 'tags']);
186
- blogPosts.forEach((blogPost) => {
187
- const {tags} = blogPost.metadata;
188
- if (!tags || tags.length === 0) {
189
- // TODO: Extract tags out into a separate plugin.
190
- // eslint-disable-next-line no-param-reassign
191
- blogPost.metadata.tags = [];
192
- return;
193
- }
200
+ const blogTags: BlogTags = getBlogTags(blogPosts);
194
201
 
195
- // eslint-disable-next-line no-param-reassign
196
- blogPost.metadata.tags = tags.map((tag) => {
197
- if (typeof tag === 'string') {
198
- const normalizedTag = kebabCase(tag);
199
- const permalink = normalizeUrl([tagsPath, normalizedTag]);
200
- if (!blogTags[normalizedTag]) {
201
- blogTags[normalizedTag] = {
202
- // Will only use the name of the first occurrence of the tag.
203
- name: tag.toLowerCase(),
204
- items: [],
205
- permalink,
206
- };
207
- }
208
-
209
- blogTags[normalizedTag].items.push(blogPost.id);
210
-
211
- return {
212
- label: tag,
213
- permalink,
214
- };
215
- }
216
- return tag;
217
- });
218
- });
202
+ const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
219
203
 
220
204
  const blogTagsListPath =
221
205
  Object.keys(blogTags).length > 0 ? tagsPath : null;
222
206
 
223
207
  return {
208
+ blogSidebarTitle,
224
209
  blogPosts,
225
210
  blogListPaginated,
226
211
  blogTags,
@@ -238,11 +223,14 @@ export default function pluginContentBlog(
238
223
  blogPostComponent,
239
224
  blogTagsListComponent,
240
225
  blogTagsPostsComponent,
226
+ routeBasePath,
227
+ archiveBasePath,
241
228
  } = options;
242
229
 
243
230
  const {addRoute, createData} = actions;
244
231
  const {
245
- blogPosts: loadedBlogPosts,
232
+ blogSidebarTitle,
233
+ blogPosts,
246
234
  blogListPaginated,
247
235
  blogTags,
248
236
  blogTagsListPath,
@@ -253,7 +241,27 @@ export default function pluginContentBlog(
253
241
  const sidebarBlogPosts =
254
242
  options.blogSidebarCount === 'ALL'
255
243
  ? blogPosts
256
- : take(blogPosts, options.blogSidebarCount);
244
+ : blogPosts.slice(0, options.blogSidebarCount);
245
+
246
+ const archiveUrl = normalizeUrl([
247
+ baseUrl,
248
+ routeBasePath,
249
+ archiveBasePath,
250
+ ]);
251
+
252
+ // creates a blog archive route
253
+ const archiveProp = await createData(
254
+ `${docuHash(archiveUrl)}.json`,
255
+ JSON.stringify({blogPosts}, null, 2),
256
+ );
257
+ addRoute({
258
+ path: archiveUrl,
259
+ component: '@theme/BlogArchivePage',
260
+ exact: true,
261
+ modules: {
262
+ archive: aliasedSource(archiveProp),
263
+ },
264
+ });
257
265
 
258
266
  // This prop is useful to provide the blog list sidebar
259
267
  const sidebarProp = await createData(
@@ -262,7 +270,7 @@ export default function pluginContentBlog(
262
270
  `blog-post-list-prop-${pluginId}.json`,
263
271
  JSON.stringify(
264
272
  {
265
- title: options.blogSidebarTitle,
273
+ title: blogSidebarTitle,
266
274
  items: sidebarBlogPosts.map((blogPost) => ({
267
275
  title: blogPost.metadata.title,
268
276
  permalink: blogPost.metadata.permalink,
@@ -275,7 +283,7 @@ export default function pluginContentBlog(
275
283
 
276
284
  // Create routes for blog entries.
277
285
  await Promise.all(
278
- loadedBlogPosts.map(async (blogPost) => {
286
+ blogPosts.map(async (blogPost) => {
279
287
  const {id, metadata} = blogPost;
280
288
  await createData(
281
289
  // Note that this created data path must be in sync with
@@ -343,6 +351,7 @@ export default function pluginContentBlog(
343
351
  Object.keys(blogTags).map(async (tag) => {
344
352
  const {name, items, permalink} = blogTags[tag];
345
353
 
354
+ // Refactor all this, see docs implementation
346
355
  tagsModule[tag] = {
347
356
  allTagsPath: blogTagsListPath,
348
357
  slug: tag,
@@ -399,10 +408,15 @@ export default function pluginContentBlog(
399
408
  }
400
409
  },
401
410
 
411
+ translateContent({content, translationFiles}) {
412
+ return translateContent(content, translationFiles);
413
+ },
414
+
402
415
  configureWebpack(
403
416
  _config: Configuration,
404
417
  isServer: boolean,
405
418
  {getJSLoader}: ConfigureWebpackUtils,
419
+ content,
406
420
  ) {
407
421
  const {
408
422
  rehypePlugins,
@@ -416,7 +430,7 @@ export default function pluginContentBlog(
416
430
  siteDir,
417
431
  contentPaths,
418
432
  truncateMarker,
419
- sourceToPermalink: getSourceToPermalink(blogPosts),
433
+ sourceToPermalink: getSourceToPermalink(content.blogPosts),
420
434
  onBrokenMarkdownLink: (brokenMarkdownLink) => {
421
435
  if (onBrokenMarkdownLinks === 'ignore') {
422
436
  return;
@@ -428,6 +442,7 @@ export default function pluginContentBlog(
428
442
  },
429
443
  };
430
444
 
445
+ const contentDirs = getContentPathList(contentPaths);
431
446
  return {
432
447
  resolve: {
433
448
  alias: {
@@ -438,7 +453,7 @@ export default function pluginContentBlog(
438
453
  rules: [
439
454
  {
440
455
  test: /(\.mdx?)$/,
441
- include: getContentPathList(contentPaths)
456
+ include: contentDirs
442
457
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
443
458
  .map(addTrailingPathSeparator),
444
459
  use: [
@@ -451,9 +466,13 @@ export default function pluginContentBlog(
451
466
  beforeDefaultRemarkPlugins,
452
467
  beforeDefaultRehypePlugins,
453
468
  staticDir: path.join(siteDir, STATIC_DIR_NAME),
454
- // Note that metadataPath must be the same/in-sync as
455
- // the path from createData for each MDX.
469
+ isMDXPartial: createAbsoluteFilePathMatcher(
470
+ options.exclude,
471
+ contentDirs,
472
+ ),
456
473
  metadataPath: (mdxPath: string) => {
474
+ // Note that metadataPath must be the same/in-sync as
475
+ // the path from createData for each MDX.
457
476
  const aliasedPath = aliasedSitePath(mdxPath, siteDir);
458
477
  return path.join(
459
478
  dataDir,
@@ -463,6 +482,22 @@ export default function pluginContentBlog(
463
482
  // For blog posts a title in markdown is always removed
464
483
  // Blog posts title are rendered separately
465
484
  removeContentTitle: true,
485
+
486
+ // Assets allow to convert some relative images paths to require() calls
487
+ createAssets: ({
488
+ frontMatter,
489
+ metadata,
490
+ }: {
491
+ frontMatter: BlogPostFrontMatter;
492
+ metadata: MetaData;
493
+ }): Assets => {
494
+ return {
495
+ image: frontMatter.image,
496
+ authorsImageUrls: metadata.authors.map(
497
+ (author) => author.imageURL,
498
+ ),
499
+ };
500
+ },
466
501
  },
467
502
  },
468
503
  {
@@ -477,37 +512,26 @@ export default function pluginContentBlog(
477
512
  },
478
513
 
479
514
  async postBuild({outDir}: Props) {
480
- if (!options.feedOptions?.type) {
515
+ if (!options.feedOptions.type) {
481
516
  return;
482
517
  }
483
518
 
484
- const feed = await generateBlogFeed(contentPaths, context, options);
485
-
486
- if (!feed) {
519
+ // TODO: we shouldn't need to re-read the posts here!
520
+ // postBuild should receive loadedContent
521
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
522
+ if (!blogPosts.length) {
487
523
  return;
488
524
  }
489
-
490
- const feedTypes = options.feedOptions.type;
491
-
492
- await Promise.all(
493
- feedTypes.map(async (feedType) => {
494
- const feedPath = path.join(
495
- outDir,
496
- options.routeBasePath,
497
- `${feedType}.xml`,
498
- );
499
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
500
- try {
501
- await fs.outputFile(feedPath, feedContent);
502
- } catch (err) {
503
- throw new Error(`Generating ${feedType} feed failed: ${err}.`);
504
- }
505
- }),
506
- );
525
+ await createBlogFeedFiles({
526
+ blogPosts,
527
+ options,
528
+ outDir,
529
+ siteConfig,
530
+ });
507
531
  },
508
532
 
509
- injectHtmlTags() {
510
- if (!blogPosts.length) {
533
+ injectHtmlTags({content}) {
534
+ if (!content.blogPosts.length) {
511
535
  return {};
512
536
  }
513
537
 
@@ -516,20 +540,17 @@ export default function pluginContentBlog(
516
540
  }
517
541
 
518
542
  const feedTypes = options.feedOptions.type;
519
- const {
520
- siteConfig: {title},
521
- baseUrl,
522
- } = context;
543
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
523
544
  const feedsConfig = {
524
545
  rss: {
525
546
  type: 'application/rss+xml',
526
547
  path: 'rss.xml',
527
- title: `${title} Blog RSS Feed`,
548
+ title: `${feedTitle} RSS Feed`,
528
549
  },
529
550
  atom: {
530
551
  type: 'application/atom+xml',
531
552
  path: 'atom.xml',
532
- title: `${title} Blog Atom Feed`,
553
+ title: `${feedTitle} Atom Feed`,
533
554
  },
534
555
  };
535
556
  const headTags: HtmlTags = [];
@@ -8,18 +8,16 @@
8
8
  import {truncate, linkify} from './blogUtils';
9
9
  import {parseQuery} from 'loader-utils';
10
10
  import {BlogMarkdownLoaderOptions} from './types';
11
+ import type {LoaderContext} from 'webpack';
11
12
 
12
- // TODO temporary until Webpack5 export this type
13
- // see https://github.com/webpack/webpack/issues/11630
14
- interface Loader extends Function {
15
- (this: any, source: string): string | Buffer | void | undefined;
16
- }
17
-
18
- const markdownLoader: Loader = function (source) {
13
+ export default function markdownLoader(
14
+ this: LoaderContext<BlogMarkdownLoaderOptions>,
15
+ source: string,
16
+ ): void {
19
17
  const filePath = this.resourcePath;
20
- const fileString = source as string;
18
+ const fileString = source;
21
19
  const callback = this.async();
22
- const markdownLoaderOptions = this.getOptions() as BlogMarkdownLoaderOptions;
20
+ const markdownLoaderOptions = this.getOptions();
23
21
 
24
22
  // Linkify blog posts
25
23
  let finalContent = linkify({
@@ -38,6 +36,4 @@ const markdownLoader: Loader = function (source) {
38
36
  }
39
37
 
40
38
  return callback && callback(null, finalContent);
41
- };
42
-
43
- export default markdownLoader;
39
+ }
@@ -5,8 +5,9 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- /* eslint-disable import/no-duplicates */
9
- /* eslint-disable camelcase */
8
+ declare module '@docusaurus/plugin-content-blog' {
9
+ export type Options = Partial<import('./types').UserPluginOptions>;
10
+ }
10
11
 
11
12
  declare module '@theme/BlogSidebar' {
12
13
  export type BlogSidebarItem = {title: string; permalink: string};
@@ -15,32 +16,20 @@ declare module '@theme/BlogSidebar' {
15
16
  items: BlogSidebarItem[];
16
17
  };
17
18
 
18
- export type Props = {
19
+ export interface Props {
19
20
  readonly sidebar: BlogSidebar;
20
- };
21
+ }
21
22
 
22
23
  const BlogSidebar: (props: Props) => JSX.Element;
23
24
  export default BlogSidebar;
24
25
  }
25
26
 
26
27
  declare module '@theme/BlogPostPage' {
27
- import type {TOCItem} from '@docusaurus/types';
28
28
  import type {BlogSidebar} from '@theme/BlogSidebar';
29
+ import type {TOCItem} from '@docusaurus/types';
29
30
 
30
- export type FrontMatter = {
31
- readonly title: string;
32
- readonly author?: string;
33
- readonly image?: string;
34
- readonly tags?: readonly string[];
35
- readonly keywords?: readonly string[];
36
- readonly author_url?: string;
37
- readonly authorURL?: string;
38
- readonly author_title?: string;
39
- readonly authorTitle?: string;
40
- readonly author_image_url?: string;
41
- readonly authorImageURL?: string;
42
- readonly hide_table_of_contents?: boolean;
43
- };
31
+ export type FrontMatter = import('./blogFrontMatter').BlogPostFrontMatter;
32
+ export type Assets = import('./types').Assets;
44
33
 
45
34
  export type Metadata = {
46
35
  readonly title: string;
@@ -53,6 +42,7 @@ declare module '@theme/BlogPostPage' {
53
42
  readonly truncated?: string;
54
43
  readonly nextItem?: {readonly title: string; readonly permalink: string};
55
44
  readonly prevItem?: {readonly title: string; readonly permalink: string};
45
+ readonly authors: import('./types').Author[];
56
46
  readonly tags: readonly {
57
47
  readonly label: string;
58
48
  readonly permalink: string;
@@ -61,15 +51,16 @@ declare module '@theme/BlogPostPage' {
61
51
 
62
52
  export type Content = {
63
53
  readonly frontMatter: FrontMatter;
54
+ readonly assets: Assets;
64
55
  readonly metadata: Metadata;
65
56
  readonly toc: readonly TOCItem[];
66
57
  (): JSX.Element;
67
58
  };
68
59
 
69
- export type Props = {
60
+ export interface Props {
70
61
  readonly sidebar: BlogSidebar;
71
62
  readonly content: Content;
72
- };
63
+ }
73
64
 
74
65
  const BlogPostPage: (props: Props) => JSX.Element;
75
66
  export default BlogPostPage;
@@ -79,10 +70,6 @@ declare module '@theme/BlogListPage' {
79
70
  import type {Content} from '@theme/BlogPostPage';
80
71
  import type {BlogSidebar} from '@theme/BlogSidebar';
81
72
 
82
- export type Item = {
83
- readonly content: () => JSX.Element;
84
- };
85
-
86
73
  export type Metadata = {
87
74
  readonly blogTitle: string;
88
75
  readonly blogDescription: string;
@@ -95,11 +82,11 @@ declare module '@theme/BlogListPage' {
95
82
  readonly totalPages: number;
96
83
  };
97
84
 
98
- export type Props = {
85
+ export interface Props {
99
86
  readonly sidebar: BlogSidebar;
100
87
  readonly metadata: Metadata;
101
88
  readonly items: readonly {readonly content: Content}[];
102
- };
89
+ }
103
90
 
104
91
  const BlogListPage: (props: Props) => JSX.Element;
105
92
  export default BlogListPage;
@@ -116,10 +103,10 @@ declare module '@theme/BlogTagsListPage' {
116
103
  slug: string;
117
104
  };
118
105
 
119
- export type Props = {
106
+ export interface Props {
120
107
  readonly sidebar: BlogSidebar;
121
108
  readonly tags: Readonly<Record<string, Tag>>;
122
- };
109
+ }
123
110
 
124
111
  const BlogTagsListPage: (props: Props) => JSX.Element;
125
112
  export default BlogTagsListPage;
@@ -130,9 +117,26 @@ declare module '@theme/BlogTagsPostsPage' {
130
117
  import type {Tag} from '@theme/BlogTagsListPage';
131
118
  import type {Content} from '@theme/BlogPostPage';
132
119
 
133
- export type Props = {
120
+ export interface Props {
134
121
  readonly sidebar: BlogSidebar;
135
122
  readonly metadata: Tag;
136
123
  readonly items: readonly {readonly content: Content}[];
137
- };
124
+ }
125
+
126
+ const BlogTagsPostsPage: (props: Props) => JSX.Element;
127
+ export default BlogTagsPostsPage;
128
+ }
129
+
130
+ declare module '@theme/BlogArchivePage' {
131
+ import type {Content} from '@theme/BlogPostPage';
132
+
133
+ export type ArchiveBlogPost = Content;
134
+
135
+ export interface Props {
136
+ readonly archive: {
137
+ readonly blogPosts: readonly ArchiveBlogPost[];
138
+ };
139
+ }
140
+
141
+ export default function BlogArchivePage(props: Props): JSX.Element;
138
142
  }
@@ -12,9 +12,11 @@ import {
12
12
  AdmonitionsSchema,
13
13
  URISchema,
14
14
  } from '@docusaurus/utils-validation';
15
+ import {GlobExcludeDefault} from '@docusaurus/utils';
16
+ import {PluginOptions} from './types';
15
17
 
16
- export const DEFAULT_OPTIONS = {
17
- feedOptions: {type: ['rss', 'atom']},
18
+ export const DEFAULT_OPTIONS: PluginOptions = {
19
+ feedOptions: {type: ['rss', 'atom'], copyright: ''},
18
20
  beforeDefaultRehypePlugins: [],
19
21
  beforeDefaultRemarkPlugins: [],
20
22
  admonitions: {},
@@ -31,22 +33,29 @@ export const DEFAULT_OPTIONS = {
31
33
  blogSidebarCount: 5,
32
34
  blogSidebarTitle: 'Recent posts',
33
35
  postsPerPage: 10,
34
- include: ['*.md', '*.mdx'],
36
+ include: ['**/*.{md,mdx}'],
37
+ exclude: GlobExcludeDefault,
35
38
  routeBasePath: 'blog',
39
+ tagsBasePath: 'tags',
40
+ archiveBasePath: 'archive',
36
41
  path: 'blog',
37
42
  editLocalizedFiles: false,
43
+ authorsMapPath: 'authors.yml',
44
+ readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
38
45
  };
39
46
 
40
- export const PluginOptionSchema = Joi.object({
47
+ export const PluginOptionSchema = Joi.object<PluginOptions>({
41
48
  path: Joi.string().default(DEFAULT_OPTIONS.path),
49
+ archiveBasePath: Joi.string().default(DEFAULT_OPTIONS.archiveBasePath),
42
50
  routeBasePath: Joi.string()
43
51
  // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
44
52
  // .allow('')
45
53
  .default(DEFAULT_OPTIONS.routeBasePath),
54
+ tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
46
55
  include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
47
- postsPerPage: Joi.number()
48
- .integer()
49
- .min(1)
56
+ exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
57
+ postsPerPage: Joi.alternatives()
58
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(1).required())
50
59
  .default(DEFAULT_OPTIONS.postsPerPage),
51
60
  blogListComponent: Joi.string().default(DEFAULT_OPTIONS.blogListComponent),
52
61
  blogPostComponent: Joi.string().default(DEFAULT_OPTIONS.blogPostComponent),
@@ -61,7 +70,7 @@ export const PluginOptionSchema = Joi.object({
61
70
  .allow('')
62
71
  .default(DEFAULT_OPTIONS.blogDescription),
63
72
  blogSidebarCount: Joi.alternatives()
64
- .try(Joi.equal('ALL').required(), Joi.number().required())
73
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(0).required())
65
74
  .default(DEFAULT_OPTIONS.blogSidebarCount),
66
75
  blogSidebarTitle: Joi.string().default(DEFAULT_OPTIONS.blogSidebarTitle),
67
76
  showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime),
@@ -94,7 +103,16 @@ export const PluginOptionSchema = Joi.object({
94
103
  .default(DEFAULT_OPTIONS.feedOptions.type),
95
104
  title: Joi.string().allow(''),
96
105
  description: Joi.string().allow(''),
97
- copyright: Joi.string(),
106
+ // only add default value when user actually wants a feed (type is not null)
107
+ copyright: Joi.when('type', {
108
+ is: Joi.any().valid(null),
109
+ then: Joi.string().optional(),
110
+ otherwise: Joi.string()
111
+ .allow('')
112
+ .default(DEFAULT_OPTIONS.feedOptions.copyright),
113
+ }),
98
114
  language: Joi.string(),
99
115
  }).default(DEFAULT_OPTIONS.feedOptions),
116
+ authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
117
+ readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
100
118
  });