@docusaurus/plugin-content-blog 3.5.2 → 3.6.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
@@ -10,6 +10,10 @@ type AuthorsParam = {
10
10
  authorsMap: AuthorsMap | undefined;
11
11
  baseUrl: string;
12
12
  };
13
+ export declare function normalizeImageUrl({ imageURL, baseUrl, }: {
14
+ imageURL: string | undefined;
15
+ baseUrl: string;
16
+ }): string | undefined;
13
17
  export declare function getBlogPostAuthors(params: AuthorsParam): Author[];
14
18
  /**
15
19
  * Group blog posts by author key
package/lib/authors.js CHANGED
@@ -6,16 +6,29 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.normalizeImageUrl = normalizeImageUrl;
9
10
  exports.getBlogPostAuthors = getBlogPostAuthors;
10
11
  exports.groupBlogPostsByAuthorKey = groupBlogPostsByAuthorKey;
11
12
  const tslib_1 = require("tslib");
12
13
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
13
14
  const utils_1 = require("@docusaurus/utils");
15
+ const authorsSocials_1 = require("./authorsSocials");
14
16
  function normalizeImageUrl({ imageURL, baseUrl, }) {
15
17
  return imageURL?.startsWith('/')
16
18
  ? (0, utils_1.normalizeUrl)([baseUrl, imageURL])
17
19
  : imageURL;
18
20
  }
21
+ function normalizeAuthorUrl({ author, baseUrl, }) {
22
+ if (author.key) {
23
+ // Ensures invariant: global authors should have already been normalized
24
+ if (author.imageURL?.startsWith('/') &&
25
+ !author.imageURL.startsWith(baseUrl)) {
26
+ throw new Error(`Docusaurus internal bug: global authors image ${author.imageURL} should start with the expected baseUrl=${baseUrl}`);
27
+ }
28
+ return author.imageURL;
29
+ }
30
+ return normalizeImageUrl({ imageURL: author.imageURL, baseUrl });
31
+ }
19
32
  // Legacy v1/early-v2 front matter fields
20
33
  // We may want to deprecate those in favor of using only frontMatter.authors
21
34
  // TODO Docusaurus v4: remove this legacy front matter
@@ -54,7 +67,10 @@ function getFrontMatterAuthors(params) {
54
67
  // becoming a name and may end up unnoticed
55
68
  return { key: authorInput };
56
69
  }
57
- return authorInput;
70
+ return {
71
+ ...authorInput,
72
+ socials: (0, authorsSocials_1.normalizeSocials)(authorInput.socials ?? {}),
73
+ };
58
74
  }
59
75
  return Array.isArray(frontMatter.authors)
60
76
  ? frontMatter.authors.map(normalizeAuthor)
@@ -88,7 +104,8 @@ ${Object.keys(authorsMap)
88
104
  ...author,
89
105
  key: author.key ?? null,
90
106
  page: author.page ?? null,
91
- imageURL: normalizeImageUrl({ imageURL: author.imageURL, baseUrl }),
107
+ // global author images have already been normalized
108
+ imageURL: normalizeAuthorUrl({ author, baseUrl }),
92
109
  };
93
110
  }
94
111
  }
@@ -18,6 +18,7 @@ export declare function getAuthorsMap(params: {
18
18
  authorsMapPath: string;
19
19
  authorsBaseRoutePath: string;
20
20
  contentPaths: BlogContentPaths;
21
+ baseUrl: string;
21
22
  }): Promise<AuthorsMap | undefined>;
22
23
  export declare function validateAuthorsMap(content: unknown): AuthorsMapInput;
23
24
  export {};
package/lib/authorsMap.js CHANGED
@@ -15,6 +15,7 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
15
15
  const utils_1 = require("@docusaurus/utils");
16
16
  const utils_validation_1 = require("@docusaurus/utils-validation");
17
17
  const authorsSocials_1 = require("./authorsSocials");
18
+ const authors_1 = require("./authors");
18
19
  const AuthorPageSchema = utils_validation_1.Joi.object({
19
20
  permalink: utils_validation_1.Joi.string().required(),
20
21
  });
@@ -63,7 +64,7 @@ function checkAuthorsMapPermalinkCollisions(authorsMap) {
63
64
  throw new Error(`The following permalinks are duplicated:\n${errorMessage}`);
64
65
  }
65
66
  }
66
- function normalizeAuthor({ authorsBaseRoutePath, authorKey, author, }) {
67
+ function normalizeAuthor({ authorsBaseRoutePath, authorKey, baseUrl, author, }) {
67
68
  function getAuthorPage() {
68
69
  if (!author.page) {
69
70
  return null;
@@ -77,12 +78,13 @@ function normalizeAuthor({ authorsBaseRoutePath, authorKey, author, }) {
77
78
  ...author,
78
79
  key: authorKey,
79
80
  page: getAuthorPage(),
81
+ imageURL: (0, authors_1.normalizeImageUrl)({ imageURL: author.imageURL, baseUrl }),
80
82
  socials: author.socials ? (0, authorsSocials_1.normalizeSocials)(author.socials) : undefined,
81
83
  };
82
84
  }
83
- function normalizeAuthorsMap({ authorsBaseRoutePath, authorsMapInput, }) {
85
+ function normalizeAuthorsMap({ authorsBaseRoutePath, authorsMapInput, baseUrl, }) {
84
86
  return lodash_1.default.mapValues(authorsMapInput, (author, authorKey) => {
85
- return normalizeAuthor({ authorsBaseRoutePath, authorKey, author });
87
+ return normalizeAuthor({ authorsBaseRoutePath, authorKey, author, baseUrl });
86
88
  });
87
89
  }
88
90
  function validateAuthorsMapInput(content) {
@@ -27,6 +27,10 @@ const PredefinedPlatformNormalizers = {
27
27
  };
28
28
  function normalizeSocialEntry([platform, value]) {
29
29
  const normalizer = PredefinedPlatformNormalizers[platform.toLowerCase()];
30
+ if (typeof value !== 'string') {
31
+ throw new Error(`Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs.
32
+ Social platform '${platform}' has illegal value '${value}'`);
33
+ }
30
34
  const isAbsoluteUrl = value.startsWith('http://') || value.startsWith('https://');
31
35
  if (isAbsoluteUrl) {
32
36
  return [platform, value];
@@ -0,0 +1,12 @@
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 { BlogContent, BlogPost } from '@docusaurus/plugin-content-blog';
8
+ export declare function createContentHelpers(): {
9
+ updateContent: (content: BlogContent) => void;
10
+ sourceToBlogPost: Map<string, BlogPost>;
11
+ sourceToPermalink: Map<string, string>;
12
+ };
@@ -0,0 +1,30 @@
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.createContentHelpers = createContentHelpers;
10
+ function indexBlogPostsBySource(content) {
11
+ return new Map(content.blogPosts.map((blogPost) => [blogPost.metadata.source, blogPost]));
12
+ }
13
+ // TODO this is bad, we should have a better way to do this (new lifecycle?)
14
+ // The source to blog/permalink is a mutable map passed to the mdx loader
15
+ // See https://github.com/facebook/docusaurus/pull/10457
16
+ // See https://github.com/facebook/docusaurus/pull/10185
17
+ function createContentHelpers() {
18
+ const sourceToBlogPost = new Map();
19
+ const sourceToPermalink = new Map();
20
+ // Mutable map update :/
21
+ function updateContent(content) {
22
+ sourceToBlogPost.clear();
23
+ sourceToPermalink.clear();
24
+ indexBlogPostsBySource(content).forEach((value, key) => {
25
+ sourceToBlogPost.set(key, value);
26
+ sourceToPermalink.set(key, value.metadata.permalink);
27
+ });
28
+ }
29
+ return { updateContent, sourceToBlogPost, sourceToPermalink };
30
+ }
@@ -23,6 +23,7 @@ const FrontMatterAuthorErrorMessage = '{{#label}} does not look like a valid blo
23
23
  const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
24
24
  id: utils_validation_1.JoiFrontMatter.string(),
25
25
  title: utils_validation_1.JoiFrontMatter.string().allow(''),
26
+ title_meta: utils_validation_1.JoiFrontMatter.string(),
26
27
  description: utils_validation_1.JoiFrontMatter.string().allow(''),
27
28
  tags: utils_validation_1.FrontMatterTagsSchema,
28
29
  date: utils_validation_1.JoiFrontMatter.date().raw(),
package/lib/index.js CHANGED
@@ -13,34 +13,15 @@ const path_1 = tslib_1.__importDefault(require("path"));
13
13
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
14
14
  const utils_1 = require("@docusaurus/utils");
15
15
  const utils_validation_1 = require("@docusaurus/utils-validation");
16
+ const mdx_loader_1 = require("@docusaurus/mdx-loader");
16
17
  const blogUtils_1 = require("./blogUtils");
17
18
  const footnoteIDFixer_1 = tslib_1.__importDefault(require("./remark/footnoteIDFixer"));
18
19
  const translations_1 = require("./translations");
19
20
  const feed_1 = require("./feed");
20
21
  const routes_1 = require("./routes");
21
22
  const authorsMap_1 = require("./authorsMap");
23
+ const contentHelpers_1 = require("./contentHelpers");
22
24
  const PluginName = 'docusaurus-plugin-content-blog';
23
- // TODO this is bad, we should have a better way to do this (new lifecycle?)
24
- // The source to permalink is currently a mutable map passed to the mdx loader
25
- // for link resolution
26
- // see https://github.com/facebook/docusaurus/pull/10185
27
- function createSourceToPermalinkHelper() {
28
- const sourceToPermalink = new Map();
29
- function computeSourceToPermalink(content) {
30
- return new Map(content.blogPosts.map(({ metadata: { source, permalink } }) => [
31
- source,
32
- permalink,
33
- ]));
34
- }
35
- // Mutable map update :/
36
- function update(content) {
37
- sourceToPermalink.clear();
38
- computeSourceToPermalink(content).forEach((value, key) => {
39
- sourceToPermalink.set(key, value);
40
- });
41
- }
42
- return { get: () => sourceToPermalink, update };
43
- }
44
25
  async function pluginContentBlog(context, options) {
45
26
  const { siteDir, siteConfig, generatedFilesDir, localizationDir, i18n: { currentLocale }, } = context;
46
27
  const router = siteConfig.future.experimental_router;
@@ -68,7 +49,76 @@ async function pluginContentBlog(context, options) {
68
49
  filePath: options.authorsMapPath,
69
50
  contentPaths,
70
51
  });
71
- const sourceToPermalinkHelper = createSourceToPermalinkHelper();
52
+ const contentHelpers = (0, contentHelpers_1.createContentHelpers)();
53
+ async function createBlogMDXLoaderRule() {
54
+ const { admonitions, rehypePlugins, remarkPlugins, recmaPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
55
+ const contentDirs = (0, utils_1.getContentPathList)(contentPaths);
56
+ const mdxLoaderItem = await (0, mdx_loader_1.createMDXLoaderItem)({
57
+ useCrossCompilerCache: siteConfig.future.experimental_faster.mdxCrossCompilerCache,
58
+ admonitions,
59
+ remarkPlugins,
60
+ rehypePlugins,
61
+ recmaPlugins,
62
+ beforeDefaultRemarkPlugins: [
63
+ footnoteIDFixer_1.default,
64
+ ...beforeDefaultRemarkPlugins,
65
+ ],
66
+ beforeDefaultRehypePlugins,
67
+ staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
68
+ siteDir,
69
+ isMDXPartial: (0, utils_1.createAbsoluteFilePathMatcher)(options.exclude, contentDirs),
70
+ metadataPath: (mdxPath) => {
71
+ // Note that metadataPath must be the same/in-sync as
72
+ // the path from createData for each MDX.
73
+ const aliasedPath = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
74
+ return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedPath)}.json`);
75
+ },
76
+ // For blog posts a title in markdown is always removed
77
+ // Blog posts title are rendered separately
78
+ removeContentTitle: true,
79
+ // createAssets converts relative paths to require() calls
80
+ createAssets: ({ filePath }) => {
81
+ const blogPost = contentHelpers.sourceToBlogPost.get((0, utils_1.aliasedSitePath)(filePath, siteDir));
82
+ if (!blogPost) {
83
+ throw new Error(`Blog post not found for filePath=${filePath}`);
84
+ }
85
+ return {
86
+ image: blogPost.metadata.frontMatter.image,
87
+ authorsImageUrls: blogPost.metadata.authors.map((author) => author.imageURL),
88
+ };
89
+ },
90
+ markdownConfig: siteConfig.markdown,
91
+ resolveMarkdownLink: ({ linkPathname, sourceFilePath }) => {
92
+ const permalink = (0, utils_1.resolveMarkdownLinkPathname)(linkPathname, {
93
+ sourceFilePath,
94
+ sourceToPermalink: contentHelpers.sourceToPermalink,
95
+ siteDir,
96
+ contentPaths,
97
+ });
98
+ if (permalink === null) {
99
+ logger_1.default.report(onBrokenMarkdownLinks) `Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
100
+ }
101
+ return permalink;
102
+ },
103
+ });
104
+ function createBlogMarkdownLoader() {
105
+ const markdownLoaderOptions = {
106
+ truncateMarker,
107
+ };
108
+ return {
109
+ loader: path_1.default.resolve(__dirname, './markdownLoader.js'),
110
+ options: markdownLoaderOptions,
111
+ };
112
+ }
113
+ return {
114
+ test: /\.mdx?$/i,
115
+ include: contentDirs
116
+ // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
117
+ .map(utils_1.addTrailingPathSeparator),
118
+ use: [mdxLoaderItem, createBlogMarkdownLoader()],
119
+ };
120
+ }
121
+ const blogMDXLoaderRule = await createBlogMDXLoaderRule();
72
122
  return {
73
123
  name: PluginName,
74
124
  getPathsToWatch() {
@@ -100,6 +150,7 @@ async function pluginContentBlog(context, options) {
100
150
  routeBasePath,
101
151
  authorsBasePath,
102
152
  ]),
153
+ baseUrl,
103
154
  });
104
155
  (0, authorsMap_1.checkAuthorsMapPermalinkCollisions)(authorsMap);
105
156
  let blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options, authorsMap);
@@ -166,7 +217,7 @@ async function pluginContentBlog(context, options) {
166
217
  };
167
218
  },
168
219
  async contentLoaded({ content, actions }) {
169
- sourceToPermalinkHelper.update(content);
220
+ contentHelpers.updateContent(content);
170
221
  await (0, routes_1.createAllRoutes)({
171
222
  baseUrl,
172
223
  content,
@@ -179,66 +230,6 @@ async function pluginContentBlog(context, options) {
179
230
  return (0, translations_1.translateContent)(content, translationFiles);
180
231
  },
181
232
  configureWebpack() {
182
- const { admonitions, rehypePlugins, remarkPlugins, recmaPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
183
- const contentDirs = (0, utils_1.getContentPathList)(contentPaths);
184
- function createMDXLoader() {
185
- const loaderOptions = {
186
- admonitions,
187
- remarkPlugins,
188
- rehypePlugins,
189
- recmaPlugins,
190
- beforeDefaultRemarkPlugins: [
191
- footnoteIDFixer_1.default,
192
- ...beforeDefaultRemarkPlugins,
193
- ],
194
- beforeDefaultRehypePlugins,
195
- staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
196
- siteDir,
197
- isMDXPartial: (0, utils_1.createAbsoluteFilePathMatcher)(options.exclude, contentDirs),
198
- metadataPath: (mdxPath) => {
199
- // Note that metadataPath must be the same/in-sync as
200
- // the path from createData for each MDX.
201
- const aliasedPath = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
202
- return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedPath)}.json`);
203
- },
204
- // For blog posts a title in markdown is always removed
205
- // Blog posts title are rendered separately
206
- removeContentTitle: true,
207
- // Assets allow to convert some relative images paths to
208
- // require() calls
209
- // @ts-expect-error: TODO fix typing issue
210
- createAssets: ({ frontMatter, metadata, }) => ({
211
- image: frontMatter.image,
212
- authorsImageUrls: metadata.authors.map((author) => author.imageURL),
213
- }),
214
- markdownConfig: siteConfig.markdown,
215
- resolveMarkdownLink: ({ linkPathname, sourceFilePath }) => {
216
- const permalink = (0, utils_1.resolveMarkdownLinkPathname)(linkPathname, {
217
- sourceFilePath,
218
- sourceToPermalink: sourceToPermalinkHelper.get(),
219
- siteDir,
220
- contentPaths,
221
- });
222
- if (permalink === null) {
223
- logger_1.default.report(onBrokenMarkdownLinks) `Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
224
- }
225
- return permalink;
226
- },
227
- };
228
- return {
229
- loader: require.resolve('@docusaurus/mdx-loader'),
230
- options: loaderOptions,
231
- };
232
- }
233
- function createBlogMarkdownLoader() {
234
- const loaderOptions = {
235
- truncateMarker,
236
- };
237
- return {
238
- loader: path_1.default.resolve(__dirname, './markdownLoader.js'),
239
- options: loaderOptions,
240
- };
241
- }
242
233
  return {
243
234
  resolve: {
244
235
  alias: {
@@ -246,15 +237,7 @@ async function pluginContentBlog(context, options) {
246
237
  },
247
238
  },
248
239
  module: {
249
- rules: [
250
- {
251
- test: /\.mdx?$/i,
252
- include: contentDirs
253
- // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
254
- .map(utils_1.addTrailingPathSeparator),
255
- use: [createMDXLoader(), createBlogMarkdownLoader()],
256
- },
257
- ],
240
+ rules: [blogMDXLoaderRule],
258
241
  },
259
242
  };
260
243
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "3.5.2",
3
+ "version": "3.6.0",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
@@ -31,14 +31,14 @@
31
31
  },
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@docusaurus/core": "3.5.2",
35
- "@docusaurus/logger": "3.5.2",
36
- "@docusaurus/mdx-loader": "3.5.2",
37
- "@docusaurus/theme-common": "3.5.2",
38
- "@docusaurus/types": "3.5.2",
39
- "@docusaurus/utils": "3.5.2",
40
- "@docusaurus/utils-common": "3.5.2",
41
- "@docusaurus/utils-validation": "3.5.2",
34
+ "@docusaurus/core": "3.6.0",
35
+ "@docusaurus/logger": "3.6.0",
36
+ "@docusaurus/mdx-loader": "3.6.0",
37
+ "@docusaurus/theme-common": "3.6.0",
38
+ "@docusaurus/types": "3.6.0",
39
+ "@docusaurus/utils": "3.6.0",
40
+ "@docusaurus/utils-common": "3.6.0",
41
+ "@docusaurus/utils-validation": "3.6.0",
42
42
  "cheerio": "1.0.0-rc.12",
43
43
  "feed": "^4.2.2",
44
44
  "fs-extra": "^11.1.1",
@@ -62,5 +62,5 @@
62
62
  "@total-typescript/shoehorn": "^0.1.2",
63
63
  "tree-node-cli": "^1.6.0"
64
64
  },
65
- "gitHead": "eeec303dd773774ed5a023884800da0b061f6942"
65
+ "gitHead": "05bba6d4f495ef6b0bec5d41453932bb97981830"
66
66
  }
package/src/authors.ts CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import _ from 'lodash';
9
9
  import {normalizeUrl} from '@docusaurus/utils';
10
+ import {normalizeSocials} from './authorsSocials';
10
11
  import type {
11
12
  Author,
12
13
  AuthorsMap,
@@ -21,18 +22,41 @@ type AuthorsParam = {
21
22
  baseUrl: string;
22
23
  };
23
24
 
24
- function normalizeImageUrl({
25
+ export function normalizeImageUrl({
25
26
  imageURL,
26
27
  baseUrl,
27
28
  }: {
28
29
  imageURL: string | undefined;
29
30
  baseUrl: string;
30
- }) {
31
+ }): string | undefined {
31
32
  return imageURL?.startsWith('/')
32
33
  ? normalizeUrl([baseUrl, imageURL])
33
34
  : imageURL;
34
35
  }
35
36
 
37
+ function normalizeAuthorUrl({
38
+ author,
39
+ baseUrl,
40
+ }: {
41
+ author: Author;
42
+ baseUrl: string;
43
+ }): string | undefined {
44
+ if (author.key) {
45
+ // Ensures invariant: global authors should have already been normalized
46
+ if (
47
+ author.imageURL?.startsWith('/') &&
48
+ !author.imageURL.startsWith(baseUrl)
49
+ ) {
50
+ throw new Error(
51
+ `Docusaurus internal bug: global authors image ${author.imageURL} should start with the expected baseUrl=${baseUrl}`,
52
+ );
53
+ }
54
+
55
+ return author.imageURL;
56
+ }
57
+ return normalizeImageUrl({imageURL: author.imageURL, baseUrl});
58
+ }
59
+
36
60
  // Legacy v1/early-v2 front matter fields
37
61
  // We may want to deprecate those in favor of using only frontMatter.authors
38
62
  // TODO Docusaurus v4: remove this legacy front matter
@@ -84,7 +108,10 @@ function getFrontMatterAuthors(params: AuthorsParam): Author[] {
84
108
  // becoming a name and may end up unnoticed
85
109
  return {key: authorInput};
86
110
  }
87
- return authorInput;
111
+ return {
112
+ ...authorInput,
113
+ socials: normalizeSocials(authorInput.socials ?? {}),
114
+ };
88
115
  }
89
116
 
90
117
  return Array.isArray(frontMatter.authors)
@@ -116,13 +143,14 @@ ${Object.keys(authorsMap)
116
143
  // Author def from authorsMap can be locally overridden by front matter
117
144
  ...getAuthorsMapAuthor(frontMatterAuthor.key),
118
145
  ...frontMatterAuthor,
119
- };
146
+ } as Author;
120
147
 
121
148
  return {
122
149
  ...author,
123
150
  key: author.key ?? null,
124
151
  page: author.page ?? null,
125
- imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}),
152
+ // global author images have already been normalized
153
+ imageURL: normalizeAuthorUrl({author, baseUrl}),
126
154
  };
127
155
  }
128
156
  }
package/src/authorsMap.ts CHANGED
@@ -9,12 +9,13 @@ import _ from 'lodash';
9
9
  import {readDataFile, normalizeUrl} from '@docusaurus/utils';
10
10
  import {Joi, URISchema} from '@docusaurus/utils-validation';
11
11
  import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials';
12
+ import {normalizeImageUrl} from './authors';
12
13
  import type {BlogContentPaths} from './types';
13
14
  import type {
14
- Author,
15
15
  AuthorAttributes,
16
16
  AuthorPage,
17
17
  AuthorsMap,
18
+ AuthorWithKey,
18
19
  } from '@docusaurus/plugin-content-blog';
19
20
 
20
21
  type AuthorInput = AuthorAttributes & {
@@ -93,12 +94,14 @@ export function checkAuthorsMapPermalinkCollisions(
93
94
  function normalizeAuthor({
94
95
  authorsBaseRoutePath,
95
96
  authorKey,
97
+ baseUrl,
96
98
  author,
97
99
  }: {
98
100
  authorsBaseRoutePath: string;
99
101
  authorKey: string;
102
+ baseUrl: string;
100
103
  author: AuthorInput;
101
- }): Author & {key: string} {
104
+ }): AuthorWithKey {
102
105
  function getAuthorPage(): AuthorPage | null {
103
106
  if (!author.page) {
104
107
  return null;
@@ -114,6 +117,7 @@ function normalizeAuthor({
114
117
  ...author,
115
118
  key: authorKey,
116
119
  page: getAuthorPage(),
120
+ imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}),
117
121
  socials: author.socials ? normalizeSocials(author.socials) : undefined,
118
122
  };
119
123
  }
@@ -121,12 +125,14 @@ function normalizeAuthor({
121
125
  function normalizeAuthorsMap({
122
126
  authorsBaseRoutePath,
123
127
  authorsMapInput,
128
+ baseUrl,
124
129
  }: {
125
130
  authorsBaseRoutePath: string;
126
131
  authorsMapInput: AuthorsMapInput;
132
+ baseUrl: string;
127
133
  }): AuthorsMap {
128
134
  return _.mapValues(authorsMapInput, (author, authorKey) => {
129
- return normalizeAuthor({authorsBaseRoutePath, authorKey, author});
135
+ return normalizeAuthor({authorsBaseRoutePath, authorKey, author, baseUrl});
130
136
  });
131
137
  }
132
138
 
@@ -153,6 +159,7 @@ export async function getAuthorsMap(params: {
153
159
  authorsMapPath: string;
154
160
  authorsBaseRoutePath: string;
155
161
  contentPaths: BlogContentPaths;
162
+ baseUrl: string;
156
163
  }): Promise<AuthorsMap | undefined> {
157
164
  const authorsMapInput = await getAuthorsMapInput(params);
158
165
  if (!authorsMapInput) {
@@ -41,6 +41,12 @@ type SocialEntry = [string, string];
41
41
 
42
42
  function normalizeSocialEntry([platform, value]: SocialEntry): SocialEntry {
43
43
  const normalizer = PredefinedPlatformNormalizers[platform.toLowerCase()];
44
+ if (typeof value !== 'string') {
45
+ throw new Error(
46
+ `Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs.
47
+ Social platform '${platform}' has illegal value '${value}'`,
48
+ );
49
+ }
44
50
  const isAbsoluteUrl =
45
51
  value.startsWith('http://') || value.startsWith('https://');
46
52
  if (isAbsoluteUrl) {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import type {BlogContent, BlogPost} from '@docusaurus/plugin-content-blog';
9
+
10
+ function indexBlogPostsBySource(content: BlogContent): Map<string, BlogPost> {
11
+ return new Map(
12
+ content.blogPosts.map((blogPost) => [blogPost.metadata.source, blogPost]),
13
+ );
14
+ }
15
+
16
+ // TODO this is bad, we should have a better way to do this (new lifecycle?)
17
+ // The source to blog/permalink is a mutable map passed to the mdx loader
18
+ // See https://github.com/facebook/docusaurus/pull/10457
19
+ // See https://github.com/facebook/docusaurus/pull/10185
20
+ export function createContentHelpers() {
21
+ const sourceToBlogPost = new Map<string, BlogPost>();
22
+ const sourceToPermalink = new Map<string, string>();
23
+
24
+ // Mutable map update :/
25
+ function updateContent(content: BlogContent): void {
26
+ sourceToBlogPost.clear();
27
+ sourceToPermalink.clear();
28
+ indexBlogPostsBySource(content).forEach((value, key) => {
29
+ sourceToBlogPost.set(key, value);
30
+ sourceToPermalink.set(key, value.metadata.permalink);
31
+ });
32
+ }
33
+
34
+ return {updateContent, sourceToBlogPost, sourceToPermalink};
35
+ }
@@ -33,6 +33,7 @@ const FrontMatterAuthorErrorMessage =
33
33
  const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
34
34
  id: Joi.string(),
35
35
  title: Joi.string().allow(''),
36
+ title_meta: Joi.string(),
36
37
  description: Joi.string().allow(''),
37
38
  tags: FrontMatterTagsSchema,
38
39
  date: Joi.date().raw(),
package/src/index.ts CHANGED
@@ -19,9 +19,9 @@ import {
19
19
  getDataFilePath,
20
20
  DEFAULT_PLUGIN_ID,
21
21
  resolveMarkdownLinkPathname,
22
- type SourceToPermalink,
23
22
  } from '@docusaurus/utils';
24
23
  import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
24
+ import {createMDXLoaderItem} from '@docusaurus/mdx-loader';
25
25
  import {
26
26
  getBlogTags,
27
27
  paginateBlogPosts,
@@ -36,49 +36,20 @@ import {createBlogFeedFiles, createFeedHtmlHeadTags} from './feed';
36
36
 
37
37
  import {createAllRoutes} from './routes';
38
38
  import {checkAuthorsMapPermalinkCollisions, getAuthorsMap} from './authorsMap';
39
+ import {createContentHelpers} from './contentHelpers';
39
40
  import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
40
41
  import type {LoadContext, Plugin} from '@docusaurus/types';
41
42
  import type {
42
43
  PluginOptions,
43
- BlogPostFrontMatter,
44
- BlogPostMetadata,
45
44
  Assets,
46
45
  BlogTags,
47
46
  BlogContent,
48
47
  BlogPaginated,
49
48
  } from '@docusaurus/plugin-content-blog';
50
- import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader/lib/loader';
51
- import type {RuleSetUseItem} from 'webpack';
49
+ import type {RuleSetRule, RuleSetUseItem} from 'webpack';
52
50
 
53
51
  const PluginName = 'docusaurus-plugin-content-blog';
54
52
 
55
- // TODO this is bad, we should have a better way to do this (new lifecycle?)
56
- // The source to permalink is currently a mutable map passed to the mdx loader
57
- // for link resolution
58
- // see https://github.com/facebook/docusaurus/pull/10185
59
- function createSourceToPermalinkHelper() {
60
- const sourceToPermalink: SourceToPermalink = new Map();
61
-
62
- function computeSourceToPermalink(content: BlogContent): SourceToPermalink {
63
- return new Map(
64
- content.blogPosts.map(({metadata: {source, permalink}}) => [
65
- source,
66
- permalink,
67
- ]),
68
- );
69
- }
70
-
71
- // Mutable map update :/
72
- function update(content: BlogContent): void {
73
- sourceToPermalink.clear();
74
- computeSourceToPermalink(content).forEach((value, key) => {
75
- sourceToPermalink.set(key, value);
76
- });
77
- }
78
-
79
- return {get: () => sourceToPermalink, update};
80
- }
81
-
82
53
  export default async function pluginContentBlog(
83
54
  context: LoadContext,
84
55
  options: PluginOptions,
@@ -125,7 +96,99 @@ export default async function pluginContentBlog(
125
96
  contentPaths,
126
97
  });
127
98
 
128
- const sourceToPermalinkHelper = createSourceToPermalinkHelper();
99
+ const contentHelpers = createContentHelpers();
100
+
101
+ async function createBlogMDXLoaderRule(): Promise<RuleSetRule> {
102
+ const {
103
+ admonitions,
104
+ rehypePlugins,
105
+ remarkPlugins,
106
+ recmaPlugins,
107
+ truncateMarker,
108
+ beforeDefaultRemarkPlugins,
109
+ beforeDefaultRehypePlugins,
110
+ } = options;
111
+
112
+ const contentDirs = getContentPathList(contentPaths);
113
+
114
+ const mdxLoaderItem = await createMDXLoaderItem({
115
+ useCrossCompilerCache:
116
+ siteConfig.future.experimental_faster.mdxCrossCompilerCache,
117
+ admonitions,
118
+ remarkPlugins,
119
+ rehypePlugins,
120
+ recmaPlugins,
121
+ beforeDefaultRemarkPlugins: [
122
+ footnoteIDFixer,
123
+ ...beforeDefaultRemarkPlugins,
124
+ ],
125
+ beforeDefaultRehypePlugins,
126
+ staticDirs: siteConfig.staticDirectories.map((dir) =>
127
+ path.resolve(siteDir, dir),
128
+ ),
129
+ siteDir,
130
+ isMDXPartial: createAbsoluteFilePathMatcher(options.exclude, contentDirs),
131
+ metadataPath: (mdxPath: string) => {
132
+ // Note that metadataPath must be the same/in-sync as
133
+ // the path from createData for each MDX.
134
+ const aliasedPath = aliasedSitePath(mdxPath, siteDir);
135
+ return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
136
+ },
137
+ // For blog posts a title in markdown is always removed
138
+ // Blog posts title are rendered separately
139
+ removeContentTitle: true,
140
+ // createAssets converts relative paths to require() calls
141
+ createAssets: ({filePath}: {filePath: string}): Assets => {
142
+ const blogPost = contentHelpers.sourceToBlogPost.get(
143
+ aliasedSitePath(filePath, siteDir),
144
+ )!;
145
+ if (!blogPost) {
146
+ throw new Error(`Blog post not found for filePath=${filePath}`);
147
+ }
148
+ return {
149
+ image: blogPost.metadata.frontMatter.image as string,
150
+ authorsImageUrls: blogPost.metadata.authors.map(
151
+ (author) => author.imageURL,
152
+ ),
153
+ };
154
+ },
155
+ markdownConfig: siteConfig.markdown,
156
+ resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
157
+ const permalink = resolveMarkdownLinkPathname(linkPathname, {
158
+ sourceFilePath,
159
+ sourceToPermalink: contentHelpers.sourceToPermalink,
160
+ siteDir,
161
+ contentPaths,
162
+ });
163
+ if (permalink === null) {
164
+ logger.report(
165
+ onBrokenMarkdownLinks,
166
+ )`Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
167
+ }
168
+ return permalink;
169
+ },
170
+ });
171
+
172
+ function createBlogMarkdownLoader(): RuleSetUseItem {
173
+ const markdownLoaderOptions: BlogMarkdownLoaderOptions = {
174
+ truncateMarker,
175
+ };
176
+ return {
177
+ loader: path.resolve(__dirname, './markdownLoader.js'),
178
+ options: markdownLoaderOptions,
179
+ };
180
+ }
181
+
182
+ return {
183
+ test: /\.mdx?$/i,
184
+ include: contentDirs
185
+ // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
186
+ .map(addTrailingPathSeparator),
187
+ use: [mdxLoaderItem, createBlogMarkdownLoader()],
188
+ };
189
+ }
190
+
191
+ const blogMDXLoaderRule = await createBlogMDXLoaderRule();
129
192
 
130
193
  return {
131
194
  name: PluginName,
@@ -177,6 +240,7 @@ export default async function pluginContentBlog(
177
240
  routeBasePath,
178
241
  authorsBasePath,
179
242
  ]),
243
+ baseUrl,
180
244
  });
181
245
  checkAuthorsMapPermalinkCollisions(authorsMap);
182
246
 
@@ -257,7 +321,7 @@ export default async function pluginContentBlog(
257
321
  },
258
322
 
259
323
  async contentLoaded({content, actions}) {
260
- sourceToPermalinkHelper.update(content);
324
+ contentHelpers.updateContent(content);
261
325
 
262
326
  await createAllRoutes({
263
327
  baseUrl,
@@ -273,91 +337,6 @@ export default async function pluginContentBlog(
273
337
  },
274
338
 
275
339
  configureWebpack() {
276
- const {
277
- admonitions,
278
- rehypePlugins,
279
- remarkPlugins,
280
- recmaPlugins,
281
- truncateMarker,
282
- beforeDefaultRemarkPlugins,
283
- beforeDefaultRehypePlugins,
284
- } = options;
285
-
286
- const contentDirs = getContentPathList(contentPaths);
287
-
288
- function createMDXLoader(): RuleSetUseItem {
289
- const loaderOptions: MDXLoaderOptions = {
290
- admonitions,
291
- remarkPlugins,
292
- rehypePlugins,
293
- recmaPlugins,
294
- beforeDefaultRemarkPlugins: [
295
- footnoteIDFixer,
296
- ...beforeDefaultRemarkPlugins,
297
- ],
298
- beforeDefaultRehypePlugins,
299
- staticDirs: siteConfig.staticDirectories.map((dir) =>
300
- path.resolve(siteDir, dir),
301
- ),
302
- siteDir,
303
- isMDXPartial: createAbsoluteFilePathMatcher(
304
- options.exclude,
305
- contentDirs,
306
- ),
307
- metadataPath: (mdxPath: string) => {
308
- // Note that metadataPath must be the same/in-sync as
309
- // the path from createData for each MDX.
310
- const aliasedPath = aliasedSitePath(mdxPath, siteDir);
311
- return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
312
- },
313
- // For blog posts a title in markdown is always removed
314
- // Blog posts title are rendered separately
315
- removeContentTitle: true,
316
- // Assets allow to convert some relative images paths to
317
- // require() calls
318
- // @ts-expect-error: TODO fix typing issue
319
- createAssets: ({
320
- frontMatter,
321
- metadata,
322
- }: {
323
- frontMatter: BlogPostFrontMatter;
324
- metadata: BlogPostMetadata;
325
- }): Assets => ({
326
- image: frontMatter.image,
327
- authorsImageUrls: metadata.authors.map((author) => author.imageURL),
328
- }),
329
- markdownConfig: siteConfig.markdown,
330
- resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
331
- const permalink = resolveMarkdownLinkPathname(linkPathname, {
332
- sourceFilePath,
333
- sourceToPermalink: sourceToPermalinkHelper.get(),
334
- siteDir,
335
- contentPaths,
336
- });
337
- if (permalink === null) {
338
- logger.report(
339
- onBrokenMarkdownLinks,
340
- )`Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
341
- }
342
- return permalink;
343
- },
344
- };
345
- return {
346
- loader: require.resolve('@docusaurus/mdx-loader'),
347
- options: loaderOptions,
348
- };
349
- }
350
-
351
- function createBlogMarkdownLoader(): RuleSetUseItem {
352
- const loaderOptions: BlogMarkdownLoaderOptions = {
353
- truncateMarker,
354
- };
355
- return {
356
- loader: path.resolve(__dirname, './markdownLoader.js'),
357
- options: loaderOptions,
358
- };
359
- }
360
-
361
340
  return {
362
341
  resolve: {
363
342
  alias: {
@@ -365,15 +344,7 @@ export default async function pluginContentBlog(
365
344
  },
366
345
  },
367
346
  module: {
368
- rules: [
369
- {
370
- test: /\.mdx?$/i,
371
- include: contentDirs
372
- // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
373
- .map(addTrailingPathSeparator),
374
- use: [createMDXLoader(), createBlogMarkdownLoader()],
375
- },
376
- ],
347
+ rules: [blogMDXLoaderRule],
377
348
  },
378
349
  };
379
350
  },
@@ -143,6 +143,11 @@ declare module '@docusaurus/plugin-content-blog' {
143
143
  * @see {@link BlogPostMetadata.title}
144
144
  */
145
145
  title?: string;
146
+ /**
147
+ * Will be used for SEO page metadata and override BlogPostMetadata.title.
148
+ * @see {@link BlogPostMetadata.title_meta}
149
+ */
150
+ title_meta?: string;
146
151
  /**
147
152
  * Will override the default excerpt.
148
153
  * @see {@link BlogPostMetadata.description}