@docusaurus/plugin-content-blog 2.0.0-beta.677e53d4d → 2.0.0-beta.7

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 (56) 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 -2
  7. package/lib/blogUtils.js +146 -104
  8. package/lib/index.js +78 -77
  9. package/lib/markdownLoader.js +3 -3
  10. package/lib/pluginOptionSchema.d.ts +3 -26
  11. package/lib/pluginOptionSchema.js +22 -7
  12. package/lib/translations.d.ts +10 -0
  13. package/lib/translations.js +53 -0
  14. package/lib/types.d.ts +38 -14
  15. package/package.json +13 -11
  16. package/src/__tests__/__fixtures__/authorsMapFiles/authors.json +29 -0
  17. package/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +27 -0
  18. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.json +5 -0
  19. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.yml +3 -0
  20. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.json +3 -0
  21. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.yml +2 -0
  22. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.json +8 -0
  23. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.yml +3 -0
  24. package/src/__tests__/__fixtures__/component/Typography.tsx +6 -0
  25. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathEmpty/empty +0 -0
  26. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson1/authors.json +0 -0
  27. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson2/authors.json +0 -0
  28. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathNestedYml/sub/folder/authors.yml +0 -0
  29. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml1/authors.yml +0 -0
  30. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml2/authors.yml +0 -0
  31. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  32. package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +3 -0
  33. package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +3 -0
  34. package/src/__tests__/__fixtures__/website/blog/_somePartial.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/simple-slug.md +4 -0
  38. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
  39. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +5 -0
  40. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +81 -3
  41. package/src/__tests__/__snapshots__/translations.test.ts.snap +64 -0
  42. package/src/__tests__/authors.test.ts +608 -0
  43. package/src/__tests__/blogFrontMatter.test.ts +93 -16
  44. package/src/__tests__/blogUtils.test.ts +94 -0
  45. package/src/__tests__/generateBlogFeed.test.ts +7 -1
  46. package/src/__tests__/index.test.ts +63 -12
  47. package/src/__tests__/pluginOptionSchema.test.ts +3 -3
  48. package/src/__tests__/translations.test.ts +92 -0
  49. package/src/authors.ts +202 -0
  50. package/src/blogFrontMatter.ts +73 -33
  51. package/src/blogUtils.ts +206 -131
  52. package/src/index.ts +98 -71
  53. package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
  54. package/src/pluginOptionSchema.ts +25 -9
  55. package/src/translations.ts +63 -0
  56. package/src/types.ts +48 -16
package/src/index.ts CHANGED
@@ -16,12 +16,14 @@ import {
16
16
  reportMessage,
17
17
  posixPath,
18
18
  addTrailingPathSeparator,
19
+ createAbsoluteFilePathMatcher,
19
20
  } from '@docusaurus/utils';
20
21
  import {
21
22
  STATIC_DIR_NAME,
22
23
  DEFAULT_PLUGIN_ID,
23
24
  } from '@docusaurus/core/lib/constants';
24
- import {flatten, take, kebabCase} from 'lodash';
25
+ import {translateContent, getTranslationFiles} from './translations';
26
+ import {flatten, take} from 'lodash';
25
27
 
