@docusaurus/plugin-content-blog 2.4.1 → 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/authors.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { BlogContentPaths } from './types';
8
8
  import type { Author, BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
9
- export declare type AuthorsMap = {
9
+ export type AuthorsMap = {
10
10
  [authorKey: string]: Author;
11
11
  };
12
12
  export declare function validateAuthorsMap(content: unknown): AuthorsMap;
@@ -14,7 +14,7 @@ export declare function getAuthorsMap(params: {
14
14
  authorsMapPath: string;
15
15
  contentPaths: BlogContentPaths;
16
16
  }): Promise<AuthorsMap | undefined>;
17
- declare type AuthorsParam = {
17
+ type AuthorsParam = {
18
18
  frontMatter: BlogPostFrontMatter;
19
19
  authorsMap: AuthorsMap | undefined;
20
20
  };
@@ -18,20 +18,21 @@ export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, b
18
18
  blogDescription: string;
19
19
  postsPerPageOption: number | 'ALL';
20
20
  }): BlogPaginated[];
21
+ export declare function shouldBeListed(blogPost: BlogPost): boolean;
21
22
  export declare function getBlogTags({ blogPosts, ...params }: {
22
23
  blogPosts: BlogPost[];
23
24
  blogTitle: string;
24
25
  blogDescription: string;
25
26
  postsPerPageOption: number | 'ALL';
26
27
  }): BlogTags;
27
- declare type ParsedBlogFileName = {
28
+ type ParsedBlogFileName = {
28
29
  date: Date | undefined;
29
30
  text: string;
30
31
  slug: string;
31
32
  };
32
33
  export declare function parseBlogFileName(blogSourceRelative: string): ParsedBlogFileName;
33
34
  export declare function generateBlogPosts(contentPaths: BlogContentPaths, context: LoadContext, options: PluginOptions): Promise<BlogPost[]>;
34
- export declare type LinkifyParams = {
35
+ export type LinkifyParams = {
35
36
  filePath: string;
36
37
  fileString: string;
37
38
  } & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
package/lib/blogUtils.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
9
+ exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.shouldBeListed = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
12
  const path_1 = tslib_1.__importDefault(require("path"));
@@ -55,18 +55,29 @@ function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription,
55
55
  return pages;
56
56
  }
57
57
  exports.paginateBlogPosts = paginateBlogPosts;
58
+ function shouldBeListed(blogPost) {
59
+ return !blogPost.metadata.unlisted;
60
+ }
61
+ exports.shouldBeListed = shouldBeListed;
58
62
  function getBlogTags({ blogPosts, ...params }) {
59
63
  const groups = (0, utils_1.groupTaggedItems)(blogPosts, (blogPost) => blogPost.metadata.tags);
60
- return lodash_1.default.mapValues(groups, ({ tag, items: tagBlogPosts }) => ({
61
- label: tag.label,
62
- items: tagBlogPosts.map((item) => item.id),
63
- permalink: tag.permalink,
64
- pages: paginateBlogPosts({
65
- blogPosts: tagBlogPosts,
66
- basePageUrl: tag.permalink,
67
- ...params,
68
- }),
69
- }));
64
+ return lodash_1.default.mapValues(groups, ({ tag, items: tagBlogPosts }) => {
65
+ const tagVisibility = (0, utils_1.getTagVisibility)({
66
+ items: tagBlogPosts,
67
+ isUnlisted: (item) => item.metadata.unlisted,
68
+ });
69
+ return {
70
+ label: tag.label,
71
+ items: tagVisibility.listedItems.map((item) => item.id),
72
+ permalink: tag.permalink,
73
+ pages: paginateBlogPosts({
74
+ blogPosts: tagVisibility.listedItems,
75
+ basePageUrl: tag.permalink,
76
+ ...params,
77
+ }),
78
+ unlisted: tagVisibility.unlisted,
79
+ };
80
+ });
70
81
  }
71
82
  exports.getBlogTags = getBlogTags;
72
83
  const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
@@ -125,7 +136,9 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
125
136
  const blogSourceAbsolute = path_1.default.join(blogDirPath, blogSourceRelative);
126
137
  const { frontMatter, content, contentTitle, excerpt } = await parseBlogPostMarkdownFile(blogSourceAbsolute);
127
138
  const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
128
- if (frontMatter.draft && process.env.NODE_ENV === 'production') {
139
+ const draft = (0, utils_1.isDraft)({ frontMatter });
140
+ const unlisted = (0, utils_1.isUnlisted)({ frontMatter });
141
+ if (draft) {
129
142
  return undefined;
130
143
  }
131
144
  if (frontMatter.id) {
@@ -213,6 +226,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
213
226
  hasTruncateMarker: truncateMarker.test(content),
214
227
  authors,
215
228
  frontMatter,
229
+ unlisted,
216
230
  },
217
231
  content,
218
232
  };
@@ -230,15 +244,15 @@ async function generateBlogPosts(contentPaths, context, options) {
230
244
  contentPaths,
231
245
  authorsMapPath: options.authorsMapPath,
232
246
  });
233
- const blogPosts = (await Promise.all(blogSourceFiles.map(async (blogSourceFile) => {
247
+ async function doProcessBlogSourceFile(blogSourceFile) {
234
248
  try {
235
249
  return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
236
250
  }
237
251
  catch (err) {
238
- logger_1.default.error `Processing of blog source file path=${blogSourceFile} failed.`;
239
- throw err;
252
+ throw new Error(`Processing of blog source file path=${blogSourceFile} failed.`, { cause: err });
240
253
  }
241
- }))).filter(Boolean);
254
+ }
255
+ const blogPosts = (await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))).filter(Boolean);
242
256
  blogPosts.sort((a, b) => b.metadata.date.getTime() - a.metadata.date.getTime());
