@docusaurus/plugin-content-blog 2.4.1 → 3.0.0-beta.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.
Files changed (53) hide show
  1. package/.docusaurus/DONT-EDIT-THIS-FOLDER +5 -0
  2. package/.docusaurus/client-modules.js +5 -0
  3. package/.docusaurus/codeTranslations.json +1 -0
  4. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-aeb.json +10 -0
  5. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-archive-245.json +312 -0
  6. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-page-2-d48.json +11 -0
  7. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-page-3-8b6.json +10 -0
  8. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-birthday-c96-list.json +9 -0
  9. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-birthday-c96.json +7 -0
  10. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-complex-cf3-list.json +9 -0
  11. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-complex-cf3.json +7 -0
  12. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-date-c24-list.json +9 -0
  13. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-date-c24.json +7 -0
  14. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-only-tags-tags-344.json +17 -0
  15. package/.docusaurus/docusaurus-plugin-content-blog/default/blog-post-list-prop-default.json +30 -0
  16. package/.docusaurus/docusaurus-plugin-content-blog/default/plugin-route-context-module-100.json +4 -0
  17. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-2018-12-14-happy-first-birthday-slash-md-d1e.json +46 -0
  18. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-blog-with-links-mdx-fe5.json +22 -0
  19. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-complex-slug-md-314.json +40 -0
  20. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-date-matter-md-191.json +33 -0
  21. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-heading-as-title-md-10c.json +25 -0
  22. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-mdx-blog-post-mdx-025.json +26 -0
  23. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-mdx-require-blog-post-mdx-4ba.json +26 -0
  24. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-simple-slug-md-324.json +36 -0
  25. package/.docusaurus/docusaurus-plugin-content-blog/default/site-src-tests-fixtures-website-blog-unlisted-md-5cb.json +18 -0
  26. package/.docusaurus/docusaurus-theme-search-algolia/default/plugin-route-context-module-100.json +4 -0
  27. package/.docusaurus/docusaurus.config.mjs +246 -0
  28. package/.docusaurus/globalData.json +1 -0
  29. package/.docusaurus/i18n.json +17 -0
  30. package/.docusaurus/registry.js +38 -0
  31. package/.docusaurus/routes.js +99 -0
  32. package/.docusaurus/routesChunkNames.json +194 -0
  33. package/.docusaurus/site-metadata.json +24 -0
  34. package/lib/authors.d.ts +2 -2
  35. package/lib/blogUtils.d.ts +3 -2
  36. package/lib/blogUtils.js +30 -16
  37. package/lib/feed.d.ts +1 -1
  38. package/lib/feed.js +39 -6
  39. package/lib/frontMatter.js +4 -3
  40. package/lib/index.js +12 -19
  41. package/lib/options.js +6 -6
  42. package/lib/props.d.ts +15 -0
  43. package/lib/props.js +23 -0
  44. package/lib/types.d.ts +3 -3
  45. package/package.json +17 -15
  46. package/src/blogUtils.ts +47 -28
  47. package/src/feed.ts +51 -6
  48. package/src/frontMatter.ts +7 -5
  49. package/src/index.ts +13 -23
  50. package/src/options.ts +7 -6
  51. package/src/plugin-content-blog.d.ts +18 -1
  52. package/src/props.ts +34 -0
  53. package/src/remark/footnoteIDFixer.ts +1 -0
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "2.4.1",
3
+ "version": "3.0.0-beta.0",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
- "watch": "tsc --watch"
9
+ "watch": "tsc --watch",
10
+ "test:generate-build-snap": "yarn docusaurus build src/__tests__/__fixtures__/website --out-dir build-snap && yarn rimraf src/__tests__/__fixtures__/website/.docusaurus && yarn rimraf src/__tests__/__fixtures__/website/build-snap/assets && git add src/__tests__/__fixtures__/website/build-snap"
10
11
  },
11
12
  "repository": {
12
13
  "type": "git",
@@ -18,29 +19,30 @@
18
19
  },
19
20
  "license": "MIT",