26
28
  import {
27
29
  PluginOptions,
@@ -30,9 +32,10 @@ import {
30
32
  BlogItemsToMetadata,
31
33
  TagsModule,
32
34
  BlogPaginated,
33
- BlogPost,
34
35
  BlogContentPaths,
35
36
  BlogMarkdownLoaderOptions,
37
+ MetaData,
38
+ Assets,
36
39
  } from './types';
37
40
  import {PluginOptionSchema} from './pluginOptionSchema';
38
41
  import {
@@ -50,7 +53,9 @@ import {
50
53
  generateBlogPosts,
51
54
  getContentPathList,
52
55
  getSourceToPermalink,
56
+ getBlogTags,
53
57
  } from './blogUtils';
58
+ import {BlogPostFrontMatter} from './blogFrontMatter';
54
59
 
55
60
  export default function pluginContentBlog(
56
61
  context: LoadContext,
@@ -64,7 +69,7 @@ export default function pluginContentBlog(
64
69
 
65
70
  const {
66
71
  siteDir,
67
- siteConfig: {onBrokenMarkdownLinks},
72
+ siteConfig: {onBrokenMarkdownLinks, baseUrl},
68
73
  generatedFilesDir,
69
74
  i18n: {currentLocale},
70
75
  } = context;
@@ -92,36 +97,44 @@ export default function pluginContentBlog(
92
97
  name: 'docusaurus-plugin-content-blog',
93
98
 
94
99
  getPathsToWatch() {
95
- const {include = []} = options;
96
- return flatten(
100
+ const {include, authorsMapPath} = options;
101
+ const contentMarkdownGlobs = flatten(
97
102
  getContentPathList(contentPaths).map((contentPath) => {
98
103
  return include.map((pattern) => `${contentPath}/${pattern}`);
99
104
  }),
100
105
  );
101
- },
102
106
 
103
- getClientModules() {
104
- const modules = [];
107
+ // TODO: we should read this path in plugin! but plugins do not support async init for now :'(
108
+ // const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,});
109
+ // simplified impl, better than nothing for now:
110
+ const authorsMapFilePath = path.join(
111
+ contentPaths.contentPath,
112
+ authorsMapPath,
113
+ );
105
114
 
106
- if (options.admonitions) {
107
- modules.push(require.resolve('remark-admonitions/styles/infima.css'));
108
- }
115
+ return [authorsMapFilePath, ...contentMarkdownGlobs];
116
+ },
109
117
 
110
- return modules;
118
+ async getTranslationFiles() {
119
+ return getTranslationFiles(options);
111
120
  },
112
121
 
113
122
  // Fetches blog contents and returns metadata for the necessary routes.
114
123
  async loadContent() {
115
- const {postsPerPage, routeBasePath} = options;
124
+ const {
125
+ postsPerPage: postsPerPageOption,
126
+ routeBasePath,
127
+ tagsBasePath,
128
+ blogDescription,
129
+ blogTitle,
130
+ blogSidebarTitle,
131
+ } = options;
116
132
 
117
- const blogPosts: BlogPost[] = await generateBlogPosts(
118
- contentPaths,
119
- context,
120
- options,
121
- );
133
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
122
134
 
123
135
  if (!blogPosts.length) {
124
136
  return {
137
+ blogSidebarTitle,
125
138
  blogPosts: [],
126
139
  blogListPaginated: [],
127
140
  blogTags: {},
@@ -152,18 +165,17 @@ export default function pluginContentBlog(
152
165
  // Blog pagination routes.
153
166
  // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
154
167
  const totalCount = blogPosts.length;
168
+ const postsPerPage =
169
+ postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
155
170
  const numberOfPages = Math.ceil(totalCount / postsPerPage);
156
- const {
157
- siteConfig: {baseUrl = ''},
158
- } = context;
159
- const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
171
+ const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
160
172
 
161
173
  const blogListPaginated: BlogPaginated[] = [];
162
174
 
163
175
  function blogPaginationPermalink(page: number) {
164
176
  return page > 0
165
- ? normalizeUrl([basePageUrl, `page/${page + 1}`])
166
- : basePageUrl;
177
+ ? normalizeUrl([baseBlogUrl, `page/${page + 1}`])
178
+ : baseBlogUrl;
167
179
  }
168
180
 
169
181
  for (let page = 0; page < numberOfPages; page += 1) {
@@ -179,8 +191,8 @@ export default function pluginContentBlog(
179
191
  page < numberOfPages - 1
180
192
  ? blogPaginationPermalink(page + 1)
181
193
  : null,
182
- blogDescription: options.blogDescription,
183
- blogTitle: options.blogTitle,
194
+ blogDescription,
195
+ blogTitle,
184
196
  },
185
197
  items: blogPosts
186
198
  .slice(page * postsPerPage, (page + 1) * postsPerPage)
@@ -188,46 +200,15 @@ export default function pluginContentBlog(
188
200
  });
189
201
  }
190
202
 
191
- const blogTags: BlogTags = {};
192
- const tagsPath = normalizeUrl([basePageUrl, 'tags']);
193
- blogPosts.forEach((blogPost) => {
194
- const {tags} = blogPost.metadata;
195
- if (!tags || tags.length === 0) {
196
- // TODO: Extract tags out into a separate plugin.
197
- // eslint-disable-next-line no-param-reassign
198
- blogPost.metadata.tags = [];
199
- return;
200
- }
203
+ const blogTags: BlogTags = getBlogTags(blogPosts);
201
204
 
202
- // eslint-disable-next-line no-param-reassign
203
- blogPost.metadata.tags = tags.map((tag) => {
204
- if (typeof tag === 'string') {
205
- const normalizedTag = kebabCase(tag);
206
- const permalink = normalizeUrl([tagsPath, normalizedTag]);
207
- if (!blogTags[normalizedTag]) {
208
- blogTags[normalizedTag] = {
209
- // Will only use the name of the first occurrence of the tag.
210
- name: tag.toLowerCase(),
211
- items: [],
212
- permalink,
213
- };
214
- }
215
-
216
- blogTags[normalizedTag].items.push(blogPost.id);
217
-
218
- return {
219
- label: tag,
220
- permalink,
221
- };
222
- }
223
- return tag;
224
- });
225
- });
205
+ const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
226
206
 
227
207
  const blogTagsListPath =
228
208
  Object.keys(blogTags).length > 0 ? tagsPath : null;
229
209
 
230
210
  return {
211
+ blogSidebarTitle,
231
212
  blogPosts,
232
213
  blogListPaginated,
233
214
  blogTags,
@@ -245,10 +226,13 @@ export default function pluginContentBlog(
245
226
  blogPostComponent,
246
227
  blogTagsListComponent,
247
228
  blogTagsPostsComponent,
229
+ routeBasePath,
230
+ archiveBasePath,
248
231
  } = options;
249
232
 
250
233
  const {addRoute, createData} = actions;
251
234
  const {
235
+ blogSidebarTitle,
252
236
  blogPosts,
253
237
  blogListPaginated,
254
238
  blogTags,
@@ -262,6 +246,26 @@ export default function pluginContentBlog(
262
246
  ? blogPosts
263
247
  : take(blogPosts, options.blogSidebarCount);
264
248
 
249
+ const archiveUrl = normalizeUrl([
250
+ baseUrl,
251
+ routeBasePath,
252
+ archiveBasePath,
253
+ ]);
254
+
255
+ // creates a blog archive route
256
+ const archiveProp = await createData(
257
+ `${docuHash(archiveUrl)}.json`,
258
+ JSON.stringify({blogPosts}, null, 2),
259
+ );
260
+ addRoute({
261
+ path: archiveUrl,
262
+ component: '@theme/BlogArchivePage',
263
+ exact: true,
264
+ modules: {
265
+ archive: aliasedSource(archiveProp),
266
+ },
267
+ });
268
+
265
269
  // This prop is useful to provide the blog list sidebar
266
270
  const sidebarProp = await createData(
267
271
  // Note that this created data path must be in sync with
@@ -269,7 +273,7 @@ export default function pluginContentBlog(
269
273
  `blog-post-list-prop-${pluginId}.json`,
270
274
  JSON.stringify(
271
275
  {
272
- title: options.blogSidebarTitle,
276
+ title: blogSidebarTitle,
273
277
  items: sidebarBlogPosts.map((blogPost) => ({
274
278
  title: blogPost.metadata.title,
275
279
  permalink: blogPost.metadata.permalink,
@@ -350,6 +354,7 @@ export default function pluginContentBlog(
350
354
  Object.keys(blogTags).map(async (tag) => {
351
355
  const {name, items, permalink} = blogTags[tag];
352
356
 
357
+ // Refactor all this, see docs implementation
353
358
  tagsModule[tag] = {
354
359
  allTagsPath: blogTagsListPath,
355
360
  slug: tag,
@@ -406,6 +411,10 @@ export default function pluginContentBlog(
406
411
  }
407
412
  },
408
413
 
414
+ translateContent({content, translationFiles}) {
415
+ return translateContent(content, translationFiles);
416
+ },
417
+
409
418
  configureWebpack(
410
419
  _config: Configuration,
411
420
  isServer: boolean,
@@ -436,6 +445,7 @@ export default function pluginContentBlog(
436
445
  },
437
446
  };
438
447
 
448
+ const contentDirs = getContentPathList(contentPaths);
439
449
  return {
440
450
  resolve: {
441
451
  alias: {
@@ -446,7 +456,7 @@ export default function pluginContentBlog(
446
456
  rules: [
447
457
  {
448
458
  test: /(\.mdx?)$/,
449
- include: getContentPathList(contentPaths)
459
+ include: contentDirs
450
460
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
451
461
  .map(addTrailingPathSeparator),
452
462
  use: [
@@ -459,9 +469,13 @@ export default function pluginContentBlog(
459
469
  beforeDefaultRemarkPlugins,
460
470
  beforeDefaultRehypePlugins,
461
471
  staticDir: path.join(siteDir, STATIC_DIR_NAME),
462
- // Note that metadataPath must be the same/in-sync as
463
- // the path from createData for each MDX.
472
+ isMDXPartial: createAbsoluteFilePathMatcher(
473
+ options.exclude,
474
+ contentDirs,
475
+ ),
464
476
  metadataPath: (mdxPath: string) => {
477
+ // Note that metadataPath must be the same/in-sync as
478
+ // the path from createData for each MDX.
465
479
  const aliasedPath = aliasedSitePath(mdxPath, siteDir);
466
480
  return path.join(
467
481
  dataDir,
@@ -471,6 +485,22 @@ export default function pluginContentBlog(
471
485
  // For blog posts a title in markdown is always removed
472
486
  // Blog posts title are rendered separately
473
487
  removeContentTitle: true,
488
+
489
+ // Assets allow to convert some relative images paths to require() calls
490
+ createAssets: ({
491
+ frontMatter,
492
+ metadata,
493
+ }: {
494
+ frontMatter: BlogPostFrontMatter;
495
+ metadata: MetaData;
496
+ }): Assets => {
497
+ return {
498
+ image: frontMatter.image,
499
+ authorsImageUrls: metadata.authors.map(
500
+ (author) => author.imageURL,
501
+ ),
502
+ };
503
+ },
474
504
  },
475
505
  },
476
506
  {
@@ -485,7 +515,7 @@ export default function pluginContentBlog(
485
515
  },
486
516
 
487
517
  async postBuild({outDir}: Props) {
488
- if (!options.feedOptions?.type) {
518
+ if (!options.feedOptions.type) {
489
519
  return;
490
520
  }
491
521
 
@@ -524,20 +554,17 @@ export default function pluginContentBlog(
524
554
  }
525
555
 
526
556
  const feedTypes = options.feedOptions.type;
527
- const {
528
- siteConfig: {title},
529
- baseUrl,
530
- } = context;
557
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
531
558
  const feedsConfig = {
532
559
  rss: {
533
560
  type: 'application/rss+xml',
534
561
  path: 'rss.xml',
535
- title: `${title} Blog RSS Feed`,
562
+ title: `${feedTitle} RSS Feed`,
536
563
  },
537
564
  atom: {
538
565
  type: 'application/atom+xml',
539
566
  path: 'atom.xml',
540
- title: `${title} Blog Atom Feed`,
567
+ title: `${feedTitle} Atom Feed`,
541
568
  },
542
569
  };
543
570
  const headTags: HtmlTags = [];
@@ -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,28 @@ 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',
38
44
  };
39
45
 
40
- export const PluginOptionSchema = Joi.object({
46
+ export const PluginOptionSchema = Joi.object<PluginOptions>({
41
47
  path: Joi.string().default(DEFAULT_OPTIONS.path),
48
+ archiveBasePath: Joi.string().default(DEFAULT_OPTIONS.archiveBasePath),
42
49
  routeBasePath: Joi.string()
43
50
  // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
44
51
  // .allow('')
45
52
  .default(DEFAULT_OPTIONS.routeBasePath),
53
+ tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
46
54
  include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
47
- postsPerPage: Joi.number()
48
- .integer()
49
- .min(1)
55
+ exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
56
+ postsPerPage: Joi.alternatives()
57
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(1).required())
50
58
  .default(DEFAULT_OPTIONS.postsPerPage),
51
59
  blogListComponent: Joi.string().default(DEFAULT_OPTIONS.blogListComponent),
52
60
  blogPostComponent: Joi.string().default(DEFAULT_OPTIONS.blogPostComponent),
@@ -61,7 +69,7 @@ export const PluginOptionSchema = Joi.object({
61
69
  .allow('')
62
70
  .default(DEFAULT_OPTIONS.blogDescription),
63
71
  blogSidebarCount: Joi.alternatives()
64
- .try(Joi.equal('ALL').required(), Joi.number().required())
72
+ .try(Joi.equal('ALL').required(), Joi.number().integer().min(0).required())
65
73
  .default(DEFAULT_OPTIONS.blogSidebarCount),
66
74
  blogSidebarTitle: Joi.string().default(DEFAULT_OPTIONS.blogSidebarTitle),
67
75
  showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime),
@@ -94,7 +102,15 @@ export const PluginOptionSchema = Joi.object({
94
102
  .default(DEFAULT_OPTIONS.feedOptions.type),
95
103
  title: Joi.string().allow(''),
96
104
  description: Joi.string().allow(''),
97
- copyright: Joi.string(),
105
+ // only add default value when user actually wants a feed (type is not null)
106
+ copyright: Joi.when('type', {
107
+ is: Joi.any().valid(null),
108
+ then: Joi.string().optional(),
109
+ otherwise: Joi.string()
110
+ .allow('')
111
+ .default(DEFAULT_OPTIONS.feedOptions.copyright),
112
+ }),
98
113
  language: Joi.string(),
99
114
  }).default(DEFAULT_OPTIONS.feedOptions),
115
+ authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
100
116
  });
@@ -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
+ }