@docusaurus/plugin-content-blog 3.3.2 → 3.4.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/blogUtils.d.ts +1 -9
- package/lib/blogUtils.js +17 -22
- package/lib/feed.d.ts +5 -1
- package/lib/feed.js +44 -1
- package/lib/index.js +109 -101
- package/lib/markdownLoader.js +2 -6
- package/lib/options.js +9 -0
- package/lib/props.js +2 -0
- package/lib/types.d.ts +1 -8
- package/package.json +9 -9
- package/src/blogUtils.ts +21 -43
- package/src/feed.ts +57 -1
- package/src/index.ts +151 -136
- package/src/markdownLoader.ts +3 -7
- package/src/options.ts +9 -0
- package/src/plugin-content-blog.d.ts +102 -101
- package/src/props.ts +2 -0
- package/src/types.ts +1 -6
package/src/blogUtils.ts
CHANGED
|
@@ -17,9 +17,7 @@ import {
|
|
|
17
17
|
getEditUrl,
|
|
18
18
|
getFolderContainingFile,
|
|
19
19
|
posixPath,
|
|
20
|
-
replaceMarkdownLinks,
|
|
21
20
|
Globby,
|
|
22
|
-
normalizeFrontMatterTags,
|
|
23
21
|
groupTaggedItems,
|
|
24
22
|
getTagVisibility,
|
|
25
23
|
getFileCommitDate,
|
|
@@ -27,9 +25,12 @@ import {
|
|
|
27
25
|
isUnlisted,
|
|
28
26
|
isDraft,
|
|
29
27
|
readLastUpdateData,
|
|
28
|
+
normalizeTags,
|
|
30
29
|
} from '@docusaurus/utils';
|
|
30
|
+
import {getTagsFile} from '@docusaurus/utils-validation';
|
|
31
31
|
import {validateBlogPostFrontMatter} from './frontMatter';
|
|
32
32
|
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
33
|
+
import type {TagsFile} from '@docusaurus/utils';
|
|
33
34
|
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
|
|
34
35
|
import type {
|
|
35
36
|
PluginOptions,
|
|
@@ -38,20 +39,12 @@ import type {
|
|
|
38
39
|
BlogTags,
|
|
39
40
|
BlogPaginated,
|
|
40
41
|
} from '@docusaurus/plugin-content-blog';
|
|
41
|
-
import type {BlogContentPaths
|
|
42
|
+
import type {BlogContentPaths} from './types';
|
|
42
43
|
|
|
43
44
|
export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
44
45
|
return fileString.split(truncateMarker, 1).shift()!;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
export function getSourceToPermalink(blogPosts: BlogPost[]): {
|
|
48
|
-
[aliasedPath: string]: string;
|
|
49
|
-
} {
|
|
50
|
-
return Object.fromEntries(
|
|
51
|
-
blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
48
|
export function paginateBlogPosts({
|
|
56
49
|
blogPosts,
|
|
57
50
|
basePageUrl,
|
|
@@ -126,9 +119,11 @@ export function getBlogTags({
|
|
|
126
119
|
isUnlisted: (item) => item.metadata.unlisted,
|
|
127
120
|
});
|
|
128
121
|
return {
|
|
122
|
+
inline: tag.inline,
|
|
129
123
|
label: tag.label,
|
|
130
|
-
items: tagVisibility.listedItems.map((item) => item.id),
|
|
131
124
|
permalink: tag.permalink,
|
|
125
|
+
description: tag.description,
|
|
126
|
+
items: tagVisibility.listedItems.map((item) => item.id),
|
|
132
127
|
pages: paginateBlogPosts({
|
|
133
128
|
blogPosts: tagVisibility.listedItems,
|
|
134
129
|
basePageUrl: tag.permalink,
|
|
@@ -198,6 +193,7 @@ async function processBlogSourceFile(
|
|
|
198
193
|
contentPaths: BlogContentPaths,
|
|
199
194
|
context: LoadContext,
|
|
200
195
|
options: PluginOptions,
|
|
196
|
+
tagsFile: TagsFile | null,
|
|
201
197
|
authorsMap?: AuthorsMap,
|
|
202
198
|
): Promise<BlogPost | undefined> {
|
|
203
199
|
const {
|
|
@@ -316,13 +312,21 @@ async function processBlogSourceFile(
|
|
|
316
312
|
return undefined;
|
|
317
313
|
}
|
|
318
314
|
|
|
319
|
-
const
|
|
315
|
+
const tagsBaseRoutePath = normalizeUrl([
|
|
320
316
|
baseUrl,
|
|
321
317
|
routeBasePath,
|
|
322
318
|
tagsRouteBasePath,
|
|
323
319
|
]);
|
|
324
320
|
const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl});
|
|
325
321
|
|
|
322
|
+
const tags = normalizeTags({
|
|
323
|
+
options,
|
|
324
|
+
source: blogSourceRelative,
|
|
325
|
+
frontMatterTags: frontMatter.tags,
|
|
326
|
+
tagsBaseRoutePath,
|
|
327
|
+
tagsFile,
|
|
328
|
+
});
|
|
329
|
+
|
|
326
330
|
return {
|
|
327
331
|
id: slug,
|
|
328
332
|
metadata: {
|
|
@@ -332,7 +336,7 @@ async function processBlogSourceFile(
|
|
|
332
336
|
title,
|
|
333
337
|
description,
|
|
334
338
|
date,
|
|
335
|
-
tags
|
|
339
|
+
tags,
|
|
336
340
|
readingTime: showReadingTime
|
|
337
341
|
? options.readingTime({
|
|
338
342
|
content,
|
|
@@ -372,6 +376,8 @@ export async function generateBlogPosts(
|
|
|
372
376
|
authorsMapPath: options.authorsMapPath,
|
|
373
377
|
});
|
|
374
378
|
|
|
379
|
+
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
|
|
380
|
+
|
|
375
381
|
async function doProcessBlogSourceFile(blogSourceFile: string) {
|
|
376
382
|
try {
|
|
377
383
|
return await processBlogSourceFile(
|
|
@@ -379,6 +385,7 @@ export async function generateBlogPosts(
|
|
|
379
385
|
contentPaths,
|
|
380
386
|
context,
|
|
381
387
|
options,
|
|
388
|
+
tagsFile,
|
|
382
389
|
authorsMap,
|
|
383
390
|
);
|
|
384
391
|
} catch (err) {
|
|
@@ -403,35 +410,6 @@ export async function generateBlogPosts(
|
|
|
403
410
|
return blogPosts;
|
|
404
411
|
}
|
|
405
412
|
|
|
406
|
-
export type LinkifyParams = {
|
|
407
|
-
filePath: string;
|
|
408
|
-
fileString: string;
|
|
409
|
-
} & Pick<
|
|
410
|
-
BlogMarkdownLoaderOptions,
|
|
411
|
-
'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'
|
|
412
|
-
>;
|
|
413
|
-
|
|
414
|
-
export function linkify({
|
|
415
|
-
filePath,
|
|
416
|
-
contentPaths,
|
|
417
|
-
fileString,
|
|
418
|
-
siteDir,
|
|
419
|
-
sourceToPermalink,
|
|
420
|
-
onBrokenMarkdownLink,
|
|
421
|
-
}: LinkifyParams): string {
|
|
422
|
-
const {newContent, brokenMarkdownLinks} = replaceMarkdownLinks({
|
|
423
|
-
siteDir,
|
|
424
|
-
fileString,
|
|
425
|
-
filePath,
|
|
426
|
-
contentPaths,
|
|
427
|
-
sourceToPermalink,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
brokenMarkdownLinks.forEach((l) => onBrokenMarkdownLink(l));
|
|
431
|
-
|
|
432
|
-
return newContent;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
413
|
export async function applyProcessBlogPosts({
|
|
436
414
|
blogPosts,
|
|
437
415
|
processBlogPosts,
|
package/src/feed.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
applyTrailingSlash,
|
|
17
17
|
} from '@docusaurus/utils-common';
|
|
18
18
|
import {load as cheerioLoad} from 'cheerio';
|
|
19
|
-
import type {DocusaurusConfig} from '@docusaurus/types';
|
|
19
|
+
import type {DocusaurusConfig, HtmlTags, LoadContext} from '@docusaurus/types';
|
|
20
20
|
import type {
|
|
21
21
|
FeedType,
|
|
22
22
|
PluginOptions,
|
|
@@ -254,3 +254,59 @@ export async function createBlogFeedFiles({
|
|
|
254
254
|
),
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
|
+
|
|
258
|
+
export function createFeedHtmlHeadTags({
|
|
259
|
+
context,
|
|
260
|
+
options,
|
|
261
|
+
}: {
|
|
262
|
+
context: LoadContext;
|
|
263
|
+
options: PluginOptions;
|
|
264
|
+
}): HtmlTags {
|
|
265
|
+
const feedTypes = options.feedOptions.type;
|
|
266
|
+
if (!feedTypes) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
|
|
270
|
+
const feedsConfig = {
|
|
271
|
+
rss: {
|
|
272
|
+
type: 'application/rss+xml',
|
|
273
|
+
path: 'rss.xml',
|
|
274
|
+
title: `${feedTitle} RSS Feed`,
|
|
275
|
+
},
|
|
276
|
+
atom: {
|
|
277
|
+
type: 'application/atom+xml',
|
|
278
|
+
path: 'atom.xml',
|
|
279
|
+
title: `${feedTitle} Atom Feed`,
|
|
280
|
+
},
|
|
281
|
+
json: {
|
|
282
|
+
type: 'application/json',
|
|
283
|
+
path: 'feed.json',
|
|
284
|
+
title: `${feedTitle} JSON Feed`,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
const headTags: HtmlTags = [];
|
|
288
|
+
|
|
289
|
+
feedTypes.forEach((feedType) => {
|
|
290
|
+
const {
|
|
291
|
+
type,
|
|
292
|
+
path: feedConfigPath,
|
|
293
|
+
title: feedConfigTitle,
|
|
294
|
+
} = feedsConfig[feedType];
|
|
295
|
+
|
|
296
|
+
headTags.push({
|
|
297
|
+
tagName: 'link',
|
|
298
|
+
attributes: {
|
|
299
|
+
rel: 'alternate',
|
|
300
|
+
type,
|
|
301
|
+
href: normalizeUrl([
|
|
302
|
+
context.siteConfig.baseUrl,
|
|
303
|
+
options.routeBasePath,
|
|
304
|
+
feedConfigPath,
|
|
305
|
+
]),
|
|
306
|
+
title: feedConfigTitle,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return headTags;
|
|
312
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,9 +18,11 @@ import {
|
|
|
18
18
|
getContentPathList,
|
|
19
19
|
getDataFilePath,
|
|
20
20
|
DEFAULT_PLUGIN_ID,
|
|
21
|
+
resolveMarkdownLinkPathname,
|
|
22
|
+
type SourceToPermalink,
|
|
21
23
|
} from '@docusaurus/utils';
|
|
24
|
+
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
|
22
25
|
import {
|
|
23
|
-
getSourceToPermalink,
|
|
24
26
|
getBlogTags,
|
|
25
27
|
paginateBlogPosts,
|
|
26
28
|
shouldBeListed,
|
|
@@ -29,11 +31,11 @@ import {
|
|
|
29
31
|
} from './blogUtils';
|
|
30
32
|
import footnoteIDFixer from './remark/footnoteIDFixer';
|
|
31
33
|
import {translateContent, getTranslationFiles} from './translations';
|
|
32
|
-
import {createBlogFeedFiles} from './feed';
|
|
34
|
+
import {createBlogFeedFiles, createFeedHtmlHeadTags} from './feed';
|
|
33
35
|
|
|
34
36
|
import {createAllRoutes} from './routes';
|
|
35
37
|
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
|
36
|
-
import type {LoadContext, Plugin
|
|
38
|
+
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
37
39
|
import type {
|
|
38
40
|
PluginOptions,
|
|
39
41
|
BlogPostFrontMatter,
|
|
@@ -43,6 +45,37 @@ import type {
|
|
|
43
45
|
BlogContent,
|
|
44
46
|
BlogPaginated,
|
|
45
47
|
} from '@docusaurus/plugin-content-blog';
|
|
48
|
+
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader/lib/loader';
|
|
49
|
+
import type {RuleSetUseItem} from 'webpack';
|
|
50
|
+
|
|
51
|
+
const PluginName = 'docusaurus-plugin-content-blog';
|
|
52
|
+
|
|
53
|
+
// TODO this is bad, we should have a better way to do this (new lifecycle?)
|
|
54
|
+
// The source to permalink is currently a mutable map passed to the mdx loader
|
|
55
|
+
// for link resolution
|
|
56
|
+
// see https://github.com/facebook/docusaurus/pull/10185
|
|
57
|
+
function createSourceToPermalinkHelper() {
|
|
58
|
+
const sourceToPermalink: SourceToPermalink = new Map();
|
|
59
|
+
|
|
60
|
+
function computeSourceToPermalink(content: BlogContent): SourceToPermalink {
|
|
61
|
+
return new Map(
|
|
62
|
+
content.blogPosts.map(({metadata: {source, permalink}}) => [
|
|
63
|
+
source,
|
|
64
|
+
permalink,
|
|
65
|
+
]),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Mutable map update :/
|
|
70
|
+
function update(content: BlogContent): void {
|
|
71
|
+
sourceToPermalink.clear();
|
|
72
|
+
computeSourceToPermalink(content).forEach((value, key) => {
|
|
73
|
+
sourceToPermalink.set(key, value);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {get: () => sourceToPermalink, update};
|
|
78
|
+
}
|
|
46
79
|
|
|
47
80
|
export default async function pluginContentBlog(
|
|
48
81
|
context: LoadContext,
|
|
@@ -55,22 +88,29 @@ export default async function pluginContentBlog(
|
|
|
55
88
|
localizationDir,
|
|
56
89
|
i18n: {currentLocale},
|
|
57
90
|
} = context;
|
|
91
|
+
|
|
92
|
+
const router = siteConfig.future.experimental_router;
|
|
93
|
+
const isBlogFeedDisabledBecauseOfHashRouter =
|
|
94
|
+
router === 'hash' && !!options.feedOptions.type;
|
|
95
|
+
if (isBlogFeedDisabledBecauseOfHashRouter) {
|
|
96
|
+
logger.warn(
|
|
97
|
+
`${PluginName} feed feature does not support the Hash Router. Feeds won't be generated.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
58
101
|
const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
|
|
59
102
|
|
|
60
103
|
const contentPaths: BlogContentPaths = {
|
|
61
104
|
contentPath: path.resolve(siteDir, options.path),
|
|
62
105
|
contentPathLocalized: getPluginI18nPath({
|
|
63
106
|
localizationDir,
|
|
64
|
-
pluginName:
|
|
107
|
+
pluginName: PluginName,
|
|
65
108
|
pluginId: options.id,
|
|
66
109
|
}),
|
|
67
110
|
};
|
|
68
111
|
const pluginId = options.id ?? DEFAULT_PLUGIN_ID;
|
|
69
112
|
|
|
70
|
-
const pluginDataDirRoot = path.join(
|
|
71
|
-
generatedFilesDir,
|
|
72
|
-
'docusaurus-plugin-content-blog',
|
|
73
|
-
);
|
|
113
|
+
const pluginDataDirRoot = path.join(generatedFilesDir, PluginName);
|
|
74
114
|
const dataDir = path.join(pluginDataDirRoot, pluginId);
|
|
75
115
|
// TODO Docusaurus v4 breaking change
|
|
76
116
|
// module aliasing should be automatic
|
|
@@ -83,8 +123,10 @@ export default async function pluginContentBlog(
|
|
|
83
123
|
contentPaths,
|
|
84
124
|
});
|
|
85
125
|
|
|
126
|
+
const sourceToPermalinkHelper = createSourceToPermalinkHelper();
|
|
127
|
+
|
|
86
128
|
return {
|
|
87
|
-
name:
|
|
129
|
+
name: PluginName,
|
|
88
130
|
|
|
89
131
|
getPathsToWatch() {
|
|
90
132
|
const {include} = options;
|
|
@@ -92,9 +134,16 @@ export default async function pluginContentBlog(
|
|
|
92
134
|
(contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
|
|
93
135
|
);
|
|
94
136
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
137
|
+
const tagsFilePaths = getTagsFilePathsToWatch({
|
|
138
|
+
contentPaths,
|
|
139
|
+
tags: options.tags,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return [
|
|
143
|
+
authorsMapFilePath,
|
|
144
|
+
...tagsFilePaths,
|
|
145
|
+
...contentMarkdownGlobs,
|
|
146
|
+
].filter(Boolean) as string[];
|
|
98
147
|
},
|
|
99
148
|
|
|
100
149
|
getTranslationFiles() {
|
|
@@ -181,6 +230,8 @@ export default async function pluginContentBlog(
|
|
|
181
230
|
},
|
|
182
231
|
|
|
183
232
|
async contentLoaded({content, actions}) {
|
|
233
|
+
sourceToPermalinkHelper.update(content);
|
|
234
|
+
|
|
184
235
|
await createAllRoutes({
|
|
185
236
|
baseUrl,
|
|
186
237
|
content,
|
|
@@ -194,7 +245,7 @@ export default async function pluginContentBlog(
|
|
|
194
245
|
return translateContent(content, translationFiles);
|
|
195
246
|
},
|
|
196
247
|
|
|
197
|
-
configureWebpack(
|
|
248
|
+
configureWebpack() {
|
|
198
249
|
const {
|
|
199
250
|
admonitions,
|
|
200
251
|
rehypePlugins,
|
|
@@ -204,22 +255,80 @@ export default async function pluginContentBlog(
|
|
|
204
255
|
beforeDefaultRehypePlugins,
|
|
205
256
|
} = options;
|
|
206
257
|
|
|
207
|
-
const markdownLoaderOptions: BlogMarkdownLoaderOptions = {
|
|
208
|
-
siteDir,
|
|
209
|
-
contentPaths,
|
|
210
|
-
truncateMarker,
|
|
211
|
-
sourceToPermalink: getSourceToPermalink(content.blogPosts),
|
|
212
|
-
onBrokenMarkdownLink: (brokenMarkdownLink) => {
|
|
213
|
-
if (onBrokenMarkdownLinks === 'ignore') {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
logger.report(
|
|
217
|
-
onBrokenMarkdownLinks,
|
|
218
|
-
)`Blog markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath}`;
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
258
|
const contentDirs = getContentPathList(contentPaths);
|
|
259
|
+
|
|
260
|
+
function createMDXLoader(): RuleSetUseItem {
|
|
261
|
+
const loaderOptions: MDXLoaderOptions = {
|
|
262
|
+
admonitions,
|
|
263
|
+
remarkPlugins,
|
|
264
|
+
rehypePlugins,
|
|
265
|
+
beforeDefaultRemarkPlugins: [
|
|
266
|
+
footnoteIDFixer,
|
|
267
|
+
...beforeDefaultRemarkPlugins,
|
|
268
|
+
],
|
|
269
|
+
beforeDefaultRehypePlugins,
|
|
270
|
+
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
|
271
|
+
path.resolve(siteDir, dir),
|
|
272
|
+
),
|
|
273
|
+
siteDir,
|
|
274
|
+
isMDXPartial: createAbsoluteFilePathMatcher(
|
|
275
|
+
options.exclude,
|
|
276
|
+
contentDirs,
|
|
277
|
+
),
|
|
278
|
+
metadataPath: (mdxPath: string) => {
|
|
279
|
+
// Note that metadataPath must be the same/in-sync as
|
|
280
|
+
// the path from createData for each MDX.
|
|
281
|
+
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
|
|
282
|
+
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
|
|
283
|
+
},
|
|
284
|
+
// For blog posts a title in markdown is always removed
|
|
285
|
+
// Blog posts title are rendered separately
|
|
286
|
+
removeContentTitle: true,
|
|
287
|
+
// Assets allow to convert some relative images paths to
|
|
288
|
+
// require() calls
|
|
289
|
+
// @ts-expect-error: TODO fix typing issue
|
|
290
|
+
createAssets: ({
|
|
291
|
+
frontMatter,
|
|
292
|
+
metadata,
|
|
293
|
+
}: {
|
|
294
|
+
frontMatter: BlogPostFrontMatter;
|
|
295
|
+
metadata: BlogPostMetadata;
|
|
296
|
+
}): Assets => ({
|
|
297
|
+
image: frontMatter.image,
|
|
298
|
+
authorsImageUrls: metadata.authors.map((author) => author.imageURL),
|
|
299
|
+
}),
|
|
300
|
+
markdownConfig: siteConfig.markdown,
|
|
301
|
+
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
|
302
|
+
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
|
303
|
+
sourceFilePath,
|
|
304
|
+
sourceToPermalink: sourceToPermalinkHelper.get(),
|
|
305
|
+
siteDir,
|
|
306
|
+
contentPaths,
|
|
307
|
+
});
|
|
308
|
+
if (permalink === null) {
|
|
309
|
+
logger.report(
|
|
310
|
+
onBrokenMarkdownLinks,
|
|
311
|
+
)`Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
|
|
312
|
+
}
|
|
313
|
+
return permalink;
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
loader: require.resolve('@docusaurus/mdx-loader'),
|
|
318
|
+
options: loaderOptions,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function createBlogMarkdownLoader(): RuleSetUseItem {
|
|
323
|
+
const loaderOptions: BlogMarkdownLoaderOptions = {
|
|
324
|
+
truncateMarker,
|
|
325
|
+
};
|
|
326
|
+
return {
|
|
327
|
+
loader: path.resolve(__dirname, './markdownLoader.js'),
|
|
328
|
+
options: loaderOptions,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
223
332
|
return {
|
|
224
333
|
resolve: {
|
|
225
334
|
alias: {
|
|
@@ -233,61 +342,7 @@ export default async function pluginContentBlog(
|
|
|
233
342
|
include: contentDirs
|
|
234
343
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
|
235
344
|
.map(addTrailingPathSeparator),
|
|
236
|
-
use: [
|
|
237
|
-
{
|
|
238
|
-
loader: require.resolve('@docusaurus/mdx-loader'),
|
|
239
|
-
options: {
|
|
240
|
-
admonitions,
|
|
241
|
-
remarkPlugins,
|
|
242
|
-
rehypePlugins,
|
|
243
|
-
beforeDefaultRemarkPlugins: [
|
|
244
|
-
footnoteIDFixer,
|
|
245
|
-
...beforeDefaultRemarkPlugins,
|
|
246
|
-
],
|
|
247
|
-
beforeDefaultRehypePlugins,
|
|
248
|
-
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
|
249
|
-
path.resolve(siteDir, dir),
|
|
250
|
-
),
|
|
251
|
-
siteDir,
|
|
252
|
-
isMDXPartial: createAbsoluteFilePathMatcher(
|
|
253
|
-
options.exclude,
|
|
254
|
-
contentDirs,
|
|
255
|
-
),
|
|
256
|
-
metadataPath: (mdxPath: string) => {
|
|
257
|
-
// Note that metadataPath must be the same/in-sync as
|
|
258
|
-
// the path from createData for each MDX.
|
|
259
|
-
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
|
|
260
|
-
return path.join(
|
|
261
|
-
dataDir,
|
|
262
|
-
`${docuHash(aliasedPath)}.json`,
|
|
263
|
-
);
|
|
264
|
-
},
|
|
265
|
-
// For blog posts a title in markdown is always removed
|
|
266
|
-
// Blog posts title are rendered separately
|
|
267
|
-
removeContentTitle: true,
|
|
268
|
-
|
|
269
|
-
// Assets allow to convert some relative images paths to
|
|
270
|
-
// require() calls
|
|
271
|
-
createAssets: ({
|
|
272
|
-
frontMatter,
|
|
273
|
-
metadata,
|
|
274
|
-
}: {
|
|
275
|
-
frontMatter: BlogPostFrontMatter;
|
|
276
|
-
metadata: BlogPostMetadata;
|
|
277
|
-
}): Assets => ({
|
|
278
|
-
image: frontMatter.image,
|
|
279
|
-
authorsImageUrls: metadata.authors.map(
|
|
280
|
-
(author) => author.imageURL,
|
|
281
|
-
),
|
|
282
|
-
}),
|
|
283
|
-
markdownConfig: siteConfig.markdown,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
loader: path.resolve(__dirname, './markdownLoader.js'),
|
|
288
|
-
options: markdownLoaderOptions,
|
|
289
|
-
},
|
|
290
|
-
].filter(Boolean),
|
|
345
|
+
use: [createMDXLoader(), createBlogMarkdownLoader()],
|
|
291
346
|
},
|
|
292
347
|
],
|
|
293
348
|
},
|
|
@@ -295,15 +350,16 @@ export default async function pluginContentBlog(
|
|
|
295
350
|
},
|
|
296
351
|
|
|
297
352
|
async postBuild({outDir, content}) {
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
353
|
+
if (
|
|
354
|
+
!content.blogPosts.length ||
|
|
355
|
+
!options.feedOptions.type ||
|
|
356
|
+
isBlogFeedDisabledBecauseOfHashRouter
|
|
357
|
+
) {
|
|
303
358
|
return;
|
|
304
359
|
}
|
|
360
|
+
|
|
305
361
|
await createBlogFeedFiles({
|
|
306
|
-
blogPosts,
|
|
362
|
+
blogPosts: content.blogPosts,
|
|
307
363
|
options,
|
|
308
364
|
outDir,
|
|
309
365
|
siteConfig,
|
|
@@ -312,56 +368,15 @@ export default async function pluginContentBlog(
|
|
|
312
368
|
},
|
|
313
369
|
|
|
314
370
|
injectHtmlTags({content}) {
|
|
315
|
-
if (
|
|
371
|
+
if (
|
|
372
|
+
!content.blogPosts.length ||
|
|
373
|
+
!options.feedOptions.type ||
|
|
374
|
+
isBlogFeedDisabledBecauseOfHashRouter
|
|
375
|
+
) {
|
|
316
376
|
return {};
|
|
317
377
|
}
|
|
318
378
|
|
|
319
|
-
|
|
320
|
-
const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
|
|
321
|
-
const feedsConfig = {
|
|
322
|
-
rss: {
|
|
323
|
-
type: 'application/rss+xml',
|
|
324
|
-
path: 'rss.xml',
|
|
325
|
-
title: `${feedTitle} RSS Feed`,
|
|
326
|
-
},
|
|
327
|
-
atom: {
|
|
328
|
-
type: 'application/atom+xml',
|
|
329
|
-
path: 'atom.xml',
|
|
330
|
-
title: `${feedTitle} Atom Feed`,
|
|
331
|
-
},
|
|
332
|
-
json: {
|
|
333
|
-
type: 'application/json',
|
|
334
|
-
path: 'feed.json',
|
|
335
|
-
title: `${feedTitle} JSON Feed`,
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
const headTags: HtmlTags = [];
|
|
339
|
-
|
|
340
|
-
feedTypes.forEach((feedType) => {
|
|
341
|
-
const {
|
|
342
|
-
type,
|
|
343
|
-
path: feedConfigPath,
|
|
344
|
-
title: feedConfigTitle,
|
|
345
|
-
} = feedsConfig[feedType];
|
|
346
|
-
|
|
347
|
-
headTags.push({
|
|
348
|
-
tagName: 'link',
|
|
349
|
-
attributes: {
|
|
350
|
-
rel: 'alternate',
|
|
351
|
-
type,
|
|
352
|
-
href: normalizeUrl([
|
|
353
|
-
baseUrl,
|
|
354
|
-
options.routeBasePath,
|
|
355
|
-
feedConfigPath,
|
|
356
|
-
]),
|
|
357
|
-
title: feedConfigTitle,
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
return {
|
|
363
|
-
headTags,
|
|
364
|
-
};
|
|
379
|
+
return {headTags: createFeedHtmlHeadTags({context, options})};
|
|
365
380
|
},
|
|
366
381
|
};
|
|
367
382
|
}
|
package/src/markdownLoader.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {truncate
|
|
8
|
+
import {truncate} from './blogUtils';
|
|
9
9
|
import type {BlogMarkdownLoaderOptions} from './types';
|
|
10
10
|
import type {LoaderContext} from 'webpack';
|
|
11
11
|
|
|
@@ -13,23 +13,19 @@ export default function markdownLoader(
|
|
|
13
13
|
this: LoaderContext<BlogMarkdownLoaderOptions>,
|
|
14
14
|
source: string,
|
|
15
15
|
): void {
|
|
16
|
-
const filePath = this.resourcePath;
|
|
17
16
|
const fileString = source;
|
|
18
17
|
const callback = this.async();
|
|
19
18
|
const markdownLoaderOptions = this.getOptions();
|
|
20
19
|
|
|
21
20
|
// Linkify blog posts
|
|
22
|
-
let finalContent =
|
|
23
|
-
fileString,
|
|
24
|
-
filePath,
|
|
25
|
-
...markdownLoaderOptions,
|
|
26
|
-
});
|
|
21
|
+
let finalContent = fileString;
|
|
27
22
|
|
|
28
23
|
// Truncate content if requested (e.g: file.md?truncated=true).
|
|
29
24
|
const truncated: boolean | undefined = this.resourceQuery
|
|
30
25
|
? !!new URLSearchParams(this.resourceQuery.slice(1)).get('truncated')
|
|
31
26
|
: undefined;
|
|
32
27
|
|
|
28
|
+
// TODO truncate with the AST instead of the string ?
|
|
33
29
|
if (truncated) {
|
|
34
30
|
finalContent = truncate(finalContent, markdownLoaderOptions.truncateMarker);
|
|
35
31
|
}
|
package/src/options.ts
CHANGED
|
@@ -54,6 +54,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
54
54
|
showLastUpdateTime: false,
|
|
55
55
|
showLastUpdateAuthor: false,
|
|
56
56
|
processBlogPosts: async () => undefined,
|
|
57
|
+
onInlineTags: 'warn',
|
|
58
|
+
tags: undefined,
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
@@ -144,6 +146,13 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
144
146
|
processBlogPosts: Joi.function()
|
|
145
147
|
.optional()
|
|
146
148
|
.default(() => DEFAULT_OPTIONS.processBlogPosts),
|
|
149
|
+
onInlineTags: Joi.string()
|
|
150
|
+
.equal('ignore', 'log', 'warn', 'throw')
|
|
151
|
+
.default(DEFAULT_OPTIONS.onInlineTags),
|
|
152
|
+
tags: Joi.string()
|
|
153
|
+
.disallow('')
|
|
154
|
+
.allow(null, false)
|
|
155
|
+
.default(() => DEFAULT_OPTIONS.tags),
|
|
147
156
|
}).default(DEFAULT_OPTIONS);
|
|
148
157
|
|
|
149
158
|
export function validateOptions({
|