@docusaurus/plugin-content-blog 2.0.0-beta.15a2b59f9 → 2.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/authors.d.ts +20 -0
  2. package/lib/authors.js +110 -0
  3. package/lib/blogFrontMatter.d.ts +1 -21
  4. package/lib/blogFrontMatter.js +31 -19
  5. package/lib/blogUtils.d.ts +24 -6
  6. package/lib/blogUtils.js +196 -143
  7. package/lib/feed.d.ts +15 -0
  8. package/lib/feed.js +99 -0
  9. package/lib/index.d.ts +4 -3
  10. package/lib/index.js +149 -163
  11. package/lib/markdownLoader.d.ts +3 -6
  12. package/lib/markdownLoader.js +5 -6
  13. package/lib/pluginOptionSchema.d.ts +3 -26
  14. package/lib/pluginOptionSchema.js +35 -10
  15. package/lib/translations.d.ts +11 -0
  16. package/lib/translations.js +53 -0
  17. package/lib/types.d.ts +10 -46
  18. package/package.json +21 -18
  19. package/src/authors.ts +153 -0
  20. package/src/blogFrontMatter.ts +44 -51
  21. package/src/blogUtils.ts +289 -195
  22. package/{types.d.ts → src/deps.d.ts} +0 -0
  23. package/src/feed.ts +170 -0
  24. package/src/index.ts +197 -194
  25. package/src/markdownLoader.ts +10 -15
  26. package/src/plugin-content-blog.d.ts +270 -0
  27. package/src/pluginOptionSchema.ts +41 -13
  28. package/src/translations.ts +64 -0
  29. package/src/types.ts +19 -53
  30. package/index.d.ts +0 -138
  31. package/lib/.tsbuildinfo +0 -1
  32. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  33. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  34. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  35. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  36. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  37. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  38. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  39. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  40. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  41. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  42. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  43. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -76
  44. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  45. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  46. package/src/__tests__/blogFrontMatter.test.ts +0 -317
  47. package/src/__tests__/generateBlogFeed.test.ts +0 -100
  48. package/src/__tests__/index.test.ts +0 -336
  49. package/src/__tests__/linkify.test.ts +0 -93
  50. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  51. package/tsconfig.json +0 -9
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import fs from 'fs-extra';
9
8
  import path from 'path';
10
9
  import admonitions from 'remark-admonitions';
11
10
  import {
@@ -16,46 +15,50 @@ import {
16
15
  reportMessage,
17
16
  posixPath,
18
17
  addTrailingPathSeparator,
19
- } from '@docusaurus/utils';
20
- import {
21
- STATIC_DIR_NAME,
18
+ createAbsoluteFilePathMatcher,
19
+ getContentPathList,
20
+ getDataFilePath,
22
21
  DEFAULT_PLUGIN_ID,
23
- } from '@docusaurus/core/lib/constants';
24
- import {flatten, take, kebabCase} from 'lodash';
22
+ } from '@docusaurus/utils';
23
+ import {translateContent, getTranslationFiles} from './translations';
25
24
 