243
257
  if (options.sortPosts === 'ascending') {
244
258
  return blogPosts.reverse();
package/lib/feed.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { DocusaurusConfig } from '@docusaurus/types';
8
8
  import type { PluginOptions, BlogPost } from '@docusaurus/plugin-content-blog';
9
- export declare function createBlogFeedFiles({ blogPosts, options, siteConfig, outDir, locale, }: {
9
+ export declare function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }: {
10
10
  blogPosts: BlogPost[];
11
11
  options: PluginOptions;
12
12
  siteConfig: DocusaurusConfig;
package/lib/feed.js CHANGED
@@ -93,7 +93,13 @@ async function createBlogFeedFile({ feed, feedType, generatePath, }) {
93
93
  throw err;
94
94
  }
95
95
  }
96
- async function createBlogFeedFiles({ blogPosts, options, siteConfig, outDir, locale, }) {
96
+ function shouldBeInFeed(blogPost) {
97
+ const excluded = blogPost.metadata.frontMatter.draft ||
98
+ blogPost.metadata.frontMatter.unlisted;
99
+ return !excluded;
100
+ }
101
+ async function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }) {
102
+ const blogPosts = allBlogPosts.filter(shouldBeInFeed);
97
103
  const feed = await generateBlogFeed({
98
104
  blogPosts,
99
105
  options,
@@ -23,7 +23,6 @@ const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
23
23
  title: utils_validation_1.JoiFrontMatter.string().allow(''),
24
24
  description: utils_validation_1.JoiFrontMatter.string().allow(''),
25
25
  tags: utils_validation_1.FrontMatterTagsSchema,
26
- draft: utils_validation_1.JoiFrontMatter.boolean(),
27
26
  date: utils_validation_1.JoiFrontMatter.date().raw(),
28
27
  // New multi-authors front matter:
29
28
  authors: utils_validation_1.JoiFrontMatter.alternatives()
@@ -53,9 +52,11 @@ const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
53
52
  keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
54
53
  hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
55
54
  ...utils_validation_1.FrontMatterTOCHeadingLevels,
56
- }).messages({
55
+ })
56
+ .messages({
57
57
  'deprecate.error': '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
58
- });
58
+ })
59
+ .concat(utils_validation_1.ContentVisibilitySchema);
59
60
  function validateBlogPostFrontMatter(frontMatter) {
60
61
  return (0, utils_validation_1.validateFrontMatter)(frontMatter, BlogFrontMatterSchema);
61
62
  }
