@docusaurus/plugin-content-blog 2.0.0-beta.8e9b829d9 → 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 (60) 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 +141 -135
  8. package/lib/feed.d.ts +20 -0
  9. package/lib/feed.js +90 -0
  10. package/lib/index.js +86 -85
  11. package/lib/markdownLoader.d.ts +3 -6
  12. package/lib/markdownLoader.js +5 -5
  13. package/lib/pluginOptionSchema.d.ts +3 -27
  14. package/lib/pluginOptionSchema.js +21 -7
  15. package/lib/translations.d.ts +10 -0
  16. package/lib/translations.js +53 -0
  17. package/lib/types.d.ts +52 -14
  18. package/package.json +15 -13
  19. package/src/__tests__/__fixtures__/authorsMapFiles/authors.json +29 -0
  20. package/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +27 -0
  21. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.json +5 -0
  22. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.yml +3 -0
  23. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.json +3 -0
  24. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.yml +2 -0
  25. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.json +8 -0
  26. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.yml +3 -0
  27. package/src/__tests__/__fixtures__/component/Typography.tsx +6 -0
  28. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathEmpty/empty +0 -0
  29. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson1/authors.json +0 -0
  30. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson2/authors.json +0 -0
  31. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathNestedYml/sub/folder/authors.yml +0 -0
  32. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml1/authors.yml +0 -0
  33. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml2/authors.yml +0 -0
  34. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  35. package/src/__tests__/__fixtures__/website/blog/authors.yml +4 -0
  36. package/src/__tests__/__fixtures__/website/blog/mdx-blog-post.mdx +36 -0
  37. package/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx +14 -0
  38. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +4 -0
  39. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  40. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +5 -0
  41. package/src/__tests__/__fixtures__/website/static/img/docusaurus.png +0 -0
  42. package/src/__tests__/__snapshots__/{generateBlogFeed.test.ts.snap → feed.test.ts.snap} +55 -7
  43. package/src/__tests__/__snapshots__/translations.test.ts.snap +64 -0
  44. package/src/__tests__/authors.test.ts +608 -0
  45. package/src/__tests__/blogFrontMatter.test.ts +93 -16
  46. package/src/__tests__/blogUtils.test.ts +94 -0
  47. package/src/__tests__/{generateBlogFeed.test.ts → feed.test.ts} +32 -8
  48. package/src/__tests__/index.test.ts +73 -12
  49. package/src/__tests__/pluginOptionSchema.test.ts +3 -3
  50. package/src/__tests__/translations.test.ts +92 -0
  51. package/src/authors.ts +202 -0
  52. package/src/blogFrontMatter.ts +73 -33
  53. package/src/blogUtils.ts +201 -180
  54. package/src/feed.ts +129 -0
  55. package/src/index.ts +105 -88
  56. package/src/markdownLoader.ts +8 -12
  57. package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
  58. package/src/pluginOptionSchema.ts +24 -9
  59. package/src/translations.ts +63 -0
  60. package/src/types.ts +67 -16
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 {
@@ -22,7 +21,7 @@ import {
22
21
  STATIC_DIR_NAME,
23
22
  DEFAULT_PLUGIN_ID,
24
23
  } from '@docusaurus/core/lib/constants';
25
- import {flatten, take, kebabCase} from 'lodash';
24
+ import {translateContent, getTranslationFiles} from './translations';
26
25
 
27
26
  import {
28
27
  PluginOptions,
@@ -31,9 +30,10 @@ import {
31
30
  BlogItemsToMetadata,
32
31
  TagsModule,
33
32
  BlogPaginated,
34
- BlogPost,
35
33
  BlogContentPaths,
36
34
  BlogMarkdownLoaderOptions,
35
+ MetaData,
36
+ Assets,
37
37
  } from './types';
38
38
  import {PluginOptionSchema} from './pluginOptionSchema';
39
39
  import {
@@ -47,11 +47,13 @@ import {
47
47
  } from '@docusaurus/types';
48
48
  import {Configuration} from 'webpack';
49
49
  import {
50
- generateBlogFeed,
51
50
  generateBlogPosts,
52
51
  getContentPathList,
53
52
  getSourceToPermalink,
53
+ getBlogTags,
54
54
  } from './blogUtils';
55
+ import {BlogPostFrontMatter} from './blogFrontMatter';
56
+ import {createBlogFeedFiles} from './feed';
55
57
 
56
58
  export default function pluginContentBlog(
57
59
  context: LoadContext,
@@ -65,10 +67,11 @@ export default function pluginContentBlog(
65
67
 
66
68
  const {
67
69
  siteDir,
68
- siteConfig: {onBrokenMarkdownLinks},
70
+ siteConfig,
69
71
  generatedFilesDir,
70
72
  i18n: {currentLocale},
71
73
  } = context;
74
+ const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
72
75
 
73
76
  const contentPaths: BlogContentPaths = {
74
77
  contentPath: path.resolve(siteDir, options.path),
@@ -93,26 +96,42 @@ export default function pluginContentBlog(
93
96
  name: 'docusaurus-plugin-content-blog',
94
97
 
95
98
  getPathsToWatch() {
96
- const {include = []} = options;
97
- return flatten(
98
- getContentPathList(contentPaths).map((contentPath) => {
99
- return include.map((pattern) => `${contentPath}/${pattern}`);
100
- }),
99
+ const {include, authorsMapPath} = options;
100
+ const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
101
+ (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
102
+ );
103
+
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,
101
110
  );
111
+
112
+ return [authorsMapFilePath, ...contentMarkdownGlobs];
113
+ },
114
+
115
+ async getTranslationFiles() {
116
+ return getTranslationFiles(options);
102
117
  },
103
118
 
104
119
  // Fetches blog contents and returns metadata for the necessary routes.
105
120
  async loadContent() {
106
- const {postsPerPage, routeBasePath} = options;
121
+ const {
122
+ postsPerPage: postsPerPageOption,
123
+ routeBasePath,
124
+ tagsBasePath,
125
+ blogDescription,
126
+ blogTitle,
127
+ blogSidebarTitle,
128
+ } = options;
107
129
 
108
- const blogPosts: BlogPost[] = await generateBlogPosts(
109
- contentPaths,
110
- context,
111
- options,
112
- );
130
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
113
131
 
114
132
  if (!blogPosts.length) {
115
133
  return {
134
+ blogSidebarTitle,
116
135
  blogPosts: [],
117
136
  blogListPaginated: [],
118
137
  blogTags: {},
@@ -143,18 +162,17 @@ export default function pluginContentBlog(
143
162
  // Blog pagination routes.
144
163
  // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
145
164
  const totalCount = blogPosts.length;
165
+ const postsPerPage =
166
+ postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
146
167
  const numberOfPages = Math.ceil(totalCount / postsPerPage);
147
- const {
148
- siteConfig: {baseUrl = ''},
149
- } = context;
150
- const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
168
+ const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
151
169
 
152
170
  const blogListPaginated: BlogPaginated[] = [];
153
171
 
154
172
  function blogPaginationPermalink(page: number) {
155
173
  return page > 0
156
- ? normalizeUrl([basePageUrl, `page/${page + 1}`])
157
- : basePageUrl;
174
+ ? normalizeUrl([baseBlogUrl, `page/${page + 1}`])
175
+ : baseBlogUrl;
158
176
  }
159
177
 
160
178
  for (let page = 0; page < numberOfPages; page += 1) {
@@ -170,8 +188,8 @@ export default function pluginContentBlog(
170
188
  page < numberOfPages - 1
171
189
  ? blogPaginationPermalink(page + 1)
172
190
  : null,
173
- blogDescription: options.blogDescription,
174
- blogTitle: options.blogTitle,
191
+ blogDescription,
192
+ blogTitle,
175
193
  },
176
194
  items: blogPosts
177
195
  .slice(page * postsPerPage, (page + 1) * postsPerPage)
@@ -179,46 +197,15 @@ export default function pluginContentBlog(
179
197
  });
180
198
  }
181
199
 
182
- const blogTags: BlogTags = {};
183
- const tagsPath = normalizeUrl([basePageUrl, 'tags']);
184
- blogPosts.forEach((blogPost) => {
185
- const {tags} = blogPost.metadata;
186
- if (!tags || tags.length === 0) {
187
- // TODO: Extract tags out into a separate plugin.
188
- // eslint-disable-next-line no-param-reassign
189
- blogPost.metadata.tags = [];
190
- return;
191
- }
200
+ const blogTags: BlogTags = getBlogTags(blogPosts);
192
201
 
193
- // eslint-disable-next-line no-param-reassign
194
- blogPost.metadata.tags = tags.map((tag) => {
195
- if (typeof tag === 'string') {
196
- const normalizedTag = kebabCase(tag);
197
- const permalink = normalizeUrl([tagsPath, normalizedTag]);
198
- if (!blogTags[normalizedTag]) {
199
- blogTags[normalizedTag] = {
200
- // Will only use the name of the first occurrence of the tag.
201
- name: tag.toLowerCase(),
202
- items: [],
203
- permalink,
204
- };
205
- }
206
-
207
- blogTags[normalizedTag].items.push(blogPost.id);
208
-
209
- return {
210
- label: tag,
211
- permalink,
212
- };
213
- }
214
- return tag;
215
- });
216
- });
202
+ const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
217
203
 
218
204
  const blogTagsListPath =
219
205
  Object.keys(blogTags).length > 0 ? tagsPath : null;
220
206
 
221
207
  return {
208
+ blogSidebarTitle,
222
209
  blogPosts,
223
210
  blogListPaginated,
224
211
  blogTags,
@@ -236,10 +223,13 @@ export default function pluginContentBlog(
236
223
  blogPostComponent,
237
224
  blogTagsListComponent,
238
225
  blogTagsPostsComponent,
226
+ routeBasePath,
227
+ archiveBasePath,
239
228
  } = options;
240
229
 
241
230
  const {addRoute, createData} = actions;
242
231
  const {
232
+ blogSidebarTitle,
243
233
  blogPosts,
244
234
  blogListPaginated,
245
235
  blogTags,
@@ -251,7 +241,27 @@ export default function pluginContentBlog(
251
241
  const sidebarBlogPosts =
252
242
  options.blogSidebarCount === 'ALL'
253
243
  ? blogPosts
254
- : 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
+ });
255
265
 
256
266
  // This prop is useful to provide the blog list sidebar
257
267
  const sidebarProp = await createData(
@@ -260,7 +270,7 @@ export default function pluginContentBlog(
260
270
  `blog-post-list-prop-${pluginId}.json`,
261
271
  JSON.stringify(
262
272
  {
263
- title: options.blogSidebarTitle,
273
+ title: blogSidebarTitle,
264
274
  items: sidebarBlogPosts.map((blogPost) => ({
265
275
  title: blogPost.metadata.title,
266
276
  permalink: blogPost.metadata.permalink,
@@ -341,6 +351,7 @@ export default function pluginContentBlog(
341
351
  Object.keys(blogTags).map(async (tag) => {
342
352
  const {name, items, permalink} = blogTags[tag];
343
353
 
354
+ // Refactor all this, see docs implementation
344
355
  tagsModule[tag] = {
345
356
  allTagsPath: blogTagsListPath,
346
357
  slug: tag,
@@ -397,6 +408,10 @@ export default function pluginContentBlog(
397
408
  }
398
409
  },
399
410
 
411
+ translateContent({content, translationFiles}) {
412
+ return translateContent(content, translationFiles);
413
+ },
414
+
400
415
  configureWebpack(
401
416
  _config: Configuration,
402
417
  isServer: boolean,
@@ -467,6 +482,22 @@ export default function pluginContentBlog(
467
482
  // For blog posts a title in markdown is always removed
468
483
  // Blog posts title are rendered separately
469
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
+ },
470
501
  },
471
502
  },
472
503
  {
@@ -481,33 +512,22 @@ export default function pluginContentBlog(
481
512
  },
482
513
 
483
514
  async postBuild({outDir}: Props) {
484
- if (!options.feedOptions?.type) {
515
+ if (!options.feedOptions.type) {
485
516
  return;
486
517
  }
487
518
 
488
- const feed = await generateBlogFeed(contentPaths, context, options);
489
-
490
- 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) {
491
523
  return;
492
524
  }
493
-
494
- const feedTypes = options.feedOptions.type;
495
-
496
- await Promise.all(
497
- feedTypes.map(async (feedType) => {
498
- const feedPath = path.join(
499
- outDir,
500
- options.routeBasePath,
501
- `${feedType}.xml`,
502
- );
503
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
504
- try {
505
- await fs.outputFile(feedPath, feedContent);
506
- } catch (err) {
507
- throw new Error(`Generating ${feedType} feed failed: ${err}.`);
508
- }
509
- }),
510
- );
525
+ await createBlogFeedFiles({
526
+ blogPosts,
527
+ options,
528
+ outDir,
529
+ siteConfig,
530
+ });
511
531
  },
512
532
 
513
533
  injectHtmlTags({content}) {
@@ -520,20 +540,17 @@ export default function pluginContentBlog(
520
540
  }
521
541
 
522
542
  const feedTypes = options.feedOptions.type;
523
- const {
524
- siteConfig: {title},
525
- baseUrl,
526
- } = context;
543
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
527
544
  const feedsConfig = {
528
545
  rss: {
529
546
  type: 'application/rss+xml',
530
547
  path: 'rss.xml',
531
- title: `${title} Blog RSS Feed`,
548
+ title: `${feedTitle} RSS Feed`,
532
549
  },
533
550
  atom: {
534
551
  type: 'application/atom+xml',
535
552
  path: 'atom.xml',
536
- title: `${title} Blog Atom Feed`,
553
+ title: `${feedTitle} Atom Feed`,
537
554
  },
538
555
  };
539
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
  }
@@ -13,9 +13,10 @@ import {
13
13
  URISchema,
14
14
  } from '@docusaurus/utils-validation';
15
15
  import {GlobExcludeDefault} from '@docusaurus/utils';
16
+ import {PluginOptions} from './types';
16
17
 
17
- export const DEFAULT_OPTIONS = {
18
- feedOptions: {type: ['rss', 'atom']},
18
+ export const DEFAULT_OPTIONS: PluginOptions = {
19
+ feedOptions: {type: ['rss', 'atom'], copyright: ''},
19
20
  beforeDefaultRehypePlugins: [],
20
21
  beforeDefaultRemarkPlugins: [],
21
22
  admonitions: {},
@@ -32,24 +33,29 @@ export const DEFAULT_OPTIONS = {
32
33
  blogSidebarCount: 5,
33
34
  blogSidebarTitle: 'Recent posts',
34
35
  postsPerPage: 10,
35
- include: ['*.md', '*.mdx'],
36
+ include: ['**/*.{md,mdx}'],
36
37
  exclude: GlobExcludeDefault,
37
38
  routeBasePath: 'blog',
39
+ tagsBasePath: 'tags',
40
+ archiveBasePath: 'archive',
38
41
  path: 'blog',
39
42
  editLocalizedFiles: false,
43
+ authorsMapPath: 'authors.yml',
44
+ readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
40
45
  };
41
46
 
42
- export const PluginOptionSchema = Joi.object({
47
+ export const PluginOptionSchema = Joi.object<PluginOptions>({
43
48
  path: Joi.string().default(DEFAULT_OPTIONS.path),
49
+ archiveBasePath: Joi.string().default(DEFAULT_OPTIONS.archiveBasePath),
44
50
  routeBasePath: Joi.string()
45
51
  // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
46
52
  // .allow('')
47
53
  .default(DEFAULT_OPTIONS.routeBasePath),
54
+ tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
48
55
  include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
49
56
  exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
50
- postsPerPage: Joi.number()
51
- .integer()
52
- .min(1)
57
+ postsPerPage: Joi.alternatives()
58
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(1).required())
53
59
  .default(DEFAULT_OPTIONS.postsPerPage),
54
60
  blogListComponent: Joi.string().default(DEFAULT_OPTIONS.blogListComponent),
55
61
  blogPostComponent: Joi.string().default(DEFAULT_OPTIONS.blogPostComponent),
@@ -64,7 +70,7 @@ export const PluginOptionSchema = Joi.object({
64
70
  .allow('')
65
71
  .default(DEFAULT_OPTIONS.blogDescription),
66
72
  blogSidebarCount: Joi.alternatives()
67
- .try(Joi.equal('ALL').required(), Joi.number().required())
73
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(0).required())
68
74
  .default(DEFAULT_OPTIONS.blogSidebarCount),
69
75
  blogSidebarTitle: Joi.string().default(DEFAULT_OPTIONS.blogSidebarTitle),
70
76
  showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime),
@@ -97,7 +103,16 @@ export const PluginOptionSchema = Joi.object({
97
103
  .default(DEFAULT_OPTIONS.feedOptions.type),
98
104
  title: Joi.string().allow(''),
99
105
  description: Joi.string().allow(''),
100
- 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
+ }),
101
114
  language: Joi.string(),
102
115
  }).default(DEFAULT_OPTIONS.feedOptions),
116
+ authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
117
+ readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
103
118
  });
@@ -0,0 +1,63 @@
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
+ import type {BlogContent, PluginOptions, BlogPaginated} from './types';
9
+ import type {TranslationFileContent, TranslationFiles} from '@docusaurus/types';
10
+
11
+ function translateListPage(
12
+ blogListPaginated: BlogPaginated[],
13
+ translations: TranslationFileContent,
14
+ ) {
15
+ return blogListPaginated.map((page) => {
16
+ const {items, metadata} = page;
17
+ return {
18
+ items,
19
+ metadata: {
20
+ ...metadata,
21
+ blogTitle: translations.title.message,
22
+ blogDescription: translations.description.message,
23
+ },
24
+ };
25
+ });
26
+ }
27
+
28
+ export function getTranslationFiles(options: PluginOptions): TranslationFiles {
29
+ return [
30
+ {
31
+ path: 'options',
32
+ content: {
33
+ title: {
34
+ message: options.blogTitle,
35
+ description: 'The title for the blog used in SEO',
36
+ },
37
+ description: {
38
+ message: options.blogDescription,
39
+ description: 'The description for the blog used in SEO',
40
+ },
41
+ 'sidebar.title': {
42
+ message: options.blogSidebarTitle,
43
+ description: 'The label for the left sidebar',
44
+ },
45
+ },
46
+ },
47
+ ];
48
+ }
49
+
50
+ export function translateContent(
51
+ content: BlogContent,
52
+ translationFiles: TranslationFiles,
53
+ ): BlogContent {
54
+ const [{content: optonsTranslations}] = translationFiles;
55
+ return {
56
+ ...content,
57
+ blogSidebarTitle: optonsTranslations['sidebar.title'].message,
58
+ blogListPaginated: translateListPage(
59
+ content.blogListPaginated,
60
+ optonsTranslations,
61
+ ),
62
+ };
63
+ }