26
- import {
27
- PluginOptions,
25
+ import type {
26
+ BlogTag,
28
27
  BlogTags,
29
28
  BlogContent,
30
29
  BlogItemsToMetadata,
31
30
  TagsModule,
32
31
  BlogPaginated,
33
- BlogPost,
34
32
  BlogContentPaths,
35
33
  BlogMarkdownLoaderOptions,
34
+ MetaData,
35
+ TagModule,
36
36
  } from './types';
37
37
  import {PluginOptionSchema} from './pluginOptionSchema';
38
- import {
38
+ import type {
39
39
  LoadContext,
40
- ConfigureWebpackUtils,
41
- Props,
42
40
  Plugin,
43
41
  HtmlTags,
44
42
  OptionValidationContext,
45
43
  ValidationResult,
46
44
  } from '@docusaurus/types';
47
- import {Configuration} from 'webpack';
48
45
  import {
49
- generateBlogFeed,
50
46
  generateBlogPosts,
51
- getContentPathList,
52
47
  getSourceToPermalink,
48
+ getBlogTags,
49
+ paginateBlogPosts,
53
50
  } from './blogUtils';
51
+ import {createBlogFeedFiles} from './feed';
52
+ import type {
53
+ PluginOptions,
54
+ BlogPostFrontMatter,
55
+ Assets,
56
+ } from '@docusaurus/plugin-content-blog';
54
57
 
55
- export default function pluginContentBlog(
58
+ export default async function pluginContentBlog(
56
59
  context: LoadContext,
57
60
  options: PluginOptions,
58
- ): Plugin<BlogContent> {
61
+ ): Promise<Plugin<BlogContent>> {
59
62
  if (options.admonitions) {
60
63
  options.remarkPlugins = options.remarkPlugins.concat([
61
64
  [admonitions, options.admonitions],
@@ -64,10 +67,11 @@ export default function pluginContentBlog(
64
67
 
65
68
  const {
66
69
  siteDir,
67
- siteConfig: {onBrokenMarkdownLinks},
70
+ siteConfig,
68
71
  generatedFilesDir,
69
72
  i18n: {currentLocale},
70
73
  } = context;
74
+ const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
71
75
 
72
76
  const contentPaths: BlogContentPaths = {
73
77
  contentPath: path.resolve(siteDir, options.path),
@@ -88,44 +92,50 @@ export default function pluginContentBlog(
88
92
  const aliasedSource = (source: string) =>
89
93
  `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
90
94
 
95
+ const authorsMapFilePath = await getDataFilePath({
96
+ filePath: options.authorsMapPath,
97
+ contentPaths,
98
+ });
99
+
91
100
  return {
92
101
  name: 'docusaurus-plugin-content-blog',
93
102
 
94
103
  getPathsToWatch() {
95
- const {include = []} = options;
96
- return flatten(
97
- getContentPathList(contentPaths).map((contentPath) => {
98
- return include.map((pattern) => `${contentPath}/${pattern}`);
99
- }),
104
+ const {include} = options;
105
+ const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
106
+ (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
100
107
  );
101
- },
102
-
103
- getClientModules() {
104
- const modules = [];
105
108
 
106
- if (options.admonitions) {
107
- modules.push(require.resolve('remark-admonitions/styles/infima.css'));
108
- }
109
+ return [authorsMapFilePath, ...contentMarkdownGlobs].filter(
110
+ Boolean,
111
+ ) as string[];
112
+ },
109
113
 
110
- return modules;
114
+ async getTranslationFiles() {
115
+ return getTranslationFiles(options);
111
116
  },
112
117
 
113
118
  // Fetches blog contents and returns metadata for the necessary routes.
114
119
  async loadContent() {
115
- const {postsPerPage, routeBasePath} = options;
120
+ const {
121
+ postsPerPage: postsPerPageOption,
122
+ routeBasePath,
123
+ tagsBasePath,
124
+ blogDescription,
125
+ blogTitle,
126
+ blogSidebarTitle,
127
+ } = options;
116
128
 
117
- const blogPosts: BlogPost[] = await generateBlogPosts(
118
- contentPaths,
119
- context,
120
- options,
121
- );
129
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
122
130
 
123
131
  if (!blogPosts.length) {
124
132
  return {
133
+ blogSidebarTitle,
125
134
  blogPosts: [],
126
135
  blogListPaginated: [],
127
136
  blogTags: {},
128
137
  blogTagsListPath: null,
138
+ blogTagsPaginated: [],
129
139
  };
130
140
  }
131
141
 
@@ -149,85 +159,30 @@ export default function pluginContentBlog(
149
159
  }
150
160
  });
151
161
 
152
- // Blog pagination routes.
153
- // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
154
- const totalCount = blogPosts.length;
155
- const numberOfPages = Math.ceil(totalCount / postsPerPage);
156
- const {
157
- siteConfig: {baseUrl = ''},
158
- } = context;
159
- const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
160
-
161
- const blogListPaginated: BlogPaginated[] = [];
162
+ const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
162
163
 
163
- function blogPaginationPermalink(page: number) {
164
- return page > 0
165
- ? normalizeUrl([basePageUrl, `page/${page + 1}`])
166
- : basePageUrl;
167
- }
168
-
169
- for (let page = 0; page < numberOfPages; page += 1) {
170
- blogListPaginated.push({
171
- metadata: {
172
- permalink: blogPaginationPermalink(page),
173
- page: page + 1,
174
- postsPerPage,
175
- totalPages: numberOfPages,
176
- totalCount,
177
- previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
178
- nextPage:
179
- page < numberOfPages - 1
180
- ? blogPaginationPermalink(page + 1)
181
- : null,
182
- blogDescription: options.blogDescription,
183
- blogTitle: options.blogTitle,
184
- },
185
- items: blogPosts
186
- .slice(page * postsPerPage, (page + 1) * postsPerPage)
187
- .map((item) => item.id),
188
- });
189
- }
190
-
191
- const blogTags: BlogTags = {};
192
- const tagsPath = normalizeUrl([basePageUrl, 'tags']);
193
- blogPosts.forEach((blogPost) => {
194
- const {tags} = blogPost.metadata;
195
- if (!tags || tags.length === 0) {
196
- // TODO: Extract tags out into a separate plugin.
197
- // eslint-disable-next-line no-param-reassign
198
- blogPost.metadata.tags = [];
199
- return;
200
- }
164
+ const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
165
+ blogPosts,
166
+ blogTitle,
167
+ blogDescription,
168
+ postsPerPageOption,
169
+ basePageUrl: baseBlogUrl,
170
+ });
201
171
 
202
- // eslint-disable-next-line no-param-reassign
203
- blogPost.metadata.tags = tags.map((tag) => {
204
- if (typeof tag === 'string') {
205
- const normalizedTag = kebabCase(tag);
206
- const permalink = normalizeUrl([tagsPath, normalizedTag]);
207
- if (!blogTags[normalizedTag]) {
208
- blogTags[normalizedTag] = {
209
- // Will only use the name of the first occurrence of the tag.
210
- name: tag.toLowerCase(),
211
- items: [],
212
- permalink,
213
- };
214
- }
215
-
216
- blogTags[normalizedTag].items.push(blogPost.id);
217
-
218
- return {
219
- label: tag,
220
- permalink,
221
- };
222
- }
223
- return tag;
224
- });
172
+ const blogTags: BlogTags = getBlogTags({
173
+ blogPosts,
174
+ postsPerPageOption,
175
+ blogDescription,
176
+ blogTitle,
225
177
  });
226
178
 
179
+ const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
180
+
227
181
  const blogTagsListPath =
228
182
  Object.keys(blogTags).length > 0 ? tagsPath : null;
229
183
 
230
184
  return {
185
+ blogSidebarTitle,
231
186
  blogPosts,
232
187
  blogListPaginated,
233
188
  blogTags,
@@ -245,10 +200,14 @@ export default function pluginContentBlog(
245
200
  blogPostComponent,
246
201
  blogTagsListComponent,
247
202
  blogTagsPostsComponent,
203
+ blogArchiveComponent,
204
+ routeBasePath,
205
+ archiveBasePath,
248
206
  } = options;
249
207
 
250
208
  const {addRoute, createData} = actions;
251
209
  const {
210
+ blogSidebarTitle,
252
211
  blogPosts,
253
212
  blogListPaginated,
254
213
  blogTags,
@@ -260,7 +219,28 @@ export default function pluginContentBlog(
260
219
  const sidebarBlogPosts =
261
220
  options.blogSidebarCount === 'ALL'
262
221
  ? blogPosts
263
- : take(blogPosts, options.blogSidebarCount);
222
+ : blogPosts.slice(0, options.blogSidebarCount);
223
+
224
+ if (archiveBasePath) {
225
+ const archiveUrl = normalizeUrl([
226
+ baseUrl,
227
+ routeBasePath,
228
+ archiveBasePath,
229
+ ]);
230
+ // creates a blog archive route
231
+ const archiveProp = await createData(
232
+ `${docuHash(archiveUrl)}.json`,
233
+ JSON.stringify({blogPosts}, null, 2),
234
+ );
235
+ addRoute({
236
+ path: archiveUrl,
237
+ component: blogArchiveComponent,
238
+ exact: true,
239
+ modules: {
240
+ archive: aliasedSource(archiveProp),
241
+ },
242
+ });
243
+ }
264
244
 
265
245
  // This prop is useful to provide the blog list sidebar
266
246
  const sidebarProp = await createData(
@@ -269,7 +249,7 @@ export default function pluginContentBlog(
269
249
  `blog-post-list-prop-${pluginId}.json`,
270
250
  JSON.stringify(
271
251
  {
272
- title: options.blogSidebarTitle,
252
+ title: blogSidebarTitle,
273
253
  items: sidebarBlogPosts.map((blogPost) => ({
274
254
  title: blogPost.metadata.title,
275
255
  permalink: blogPost.metadata.permalink,
@@ -321,9 +301,10 @@ export default function pluginContentBlog(
321
301
  exact: true,
322
302
  modules: {
323
303
  sidebar: aliasedSource(sidebarProp),
324
- items: items.map((postID) => {
325
- // To tell routes.js this is an import and not a nested object to recurse.
326
- return {
304
+ items: items.map((postID) =>
305
+ // To tell routes.js this is an import and not a nested object
306
+ // to recurse.
307
+ ({
327
308
  content: {
328
309
  __import: true,
329
310
  path: blogItemsToMetadata[postID].source,
@@ -331,8 +312,8 @@ export default function pluginContentBlog(
331
312
  truncated: true,
332
313
  },
333
314
  },
334
- };
335
- }),
315
+ }),
316
+ ),
336
317
  metadata: aliasedSource(pageMetadataPath),
337
318
  },
338
319
  });
@@ -344,49 +325,61 @@ export default function pluginContentBlog(
344
325
  return;
345
326
  }
346
327
 
347
- const tagsModule: TagsModule = {};
348
-
349
- await Promise.all(
350
- Object.keys(blogTags).map(async (tag) => {
351
- const {name, items, permalink} = blogTags[tag];
352
-
353
- tagsModule[tag] = {
328
+ const tagsModule: TagsModule = Object.fromEntries(
329
+ Object.entries(blogTags).map(([tagKey, tag]) => {
330
+ const tagModule: TagModule = {
354
331
  allTagsPath: blogTagsListPath,
355
- slug: tag,
356
- name,
357
- count: items.length,
358
- permalink,
332
+ slug: tagKey,
333
+ name: tag.name,
334
+ count: tag.items.length,
335
+ permalink: tag.permalink,
359
336
  };
360
-
361
- const tagsMetadataPath = await createData(
362
- `${docuHash(permalink)}.json`,
363
- JSON.stringify(tagsModule[tag], null, 2),
364
- );
365
-
366
- addRoute({
367
- path: permalink,
368
- component: blogTagsPostsComponent,
369
- exact: true,
370
- modules: {
371
- sidebar: aliasedSource(sidebarProp),
372
- items: items.map((postID) => {
373
- const metadata = blogItemsToMetadata[postID];
374
- return {
375
- content: {
376
- __import: true,
377
- path: metadata.source,
378
- query: {
379
- truncated: true,
380
- },
381
- },
382
- };
383
- }),
384
- metadata: aliasedSource(tagsMetadataPath),
385
- },
386
- });
337
+ return [tag.name, tagModule];
387
338
  }),
388
339
  );
389
340
 
341
+ async function createTagRoutes(tag: BlogTag): Promise<void> {
342
+ await Promise.all(
343
+ tag.pages.map(async (blogPaginated) => {
344
+ const {metadata, items} = blogPaginated;
345
+ const tagsMetadataPath = await createData(
346
+ `${docuHash(metadata.permalink)}.json`,
347
+ JSON.stringify(tagsModule[tag.name], null, 2),
348
+ );
349
+
350
+ const listMetadataPath = await createData(
351
+ `${docuHash(metadata.permalink)}-list.json`,
352
+ JSON.stringify(metadata, null, 2),
353
+ );
354
+
355
+ addRoute({
356
+ path: metadata.permalink,
357
+ component: blogTagsPostsComponent,
358
+ exact: true,
359
+ modules: {
360
+ sidebar: aliasedSource(sidebarProp),
361
+ items: items.map((postID) => {
362
+ const blogPostMetadata = blogItemsToMetadata[postID];
363
+ return {
364
+ content: {
365
+ __import: true,
366
+ path: blogPostMetadata.source,
367
+ query: {
368
+ truncated: true,
369
+ },
370
+ },
371
+ };
372
+ }),
373
+ metadata: aliasedSource(tagsMetadataPath),
374
+ listMetadata: aliasedSource(listMetadataPath),
375
+ },
376
+ });
377
+ }),
378
+ );
379
+ }
380
+
381
+ await Promise.all(Object.values(blogTags).map(createTagRoutes));
382
+
390
383
  // Only create /tags page if there are tags.
391
384
  if (Object.keys(blogTags).length > 0) {
392
385
  const tagsListPath = await createData(
@@ -406,12 +399,11 @@ export default function pluginContentBlog(
406
399
  }
407
400
  },
408
401
 
409
- configureWebpack(
410
- _config: Configuration,
411
- isServer: boolean,
412
- {getJSLoader}: ConfigureWebpackUtils,
413
- content,
414
- ) {
402
+ translateContent({content, translationFiles}) {
403
+ return translateContent(content, translationFiles);
404
+ },
405
+
406
+ configureWebpack(_config, isServer, {getJSLoader}, content) {
415
407
  const {
416
408
  rehypePlugins,
417
409
  remarkPlugins,
@@ -436,6 +428,7 @@ export default function pluginContentBlog(
436
428
  },
437
429
  };
438
430
 
431
+ const contentDirs = getContentPathList(contentPaths);
439
432
  return {
440
433
  resolve: {
441
434
  alias: {
@@ -445,8 +438,8 @@ export default function pluginContentBlog(
445
438
  module: {
446
439
  rules: [
447
440
  {
448
- test: /(\.mdx?)$/,
449
- include: getContentPathList(contentPaths)
441
+ test: /\.mdx?$/i,
442
+ include: contentDirs
450
443
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
451
444
  .map(addTrailingPathSeparator),
452
445
  use: [
@@ -458,10 +451,17 @@ export default function pluginContentBlog(
458
451
  rehypePlugins,
459
452
  beforeDefaultRemarkPlugins,
460
453
  beforeDefaultRehypePlugins,
461
- staticDir: path.join(siteDir, STATIC_DIR_NAME),
462
- // Note that metadataPath must be the same/in-sync as
463
- // the path from createData for each MDX.
454
+ staticDirs: siteConfig.staticDirectories.map((dir) =>
455
+ path.resolve(siteDir, dir),
456
+ ),
457
+ siteDir,
458
+ isMDXPartial: createAbsoluteFilePathMatcher(
459
+ options.exclude,
460
+ contentDirs,
461
+ ),
464
462
  metadataPath: (mdxPath: string) => {
463
+ // Note that metadataPath must be the same/in-sync as
464
+ // the path from createData for each MDX.
465
465
  const aliasedPath = aliasedSitePath(mdxPath, siteDir);
466
466
  return path.join(
467
467
  dataDir,
@@ -471,6 +471,21 @@ export default function pluginContentBlog(
471
471
  // For blog posts a title in markdown is always removed
472
472
  // Blog posts title are rendered separately
473
473
  removeContentTitle: true,
474
+
475
+ // Assets allow to convert some relative images paths to
476
+ // require() calls
477
+ createAssets: ({
478
+ frontMatter,
479
+ metadata,
480
+ }: {
481
+ frontMatter: BlogPostFrontMatter;
482
+ metadata: MetaData;
483
+ }): Assets => ({
484
+ image: frontMatter.image,
485
+ authorsImageUrls: metadata.authors.map(
486
+ (author) => author.imageURL,
487
+ ),
488
+ }),
474
489
  },
475
490
  },
476
491
  {
@@ -484,34 +499,20 @@ export default function pluginContentBlog(
484
499
  };
485
500
  },
486
501
 
487
- async postBuild({outDir}: Props) {
488
- if (!options.feedOptions?.type) {
502
+ async postBuild({outDir, content}) {
503
+ if (!options.feedOptions.type) {
489
504
  return;
490
505
  }
491
-
492
- const feed = await generateBlogFeed(contentPaths, context, options);
493
-
494
- if (!feed) {
506
+ const {blogPosts} = content;
507
+ if (!blogPosts.length) {
495
508
  return;
496
509
  }
497
-
498
- const feedTypes = options.feedOptions.type;
499
-
500
- await Promise.all(
501
- feedTypes.map(async (feedType) => {
502
- const feedPath = path.join(
503
- outDir,
504
- options.routeBasePath,
505
- `${feedType}.xml`,
506
- );
507
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
508
- try {
509
- await fs.outputFile(feedPath, feedContent);
510
- } catch (err) {
511
- throw new Error(`Generating ${feedType} feed failed: ${err}.`);
512
- }
513
- }),
514
- );
510
+ await createBlogFeedFiles({
511
+ blogPosts,
512
+ options,
513
+ outDir,
514
+ siteConfig,
515
+ });
515
516
  },
516
517
 
517
518
  injectHtmlTags({content}) {
@@ -524,20 +525,22 @@ export default function pluginContentBlog(
524
525
  }
525
526
 
526
527
  const feedTypes = options.feedOptions.type;
527
- const {
528
- siteConfig: {title},
529
- baseUrl,
530
- } = context;
528
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
531
529
  const feedsConfig = {
532
530
  rss: {
533
531
  type: 'application/rss+xml',
534
532
  path: 'rss.xml',
535
- title: `${title} Blog RSS Feed`,
533
+ title: `${feedTitle} RSS Feed`,
536
534
  },
537
535
  atom: {
538
536
  type: 'application/atom+xml',
539
537
  path: 'atom.xml',
540
- title: `${title} Blog Atom Feed`,
538
+ title: `${feedTitle} Atom Feed`,
539
+ },
540
+ json: {
541
+ type: 'application/json',
542
+ path: 'feed.json',
543
+ title: `${feedTitle} JSON Feed`,
541
544
  },
542
545
  };
543
546
  const headTags: HtmlTags = [];
@@ -6,20 +6,17 @@
6
6
  */
7
7
 
8
8
  import {truncate, linkify} from './blogUtils';
9
- import {parseQuery} from 'loader-utils';
10
- import {BlogMarkdownLoaderOptions} from './types';
9
+ import type {BlogMarkdownLoaderOptions} from './types';
10
+ import type {LoaderContext} from 'webpack';
11
11
 
12
- // TODO temporary until Webpack5 export this type
13
- // see https://github.com/webpack/webpack/issues/11630
14
- interface Loader extends Function {
15
- (this: any, source: string): string | Buffer | void | undefined;
16
- }
17
-
18
- const markdownLoader: Loader = function (source) {
12
+ export default function markdownLoader(
13
+ this: LoaderContext<BlogMarkdownLoaderOptions>,
14
+ source: string,
15
+ ): void {
19
16
  const filePath = this.resourcePath;
20
- const fileString = source as string;
17
+ const fileString = source;
21
18
  const callback = this.async();
22
- const markdownLoaderOptions = this.getOptions() as BlogMarkdownLoaderOptions;
19
+ const markdownLoaderOptions = this.getOptions();
23
20
 
24
21
  // Linkify blog posts
25
22
  let finalContent = linkify({
@@ -30,7 +27,7 @@ const markdownLoader: Loader = function (source) {
30
27
 
31
28
  // Truncate content if requested (e.g: file.md?truncated=true).
32
29
  const truncated: boolean | undefined = this.resourceQuery
33
- ? !!parseQuery(this.resourceQuery).truncated
30
+ ? !!new URLSearchParams(this.resourceQuery.slice(1)).get('truncated')
34
31
  : undefined;
35
32
 
36
33
  if (truncated) {
@@ -38,6 +35,4 @@ const markdownLoader: Loader = function (source) {
38
35
  }
39
36
 
40
37
  return callback && callback(null, finalContent);
41
- };
42
-
43
- export default markdownLoader;
38
+ }