package/lib/index.js CHANGED
@@ -15,6 +15,7 @@ const blogUtils_1 = require("./blogUtils");
15
15
  const footnoteIDFixer_1 = tslib_1.__importDefault(require("./remark/footnoteIDFixer"));
16
16
  const translations_1 = require("./translations");
17
17
  const feed_1 = require("./feed");
18
+ const props_1 = require("./props");
18
19
  async function pluginContentBlog(context, options) {
19
20
  const { siteDir, siteConfig, generatedFilesDir, localizationDir, i18n: { currentLocale }, } = context;
20
21
  const { onBrokenMarkdownLinks, baseUrl } = siteConfig;
@@ -50,6 +51,7 @@ async function pluginContentBlog(context, options) {
50
51
  const baseBlogUrl = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]);
51
52
  const blogTagsListPath = (0, utils_1.normalizeUrl)([baseBlogUrl, tagsBasePath]);
52
53
  const blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options);
54
+ const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
53
55
  if (!blogPosts.length) {
54
56
  return {
55
57
  blogSidebarTitle,
@@ -61,15 +63,17 @@ async function pluginContentBlog(context, options) {
61
63
  };
62
64
  }
63
65
  // Colocate next and prev metadata.
64
- blogPosts.forEach((blogPost, index) => {
65
- const prevItem = index > 0 ? blogPosts[index - 1] : null;
66
+ listedBlogPosts.forEach((blogPost, index) => {
67
+ const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
66
68
  if (prevItem) {
67
69
  blogPost.metadata.prevItem = {
68
70
  title: prevItem.metadata.title,
69
71
  permalink: prevItem.metadata.permalink,
70
72
  };
71
73
  }
72
- const nextItem = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
74
+ const nextItem = index < listedBlogPosts.length - 1
75
+ ? listedBlogPosts[index + 1]
76
+ : null;
73
77
  if (nextItem) {
74
78
  blogPost.metadata.nextItem = {
75
79
  title: nextItem.metadata.title,
@@ -78,7 +82,7 @@ async function pluginContentBlog(context, options) {
78
82
  }
79
83
  });
80
84
  const blogListPaginated = (0, blogUtils_1.paginateBlogPosts)({
81
- blogPosts,
85
+ blogPosts: listedBlogPosts,
82
86
  blogTitle,
83
87
  blogDescription,
84
88
  postsPerPageOption,
@@ -146,6 +150,7 @@ async function pluginContentBlog(context, options) {
146
150
  items: sidebarBlogPosts.map((blogPost) => ({
147
151
  title: blogPost.metadata.title,
148
152
  permalink: blogPost.metadata.permalink,
153
+ unlisted: blogPost.metadata.unlisted,
149
154
  })),
150
155
  }, null, 2));
151
156
  // Create routes for blog entries.
@@ -187,12 +192,7 @@ async function pluginContentBlog(context, options) {
187
192
  return;
188
193
  }
189
194
  async function createTagsListPage() {
190
- const tagsProp = Object.values(blogTags).map((tag) => ({
191
- label: tag.label,
192
- permalink: tag.permalink,
193
- count: tag.items.length,
194
- }));
195
- const tagsPropPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify(tagsProp, null, 2));
195
+ const tagsPropPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify((0, props_1.toTagsProp)({ blogTags }), null, 2));
196
196
  addRoute({
197
197
  path: blogTagsListPath,
198
198
  component: blogTagsListComponent,
@@ -206,13 +206,7 @@ async function pluginContentBlog(context, options) {
206
206
  async function createTagPostsListPage(tag) {
207
207
  await Promise.all(tag.pages.map(async (blogPaginated) => {
208
208
  const { metadata, items } = blogPaginated;
209
- const tagProp = {
210
- label: tag.label,
211
- permalink: tag.permalink,
212
- allTagsPath: blogTagsListPath,
213
- count: tag.items.length,
214
- };
215
- const tagPropPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify(tagProp, null, 2));
209
+ const tagPropPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify((0, props_1.toTagProp)({ tag, blogTagsListPath }), null, 2));
216
210
  const listMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}-list.json`, JSON.stringify(metadata, null, 2));
217
211
  addRoute({
218
212
  path: metadata.permalink,
@@ -233,7 +227,7 @@ async function pluginContentBlog(context, options) {
233
227
  translateContent({ content, translationFiles }) {
234
228
  return (0, translations_1.translateContent)(content, translationFiles);
235
229
  },
236
- configureWebpack(_config, isServer, { getJSLoader }, content) {
230
+ configureWebpack(_config, isServer, utils, content) {
237
231
  const { admonitions, rehypePlugins, remarkPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
238
232
  const markdownLoaderOptions = {
239
233
  siteDir,
@@ -262,7 +256,6 @@ async function pluginContentBlog(context, options) {
262
256
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
263
257
  .map(utils_1.addTrailingPathSeparator),
264
258
  use: [
265
- getJSLoader({ isServer }),
266
259
  {
267
260
  loader: require.resolve('@docusaurus/mdx-loader'),
268
261
  options: {
package/lib/options.js CHANGED
@@ -14,7 +14,7 @@ exports.DEFAULT_OPTIONS = {
14
14
  beforeDefaultRehypePlugins: [],
15
15
  beforeDefaultRemarkPlugins: [],
16
16
  admonitions: true,
17
- truncateMarker: /<!--\s*truncate\s*-->/,
17
+ truncateMarker: /<!--\s*truncate\s*-->|\{\/\*\s*truncate\s*\*\/\}/,
18
18
  rehypePlugins: [],
19
19
  remarkPlugins: [],
20
20
  showReadingTime: true,
@@ -44,10 +44,7 @@ const PluginOptionSchema = utils_validation_1.Joi.object({
44
44
  archiveBasePath: utils_validation_1.Joi.string()
45
45
  .default(exports.DEFAULT_OPTIONS.archiveBasePath)
46
46
  .allow(null),
47
- routeBasePath: utils_validation_1.Joi.string()
48
- // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
49
- // .allow('')
50
- .default(exports.DEFAULT_OPTIONS.routeBasePath),
47
+ routeBasePath: utils_validation_1.RouteBasePathSchema.default(exports.DEFAULT_OPTIONS.routeBasePath),
51
48
  tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
52
49
  include: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.include),
53
50
  exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
package/lib/props.d.ts ADDED
@@ -0,0 +1,15 @@
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
+ export declare function toTagsProp({ blogTags }: {
10
+ blogTags: BlogTags;
11
+ }): TagsListItem[];
12
+ export declare function toTagProp({ blogTagsListPath, tag, }: {
13
+ blogTagsListPath: string;
14
+ tag: BlogTag;
15
+ }): TagModule;
package/lib/props.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toTagProp = exports.toTagsProp = void 0;
4
+ function toTagsProp({ blogTags }) {
5
+ return Object.values(blogTags)
6
+ .filter((tag) => !tag.unlisted)
7
+ .map((tag) => ({
8
+ label: tag.label,
9
+ permalink: tag.permalink,
10
+ count: tag.items.length,
11
+ }));
12
+ }
13
+ exports.toTagsProp = toTagsProp;
14
+ function toTagProp({ blogTagsListPath, tag, }) {
15
+ return {
16
+ label: tag.label,
17
+ permalink: tag.permalink,
18
+ allTagsPath: blogTagsListPath,
19
+ count: tag.items.length,
20
+ unlisted: tag.unlisted,
21
+ };
22
+ }
23
+ exports.toTagProp = toTagProp;
package/lib/types.d.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils';
8
- export declare type BlogContentPaths = ContentPaths;
9
- export declare type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
10
- export declare type BlogMarkdownLoaderOptions = {
8
+ export type BlogContentPaths = ContentPaths;
9
+ export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
10
+ export type BlogMarkdownLoaderOptions = {
11
11
  siteDir: string;
12
12
  contentPaths: BlogContentPaths;
13
13
  truncateMarker: RegExp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "2.4.1",
3
+ "version": "3.0.0-alpha.0",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
@@ -18,29 +18,29 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "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",
21
+ "@docusaurus/core": "3.0.0-alpha.0",
22
+ "@docusaurus/logger": "3.0.0-alpha.0",
23
+ "@docusaurus/mdx-loader": "3.0.0-alpha.0",
24
+ "@docusaurus/types": "3.0.0-alpha.0",
25
+ "@docusaurus/utils": "3.0.0-alpha.0",
26
+ "@docusaurus/utils-common": "3.0.0-alpha.0",
27
+ "@docusaurus/utils-validation": "3.0.0-alpha.0",
28
28
  "cheerio": "^1.0.0-rc.12",
29
29
  "feed": "^4.2.2",
30
- "fs-extra": "^10.1.0",
30
+ "fs-extra": "^11.1.0",
31
31
  "lodash": "^4.17.21",
32
32
  "reading-time": "^1.5.0",
33
- "tslib": "^2.4.0",
33
+ "tslib": "^2.5.0",
34
34
  "unist-util-visit": "^2.0.3",
35
35
  "utility-types": "^3.10.0",
36
- "webpack": "^5.73.0"
36
+ "webpack": "^5.76.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "react": "^16.8.4 || ^17.0.0",
40
- "react-dom": "^16.8.4 || ^17.0.0"
39
+ "react": "^18.0.0",
40
+ "react-dom": "^18.0.0"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=16.14"
44
44
  },
45
- "gitHead": "60e657d8ae5a4a9ed1c2d777f9defd882cc12681"
45
+ "gitHead": "7327f7ff880ed97ad7855744e59c9c55d467a950"
46
46
  }
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
@@ -158,8 +158,15 @@ async function createBlogFeedFile({
158
158
  }
159
159
  }
160
160
 
161
+ function shouldBeInFeed(blogPost: BlogPost): boolean {
162
+ const excluded =
163
+ blogPost.metadata.frontMatter.draft ||
164
+ blogPost.metadata.frontMatter.unlisted;
165
+ return !excluded;
166
+ }
167
+
161
168
  export async function createBlogFeedFiles({
162
- blogPosts,
169
+ blogPosts: allBlogPosts,
163
170
  options,
164
171
  siteConfig,
165
172
  outDir,
@@ -171,6 +178,8 @@ export async function createBlogFeedFiles({
171
178
  outDir: string;
172
179
  locale: string;
173
180
  }): Promise<void> {
181
+ const blogPosts = allBlogPosts.filter(shouldBeInFeed);
182
+
174
183
  const feed = await generateBlogFeed({
175
184
  blogPosts,
176
185
  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';
@@ -25,7 +26,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
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),
@@ -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.
@@ -432,9 +440,15 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
432
440
  }
433
441
  >;
434
442
 
443
+ export type BlogSidebarItem = {
444
+ title: string;
445
+ permalink: string;
446
+ unlisted: boolean;
447
+ };
448
+
435
449
  export type BlogSidebar = {
436
450
  title: string;
437
- items: {title: string; permalink: string}[];
451
+ items: BlogSidebarItem[];
438
452
  };
439
453
 
440
454
  export type BlogContent = {
@@ -453,6 +467,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
453
467
  /** Blog post permalinks. */
454
468
  items: string[];
455
469
  pages: BlogPaginated[];
470
+ unlisted: boolean;
456
471
  };
457
472
 
458
473
  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