20
21
  "dependencies": {
21
- "@docusaurus/core": "2.4.1",
22
- "@docusaurus/logger": "2.4.1",
23
- "@docusaurus/mdx-loader": "2.4.1",
24
- "@docusaurus/types": "2.4.1",
25
- "@docusaurus/utils": "2.4.1",
26
- "@docusaurus/utils-common": "2.4.1",
27
- "@docusaurus/utils-validation": "2.4.1",
22
+ "@docusaurus/core": "3.0.0-beta.0",
23
+ "@docusaurus/logger": "3.0.0-beta.0",
24
+ "@docusaurus/mdx-loader": "3.0.0-beta.0",
25
+ "@docusaurus/types": "3.0.0-beta.0",
26
+ "@docusaurus/utils": "3.0.0-beta.0",
27
+ "@docusaurus/utils-common": "3.0.0-beta.0",
28
+ "@docusaurus/utils-validation": "3.0.0-beta.0",
28
29
  "cheerio": "^1.0.0-rc.12",
29
30
  "feed": "^4.2.2",
30
- "fs-extra": "^10.1.0",
31
+ "fs-extra": "^11.1.1",
31
32
  "lodash": "^4.17.21",
32
33
  "reading-time": "^1.5.0",
33
- "tslib": "^2.4.0",
34
+ "srcset": "^4.0.0",
35
+ "tslib": "^2.6.0",
34
36
  "unist-util-visit": "^2.0.3",
35
37
  "utility-types": "^3.10.0",
36
- "webpack": "^5.73.0"
38
+ "webpack": "^5.88.1"
37
39
  },
38
40
  "peerDependencies": {
39
- "react": "^16.8.4 || ^17.0.0",
40
- "react-dom": "^16.8.4 || ^17.0.0"
41
+ "react": "^18.0.0",
42
+ "react-dom": "^18.0.0"
41
43
  },
42
44
  "engines": {
43
45
  "node": ">=16.14"
44
46
  },
45
- "gitHead": "60e657d8ae5a4a9ed1c2d777f9defd882cc12681"
47
+ "gitHead": "27a1e90d9fff88af90ecad35bea16d4d7230482a"
46
48
  }
package/src/blogUtils.ts CHANGED
@@ -21,8 +21,11 @@ import {
21
21
  Globby,
22
22
  normalizeFrontMatterTags,
23
23
  groupTaggedItems,
24
+ getTagVisibility,
24
25
  getFileCommitDate,
25
26
  getContentPathList,
27
+ isUnlisted,
28
+ isDraft,
26
29
  } from '@docusaurus/utils';
27
30
  import {validateBlogPostFrontMatter} from './frontMatter';
28
31
  import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
@@ -96,6 +99,10 @@ export function paginateBlogPosts({
96
99
  return pages;
97
100
  }
98
101
 
102
+ export function shouldBeListed(blogPost: BlogPost): boolean {
103
+ return !blogPost.metadata.unlisted;
104
+ }
105
+
99
106
  export function getBlogTags({
100
107
  blogPosts,
101
108
  ...params
@@ -109,17 +116,23 @@ export function getBlogTags({
109
116
  blogPosts,
110
117
  (blogPost) => blogPost.metadata.tags,
111
118
  );
112
-
113
- return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
114
- label: tag.label,
115
- items: tagBlogPosts.map((item) => item.id),
116
- permalink: tag.permalink,
117
- pages: paginateBlogPosts({
118
- blogPosts: tagBlogPosts,
119
- basePageUrl: tag.permalink,
120
- ...params,
121
- }),
122
- }));
119
+ return _.mapValues(groups, ({tag, items: tagBlogPosts}) => {
120
+ const tagVisibility = getTagVisibility({
121
+ items: tagBlogPosts,
122
+ isUnlisted: (item) => item.metadata.unlisted,
123
+ });
124
+ return {
125
+ label: tag.label,
126
+ items: tagVisibility.listedItems.map((item) => item.id),
127
+ permalink: tag.permalink,
128
+ pages: paginateBlogPosts({
129
+ blogPosts: tagVisibility.listedItems,
130
+ basePageUrl: tag.permalink,
131
+ ...params,
132
+ }),
133
+ unlisted: tagVisibility.unlisted,
134
+ };
135
+ });
123
136
  }
124
137
 
125
138
  const DATE_FILENAME_REGEX =
@@ -219,7 +232,10 @@ async function processBlogSourceFile(
219
232
 
220
233
  const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
221
234
 
222
- if (frontMatter.draft && process.env.NODE_ENV === 'production') {
235
+ const draft = isDraft({frontMatter});
236
+ const unlisted = isUnlisted({frontMatter});
237
+
238
+ if (draft) {
223
239
  return undefined;
224
240
  }
225
241
 
@@ -326,6 +342,7 @@ async function processBlogSourceFile(
326
342
  hasTruncateMarker: truncateMarker.test(content),
327
343
  authors,
328
344
  frontMatter,
345
+ unlisted,
329
346
  },
330
347
  content,
331
348
  };
@@ -352,23 +369,25 @@ export async function generateBlogPosts(
352
369
  authorsMapPath: options.authorsMapPath,
353
370
  });
