@docusaurus/plugin-content-docs 0.0.0-4539 → 0.0.0-4545

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/lastUpdate.js CHANGED
@@ -10,6 +10,7 @@ exports.getFileLastUpdate = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const shelljs_1 = (0, tslib_1.__importDefault)(require("shelljs"));
12
12
  const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
13
+ const path_1 = (0, tslib_1.__importDefault)(require("path"));
13
14
  const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(?<timestamp>\d+),(?<author>.+)$/;
14
15
  let showedGitRequirementError = false;
15
16
  async function getFileLastUpdate(filePath) {
@@ -36,7 +37,13 @@ async function getFileLastUpdate(filePath) {
36
37
  }
37
38
  return null;
38
39
  }
39
- const result = shelljs_1.default.exec(`git log -1 --format=%ct,%an "${filePath}"`, {
40
+ if (!shelljs_1.default.test('-f', filePath)) {
41
+ throw new Error(`Retrieval of git history failed at "${filePath}" because the file does not exist.`);
42
+ }
43
+ const fileBasename = path_1.default.basename(filePath);
44
+ const fileDirname = path_1.default.dirname(filePath);
45
+ const result = shelljs_1.default.exec(`git log --max-count=1 --format=%ct,%an -- "${fileBasename}"`, {
46
+ cwd: fileDirname,
40
47
  silent: true,
41
48
  });
42
49
  if (result.code !== 0) {
@@ -4,15 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import type { SidebarItemsGenerator, SidebarItemCategoryLinkConfig } from './types';
7
+ import type { SidebarItemsGenerator } from './types';
8
8
  export declare const CategoryMetadataFilenameBase = "_category_";
9
9
  export declare const CategoryMetadataFilenamePattern = "_category_.{json,yml,yaml}";
10
- export declare type CategoryMetadataFile = {
11
- label?: string;
12
- position?: number;
13
- collapsed?: boolean;
14
- collapsible?: boolean;
15
- className?: string;
16
- link?: SidebarItemCategoryLinkConfig | null;
17
- };
18
10
  export declare const DefaultSidebarItemsGenerator: SidebarItemsGenerator;
@@ -12,9 +12,6 @@ const lodash_1 = require("lodash");
12
12
  const utils_1 = require("@docusaurus/utils");
13
13
  const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
14
14
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
15
- const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
16
- const js_yaml_1 = (0, tslib_1.__importDefault)(require("js-yaml"));
17
- const validation_1 = require("./validation");
18
15
  const docs_1 = require("../docs");
19
16
  const BreadcrumbSeparator = '/';
20
17
  // To avoid possible name clashes with a folder of the same name as the ID
@@ -25,30 +22,8 @@ function getLocalDocId(docId) {
25
22
  }
26
23
  exports.CategoryMetadataFilenameBase = '_category_';
27
24
  exports.CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
28
- // TODO I now believe we should read all the category metadata files ahead of
29
- // time: we may need this metadata to customize docs metadata
30
- // Example use-case being able to disable number prefix parsing at the folder
31
- // level, or customize the default base slug for an intermediate directory
32
- // TODO later if there is `CategoryFolder/with-category-name-doc.md`, we may
33
- // want to read the metadata as yaml on it
34
- // see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
35
- async function readCategoryMetadataFile(categoryDirPath) {
36
- async function tryReadFile(filePath) {
37
- const contentString = await fs_extra_1.default.readFile(filePath, { encoding: 'utf8' });
38
- const unsafeContent = js_yaml_1.default.load(contentString);
39
- try {
40
- return (0, validation_1.validateCategoryMetadataFile)(unsafeContent);
41
- }
42
- catch (e) {
43
- logger_1.default.error `The docs sidebar category metadata file path=${filePath} looks invalid!`;
44
- throw e;
45
- }
46
- }
47
- const filePath = await (0, utils_1.findAsyncSequential)(['.json', '.yml', '.yaml'].map((ext) => (0, utils_1.posixPath)(path_1.default.join(categoryDirPath, `${exports.CategoryMetadataFilenameBase}${ext}`))), fs_extra_1.default.pathExists);
48
- return filePath ? tryReadFile(filePath) : null;
49
- }
50
25
  // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
51
- const DefaultSidebarItemsGenerator = async ({ numberPrefixParser, isCategoryIndex, docs: allDocs, options, item: { dirName: autogenDir }, version, }) => {
26
+ const DefaultSidebarItemsGenerator = async ({ numberPrefixParser, isCategoryIndex, docs: allDocs, options, item: { dirName: autogenDir }, categoriesMetadata, }) => {
52
27
  const docsById = (0, docs_1.createDocsByIdIndex)(allDocs);
53
28
  const findDoc = (docId) => docsById[docId];
54
29
  const getDoc = (docId) => {
@@ -127,8 +102,7 @@ const DefaultSidebarItemsGenerator = async ({ numberPrefixParser, isCategoryInde
127
102
  }
128
103
  async function createCategoryItem(dir, fullPath, folderName) {
129
104
  var _a, _b, _c, _d;
130
- const categoryPath = path_1.default.join(version.contentPath, autogenDir, fullPath);
131
- const categoryMetadata = await readCategoryMetadataFile(categoryPath);
105
+ const categoryMetadata = categoriesMetadata[(0, utils_1.posixPath)(path_1.default.join(autogenDir, fullPath))];
132
106
  const className = categoryMetadata === null || categoryMetadata === void 0 ? void 0 : categoryMetadata.className;
133
107
  const { filename, numberPrefix } = numberPrefixParser(folderName);
134
108
  const allItems = await Promise.all(Object.entries(dir).map(([key, content]) => dirToItem(content, key, `${fullPath}/${key}`)));
@@ -13,4 +13,4 @@ export declare const DisabledSidebars: SidebarsConfig;
13
13
  export declare function resolveSidebarPathOption(siteDir: string, sidebarPathOption: PluginOptions['sidebarPath']): PluginOptions['sidebarPath'];
14
14
  export declare function loadSidebarsFile(sidebarFilePath: string | false | undefined): Promise<SidebarsConfig>;
15
15
  export declare function loadNormalizedSidebars(sidebarFilePath: string | false | undefined, params: NormalizeSidebarsParams): Promise<NormalizedSidebars>;
16
- export declare function loadSidebars(sidebarFilePath: string | false | undefined, options: SidebarProcessorParams): Promise<Sidebars>;
16
+ export declare function loadSidebars(sidebarFilePath: string | false | undefined, options: Omit<SidebarProcessorParams, 'categoriesMetadata'>): Promise<Sidebars>;
@@ -15,6 +15,10 @@ const normalization_1 = require("./normalization");
15
15
  const processor_1 = require("./processor");
16
16
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
17
17
  const utils_1 = require("@docusaurus/utils");
18
+ const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
19
+ const js_yaml_1 = (0, tslib_1.__importDefault)(require("js-yaml"));
20
+ const lodash_1 = require("lodash");
21
+ const combine_promises_1 = (0, tslib_1.__importDefault)(require("combine-promises"));
18
22
  exports.DefaultSidebars = {
19
23
  defaultSidebar: [
20
24
  {
@@ -32,6 +36,26 @@ function resolveSidebarPathOption(siteDir, sidebarPathOption) {
32
36
  : sidebarPathOption;
33
37
  }
34
38
  exports.resolveSidebarPathOption = resolveSidebarPathOption;
39
+ async function readCategoriesMetadata(contentPath) {
40
+ const categoryFiles = await (0, utils_1.Globby)('**/_category_.{json,yml,yaml}', {
41
+ cwd: contentPath,
42
+ });
43
+ const categoryToFile = (0, lodash_1.groupBy)(categoryFiles, path_1.default.dirname);
44
+ return (0, combine_promises_1.default)((0, lodash_1.mapValues)(categoryToFile, async (files, folder) => {
45
+ const [filePath] = files;
46
+ if (files.length > 1) {
47
+ logger_1.default.warn `There are more than one category metadata files for path=${folder}: ${files.join(', ')}. The behavior is undetermined.`;
48
+ }
49
+ const content = await fs_extra_1.default.readFile(path_1.default.join(contentPath, filePath), 'utf-8');
50
+ try {
51
+ return (0, validation_1.validateCategoryMetadataFile)(js_yaml_1.default.load(content));
52
+ }
53
+ catch (e) {
54
+ logger_1.default.error `The docs sidebar category metadata file path=${filePath} looks invalid!`;
55
+ throw e;
56
+ }
57
+ }));
58
+ }
35
59
  async function loadSidebarsFileUnsafe(sidebarFilePath) {
36
60
  // false => no sidebars
37
61
  if (sidebarFilePath === false) {
@@ -68,6 +92,7 @@ async function loadSidebars(sidebarFilePath, options) {
68
92
  categoryLabelSlugger: (0, utils_1.createSlugger)(),
69
93
  };
70
94
  const normalizedSidebars = await loadNormalizedSidebars(sidebarFilePath, normalizeSidebarsParams);
71
- return (0, processor_1.processSidebars)(normalizedSidebars, options);
95
+ const categoriesMetadata = await readCategoriesMetadata(options.version.contentPath);
96
+ return (0, processor_1.processSidebars)(normalizedSidebars, { ...options, categoriesMetadata });
72
97
  }
73
98
  exports.loadSidebars = loadSidebars;
@@ -5,7 +5,7 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { DocMetadataBase, VersionMetadata } from '../types';
8
- import type { Sidebars, NormalizedSidebars, SidebarItemsGeneratorOption } from './types';
8
+ import type { Sidebars, NormalizedSidebars, SidebarItemsGeneratorOption, CategoryMetadataFile } from './types';
9
9
  import type { Slugger } from '@docusaurus/utils';
10
10
  import type { NumberPrefixParser, SidebarOptions } from '@docusaurus/plugin-content-docs';
11
11
  export declare type SidebarProcessorParams = {
@@ -15,5 +15,6 @@ export declare type SidebarProcessorParams = {
15
15
  version: VersionMetadata;
16
16
  categoryLabelSlugger: Slugger;
17
17
  sidebarOptions: SidebarOptions;
18
+ categoriesMetadata: Record<string, CategoryMetadataFile>;
18
19
  };
19
20
  export declare function processSidebars(unprocessedSidebars: NormalizedSidebars, params: SidebarProcessorParams): Promise<Sidebars>;
@@ -30,7 +30,7 @@ function toSidebarItemsGeneratorVersion(version) {
30
30
  // Handle the generation of autogenerated sidebar items and other
31
31
  // post-processing checks
32
32
  async function processSidebar(unprocessedSidebar, params) {
33
- const { sidebarItemsGenerator, numberPrefixParser, docs, version, sidebarOptions, } = params;
33
+ const { sidebarItemsGenerator, numberPrefixParser, docs, version, sidebarOptions, categoriesMetadata, } = params;
34
34
  // Just a minor lazy transformation optimization
35
35
  const getSidebarItemsGeneratorDocsAndVersion = (0, lodash_1.memoize)(() => ({
36
36
  docs: docs.map(toSidebarItemsGeneratorDoc),
@@ -52,6 +52,7 @@ async function processSidebar(unprocessedSidebar, params) {
52
52
  isCategoryIndex: docs_1.isCategoryIndex,
53
53
  ...getSidebarItemsGeneratorDocsAndVersion(),
54
54
  options: sidebarOptions,
55
+ categoriesMetadata,
55
56
  });
56
57
  // TODO validate generated items: user can generate bad items
57
58
  const generatedItemsNormalized = generatedItems.flatMap((generatedItem) => (0, normalization_1.normalizeItem)(generatedItem, { ...params, ...sidebarOptions }));
@@ -19,6 +19,11 @@ export declare type SidebarItemDoc = SidebarItemBase & {
19
19
  label?: string;
20
20
  id: string;
21
21
  };
22
+ export declare type SidebarItemHtml = SidebarItemBase & {
23
+ type: 'html';
24
+ value: string;
25
+ defaultStyle?: boolean;
26
+ };
22
27
  export declare type SidebarItemLink = SidebarItemBase & {
23
28
  type: 'link';
24
29
  href: string;
@@ -64,7 +69,7 @@ export declare type SidebarItemCategoryConfig = Expand<Optional<SidebarItemCateg
64
69
  export declare type SidebarCategoriesShorthand = {
65
70
  [sidebarCategory: string]: SidebarItemConfig[];
66
71
  };
67
- export declare type SidebarItemConfig = SidebarItemDoc | SidebarItemLink | SidebarItemAutogenerated | SidebarItemCategoryConfig | string | SidebarCategoriesShorthand;
72
+ export declare type SidebarItemConfig = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | SidebarItemAutogenerated | SidebarItemCategoryConfig | string | SidebarCategoriesShorthand;
68
73
  export declare type SidebarConfig = SidebarCategoriesShorthand | SidebarItemConfig[];
69
74
  export declare type SidebarsConfig = {
70
75
  [sidebarId: string]: SidebarConfig;
@@ -73,7 +78,7 @@ export declare type NormalizedSidebarItemCategory = Expand<SidebarItemCategoryBa
73
78
  items: NormalizedSidebarItem[];
74
79
  link?: SidebarItemCategoryLink;
75
80
  }>;
76
- export declare type NormalizedSidebarItem = SidebarItemDoc | SidebarItemLink | NormalizedSidebarItemCategory | SidebarItemAutogenerated;
81
+ export declare type NormalizedSidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | NormalizedSidebarItemCategory | SidebarItemAutogenerated;
77
82
  export declare type NormalizedSidebar = NormalizedSidebarItem[];
78
83
  export declare type NormalizedSidebars = {
79
84
  [sidebarId: string]: NormalizedSidebar;
@@ -86,7 +91,7 @@ export declare type SidebarItemCategoryWithLink = Required<SidebarItemCategory,
86
91
  export declare type SidebarItemCategoryWithGeneratedIndex = SidebarItemCategoryWithLink & {
87
92
  link: SidebarItemCategoryLinkGeneratedIndex;
88
93
  };
89
- export declare type SidebarItem = SidebarItemDoc | SidebarItemLink | SidebarItemCategory;
94
+ export declare type SidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | SidebarItemCategory;
90
95
  export declare type SidebarNavigationItem = SidebarItemDoc | SidebarItemCategoryWithLink;
91
96
  export declare type Sidebar = SidebarItem[];
92
97
  export declare type SidebarItemType = SidebarItem['type'];
@@ -100,7 +105,8 @@ export declare type PropSidebarItemCategory = Expand<SidebarItemCategoryBase & {
100
105
  export declare type PropSidebarItemLink = SidebarItemLink & {
101
106
  docId?: string;
102
107
  };
103
- export declare type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory;
108
+ export declare type PropSidebarItemHtml = SidebarItemHtml;
109
+ export declare type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory | PropSidebarItemHtml;
104
110
  export declare type PropSidebar = PropSidebarItem[];
105
111
  export declare type PropSidebars = {
106
112
  [sidebarId: string]: PropSidebar;
@@ -114,6 +120,14 @@ export declare type PropVersionDoc = {
114
120
  export declare type PropVersionDocs = {
115
121
  [docId: string]: PropVersionDoc;
116
122
  };
123
+ export declare type CategoryMetadataFile = {
124
+ label?: string;
125
+ position?: number;
126
+ collapsed?: boolean;
127
+ collapsible?: boolean;
128
+ className?: string;
129
+ link?: SidebarItemCategoryLinkConfig | null;
130
+ };
117
131
  export declare type SidebarItemsGeneratorDoc = Pick<DocMetadataBase, 'id' | 'unversionedId' | 'frontMatter' | 'source' | 'sourceDirName' | 'sidebarPosition'>;
118
132
  export declare type SidebarItemsGeneratorVersion = Pick<VersionMetadata, 'versionName' | 'contentPath'>;
119
133
  export declare type SidebarItemsGeneratorArgs = {
@@ -122,6 +136,7 @@ export declare type SidebarItemsGeneratorArgs = {
122
136
  docs: SidebarItemsGeneratorDoc[];
123
137
  numberPrefixParser: NumberPrefixParser;
124
138
  isCategoryIndex: CategoryIndexMatcher;
139
+ categoriesMetadata: Record<string, CategoryMetadataFile>;
125
140
  options: SidebarOptions;
126
141
  };
127
142
  export declare type SidebarItemsGenerator = (generatorArgs: SidebarItemsGeneratorArgs) => Promise<SidebarItem[]>;
@@ -4,7 +4,6 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import type { SidebarsConfig } from './types';
8
- import type { CategoryMetadataFile } from './generator';
7
+ import type { SidebarsConfig, CategoryMetadataFile } from './types';
9
8
  export declare function validateSidebars(sidebars: unknown): asserts sidebars is SidebarsConfig;
10
9
  export declare function validateCategoryMetadataFile(unsafeContent: unknown): CategoryMetadataFile;
@@ -28,6 +28,11 @@ const sidebarItemDocSchema = sidebarItemBaseSchema.append({
28
28
  id: utils_validation_1.Joi.string().required(),
29
29
  label: utils_validation_1.Joi.string(),
30
30
  });
31
+ const sidebarItemHtmlSchema = sidebarItemBaseSchema.append({
32
+ type: 'html',
33
+ value: utils_validation_1.Joi.string().required(),
34
+ defaultStyle: utils_validation_1.Joi.boolean().default(false),
35
+ });
31
36
  const sidebarItemLinkSchema = sidebarItemBaseSchema.append({
32
37
  type: 'link',
33
38
  href: utils_validation_1.URISchema.required(),
@@ -93,6 +98,7 @@ const sidebarItemSchema = utils_validation_1.Joi.object()
93
98
  is: utils_validation_1.Joi.string().valid('doc', 'ref').required(),
94
99
  then: sidebarItemDocSchema,
95
100
  },
101
+ { is: 'html', then: sidebarItemHtmlSchema },
96
102
  { is: 'autogenerated', then: sidebarItemAutogeneratedSchema },
97
103
  { is: 'category', then: sidebarItemCategorySchema },
98
104
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-docs",
3
- "version": "0.0.0-4539",
3
+ "version": "0.0.0-4545",
4
4
  "description": "Docs plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -23,11 +23,11 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@docusaurus/core": "0.0.0-4539",
27
- "@docusaurus/logger": "0.0.0-4539",
28
- "@docusaurus/mdx-loader": "0.0.0-4539",
29
- "@docusaurus/utils": "0.0.0-4539",
30
- "@docusaurus/utils-validation": "0.0.0-4539",
26
+ "@docusaurus/core": "0.0.0-4545",
27
+ "@docusaurus/logger": "0.0.0-4545",
28
+ "@docusaurus/mdx-loader": "0.0.0-4545",
29
+ "@docusaurus/utils": "0.0.0-4545",
30
+ "@docusaurus/utils-validation": "0.0.0-4545",
31
31
  "combine-promises": "^1.1.0",
32
32
  "fs-extra": "^10.0.0",
33
33
  "import-fresh": "^3.2.2",
@@ -40,8 +40,8 @@
40
40
  "webpack": "^5.68.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@docusaurus/module-type-aliases": "0.0.0-4539",
44
- "@docusaurus/types": "0.0.0-4539",
43
+ "@docusaurus/module-type-aliases": "0.0.0-4545",
44
+ "@docusaurus/types": "0.0.0-4545",
45
45
  "@types/js-yaml": "^4.0.0",
46
46
  "@types/picomatch": "^2.2.1",
47
47
  "commander": "^5.1.0",
@@ -57,5 +57,5 @@
57
57
  "engines": {
58
58
  "node": ">=14"
59
59
  },
60
- "gitHead": "7b82719906447f76637ad0452ad8df1d9698f805"
60
+ "gitHead": "2fd48f443b78b58b3b511f797a0050d407370f07"
61
61
  }
package/src/lastUpdate.ts CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import shell from 'shelljs';
9
9
  import logger from '@docusaurus/logger';
10
+ import path from 'path';
10
11
 
11
12
  type FileLastUpdateData = {timestamp?: number; author?: string};
12
13
 
@@ -43,9 +44,21 @@ export async function getFileLastUpdate(
43
44
  return null;
44
45
  }
45
46
 
46
- const result = shell.exec(`git log -1 --format=%ct,%an "${filePath}"`, {
47
- silent: true,
48
- });
47
+ if (!shell.test('-f', filePath)) {
48
+ throw new Error(
49
+ `Retrieval of git history failed at "${filePath}" because the file does not exist.`,
50
+ );
51
+ }
52
+
53
+ const fileBasename = path.basename(filePath);
54
+ const fileDirname = path.dirname(filePath);
55
+ const result = shell.exec(
56
+ `git log --max-count=1 --format=%ct,%an -- "${fileBasename}"`,
57
+ {
58
+ cwd: fileDirname, // this is needed: https://github.com/facebook/docusaurus/pull/5048
59
+ silent: true,
60
+ },
61
+ );
49
62
  if (result.code !== 0) {
50
63
  throw new Error(
51
64
  `Retrieval of git history failed at "${filePath}" with exit code ${result.code}: ${result.stderr}`,
@@ -12,19 +12,11 @@ import type {
12
12
  SidebarItemsGenerator,
13
13
  SidebarItemsGeneratorDoc,
14
14
  SidebarItemCategoryLink,
15
- SidebarItemCategoryLinkConfig,
16
15
  } from './types';
17
16
  import {sortBy, last} from 'lodash';
18
- import {
19
- addTrailingSlash,
20
- posixPath,
21
- findAsyncSequential,
22
- } from '@docusaurus/utils';
17
+ import {addTrailingSlash, posixPath} from '@docusaurus/utils';
23
18
  import logger from '@docusaurus/logger';
24
19
  import path from 'path';
25
- import fs from 'fs-extra';
26
- import Yaml from 'js-yaml';
27
- import {validateCategoryMetadataFile} from './validation';
28
20
  import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
29
21
 
30
22
  const BreadcrumbSeparator = '/';
@@ -39,20 +31,6 @@ function getLocalDocId(docId: string): string {
39
31
  export const CategoryMetadataFilenameBase = '_category_';
40
32
  export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
41
33
 
42
- export type CategoryMetadataFile = {
43
- label?: string;
44
- position?: number;
45
- collapsed?: boolean;
46
- collapsible?: boolean;
47
- className?: string;
48
- link?: SidebarItemCategoryLinkConfig | null;
49
-
50
- // TODO should we allow "items" here? how would this work? would an
51
- // "autogenerated" type be allowed?
52
- // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
53
- // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199
54
- };
55
-
56
34
  type WithPosition<T> = T & {position?: number};
57
35
 
58
36
  /**
@@ -65,37 +43,6 @@ type Dir = {
65
43
  [item: string]: Dir | null;
66
44
  };
67
45
 
68
- // TODO I now believe we should read all the category metadata files ahead of
69
- // time: we may need this metadata to customize docs metadata
70
- // Example use-case being able to disable number prefix parsing at the folder
71
- // level, or customize the default base slug for an intermediate directory
72
- // TODO later if there is `CategoryFolder/with-category-name-doc.md`, we may
73
- // want to read the metadata as yaml on it
74
- // see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
75
- async function readCategoryMetadataFile(
76
- categoryDirPath: string,
77
- ): Promise<CategoryMetadataFile | null> {
78
- async function tryReadFile(filePath: string): Promise<CategoryMetadataFile> {
79
- const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
80
- const unsafeContent = Yaml.load(contentString);
81
- try {
82
- return validateCategoryMetadataFile(unsafeContent);
83
- } catch (e) {
84
- logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`;
85
- throw e;
86
- }
87
- }
88
- const filePath = await findAsyncSequential(
89
- ['.json', '.yml', '.yaml'].map((ext) =>
90
- posixPath(
91
- path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`),
92
- ),
93
- ),
94
- fs.pathExists,
95
- );
96
- return filePath ? tryReadFile(filePath) : null;
97
- }
98
-
99
46
  // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
100
47
  export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
101
48
  numberPrefixParser,
@@ -103,7 +50,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
103
50
  docs: allDocs,
104
51
  options,
105
52
  item: {dirName: autogenDir},
106
- version,
53
+ categoriesMetadata,
107
54
  }) => {
108
55
  const docsById = createDocsByIdIndex(allDocs);
109
56
  const findDoc = (docId: string): SidebarItemsGeneratorDoc | undefined =>
@@ -199,8 +146,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
199
146
  fullPath: string,
200
147
  folderName: string,
201
148
  ): Promise<WithPosition<SidebarItemCategory>> {
202
- const categoryPath = path.join(version.contentPath, autogenDir, fullPath);
203
- const categoryMetadata = await readCategoryMetadataFile(categoryPath);
149
+ const categoryMetadata =
150
+ categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
204
151
  const className = categoryMetadata?.className;
205
152
  const {filename, numberPrefix} = numberPrefixParser(folderName);
206
153
  const allItems = await Promise.all(
@@ -9,12 +9,16 @@ import fs from 'fs-extra';
9
9
  import importFresh from 'import-fresh';
10
10
  import type {SidebarsConfig, Sidebars, NormalizedSidebars} from './types';
11
11
  import type {NormalizeSidebarsParams} from '../types';
12
- import {validateSidebars} from './validation';
12
+ import {validateSidebars, validateCategoryMetadataFile} from './validation';
13
13
  import {normalizeSidebars} from './normalization';
14
14
  import {processSidebars, type SidebarProcessorParams} from './processor';
15
15
  import path from 'path';
16
- import {createSlugger} from '@docusaurus/utils';
16
+ import {createSlugger, Globby} from '@docusaurus/utils';
17
+ import logger from '@docusaurus/logger';
17
18
  import type {PluginOptions} from '@docusaurus/plugin-content-docs';
19
+ import Yaml from 'js-yaml';
20
+ import {groupBy, mapValues} from 'lodash';
21
+ import combinePromises from 'combine-promises';
18
22
 
19
23
  export const DefaultSidebars: SidebarsConfig = {
20
24
  defaultSidebar: [
@@ -38,6 +42,33 @@ export function resolveSidebarPathOption(
38
42
  : sidebarPathOption;
39
43
  }
40
44
 
45
+ async function readCategoriesMetadata(contentPath: string) {
46
+ const categoryFiles = await Globby('**/_category_.{json,yml,yaml}', {
47
+ cwd: contentPath,
48
+ });
49
+ const categoryToFile = groupBy(categoryFiles, path.dirname);
50
+ return combinePromises(
51
+ mapValues(categoryToFile, async (files, folder) => {
52
+ const [filePath] = files;
53
+ if (files.length > 1) {
54
+ logger.warn`There are more than one category metadata files for path=${folder}: ${files.join(
55
+ ', ',
56
+ )}. The behavior is undetermined.`;
57
+ }
58
+ const content = await fs.readFile(
59
+ path.join(contentPath, filePath),
60
+ 'utf-8',
61
+ );
62
+ try {
63
+ return validateCategoryMetadataFile(Yaml.load(content));
64
+ } catch (e) {
65
+ logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`;
66
+ throw e;
67
+ }
68
+ }),
69
+ );
70
+ }
71
+
41
72
  async function loadSidebarsFileUnsafe(
42
73
  sidebarFilePath: string | false | undefined,
43
74
  ): Promise<SidebarsConfig> {
@@ -80,7 +111,7 @@ export async function loadNormalizedSidebars(
80
111
  // Note: sidebarFilePath must be absolute, use resolveSidebarPathOption
81
112
  export async function loadSidebars(
82
113
  sidebarFilePath: string | false | undefined,
83
- options: SidebarProcessorParams,
114
+ options: Omit<SidebarProcessorParams, 'categoriesMetadata'>,
84
115
  ): Promise<Sidebars> {
85
116
  const normalizeSidebarsParams: NormalizeSidebarsParams = {
86
117
  ...options.sidebarOptions,
@@ -91,5 +122,8 @@ export async function loadSidebars(
91
122
  sidebarFilePath,
92
123
  normalizeSidebarsParams,
93
124
  );
94
- return processSidebars(normalizedSidebars, options);
125
+ const categoriesMetadata = await readCategoriesMetadata(
126
+ options.version.contentPath,
127
+ );
128
+ return processSidebars(normalizedSidebars, {...options, categoriesMetadata});
95
129
  }
@@ -19,6 +19,7 @@ import type {
19
19
  NormalizedSidebarItemCategory,
20
20
  SidebarItemCategory,
21
21
  SidebarItemAutogenerated,
22
+ CategoryMetadataFile,
22
23
  } from './types';
23
24
  import {transformSidebarItems} from './utils';
24
25
  import {DefaultSidebarItemsGenerator} from './generator';
@@ -39,6 +40,7 @@ export type SidebarProcessorParams = {
39
40
  version: VersionMetadata;
40
41
  categoryLabelSlugger: Slugger;
41
42
  sidebarOptions: SidebarOptions;
43
+ categoriesMetadata: Record<string, CategoryMetadataFile>;
42
44
  };
43
45
 
44
46
  function toSidebarItemsGeneratorDoc(
@@ -72,6 +74,7 @@ async function processSidebar(
72
74
  docs,
73
75
  version,
74
76
  sidebarOptions,
77
+ categoriesMetadata,
75
78
  } = params;
76
79
 
77
80
  // Just a minor lazy transformation optimization
@@ -101,6 +104,7 @@ async function processSidebar(
101
104
  isCategoryIndex,
102
105
  ...getSidebarItemsGeneratorDocsAndVersion(),
103
106
  options: sidebarOptions,
107
+ categoriesMetadata,
104
108
  });
105
109
  // TODO validate generated items: user can generate bad items
106
110
 
@@ -27,6 +27,12 @@ export type SidebarItemDoc = SidebarItemBase & {
27
27
  id: string;
28
28
  };
29
29
 
30
+ export type SidebarItemHtml = SidebarItemBase & {
31
+ type: 'html';
32
+ value: string;
33
+ defaultStyle?: boolean;
34
+ };
35
+
30
36
  export type SidebarItemLink = SidebarItemBase & {
31
37
  type: 'link';
32
38
  href: string;
@@ -87,6 +93,7 @@ export type SidebarCategoriesShorthand = {
87
93
 
88
94
  export type SidebarItemConfig =
89
95
  | SidebarItemDoc
96
+ | SidebarItemHtml
90
97
  | SidebarItemLink
91
98
  | SidebarItemAutogenerated
92
99
  | SidebarItemCategoryConfig
@@ -108,6 +115,7 @@ export type NormalizedSidebarItemCategory = Expand<
108
115
 
109
116
  export type NormalizedSidebarItem =
110
117
  | SidebarItemDoc
118
+ | SidebarItemHtml
111
119
  | SidebarItemLink
112
120
  | NormalizedSidebarItemCategory
113
121
  | SidebarItemAutogenerated;
@@ -131,6 +139,7 @@ export type SidebarItemCategoryWithGeneratedIndex =
131
139
 
132
140
  export type SidebarItem =
133
141
  | SidebarItemDoc
142
+ | SidebarItemHtml
134
143
  | SidebarItemLink
135
144
  | SidebarItemCategory;
136
145
 
@@ -158,7 +167,12 @@ export type PropSidebarItemLink = SidebarItemLink & {
158
167
  docId?: string;
159
168
  };
160
169
 
161
- export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory;
170
+ export type PropSidebarItemHtml = SidebarItemHtml;
171
+
172
+ export type PropSidebarItem =
173
+ | PropSidebarItemLink
174
+ | PropSidebarItemCategory
175
+ | PropSidebarItemHtml;
162
176
  export type PropSidebar = PropSidebarItem[];
163
177
  export type PropSidebars = {
164
178
  [sidebarId: string]: PropSidebar;
@@ -174,6 +188,20 @@ export type PropVersionDocs = {
174
188
  [docId: string]: PropVersionDoc;
175
189
  };
176
190
 
191
+ export type CategoryMetadataFile = {
192
+ label?: string;
193
+ position?: number;
194
+ collapsed?: boolean;
195
+ collapsible?: boolean;
196
+ className?: string;
197
+ link?: SidebarItemCategoryLinkConfig | null;
198
+
199
+ // TODO should we allow "items" here? how would this work? would an
200
+ // "autogenerated" type be allowed?
201
+ // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
202
+ // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199
203
+ };
204
+
177
205
  // Reduce API surface for options.sidebarItemsGenerator
178
206
  // The user-provided generator fn should receive only a subset of metadata
179
207
  // A change to any of these metadata can be considered as a breaking change
@@ -197,6 +225,7 @@ export type SidebarItemsGeneratorArgs = {
197
225
  docs: SidebarItemsGeneratorDoc[];
198
226
  numberPrefixParser: NumberPrefixParser;
199
227
  isCategoryIndex: CategoryIndexMatcher;
228
+ categoriesMetadata: Record<string, CategoryMetadataFile>;
200
229
  options: SidebarOptions;
201
230
  };
202
231
  export type SidebarItemsGenerator = (
@@ -12,15 +12,16 @@ import type {
12
12
  SidebarItemBase,
13
13
  SidebarItemAutogenerated,
14
14
  SidebarItemDoc,
15
+ SidebarItemHtml,
15
16
  SidebarItemLink,
16
17
  SidebarItemCategoryConfig,
17
18
  SidebarItemCategoryLink,
18
19
  SidebarsConfig,
19
20
  SidebarItemCategoryLinkDoc,
20
21
  SidebarItemCategoryLinkGeneratedIndex,
22
+ CategoryMetadataFile,
21
23
  } from './types';
22
24
  import {isCategoriesShorthand} from './utils';
23
- import type {CategoryMetadataFile} from './generator';
24
25
 
25
26
  // NOTE: we don't add any default values during validation on purpose!
26
27
  // Config types are exposed to users for typechecking and we use the same type
@@ -48,6 +49,12 @@ const sidebarItemDocSchema = sidebarItemBaseSchema.append<SidebarItemDoc>({
48
49
  label: Joi.string(),
49
50
  });
50
51
 
52
+ const sidebarItemHtmlSchema = sidebarItemBaseSchema.append<SidebarItemHtml>({
53
+ type: 'html',
54
+ value: Joi.string().required(),
55
+ defaultStyle: Joi.boolean().default(false),
56
+ });
57
+
51
58
  const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
52
59
  type: 'link',
53
60
  href: URISchema.required(),
@@ -117,6 +124,7 @@ const sidebarItemSchema: Joi.Schema<SidebarItemConfig> = Joi.object()
117
124
  is: Joi.string().valid('doc', 'ref').required(),
118
125
  then: sidebarItemDocSchema,
119
126
  },
127
+ {is: 'html', then: sidebarItemHtmlSchema},
120
128
  {is: 'autogenerated', then: sidebarItemAutogeneratedSchema},
121
129
  {is: 'category', then: sidebarItemCategorySchema},
122
130
  {