@docusaurus/plugin-content-blog 3.7.0 → 3.8.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/assets/atom.xsl CHANGED
@@ -71,7 +71,7 @@
71
71
  <div class="blog-posts">
72
72
  <xsl:for-each select="atom:feed/atom:entry">
73
73
  <div class="blog-post">
74
- <h3><a href="{atom:link[@rel='alternate']/@href}"><xsl:value-of
74
+ <h3><a href="{atom:link/@href}"><xsl:value-of
75
75
  select="atom:title"
76
76
  /></a></h3>
77
77
  <div class="blog-post-date">
package/lib/blogUtils.js CHANGED
@@ -19,12 +19,12 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
19
19
  const path_1 = tslib_1.__importDefault(require("path"));
20
20
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
21
21
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
22
- const reading_time_1 = tslib_1.__importDefault(require("reading-time"));
23
22
  const utils_1 = require("@docusaurus/utils");
24
23
  const utils_validation_1 = require("@docusaurus/utils-validation");
25
24
  const frontMatter_1 = require("./frontMatter");
26
25
  const authors_1 = require("./authors");
27
26
  const authorsProblems_1 = require("./authorsProblems");
27
+ const readingTime_1 = require("./readingTime");
28
28
  function truncate(fileString, truncateMarker) {
29
29
  return fileString.split(truncateMarker, 1).shift();
30
30
  }
@@ -130,7 +130,7 @@ async function parseBlogPostMarkdownFile({ filePath, parseFrontMatter, }) {
130
130
  throw err;
131
131
  }
132
132
  }
133
- const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
133
+ const defaultReadingTime = ({ content, locale, options }) => (0, readingTime_1.calculateReadingTime)(content, locale, options);
134
134
  async function processBlogSourceFile(blogSourceRelative, contentPaths, context, options, tagsFile, authorsMap) {
135
135
  const { siteConfig: { baseUrl, markdown: { parseFrontMatter }, }, siteDir, i18n, } = context;
136
136
  const { routeBasePath, tagsBasePath: tagsRouteBasePath, truncateMarker, showReadingTime, editUrl, } = options;
@@ -238,6 +238,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
238
238
  content,
239
239
  frontMatter,
240
240
  defaultReadingTime,
241
+ locale: i18n.currentLocale,
241
242
  })
242
243
  : undefined,
243
244
  hasTruncateMarker: truncateMarker.test(content),
package/lib/options.js CHANGED
@@ -51,7 +51,7 @@ exports.DEFAULT_OPTIONS = {
51
51
  path: 'blog',
52
52
  editLocalizedFiles: false,
53
53
  authorsMapPath: 'authors.yml',
54
- readingTime: ({ content, defaultReadingTime }) => defaultReadingTime({ content }),
54
+ readingTime: ({ content, defaultReadingTime, locale }) => defaultReadingTime({ content, locale }),
55
55
  sortPosts: 'descending',
56
56
  showLastUpdateTime: false,
57
57
  showLastUpdateAuthor: false,
@@ -0,0 +1,17 @@
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
+ * Calculates the reading time for a given content string using Intl.Segmenter.
9
+ * @param content The text content to calculate reading time for.
10
+ * @param locale Required locale string for Intl.Segmenter
11
+ * @param options Options for reading time calculation.
12
+ * - wordsPerMinute: number of words per minute (default 200)
13
+ * @returns Estimated reading time in minutes (float, rounded to 2 decimals)
14
+ */
15
+ export declare function calculateReadingTime(content: string, locale: string, options?: {
16
+ wordsPerMinute?: number;
17
+ }): number;
@@ -0,0 +1,45 @@
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.calculateReadingTime = calculateReadingTime;
10
+ const DEFAULT_WORDS_PER_MINUTE = 200;
11
+ /**
12
+ * Counts the number of words in a string using Intl.Segmenter.
13
+ * @param content The text content to count words in.
14
+ * @param locale The locale to use for segmentation.
15
+ */
16
+ function countWords(content, locale) {
17
+ if (!content) {
18
+ return 0;
19
+ }
20
+ const segmenter = new Intl.Segmenter(locale, { granularity: 'word' });
21
+ let wordCount = 0;
22
+ for (const { isWordLike } of segmenter.segment(content)) {
23
+ if (isWordLike) {
24
+ wordCount += 1;
25
+ }
26
+ }
27
+ return wordCount;
28
+ }
29
+ /**
30
+ * Calculates the reading time for a given content string using Intl.Segmenter.
31
+ * @param content The text content to calculate reading time for.
32
+ * @param locale Required locale string for Intl.Segmenter
33
+ * @param options Options for reading time calculation.
34
+ * - wordsPerMinute: number of words per minute (default 200)
35
+ * @returns Estimated reading time in minutes (float, rounded to 2 decimals)
36
+ */
37
+ function calculateReadingTime(content, locale, options) {
38
+ const wordsPerMinute = options?.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE;
39
+ const words = countWords(content, locale);
40
+ if (words === 0) {
41
+ return 0;
42
+ }
43
+ // Calculate reading time in minutes and round to 2 decimal places
44
+ return Math.round((words / wordsPerMinute) * 100) / 100;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
@@ -31,19 +31,19 @@
31
31
  },
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@docusaurus/core": "3.7.0",
35
- "@docusaurus/logger": "3.7.0",
36
- "@docusaurus/mdx-loader": "3.7.0",
37
- "@docusaurus/theme-common": "3.7.0",
38
- "@docusaurus/types": "3.7.0",
39
- "@docusaurus/utils": "3.7.0",
40
- "@docusaurus/utils-common": "3.7.0",
41
- "@docusaurus/utils-validation": "3.7.0",
34
+ "@docusaurus/core": "3.8.0",
35
+ "@docusaurus/logger": "3.8.0",
36
+ "@docusaurus/mdx-loader": "3.8.0",
37
+ "@docusaurus/theme-common": "3.8.0",
38
+ "@docusaurus/types": "3.8.0",
39
+ "@docusaurus/utils": "3.8.0",
40
+ "@docusaurus/utils-common": "3.8.0",
41
+ "@docusaurus/utils-validation": "3.8.0",
42
42
  "cheerio": "1.0.0-rc.12",
43
43
  "feed": "^4.2.2",
44
44
  "fs-extra": "^11.1.1",
45
45
  "lodash": "^4.17.21",
46
- "reading-time": "^1.5.0",
46
+ "schema-dts": "^1.1.2",
47
47
  "srcset": "^4.0.0",
48
48
  "tslib": "^2.6.0",
49
49
  "unist-util-visit": "^5.0.0",
@@ -62,5 +62,5 @@
62
62
  "@total-typescript/shoehorn": "^0.1.2",
63
63
  "tree-node-cli": "^1.6.0"
64
64
  },
