@docusaurus/plugin-content-blog 3.2.1 → 3.3.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
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { BlogContentPaths } from './types';
8
9
  import type { Author, BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
9
10
  export type AuthorsMap = {
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { LoadContext } from '@docusaurus/types';
8
9
  import type { PluginOptions, BlogPost, BlogTags, BlogPaginated } from '@docusaurus/plugin-content-blog';
9
10
  import type { BlogContentPaths, BlogMarkdownLoaderOptions } from './types';
@@ -4,5 +4,6 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../../src/plugin-content-blog.d.ts" />
7
8
  import type { BlogMetadata } from '@docusaurus/plugin-content-blog';
8
9
  export declare function useBlogMetadata(): BlogMetadata;
package/lib/feed.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { DocusaurusConfig } from '@docusaurus/types';
8
9
  import type { PluginOptions, BlogPost } from '@docusaurus/plugin-content-blog';
9
10
  export declare function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }: {
@@ -1,3 +1,4 @@
1
+ /// <reference path="../src/plugin-content-blog.d.ts" />
1
2
  import type { BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
2
3
  export declare function validateBlogPostFrontMatter(frontMatter: {
3
4
  [key: string]: unknown;
package/lib/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { LoadContext, Plugin } from '@docusaurus/types';
8
9
  import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
9
10
  export default function pluginContentBlog(context: LoadContext, options: PluginOptions): Promise<Plugin<BlogContent>>;
package/lib/index.js CHANGED
@@ -15,7 +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
+ const routes_1 = require("./routes");
19
19
  async function pluginContentBlog(context, options) {
20
20
  const { siteDir, siteConfig, generatedFilesDir, localizationDir, i18n: { currentLocale }, } = context;
21
21
  const { onBrokenMarkdownLinks, baseUrl } = siteConfig;
@@ -30,6 +30,9 @@ async function pluginContentBlog(context, options) {
30
30
  const pluginId = options.id ?? utils_1.DEFAULT_PLUGIN_ID;
31
31
  const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-blog');
32
32
  const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
33
+ // TODO Docusaurus v4 breaking change
34
+ // module aliasing should be automatic
35
+ // we should never find local absolute FS paths in the codegen registry
33
36
  const aliasedSource = (source) => `~blog/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
34
37
  const authorsMapFilePath = await (0, utils_1.getDataFilePath)({
35
38
  filePath: options.authorsMapPath,
@@ -107,143 +110,14 @@ async function pluginContentBlog(context, options) {
107
110
  blogTagsListPath,
108
111
  };
109
112
  },
110
- async contentLoaded({ content: blogContents, actions }) {
111
- const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, } = options;
112
- const { addRoute, createData } = actions;
113
- const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
114
- const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
115
- const blogItemsToMetadata = {};
116
- const sidebarBlogPosts = options.blogSidebarCount === 'ALL'
117
- ? blogPosts
118
- : blogPosts.slice(0, options.blogSidebarCount);
119
- function blogPostItemsModule(items) {
120
- return items.map((postId) => {
121
- const blogPostMetadata = blogItemsToMetadata[postId];
122
- return {
123
- content: {
124
- __import: true,
125
- path: blogPostMetadata.source,
126
- query: {
127
- truncated: true,
128
- },
129
- },
130
- };
131
- });
132
- }
133
- if (archiveBasePath && listedBlogPosts.length) {
134
- const archiveUrl = (0, utils_1.normalizeUrl)([
135
- baseUrl,
136
- routeBasePath,
137
- archiveBasePath,
138
- ]);
139
- // Create a blog archive route
140
- const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts: listedBlogPosts }, null, 2));
141
- addRoute({
142
- path: archiveUrl,
143
- component: blogArchiveComponent,
144
- exact: true,
145
- modules: {
146
- archive: aliasedSource(archiveProp),
147
- },
148
- });
149
- }
150
- // This prop is useful to provide the blog list sidebar
151
- const sidebarProp = await createData(
152
- // Note that this created data path must be in sync with
153
- // metadataPath provided to mdx-loader.
154
- `blog-post-list-prop-${pluginId}.json`, JSON.stringify({
155
- title: blogSidebarTitle,
156
- items: sidebarBlogPosts.map((blogPost) => ({
157
- title: blogPost.metadata.title,
158
- permalink: blogPost.metadata.permalink,
159
- unlisted: blogPost.metadata.unlisted,
160
- })),
161
- }, null, 2));
162
- const blogMetadata = {
163
- blogBasePath: (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]),
164
- blogTitle,
165
- };
166
- const blogMetadataPath = await createData(`blogMetadata-${pluginId}.json`, JSON.stringify(blogMetadata, null, 2));
167
- function createBlogPostRouteMetadata(blogPostMeta) {
168
- return {
169
- sourceFilePath: (0, utils_1.aliasedSitePathToRelativePath)(blogPostMeta.source),
170
- lastUpdatedAt: blogPostMeta.lastUpdatedAt,
171
- };
172
- }
173
- // Create routes for blog entries.
174
- await Promise.all(blogPosts.map(async (blogPost) => {
175
- const { id, metadata } = blogPost;
176
- await createData(
177
- // Note that this created data path must be in sync with
178
- // metadataPath provided to mdx-loader.
179
- `${(0, utils_1.docuHash)(metadata.source)}.json`, JSON.stringify(metadata, null, 2));
180
- addRoute({
181
- path: metadata.permalink,
182
- component: blogPostComponent,
183
- exact: true,
184
- modules: {
185
- sidebar: aliasedSource(sidebarProp),
186
- content: metadata.source,
187
- },
188
- metadata: createBlogPostRouteMetadata(metadata),
189
- context: {
190
- blogMetadata: aliasedSource(blogMetadataPath),
191
- },
192
- });
193
- blogItemsToMetadata[id] = metadata;
194
- }));
195
- // Create routes for blog's paginated list entries.
196
- await Promise.all(blogListPaginated.map(async (listPage) => {
197
- const { metadata, items } = listPage;
198
- const { permalink } = metadata;
199
- const pageMetadataPath = await createData(`${(0, utils_1.docuHash)(permalink)}.json`, JSON.stringify(metadata, null, 2));
200
- addRoute({
201
- path: permalink,
202
- component: blogListComponent,
203
- exact: true,
204
- modules: {
205
- sidebar: aliasedSource(sidebarProp),
206
- items: blogPostItemsModule(items),
207
- metadata: aliasedSource(pageMetadataPath),
208
- },
209
- });
210
- }));
211
- // Tags. This is the last part so we early-return if there are no tags.
212
- if (Object.keys(blogTags).length === 0) {
213
- return;
214
- }
215
- async function createTagsListPage() {
216
- const tagsPropPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify((0, props_1.toTagsProp)({ blogTags }), null, 2));
217
- addRoute({
218
- path: blogTagsListPath,
219
- component: blogTagsListComponent,
220
- exact: true,
221
- modules: {
222
- sidebar: aliasedSource(sidebarProp),
223
- tags: aliasedSource(tagsPropPath),
224
- },
225
- });
226
- }
227
- async function createTagPostsListPage(tag) {
228
- await Promise.all(tag.pages.map(async (blogPaginated) => {
229
- const { metadata, items } = blogPaginated;
230
- const tagPropPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify((0, props_1.toTagProp)({ tag, blogTagsListPath }), null, 2));
231
- const listMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}-list.json`, JSON.stringify(metadata, null, 2));
232
- addRoute({
233
- path: metadata.permalink,
234
- component: blogTagsPostsComponent,
235
- exact: true,
236
- modules: {
237
- sidebar: aliasedSource(sidebarProp),
238
- items: blogPostItemsModule(items),
239
- tag: aliasedSource(tagPropPath),
240
- listMetadata: aliasedSource(listMetadataPath),
241
- },
242
- });
243
- }));
244
- }
245
- await createTagsListPage();
246
- await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
113
+ async contentLoaded({ content, actions }) {
114
+ await (0, routes_1.createAllRoutes)({
115
+ baseUrl,
116
+ content,
117
+ actions,
118
+ options,
119
+ aliasedSource,
120
+ });
247
121
  },
248
122
  translateContent({ content, translationFiles }) {
249
123
  return (0, translations_1.translateContent)(content, translationFiles);
package/lib/options.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { PluginOptions, Options } from '@docusaurus/plugin-content-blog';
8
9
  import type { OptionValidationContext } from '@docusaurus/types';
9
10
  export declare const DEFAULT_OPTIONS: PluginOptions;
package/lib/props.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference path="../src/plugin-content-blog.d.ts" />
1
2
  /**
2
3
  * Copyright (c) Facebook, Inc. and its affiliates.
3
4
  *
@@ -0,0 +1,19 @@
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
+ /// <reference path="../src/plugin-content-blog.d.ts" />
8
+ import type { PluginContentLoadedActions, RouteConfig } from '@docusaurus/types';
9
+ import type { BlogContent, PluginOptions } from '@docusaurus/plugin-content-blog';
10
+ type CreateAllRoutesParam = {
11
+ baseUrl: string;
12
+ content: BlogContent;
13
+ options: PluginOptions;
14
+ actions: PluginContentLoadedActions;
15
+ aliasedSource: (str: string) => string;
16
+ };
17
+ export declare function createAllRoutes(param: CreateAllRoutesParam): Promise<void>;
18
+ export declare function buildAllRoutes({ baseUrl, content, actions, options, aliasedSource, }: CreateAllRoutesParam): Promise<RouteConfig[]>;
19
+ export {};
package/lib/routes.js ADDED
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.buildAllRoutes = exports.createAllRoutes = void 0;
10
+ const tslib_1 = require("tslib");
11
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
12
+ const utils_1 = require("@docusaurus/utils");
13
+ const blogUtils_1 = require("./blogUtils");
14
+ const props_1 = require("./props");
15
+ async function createAllRoutes(param) {
16
+ const routes = await buildAllRoutes(param);
17
+ routes.forEach(param.actions.addRoute);
18
+ }
19
+ exports.createAllRoutes = createAllRoutes;
20
+ async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSource, }) {
21
+ const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, } = options;
22
+ const pluginId = options.id;
23
+ const { createData } = actions;
24
+ const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = content;
25
+ const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
26
+ const blogPostsById = lodash_1.default.keyBy(blogPosts, (post) => post.id);
27
+ function getBlogPostById(id) {
28
+ const blogPost = blogPostsById[id];
29
+ if (!blogPost) {
30
+ throw new Error(`unexpected, can't find blog post id=${id}`);
31
+ }
32
+ return blogPost;
33
+ }
34
+ const sidebarBlogPosts = options.blogSidebarCount === 'ALL'
35
+ ? blogPosts
36
+ : blogPosts.slice(0, options.blogSidebarCount);
37
+ async function createSidebarModule() {
38
+ const sidebar = {
39
+ title: blogSidebarTitle,
40
+ items: sidebarBlogPosts.map((blogPost) => ({
41
+ title: blogPost.metadata.title,
42
+ permalink: blogPost.metadata.permalink,
43
+ unlisted: blogPost.metadata.unlisted,
44
+ })),
45
+ };
46
+ const modulePath = await createData(`blog-post-list-prop-${pluginId}.json`, sidebar);
47
+ return aliasedSource(modulePath);
48
+ }
49
+ async function createBlogMetadataModule() {
50
+ const blogMetadata = {
51
+ blogBasePath: (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]),
52
+ blogTitle,
53
+ };
54
+ const modulePath = await createData(`blogMetadata-${pluginId}.json`, blogMetadata);
55
+ return aliasedSource(modulePath);
56
+ }
57
+ // TODO we should have a parent blog route,
58
+ // and inject blog metadata + sidebar as a parent context
59
+ // unfortunately we can't have a parent route for blog yet
60
+ // because if both blog/docs are using routeBasePath /,
61
+ // React router config rendering doesn't support that well
62
+ const sidebarModulePath = await createSidebarModule();
63
+ const blogMetadataModulePath = await createBlogMetadataModule();
64
+ function blogPostItemsModule(ids) {
65
+ return ids.map((id) => {
66
+ return {
67
+ content: {
68
+ __import: true,
69
+ path: getBlogPostById(id).metadata.source,
70
+ query: {
71
+ truncated: true,
72
+ },
73
+ },
74
+ };
75
+ });
76
+ }
77
+ function createArchiveRoute() {
78
+ if (archiveBasePath && listedBlogPosts.length) {
79
+ return [
80
+ {
81
+ path: (0, utils_1.normalizeUrl)([baseUrl, routeBasePath, archiveBasePath]),
82
+ component: blogArchiveComponent,
83
+ exact: true,
84
+ props: {
85
+ archive: { blogPosts: listedBlogPosts },
86
+ },
87
+ },
88
+ ];
89
+ }
90
+ return [];
91
+ }
92
+ function createBlogPostRouteMetadata(blogPostMeta) {
93
+ return {
94
+ sourceFilePath: (0, utils_1.aliasedSitePathToRelativePath)(blogPostMeta.source),
95
+ lastUpdatedAt: blogPostMeta.lastUpdatedAt,
96
+ };
97
+ }
98
+ await Promise.all(blogPosts.map(async (blogPost) => {
99
+ const { metadata } = blogPost;
100
+ await createData(
101
+ // Note that this created data path must be in sync with
102
+ // metadataPath provided to mdx-loader.
103
+ `${(0, utils_1.docuHash)(metadata.source)}.json`, metadata);
104
+ }));
105
+ function createBlogPostRoute(blogPost) {
106
+ return {
107
+ path: blogPost.metadata.permalink,
108
+ component: blogPostComponent,
109
+ exact: true,
110
+ modules: {
111
+ sidebar: sidebarModulePath,
112
+ content: blogPost.metadata.source,
113
+ },
114
+ metadata: createBlogPostRouteMetadata(blogPost.metadata),
115
+ context: {
116
+ blogMetadata: blogMetadataModulePath,
117
+ },
118
+ };
119
+ }
120
+ function createBlogPostRoutes() {
121
+ return blogPosts.map(createBlogPostRoute);
122
+ }
123
+ function createBlogPostsPaginatedRoutes() {
124
+ return blogListPaginated.map((paginated) => {
125
+ return {
126
+ path: paginated.metadata.permalink,
127
+ component: blogListComponent,
128
+ exact: true,
129
+ modules: {
130
+ sidebar: sidebarModulePath,
131
+ items: blogPostItemsModule(paginated.items),
132
+ },
133
+ props: {
134
+ metadata: paginated.metadata,
135
+ },
136
+ };
137
+ });
138
+ }
139
+ function createTagsRoutes() {
140
+ // Tags. This is the last part so we early-return if there are no tags.
141
+ if (Object.keys(blogTags).length === 0) {
142
+ return [];
143
+ }
144
+ const tagsListRoute = {
145
+ path: blogTagsListPath,
146
+ component: blogTagsListComponent,
147
+ exact: true,
148
+ modules: {
149
+ sidebar: sidebarModulePath,
150
+ },
151
+ props: {
152
+ tags: (0, props_1.toTagsProp)({ blogTags }),
153
+ },
154
+ };
155
+ function createTagPaginatedRoutes(tag) {
156
+ return tag.pages.map((paginated) => {
157
+ return {
158
+ path: paginated.metadata.permalink,
159
+ component: blogTagsPostsComponent,
160
+ exact: true,
161
+ modules: {
162
+ sidebar: sidebarModulePath,
163
+ items: blogPostItemsModule(paginated.items),
164
+ },
165
+ props: {
166
+ tag: (0, props_1.toTagProp)({ tag, blogTagsListPath }),
167
+ listMetadata: paginated.metadata,
168
+ },
169
+ };
170
+ });
171
+ }
172
+ const tagsPaginatedRoutes = Object.values(blogTags).flatMap(createTagPaginatedRoutes);
173
+ return [tagsListRoute, ...tagsPaginatedRoutes];
174
+ }
175
+ return [
176
+ ...createBlogPostRoutes(),
177
+ ...createBlogPostsPaginatedRoutes(),
178
+ ...createTagsRoutes(),
179
+ ...createArchiveRoute(),
180
+ ];
181
+ }
182
+ exports.buildAllRoutes = buildAllRoutes;
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-blog.d.ts" />
7
8
  import type { TranslationFile } from '@docusaurus/types';
8
9
  import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
9
10
  export declare function getTranslationFiles(options: PluginOptions): TranslationFile[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
@@ -31,13 +31,13 @@
31
31
  },
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@docusaurus/core": "3.2.1",
35
- "@docusaurus/logger": "3.2.1",
36
- "@docusaurus/mdx-loader": "3.2.1",
37
- "@docusaurus/types": "3.2.1",
38
- "@docusaurus/utils": "3.2.1",
39
- "@docusaurus/utils-common": "3.2.1",
40
- "@docusaurus/utils-validation": "3.2.1",
34
+ "@docusaurus/core": "3.3.0",
35
+ "@docusaurus/logger": "3.3.0",
36
+ "@docusaurus/mdx-loader": "3.3.0",
37
+ "@docusaurus/types": "3.3.0",
38
+ "@docusaurus/utils": "3.3.0",
39
+ "@docusaurus/utils-common": "3.3.0",
40
+ "@docusaurus/utils-validation": "3.3.0",
41
41
  "cheerio": "^1.0.0-rc.12",
42
42
  "feed": "^4.2.2",
43
43
  "fs-extra": "^11.1.1",
@@ -59,5 +59,5 @@
59
59
  "devDependencies": {
60
60
  "@total-typescript/shoehorn": "^0.1.2"
61
61
  },
62
- "gitHead": "f268e15264e208e6faf26117258162e988b53773"
62
+ "gitHead": "2ec4e078b5ca0c57f2cc04f2fe564d524bb5e858"
63
63
  }
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ import {
11
11
  normalizeUrl,
12
12
  docuHash,
13
13
  aliasedSitePath,
14
- aliasedSitePathToRelativePath,
15
14
  getPluginI18nPath,
16
15
  posixPath,
17
16
  addTrailingPathSeparator,
@@ -32,24 +31,17 @@ import footnoteIDFixer from './remark/footnoteIDFixer';
32
31
  import {translateContent, getTranslationFiles} from './translations';
33
32
  import {createBlogFeedFiles} from './feed';
34
33
 
35
- import {toTagProp, toTagsProp} from './props';
34
+ import {createAllRoutes} from './routes';
36
35
  import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
37
- import type {
38
- LoadContext,
39
- Plugin,
40
- HtmlTags,
41
- RouteMetadata,
42
- } from '@docusaurus/types';
36
+ import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
43
37
  import type {
44
38
  PluginOptions,
45
39
  BlogPostFrontMatter,
46
40
  BlogPostMetadata,
47
41
  Assets,
48
- BlogTag,
49
42
  BlogTags,
50
43
  BlogContent,
51
44
  BlogPaginated,
52
- BlogMetadata,
53
45
  } from '@docusaurus/plugin-content-blog';
54
46
 
55
47
  export default async function pluginContentBlog(
@@ -80,6 +72,9 @@ export default async function pluginContentBlog(
80
72
  'docusaurus-plugin-content-blog',
81
73
  );
82
74
  const dataDir = path.join(pluginDataDirRoot, pluginId);
75
+ // TODO Docusaurus v4 breaking change
76
+ // module aliasing should be automatic
77
+ // we should never find local absolute FS paths in the codegen registry
83
78
  const aliasedSource = (source: string) =>
84
79
  `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
85
80
 
@@ -185,213 +180,14 @@ export default async function pluginContentBlog(
185
180
  };
186
181
  },
187
182
 
188
- async contentLoaded({content: blogContents, actions}) {
189
- const {
190
- blogListComponent,
191
- blogPostComponent,
192
- blogTagsListComponent,
193
- blogTagsPostsComponent,
194
- blogArchiveComponent,
195
- routeBasePath,
196
- archiveBasePath,
197
- blogTitle,
198
- } = options;
199
-
200
- const {addRoute, createData} = actions;
201
- const {
202
- blogSidebarTitle,
203
- blogPosts,
204
- blogListPaginated,
205
- blogTags,
206
- blogTagsListPath,
207
- } = blogContents;
208
-
209
- const listedBlogPosts = blogPosts.filter(shouldBeListed);
210
-
211
- const blogItemsToMetadata: {[postId: string]: BlogPostMetadata} = {};
212
-
213
- const sidebarBlogPosts =
214
- options.blogSidebarCount === 'ALL'
215
- ? blogPosts
216
- : blogPosts.slice(0, options.blogSidebarCount);
217
-
218
- function blogPostItemsModule(items: string[]) {
219
- return items.map((postId) => {
220
- const blogPostMetadata = blogItemsToMetadata[postId]!;
221
- return {
222
- content: {
223
- __import: true,
224
- path: blogPostMetadata.source,
225
- query: {
226
- truncated: true,
227
- },
228
- },
229
- };
230
- });
231
- }
232
-
233
- if (archiveBasePath && listedBlogPosts.length) {
234
- const archiveUrl = normalizeUrl([
235
- baseUrl,
236
- routeBasePath,
237
- archiveBasePath,
238
- ]);
239
- // Create a blog archive route
240
- const archiveProp = await createData(
241
- `${docuHash(archiveUrl)}.json`,
242
- JSON.stringify({blogPosts: listedBlogPosts}, null, 2),
243
- );
244
- addRoute({
245
- path: archiveUrl,
246
- component: blogArchiveComponent,
247
- exact: true,
248
- modules: {
249
- archive: aliasedSource(archiveProp),
250
- },
251
- });
252
- }
253
-
254
- // This prop is useful to provide the blog list sidebar
255
- const sidebarProp = await createData(
256
- // Note that this created data path must be in sync with
257
- // metadataPath provided to mdx-loader.
258
- `blog-post-list-prop-${pluginId}.json`,
259
- JSON.stringify(
260
- {
261
- title: blogSidebarTitle,
262
- items: sidebarBlogPosts.map((blogPost) => ({
263
- title: blogPost.metadata.title,
264
- permalink: blogPost.metadata.permalink,
265
- unlisted: blogPost.metadata.unlisted,
266
- })),
267
- },
268
- null,
269
- 2,
270
- ),
271
- );
272
-
273
- const blogMetadata: BlogMetadata = {
274
- blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
275
- blogTitle,
276
- };
277
- const blogMetadataPath = await createData(
278
- `blogMetadata-${pluginId}.json`,
279
- JSON.stringify(blogMetadata, null, 2),
280
- );
281
-
282
- function createBlogPostRouteMetadata(
283
- blogPostMeta: BlogPostMetadata,
284
- ): RouteMetadata {
285
- return {
286
- sourceFilePath: aliasedSitePathToRelativePath(blogPostMeta.source),
287
- lastUpdatedAt: blogPostMeta.lastUpdatedAt,
288
- };
289
- }
290
-
291
- // Create routes for blog entries.
292
- await Promise.all(
293
- blogPosts.map(async (blogPost) => {
294
- const {id, metadata} = blogPost;
295
- await createData(
296
- // Note that this created data path must be in sync with
297
- // metadataPath provided to mdx-loader.
298
- `${docuHash(metadata.source)}.json`,
299
- JSON.stringify(metadata, null, 2),
300
- );
301
-
302
- addRoute({
303
- path: metadata.permalink,
304
- component: blogPostComponent,
305
- exact: true,
306
- modules: {
307
- sidebar: aliasedSource(sidebarProp),
308
- content: metadata.source,
309
- },
310
- metadata: createBlogPostRouteMetadata(metadata),
311
- context: {
312
- blogMetadata: aliasedSource(blogMetadataPath),
313
- },
314
- });
315
-
316
- blogItemsToMetadata[id] = metadata;
317
- }),
318
- );
319
-
320
- // Create routes for blog's paginated list entries.
321
- await Promise.all(
322
- blogListPaginated.map(async (listPage) => {
323
- const {metadata, items} = listPage;
324
- const {permalink} = metadata;
325
- const pageMetadataPath = await createData(
326
- `${docuHash(permalink)}.json`,
327
- JSON.stringify(metadata, null, 2),
328
- );
329
-
330
- addRoute({
331
- path: permalink,
332
- component: blogListComponent,
333
- exact: true,
334
- modules: {
335
- sidebar: aliasedSource(sidebarProp),
336
- items: blogPostItemsModule(items),
337
- metadata: aliasedSource(pageMetadataPath),
338
- },
339
- });
340
- }),
341
- );
342
-
343
- // Tags. This is the last part so we early-return if there are no tags.
344
- if (Object.keys(blogTags).length === 0) {
345
- return;
346
- }
347
-
348
- async function createTagsListPage() {
349
- const tagsPropPath = await createData(
350
- `${docuHash(`${blogTagsListPath}-tags`)}.json`,
351
- JSON.stringify(toTagsProp({blogTags}), null, 2),
352
- );
353
- addRoute({
354
- path: blogTagsListPath,
355
- component: blogTagsListComponent,
356
- exact: true,
357
- modules: {
358
- sidebar: aliasedSource(sidebarProp),
359
- tags: aliasedSource(tagsPropPath),
360
- },
361
- });
362
- }
363
-
364
- async function createTagPostsListPage(tag: BlogTag): Promise<void> {
365
- await Promise.all(
366
- tag.pages.map(async (blogPaginated) => {
367
- const {metadata, items} = blogPaginated;
368
- const tagPropPath = await createData(
369
- `${docuHash(metadata.permalink)}.json`,
370
- JSON.stringify(toTagProp({tag, blogTagsListPath}), null, 2),
371
- );
372
-
373
- const listMetadataPath = await createData(
374
- `${docuHash(metadata.permalink)}-list.json`,
375
- JSON.stringify(metadata, null, 2),
376
- );
377
-
378
- addRoute({
379
- path: metadata.permalink,
380
- component: blogTagsPostsComponent,
381
- exact: true,
382
- modules: {
383
- sidebar: aliasedSource(sidebarProp),
384
- items: blogPostItemsModule(items),
385
- tag: aliasedSource(tagPropPath),
386
- listMetadata: aliasedSource(listMetadataPath),
387
- },
388
- });
389
- }),
390
- );
391
- }
392
-
393
- await createTagsListPage();
394
- await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
183
+ async contentLoaded({content, actions}) {
184
+ await createAllRoutes({
185
+ baseUrl,
186
+ content,
187
+ actions,
188
+ options,
189
+ aliasedSource,
190
+ });
395
191
  },
396
192
 
397
193
  translateContent({content, translationFiles}) {
package/src/routes.ts ADDED
@@ -0,0 +1,263 @@
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 _ from 'lodash';
9
+ import {
10
+ normalizeUrl,
11
+ docuHash,
12
+ aliasedSitePathToRelativePath,
13
+ } from '@docusaurus/utils';
14
+ import {shouldBeListed} from './blogUtils';
15
+
16
+ import {toTagProp, toTagsProp} from './props';
17
+ import type {
18
+ PluginContentLoadedActions,
19
+ RouteConfig,
20
+ RouteMetadata,
21
+ } from '@docusaurus/types';
22
+ import type {
23
+ BlogPostMetadata,
24
+ BlogTag,
25
+ BlogMetadata,
26
+ BlogContent,
27
+ PluginOptions,
28
+ BlogPost,
29
+ BlogSidebar,
30
+ } from '@docusaurus/plugin-content-blog';
31
+
32
+ type CreateAllRoutesParam = {
33
+ baseUrl: string;
34
+ content: BlogContent;
35
+ options: PluginOptions;
36
+ actions: PluginContentLoadedActions;
37
+ aliasedSource: (str: string) => string;
38
+ };
39
+
40
+ export async function createAllRoutes(
41
+ param: CreateAllRoutesParam,
42
+ ): Promise<void> {
43
+ const routes = await buildAllRoutes(param);
44
+ routes.forEach(param.actions.addRoute);
45
+ }
46
+
47
+ export async function buildAllRoutes({
48
+ baseUrl,
49
+ content,
50
+ actions,
51
+ options,
52
+ aliasedSource,
53
+ }: CreateAllRoutesParam): Promise<RouteConfig[]> {
54
+ const {
55
+ blogListComponent,
56
+ blogPostComponent,
57
+ blogTagsListComponent,
58
+ blogTagsPostsComponent,
59
+ blogArchiveComponent,
60
+ routeBasePath,
61
+ archiveBasePath,
62
+ blogTitle,
63
+ } = options;
64
+ const pluginId = options.id!;
65
+ const {createData} = actions;
66
+ const {
67
+ blogSidebarTitle,
68
+ blogPosts,
69
+ blogListPaginated,
70
+ blogTags,
71
+ blogTagsListPath,
72
+ } = content;
73
+
74
+ const listedBlogPosts = blogPosts.filter(shouldBeListed);
75
+
76
+ const blogPostsById = _.keyBy(blogPosts, (post) => post.id);
77
+ function getBlogPostById(id: string): BlogPost {
78
+ const blogPost = blogPostsById[id];
79
+ if (!blogPost) {
80
+ throw new Error(`unexpected, can't find blog post id=${id}`);
81
+ }
82
+ return blogPost;
83
+ }
84
+
85
+ const sidebarBlogPosts =
86
+ options.blogSidebarCount === 'ALL'
87
+ ? blogPosts
88
+ : blogPosts.slice(0, options.blogSidebarCount);
89
+
90
+ async function createSidebarModule() {
91
+ const sidebar: BlogSidebar = {
92
+ title: blogSidebarTitle,
93
+ items: sidebarBlogPosts.map((blogPost) => ({
94
+ title: blogPost.metadata.title,
95
+ permalink: blogPost.metadata.permalink,
96
+ unlisted: blogPost.metadata.unlisted,
97
+ })),
98
+ };
99
+ const modulePath = await createData(
100
+ `blog-post-list-prop-${pluginId}.json`,
101
+ sidebar,
102
+ );
103
+ return aliasedSource(modulePath);
104
+ }
105
+
106
+ async function createBlogMetadataModule() {
107
+ const blogMetadata: BlogMetadata = {
108
+ blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
109
+ blogTitle,
110
+ };
111
+ const modulePath = await createData(
112
+ `blogMetadata-${pluginId}.json`,
113
+ blogMetadata,
114
+ );
115
+ return aliasedSource(modulePath);
116
+ }
117
+
118
+ // TODO we should have a parent blog route,
119
+ // and inject blog metadata + sidebar as a parent context
120
+ // unfortunately we can't have a parent route for blog yet
121
+ // because if both blog/docs are using routeBasePath /,
122
+ // React router config rendering doesn't support that well
123
+ const sidebarModulePath = await createSidebarModule();
124
+ const blogMetadataModulePath = await createBlogMetadataModule();
125
+
126
+ function blogPostItemsModule(ids: string[]) {
127
+ return ids.map((id) => {
128
+ return {
129
+ content: {
130
+ __import: true,
131
+ path: getBlogPostById(id).metadata.source,
132
+ query: {
133
+ truncated: true,
134
+ },
135
+ },
136
+ };
137
+ });
138
+ }
139
+
140
+ function createArchiveRoute(): RouteConfig[] {
141
+ if (archiveBasePath && listedBlogPosts.length) {
142
+ return [
143
+ {
144
+ path: normalizeUrl([baseUrl, routeBasePath, archiveBasePath]),
145
+ component: blogArchiveComponent,
146
+ exact: true,
147
+ props: {
148
+ archive: {blogPosts: listedBlogPosts},
149
+ },
150
+ },
151
+ ];
152
+ }
153
+ return [];
154
+ }
155
+
156
+ function createBlogPostRouteMetadata(
157
+ blogPostMeta: BlogPostMetadata,
158
+ ): RouteMetadata {
159
+ return {
160
+ sourceFilePath: aliasedSitePathToRelativePath(blogPostMeta.source),
161
+ lastUpdatedAt: blogPostMeta.lastUpdatedAt,
162
+ };
163
+ }
164
+
165
+ await Promise.all(
166
+ blogPosts.map(async (blogPost) => {
167
+ const {metadata} = blogPost;
168
+ await createData(
169
+ // Note that this created data path must be in sync with
170
+ // metadataPath provided to mdx-loader.
171
+ `${docuHash(metadata.source)}.json`,
172
+ metadata,
173
+ );
174
+ }),
175
+ );
176
+
177
+ function createBlogPostRoute(blogPost: BlogPost): RouteConfig {
178
+ return {
179
+ path: blogPost.metadata.permalink,
180
+ component: blogPostComponent,
181
+ exact: true,
182
+ modules: {
183
+ sidebar: sidebarModulePath,
184
+ content: blogPost.metadata.source,
185
+ },
186
+ metadata: createBlogPostRouteMetadata(blogPost.metadata),
187
+ context: {
188
+ blogMetadata: blogMetadataModulePath,
189
+ },
190
+ };
191
+ }
192
+
193
+ function createBlogPostRoutes(): RouteConfig[] {
194
+ return blogPosts.map(createBlogPostRoute);
195
+ }
196
+
197
+ function createBlogPostsPaginatedRoutes(): RouteConfig[] {
198
+ return blogListPaginated.map((paginated) => {
199
+ return {
200
+ path: paginated.metadata.permalink,
201
+ component: blogListComponent,
202
+ exact: true,
203
+ modules: {
204
+ sidebar: sidebarModulePath,
205
+ items: blogPostItemsModule(paginated.items),
206
+ },
207
+ props: {
208
+ metadata: paginated.metadata,
209
+ },
210
+ };
211
+ });
212
+ }
213
+
214
+ function createTagsRoutes(): RouteConfig[] {
215
+ // Tags. This is the last part so we early-return if there are no tags.
216
+ if (Object.keys(blogTags).length === 0) {
217
+ return [];
218
+ }
219
+
220
+ const tagsListRoute: RouteConfig = {
221
+ path: blogTagsListPath,
222
+ component: blogTagsListComponent,
223
+ exact: true,
224
+ modules: {
225
+ sidebar: sidebarModulePath,
226
+ },
227
+ props: {
228
+ tags: toTagsProp({blogTags}),
229
+ },
230
+ };
231
+
232
+ function createTagPaginatedRoutes(tag: BlogTag): RouteConfig[] {
233
+ return tag.pages.map((paginated) => {
234
+ return {
235
+ path: paginated.metadata.permalink,
236
+ component: blogTagsPostsComponent,
237
+ exact: true,
238
+ modules: {
239
+ sidebar: sidebarModulePath,
240
+ items: blogPostItemsModule(paginated.items),
241
+ },
242
+ props: {
243
+ tag: toTagProp({tag, blogTagsListPath}),
244
+ listMetadata: paginated.metadata,
245
+ },
246
+ };
247
+ });
248
+ }
249
+
250
+ const tagsPaginatedRoutes: RouteConfig[] = Object.values(blogTags).flatMap(
251
+ createTagPaginatedRoutes,
252
+ );
253
+
254
+ return [tagsListRoute, ...tagsPaginatedRoutes];
255
+ }
256
+
257
+ return [
258
+ ...createBlogPostRoutes(),
259
+ ...createBlogPostsPaginatedRoutes(),
260
+ ...createTagsRoutes(),
261
+ ...createArchiveRoute(),
262
+ ];
263
+ }