354
371
 
372
+ async function doProcessBlogSourceFile(blogSourceFile: string) {
373
+ try {
374
+ return await processBlogSourceFile(
375
+ blogSourceFile,
376
+ contentPaths,
377
+ context,
378
+ options,
379
+ authorsMap,
380
+ );
381
+ } catch (err) {
382
+ throw new Error(
383
+ `Processing of blog source file path=${blogSourceFile} failed.`,
384
+ {cause: err as Error},
385
+ );
386
+ }
387
+ }
388
+
355
389
  const blogPosts = (
356
- await Promise.all(
357
- blogSourceFiles.map(async (blogSourceFile: string) => {
358
- try {
359
- return await processBlogSourceFile(
360
- blogSourceFile,
361
- contentPaths,
362
- context,
363
- options,
364
- authorsMap,
365
- );
366
- } catch (err) {
367
- logger.error`Processing of blog source file path=${blogSourceFile} failed.`;
368
- throw err;
369
- }
370
- }),
371
- )
390
+ await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))
372
391
  ).filter(Boolean) as BlogPost[];
373
392
 
374
393
  blogPosts.sort(
package/src/feed.ts CHANGED
@@ -9,6 +9,7 @@ import path from 'path';
9
9
  import fs from 'fs-extra';
10
10
  import logger from '@docusaurus/logger';
11
11
  import {Feed, type Author as FeedAuthor} from 'feed';
12
+ import * as srcset from 'srcset';
12
13
  import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
13
14
  import {blogPostContainerID} from '@docusaurus/utils-common';
14
15
  import {load as cheerioLoad} from 'cheerio';
@@ -42,7 +43,12 @@ async function generateBlogFeed({
42
43
  const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
43
44
  const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
44
45
 
45
- const updated = blogPosts[0]?.metadata.date;
46
+ const blogPostsForFeed =
47
+ feedOptions.limit === false || feedOptions.limit === null
48
+ ? blogPosts
49
+ : blogPosts.slice(0, feedOptions.limit);
50
+
51
+ const updated = blogPostsForFeed[0]?.metadata.date;
46
52
 
47
53
  const feed = new Feed({
48
54
  id: blogBaseUrl,
@@ -59,7 +65,7 @@ async function generateBlogFeed({
59
65
  options.feedOptions.createFeedItems ?? defaultCreateFeedItems;
60
66
 
61
67
  const feedItems = await createFeedItems({
62
- blogPosts,
68
+ blogPosts: blogPostsForFeed,
63
69
  siteConfig,
64
70
  outDir,
65
71
  defaultCreateFeedItems,
@@ -105,11 +111,41 @@ async function defaultCreateFeedItems({
105
111
  );
106
112
  const $ = cheerioLoad(content);
107
113
 
108
- const link = normalizeUrl([siteUrl, permalink]);
114
+ const blogPostAbsoluteUrl = normalizeUrl([siteUrl, permalink]);
115
+
116
+ const toAbsoluteUrl = (src: string) =>
117
+ String(new URL(src, blogPostAbsoluteUrl));
118
+
119
+ // Make links and image urls absolute
120
+ // See https://github.com/facebook/docusaurus/issues/9136
121
+ $(`div#${blogPostContainerID} a, div#${blogPostContainerID} img`).each(
122
+ (_, elm) => {
123
+ if (elm.tagName === 'a') {
124
+ const {href} = elm.attribs;
125
+ if (href) {
126
+ elm.attribs.href = toAbsoluteUrl(href);
127
+ }
128
+ } else if (elm.tagName === 'img') {
129
+ const {src, srcset: srcsetAttr} = elm.attribs;
130
+ if (src) {
131
+ elm.attribs.src = toAbsoluteUrl(src);
132
+ }
133
+ if (srcsetAttr) {
134
+ elm.attribs.srcset = srcset.stringify(
135
+ srcset.parse(srcsetAttr).map((props) => ({
136
+ ...props,
137
+ url: toAbsoluteUrl(props.url),
138
+ })),
139
+ );
140
+ }
141
+ }
142
+ },
143
+ );
144
+
109
145
  const feedItem: BlogFeedItem = {
110
146
  title: metadataTitle,
111
- id: link,
112
- link,
147
+ id: blogPostAbsoluteUrl,
148
+ link: blogPostAbsoluteUrl,
113
149
  date,
114
150
  description,
115
151
  // Atom feed demands the "term", while other feeds use "name"
@@ -158,8 +194,15 @@ async function createBlogFeedFile({
158
194
  }
159
195
  }
160
196
 
197
+ function shouldBeInFeed(blogPost: BlogPost): boolean {
198
+ const excluded =
199
+ blogPost.metadata.frontMatter.draft ||
200
+ blogPost.metadata.frontMatter.unlisted;
201
+ return !excluded;
202
+ }
203
+
161
204
  export async function createBlogFeedFiles({
162
- blogPosts,
205
+ blogPosts: allBlogPosts,
163
206
  options,
164
207
  siteConfig,
165
208
  outDir,
@@ -171,6 +214,8 @@ export async function createBlogFeedFiles({
171
214
  outDir: string;
172
215
  locale: string;
173
216
  }): Promise<void> {
217
+ const blogPosts = allBlogPosts.filter(shouldBeInFeed);
218
+
174
219
  const feed = await generateBlogFeed({
175
220
  blogPosts,
176
221
  options,
@@ -11,6 +11,7 @@ import {
11
11
  validateFrontMatter,
12
12
  FrontMatterTagsSchema,
13
13
  FrontMatterTOCHeadingLevels,
14
+ ContentVisibilitySchema,
14
15
  } from '@docusaurus/utils-validation';
15
16
  import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
16
17
 
@@ -32,7 +33,6 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
32
33
  title: Joi.string().allow(''),
33
34
  description: Joi.string().allow(''),
34
35
  tags: FrontMatterTagsSchema,
35
- draft: Joi.boolean(),
36
36
  date: Joi.date().raw(),
37
37
 
38
38
  // New multi-authors front matter:
@@ -69,10 +69,12 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
69
69
  hide_table_of_contents: Joi.boolean(),
70
70
 
71
71
  ...FrontMatterTOCHeadingLevels,
72
- }).messages({
73
- 'deprecate.error':
74
- '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
75
- });
72
+ })
73
+ .messages({
74
+ 'deprecate.error':
75
+ '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
76
+ })
77
+ .concat(ContentVisibilitySchema);
76
78
 
77
79
  export function validateBlogPostFrontMatter(frontMatter: {
78
80
  [key: string]: unknown;
package/src/index.ts CHANGED
@@ -18,19 +18,19 @@ import {
18
18
  getContentPathList,
19
19
  getDataFilePath,
20
20
  DEFAULT_PLUGIN_ID,
21
- type TagsListItem,
22
- type TagModule,
23
21
  } from '@docusaurus/utils';
24
22
  import {
25
23
  generateBlogPosts,
26
24
  getSourceToPermalink,
27
25
  getBlogTags,
28
26
  paginateBlogPosts,
27
+ shouldBeListed,
29
28
  } from './blogUtils';
30
29
  import footnoteIDFixer from './remark/footnoteIDFixer';
31
30
  import {translateContent, getTranslationFiles} from './translations';
32
31
  import {createBlogFeedFiles} from './feed';
33
32
 
33
+ import {toTagProp, toTagsProp} from './props';
34
34
  import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
35
35
  import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
36
36
  import type {
@@ -112,6 +112,7 @@ export default async function pluginContentBlog(
112
112
  const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
113
113
  const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
114
114
  const blogPosts = await generateBlogPosts(contentPaths, context, options);
115
+ const listedBlogPosts = blogPosts.filter(shouldBeListed);
115
116
 
116
117
  if (!blogPosts.length) {
117
118
  return {
@@ -125,8 +126,8 @@ export default async function pluginContentBlog(
125
126
  }
126
127
 
127
128
  // Colocate next and prev metadata.
128
- blogPosts.forEach((blogPost, index) => {
129
- const prevItem = index > 0 ? blogPosts[index - 1] : null;
129
+ listedBlogPosts.forEach((blogPost, index) => {
130
+ const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
130
131
  if (prevItem) {
131
132
  blogPost.metadata.prevItem = {
132
133
  title: prevItem.metadata.title,
@@ -135,7 +136,9 @@ export default async function pluginContentBlog(
135
136
  }
136
137
 
137
138
  const nextItem =
138
- index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
139
+ index < listedBlogPosts.length - 1
140
+ ? listedBlogPosts[index + 1]
141
+ : null;
139
142
  if (nextItem) {
140
143
  blogPost.metadata.nextItem = {
141
144
  title: nextItem.metadata.title,
@@ -145,7 +148,7 @@ export default async function pluginContentBlog(
145
148
  });
146
149
 
147
150
  const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
148
- blogPosts,
151
+ blogPosts: listedBlogPosts,
149
152
  blogTitle,
150
153
  blogDescription,
151
154
  postsPerPageOption,
@@ -242,6 +245,7 @@ export default async function pluginContentBlog(
242
245
  items: sidebarBlogPosts.map((blogPost) => ({
243
246
  title: blogPost.metadata.title,
244
247
  permalink: blogPost.metadata.permalink,
248
+ unlisted: blogPost.metadata.unlisted,
245
249
  })),
246
250
  },
247
251
  null,
@@ -303,17 +307,10 @@ export default async function pluginContentBlog(
303
307
  }
304
308
 
305
309
  async function createTagsListPage() {
306
- const tagsProp: TagsListItem[] = Object.values(blogTags).map((tag) => ({
307
- label: tag.label,
308
- permalink: tag.permalink,
309
- count: tag.items.length,
310
- }));
311
-
312
310
  const tagsPropPath = await createData(
313
311
  `${docuHash(`${blogTagsListPath}-tags`)}.json`,
314
- JSON.stringify(tagsProp, null, 2),
312
+ JSON.stringify(toTagsProp({blogTags}), null, 2),
315
313
  );
316
-
317
314
  addRoute({
318
315
  path: blogTagsListPath,
319
316
  component: blogTagsListComponent,
@@ -329,15 +326,9 @@ export default async function pluginContentBlog(
329
326
  await Promise.all(
330
327
  tag.pages.map(async (blogPaginated) => {
331
328
  const {metadata, items} = blogPaginated;
332
- const tagProp: TagModule = {
333
- label: tag.label,
334
- permalink: tag.permalink,
335
- allTagsPath: blogTagsListPath,
336
- count: tag.items.length,
337
- };
338
329
  const tagPropPath = await createData(
339
330
  `${docuHash(metadata.permalink)}.json`,
340
- JSON.stringify(tagProp, null, 2),
331
+ JSON.stringify(toTagProp({tag, blogTagsListPath}), null, 2),
341
332
  );
342
333
 
343
334
  const listMetadataPath = await createData(
@@ -368,7 +359,7 @@ export default async function pluginContentBlog(
368
359
  return translateContent(content, translationFiles);
369
360
  },
370
361
 
371
- configureWebpack(_config, isServer, {getJSLoader}, content) {
362
+ configureWebpack(_config, isServer, utils, content) {
372
363
  const {
373
364
  admonitions,
374
365
  rehypePlugins,
@@ -408,7 +399,6 @@ export default async function pluginContentBlog(
408
399
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
409
400
  .map(addTrailingPathSeparator),
410
401
  use: [
411
- getJSLoader({isServer}),
412
402
  {
413
403
  loader: require.resolve('@docusaurus/mdx-loader'),
414
404
  options: {
package/src/options.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  RemarkPluginsSchema,
11
11
  RehypePluginsSchema,
12
12
  AdmonitionsSchema,
13
+ RouteBasePathSchema,
13
14
  URISchema,
14
15
  } from '@docusaurus/utils-validation';
15
16
  import {GlobExcludeDefault} from '@docusaurus/utils';
@@ -21,11 +22,11 @@ import type {
21
22
  import type {OptionValidationContext} from '@docusaurus/types';
22
23
 
23
24
  export const DEFAULT_OPTIONS: PluginOptions = {
24
- feedOptions: {type: ['rss', 'atom'], copyright: ''},
25
+ feedOptions: {type: ['rss', 'atom'], copyright: '', limit: 20},
25
26
  beforeDefaultRehypePlugins: [],
26
27
  beforeDefaultRemarkPlugins: [],
27
28
  admonitions: true,
28
- truncateMarker: /<!--\s*truncate\s*-->/,
29
+ truncateMarker: /<!--\s*truncate\s*-->|\{\/\*\s*truncate\s*\*\/\}/,
29
30
  rehypePlugins: [],
30
31
  remarkPlugins: [],
31
32
  showReadingTime: true,
@@ -56,10 +57,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
56
57
  archiveBasePath: Joi.string()
57
58
  .default(DEFAULT_OPTIONS.archiveBasePath)
58
59
  .allow(null),
59
- routeBasePath: Joi.string()
60
- // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
61
- // .allow('')
62
- .default(DEFAULT_OPTIONS.routeBasePath),
60
+ routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath),
63
61
  tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
64
62
  include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
65
63
  exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
@@ -125,6 +123,9 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
125
123
  }),
126
124
  language: Joi.string(),
127
125
  createFeedItems: Joi.function(),
126
+ limit: Joi.alternatives()
127
+ .try(Joi.number(), Joi.valid(null), Joi.valid(false))
128
+ .default(DEFAULT_OPTIONS.feedOptions.limit),
128
129
  }).default(DEFAULT_OPTIONS.feedOptions),
129
130
  authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
130
131
  readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
@@ -97,6 +97,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
97
97
  * Marks the post as draft and excludes it from the production build.
98
98
  */
99
99
  draft?: boolean;
100
+ /**
101
+ * Marks the post as unlisted and visibly hides it unless directly accessed.
102
+ */
103
+ unlisted?: boolean;
100
104
  /**
101
105
  * Will override the default publish date inferred from git/filename. Yaml
102
106
  * only converts standard yyyy-MM-dd format to dates, so this may stay as a
@@ -229,6 +233,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
229
233
  readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
230
234
  /** Tags, normalized. */
231
235
  readonly tags: Tag[];
236
+ /**
237
+ * Marks the post as unlisted and visibly hides it unless directly accessed.
238
+ */
239
+ readonly unlisted: boolean;
232
240
  };
233
241
  /**
234
242
  * @returns The edit URL that's directly plugged into metadata.
@@ -264,6 +272,8 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
264
272
  language?: string;
265
273
  /** Allow control over the construction of BlogFeedItems */
266
274
  createFeedItems?: CreateFeedItemsFn;
275
+ /** Limits the feed to the specified number of posts, false|null for all */
276
+ limit?: number | false | null;
267
277
  };
268
278
 
269
279
  type DefaultCreateFeedItemsParams = {
@@ -432,9 +442,15 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
432
442
  }
433
443
  >;
434
444
 
445
+ export type BlogSidebarItem = {
446
+ title: string;
447
+ permalink: string;
448
+ unlisted: boolean;
449
+ };
450
+
435
451
  export type BlogSidebar = {
436
452
  title: string;
437
- items: {title: string; permalink: string}[];
453
+ items: BlogSidebarItem[];
438
454
  };
439
455
 
440
456
  export type BlogContent = {
@@ -453,6 +469,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
453
469
  /** Blog post permalinks. */
454
470
  items: string[];
455
471
  pages: BlogPaginated[];
472
+ unlisted: boolean;
456
473
  };
457
474
 
458
475
  export type BlogPost = {
package/src/props.ts ADDED
@@ -0,0 +1,34 @@
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
+ import type {TagsListItem, TagModule} from '@docusaurus/utils';
8
+ import type {BlogTag, BlogTags} from '@docusaurus/plugin-content-blog';
9
+
10
+ export function toTagsProp({blogTags}: {blogTags: BlogTags}): TagsListItem[] {
11
+ return Object.values(blogTags)
12
+ .filter((tag) => !tag.unlisted)
13
+ .map((tag) => ({
14
+ label: tag.label,
15
+ permalink: tag.permalink,
16
+ count: tag.items.length,
17
+ }));
18
+ }
19
+
20
+ export function toTagProp({
21
+ blogTagsListPath,
22
+ tag,
23
+ }: {
24
+ blogTagsListPath: string;
25
+ tag: BlogTag;
26
+ }): TagModule {
27
+ return {
28
+ label: tag.label,
29
+ permalink: tag.permalink,
30
+ allTagsPath: blogTagsListPath,
31
+ count: tag.items.length,
32
+ unlisted: tag.unlisted,
33
+ };
34
+ }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import visit from 'unist-util-visit';
9
9
  import {simpleHash} from '@docusaurus/utils';
10
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
10
11
  import type {Transformer} from 'unified';
11
12
  import type {FootnoteReference, FootnoteDefinition} from 'mdast';
12
13