65
- "gitHead": "dd59750c16fe6908a26f18806a54d4c3dbe6db43"
65
+ "gitHead": "948d63c42fad0ba24b7b531a9deb6167e64dc845"
66
66
  }
package/src/blogUtils.ts CHANGED
@@ -9,7 +9,6 @@ import fs from 'fs-extra';
9
9
  import path from 'path';
10
10
  import _ from 'lodash';
11
11
  import logger from '@docusaurus/logger';
12
- import readingTime from 'reading-time';
13
12
  import {
14
13
  parseMarkdownFile,
15
14
  normalizeUrl,
@@ -32,6 +31,7 @@ import {getTagsFile} from '@docusaurus/utils-validation';
32
31
  import {validateBlogPostFrontMatter} from './frontMatter';
33
32
  import {getBlogPostAuthors} from './authors';
34
33
  import {reportAuthorsProblems} from './authorsProblems';
34
+ import {calculateReadingTime} from './readingTime';
35
35
  import type {TagsFile} from '@docusaurus/utils';
36
36
  import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
37
37
  import type {
@@ -210,8 +210,8 @@ async function parseBlogPostMarkdownFile({
210
210
  }
211
211
  }
212
212
 
213
- const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
214
- readingTime(content, options).minutes;
213
+ const defaultReadingTime: ReadingTimeFunction = ({content, locale, options}) =>
214
+ calculateReadingTime(content, locale, options);
215
215
 
216
216
  async function processBlogSourceFile(
217
217
  blogSourceRelative: string,
@@ -373,6 +373,7 @@ async function processBlogSourceFile(
373
373
  content,
374
374
  frontMatter,
375
375
  defaultReadingTime,
376
+ locale: i18n.currentLocale,
376
377
  })
377
378
  : undefined,
378
379
  hasTruncateMarker: truncateMarker.test(content),
package/src/options.ts CHANGED
@@ -63,7 +63,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
63
63
  path: 'blog',
64
64
  editLocalizedFiles: false,
65
65
  authorsMapPath: 'authors.yml',
66
- readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
66
+ readingTime: ({content, defaultReadingTime, locale}) =>
67
+ defaultReadingTime({content, locale}),
67
68
  sortPosts: 'descending',
68
69
  showLastUpdateTime: false,
69
70
  showLastUpdateAuthor: false,
@@ -16,7 +16,12 @@ declare module '@docusaurus/plugin-content-blog' {
16
16
  FrontMatterLastUpdate,
17
17
  TagsPluginOptions,
18
18
  } from '@docusaurus/utils';
19
- import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
19
+ import type {
20
+ DocusaurusConfig,
21
+ Plugin,
22
+ LoadContext,
23
+ OptionValidationContext,
24
+ } from '@docusaurus/types';
20
25
  import type {Item as FeedItem} from 'feed';
21
26
  import type {Overwrite} from 'utility-types';
22
27
 
@@ -382,15 +387,10 @@ declare module '@docusaurus/plugin-content-blog' {
382
387
  };
383
388
 
384
389
  /**
385
- * Duplicate from ngryman/reading-time to keep stability of API.
390
+ * Options for reading time calculation using Intl.Segmenter.
386
391
  */
387
392
  type ReadingTimeOptions = {
388
393
  wordsPerMinute?: number;
389
- /**
390
- * @param char The character to be matched.
391
- * @returns `true` if this character is a word bound.
392
- */
393
- wordBound?: (char: string) => boolean;
394
394
  };
395
395
 
396
396
  /**
@@ -400,24 +400,22 @@ declare module '@docusaurus/plugin-content-blog' {
400
400
  export type ReadingTimeFunction = (params: {
401
401
  /** Markdown content. */
402
402
  content: string;
403
+ /** Locale for word segmentation. */
404
+ locale: string;
403
405
  /** Front matter. */
404
406
  frontMatter?: BlogPostFrontMatter & {[key: string]: unknown};
405
- /** Options accepted by ngryman/reading-time. */
407
+ /** Options for reading time calculation. */
406
408
  options?: ReadingTimeOptions;
407
409
  }) => number;
408
410
 
409
411
  /**
410
- * @returns The reading time directly plugged into metadata. `undefined` to
411
- * hide reading time for a specific post.
412
+ * @returns The reading time directly plugged into metadata.
413
+ * `undefined` to hide reading time for a specific post.
412
414
  */
413
415
  export type ReadingTimeFunctionOption = (
414
- /**
415
- * The `options` is not provided by the caller; the user can inject their
416
- * own option values into `defaultReadingTime`
417
- */
418
416
  params: Required<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
419
417
  /**
420
- * The default reading time implementation from ngryman/reading-time.
418
+ * The default reading time implementation.
421
419
  */
422
420
  defaultReadingTime: ReadingTimeFunction;
423
421
  },
@@ -666,6 +664,10 @@ declare module '@docusaurus/plugin-content-blog' {
666
664
  context: LoadContext,
667
665
  options: PluginOptions,
668
666
  ): Promise<Plugin<BlogContent>>;
667
+
668
+ export function validateOptions(
669
+ args: OptionValidationContext<Options | undefined, PluginOptions>,
670
+ ): PluginOptions;
669
671
  }
670
672
 
671
673
  declare module '@theme/BlogPostPage' {
@@ -0,0 +1,49 @@
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
+ const DEFAULT_WORDS_PER_MINUTE = 200;
9
+
10
+ /**
11
+ * Counts the number of words in a string using Intl.Segmenter.
12
+ * @param content The text content to count words in.
13
+ * @param locale The locale to use for segmentation.
14
+ */
15
+ function countWords(content: string, locale: string): number {
16
+ if (!content) {
17
+ return 0;
18
+ }
19
+ const segmenter = new Intl.Segmenter(locale, {granularity: 'word'});
20
+ let wordCount = 0;
21
+ for (const {isWordLike} of segmenter.segment(content)) {
22
+ if (isWordLike) {
23
+ wordCount += 1;
24
+ }
25
+ }
26
+ return wordCount;
27
+ }
28
+
29
+ /**
30
+ * Calculates the reading time for a given content string using Intl.Segmenter.
31
+ * @param content The text content to calculate reading time for.
32
+ * @param locale Required locale string for Intl.Segmenter
33
+ * @param options Options for reading time calculation.
34
+ * - wordsPerMinute: number of words per minute (default 200)
35
+ * @returns Estimated reading time in minutes (float, rounded to 2 decimals)
36
+ */
37
+ export function calculateReadingTime(
38
+ content: string,
39
+ locale: string,
40
+ options?: {wordsPerMinute?: number},
41
+ ): number {
42
+ const wordsPerMinute = options?.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE;
43
+ const words = countWords(content, locale);
44
+ if (words === 0) {
45
+ return 0;
46
+ }
47
+ // Calculate reading time in minutes and round to 2 decimal places
48
+ return Math.round((words / wordsPerMinute) * 100) / 100;
49
+ }
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import {simpleHash} from '@docusaurus/utils';
9
- // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
10
9
  import type {Transformer} from 'unified';
11
10
  import type {FootnoteReference, FootnoteDefinition} from 'mdast';
12
11