mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
feat(v2): docs, make numberPrefixParser configurable, better defaults, minor breaking-changes (#4655)
* make number prefix parsing logic configurable * Make numberPrefixParser configurable + rename frontmatter + avoid parsing date/version patterns by default * add more tests * more test cases
This commit is contained in:
parent
d0d29f43cc
commit
c04e613ffe
14 changed files with 325 additions and 82 deletions
|
@ -781,6 +781,7 @@ Object {
|
||||||
"dirName": ".",
|
"dirName": ".",
|
||||||
"type": "autogenerated",
|
"type": "autogenerated",
|
||||||
},
|
},
|
||||||
|
"numberPrefixParser": [Function],
|
||||||
"version": Object {
|
"version": Object {
|
||||||
"contentPath": "docs",
|
"contentPath": "docs",
|
||||||
"versionName": "current",
|
"versionName": "current",
|
||||||
|
|
|
@ -6,53 +6,114 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
extractNumberPrefix,
|
DefaultNumberPrefixParser,
|
||||||
|
DisabledNumberPrefixParser,
|
||||||
stripNumberPrefix,
|
stripNumberPrefix,
|
||||||
stripPathNumberPrefixes,
|
stripPathNumberPrefixes,
|
||||||
} from '../numberPrefix';
|
} from '../numberPrefix';
|
||||||
|
|
||||||
const BadNumberPrefixPatterns = [
|
const IgnoredNumberPrefixPatterns = [
|
||||||
|
// Patterns without number prefix
|
||||||
|
'MyDoc',
|
||||||
'a1-My Doc',
|
'a1-My Doc',
|
||||||
'My Doc-000',
|
'My Doc-000',
|
||||||
|
'My Doc - 1',
|
||||||
|
'My Doc - 02',
|
||||||
|
'Hey - 03 - My Doc',
|
||||||
'00abc01-My Doc',
|
'00abc01-My Doc',
|
||||||
'My 001- Doc',
|
'My 001- Doc',
|
||||||
'My -001 Doc',
|
'My -001 Doc',
|
||||||
|
// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
|
||||||
|
'2021-01-31 - Doc',
|
||||||
|
'31-01-2021 - Doc',
|
||||||
|
'2021_01_31 - Doc',
|
||||||
|
'31_01_2021 - Doc',
|
||||||
|
'2021.01.31 - Doc',
|
||||||
|
'31.01.2021 - Doc',
|
||||||
|
'2021-01 - Doc',
|
||||||
|
'2021_01 - Doc',
|
||||||
|
'2021.01 - Doc',
|
||||||
|
'01-2021 - Doc',
|
||||||
|
'01_2021 - Doc',
|
||||||
|
'01.2021 - Doc',
|
||||||
|
// date patterns without suffix
|
||||||
|
'2021-01-31',
|
||||||
|
'2021-01',
|
||||||
|
'21-01-31',
|
||||||
|
'21-01',
|
||||||
|
'2021_01_31',
|
||||||
|
'2021_01',
|
||||||
|
'21_01_31',
|
||||||
|
'21_01',
|
||||||
|
'01_31',
|
||||||
|
'01',
|
||||||
|
'2021',
|
||||||
|
'01',
|
||||||
|
// ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653
|
||||||
|
'8.0',
|
||||||
|
'8.0.0',
|
||||||
|
'14.2.16',
|
||||||
|
'18.2',
|
||||||
|
'8.0 - Doc',
|
||||||
|
'8.0.0 - Doc',
|
||||||
|
'8_0',
|
||||||
|
'8_0_0',
|
||||||
|
'14_2_16',
|
||||||
|
'18_2',
|
||||||
|
'8.0 - Doc',
|
||||||
|
'8.0.0 - Doc',
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('stripNumberPrefix', () => {
|
describe('stripNumberPrefix', () => {
|
||||||
|
function stripNumberPrefixDefault(str: string) {
|
||||||
|
return stripNumberPrefix(str, DefaultNumberPrefixParser);
|
||||||
|
}
|
||||||
|
|
||||||
test('should strip number prefix if present', () => {
|
test('should strip number prefix if present', () => {
|
||||||
expect(stripNumberPrefix('1-My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('1-My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('01-My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('01-My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001-My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001-My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 - My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 - My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 - My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 - My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('999 - My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('999 - My Doc')).toEqual(
|
||||||
|
'My Doc',
|
||||||
|
);
|
||||||
//
|
//
|
||||||
expect(stripNumberPrefix('1---My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('1---My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('01---My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('01---My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001---My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001---My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 --- My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 --- My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 --- My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 --- My Doc')).toEqual(
|
||||||
expect(stripNumberPrefix('999 --- My Doc')).toEqual('My Doc');
|
'My Doc',
|
||||||
|
);
|
||||||
|
expect(stripNumberPrefixDefault('999 --- My Doc')).toEqual(
|
||||||
|
'My Doc',
|
||||||
|
);
|
||||||
//
|
//
|
||||||
expect(stripNumberPrefix('1___My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('1___My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('01___My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('01___My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001___My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001___My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 ___ My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 ___ My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 ___ My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 ___ My Doc')).toEqual(
|
||||||
expect(stripNumberPrefix('999 ___ My Doc')).toEqual('My Doc');
|
'My Doc',
|
||||||
|
);
|
||||||
|
expect(stripNumberPrefixDefault('999 ___ My Doc')).toEqual(
|
||||||
|
'My Doc',
|
||||||
|
);
|
||||||
//
|
//
|
||||||
expect(stripNumberPrefix('1.My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('1.My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('01.My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('01.My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001.My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001.My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 . My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 . My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('001 . My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('001 . My Doc')).toEqual('My Doc');
|
||||||
expect(stripNumberPrefix('999 . My Doc')).toEqual('My Doc');
|
expect(stripNumberPrefixDefault('999 . My Doc')).toEqual(
|
||||||
|
'My Doc',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not strip number prefix if pattern does not match', () => {
|
test('should not strip number prefix if pattern does not match', () => {
|
||||||
BadNumberPrefixPatterns.forEach((badPattern) => {
|
IgnoredNumberPrefixPatterns.forEach((badPattern) => {
|
||||||
expect(stripNumberPrefix(badPattern)).toEqual(badPattern);
|
expect(stripNumberPrefixDefault(badPattern)).toEqual(badPattern);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -62,51 +123,74 @@ describe('stripPathNumberPrefix', () => {
|
||||||
expect(
|
expect(
|
||||||
stripPathNumberPrefixes(
|
stripPathNumberPrefixes(
|
||||||
'0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3',
|
'0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3',
|
||||||
|
DefaultNumberPrefixParser,
|
||||||
),
|
),
|
||||||
).toEqual('MyRootFolder0/MySubFolder1/MyDeepFolder2/MyDoc3');
|
).toEqual('MyRootFolder0/MySubFolder1/MyDeepFolder2/MyDoc3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should strip number prefixes in paths with custom parser', () => {
|
||||||
|
function stripPathNumberPrefixCustom(str: string) {
|
||||||
|
return {
|
||||||
|
filename: str.substring(1, str.length),
|
||||||
|
numberPrefix: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
stripPathNumberPrefixes('aaaa/bbbb/cccc', stripPathNumberPrefixCustom),
|
||||||
|
).toEqual('aaa/bbb/ccc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should strip number prefixes in paths with disabled parser', () => {
|
||||||
|
expect(
|
||||||
|
stripPathNumberPrefixes(
|
||||||
|
'0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3',
|
||||||
|
DisabledNumberPrefixParser,
|
||||||
|
),
|
||||||
|
).toEqual('0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extractNumberPrefix', () => {
|
describe('DefaultNumberPrefixParser', () => {
|
||||||
test('should extract number prefix if present', () => {
|
test('should extract number prefix if present', () => {
|
||||||
expect(extractNumberPrefix('0-My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('0-My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 0,
|
numberPrefix: 0,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('1-My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('1-My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 1,
|
numberPrefix: 1,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('01-My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('01-My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 1,
|
numberPrefix: 1,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('001-My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('001-My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 1,
|
numberPrefix: 1,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('001 - My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('001 - My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 1,
|
numberPrefix: 1,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('001 - My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('001 - My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 1,
|
numberPrefix: 1,
|
||||||
});
|
});
|
||||||
expect(extractNumberPrefix('999 - My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('999 - My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 999,
|
numberPrefix: 999,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(extractNumberPrefix('0046036 - My Doc')).toEqual({
|
expect(DefaultNumberPrefixParser('0046036 - My Doc')).toEqual({
|
||||||
filename: 'My Doc',
|
filename: 'My Doc',
|
||||||
numberPrefix: 46036,
|
numberPrefix: 46036,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not extract number prefix if pattern does not match', () => {
|
test('should not extract number prefix if pattern does not match', () => {
|
||||||
BadNumberPrefixPatterns.forEach((badPattern) => {
|
IgnoredNumberPrefixPatterns.forEach((badPattern) => {
|
||||||
expect(extractNumberPrefix(badPattern)).toEqual({
|
expect(DefaultNumberPrefixParser(badPattern)).toEqual({
|
||||||
filename: badPattern,
|
filename: badPattern,
|
||||||
numberPrefix: undefined,
|
numberPrefix: undefined,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
import {OptionsSchema, DEFAULT_OPTIONS} from '../options';
|
import {OptionsSchema, DEFAULT_OPTIONS} from '../options';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
|
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
|
||||||
|
import {
|
||||||
|
DefaultNumberPrefixParser,
|
||||||
|
DisabledNumberPrefixParser,
|
||||||
|
} from '../numberPrefix';
|
||||||
|
|
||||||
// the type of remark/rehype plugins is function
|
// the type of remark/rehype plugins is function
|
||||||
const markdownPluginsFunctionStub = () => {};
|
const markdownPluginsFunctionStub = () => {};
|
||||||
|
@ -28,6 +32,7 @@ describe('normalizeDocsPluginOptions', () => {
|
||||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||||
sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages.
|
sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages.
|
||||||
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
docLayoutComponent: '@theme/DocPage',
|
docLayoutComponent: '@theme/DocPage',
|
||||||
docItemComponent: '@theme/DocItem',
|
docItemComponent: '@theme/DocItem',
|
||||||
remarkPlugins: [markdownPluginsObjectStub],
|
remarkPlugins: [markdownPluginsObjectStub],
|
||||||
|
@ -84,6 +89,46 @@ describe('normalizeDocsPluginOptions', () => {
|
||||||
expect(error).toBe(undefined);
|
expect(error).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should accept numberPrefixParser function', () => {
|
||||||
|
function customNumberPrefixParser() {}
|
||||||
|
expect(
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
numberPrefixParser: customNumberPrefixParser,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
id: 'default',
|
||||||
|
numberPrefixParser: customNumberPrefixParser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should accept numberPrefixParser false', () => {
|
||||||
|
expect(
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
numberPrefixParser: false,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
id: 'default',
|
||||||
|
numberPrefixParser: DisabledNumberPrefixParser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should accept numberPrefixParser true', () => {
|
||||||
|
expect(
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
numberPrefixParser: true,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
id: 'default',
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should reject admonitions true', async () => {
|
test('should reject admonitions true', async () => {
|
||||||
const admonitionsTrue = {
|
const admonitionsTrue = {
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
|
|
|
@ -12,12 +12,14 @@ import {
|
||||||
import {DefaultCategoryCollapsedValue} from '../sidebars';
|
import {DefaultCategoryCollapsedValue} from '../sidebars';
|
||||||
import {Sidebar, SidebarItemsGenerator} from '../types';
|
import {Sidebar, SidebarItemsGenerator} from '../types';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import {DefaultNumberPrefixParser} from '../numberPrefix';
|
||||||
|
|
||||||
describe('DefaultSidebarItemsGenerator', () => {
|
describe('DefaultSidebarItemsGenerator', () => {
|
||||||
function testDefaultSidebarItemsGenerator(
|
function testDefaultSidebarItemsGenerator(
|
||||||
options: Partial<Parameters<SidebarItemsGenerator>[0]>,
|
options: Partial<Parameters<SidebarItemsGenerator>[0]>,
|
||||||
) {
|
) {
|
||||||
return DefaultSidebarItemsGenerator({
|
return DefaultSidebarItemsGenerator({
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
item: {
|
item: {
|
||||||
type: 'autogenerated',
|
type: 'autogenerated',
|
||||||
dirName: '.',
|
dirName: '.',
|
||||||
|
@ -60,6 +62,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
|
|
||||||
test('generates simple flat sidebar', async () => {
|
test('generates simple flat sidebar', async () => {
|
||||||
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
item: {
|
item: {
|
||||||
type: 'autogenerated',
|
type: 'autogenerated',
|
||||||
dirName: '.',
|
dirName: '.',
|
||||||
|
@ -127,6 +130,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
item: {
|
item: {
|
||||||
type: 'autogenerated',
|
type: 'autogenerated',
|
||||||
dirName: '.',
|
dirName: '.',
|
||||||
|
@ -234,6 +238,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
item: {
|
item: {
|
||||||
type: 'autogenerated',
|
type: 'autogenerated',
|
||||||
dirName: 'subfolder/subsubfolder',
|
dirName: 'subfolder/subsubfolder',
|
||||||
|
|
|
@ -16,9 +16,13 @@ type DocFrontMatter = {
|
||||||
sidebar_label?: string;
|
sidebar_label?: string;
|
||||||
sidebar_position?: number;
|
sidebar_position?: number;
|
||||||
custom_edit_url?: string;
|
custom_edit_url?: string;
|
||||||
strip_number_prefixes?: boolean;
|
parse_number_prefixes?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOTE: we don't add any default value on purpose here
|
||||||
|
// We don't want default values to magically appear in doc metadatas and props
|
||||||
|
// While the user did not provide those values explicitly
|
||||||
|
// We use default values in code instead
|
||||||
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
id: Joi.string(),
|
id: Joi.string(),
|
||||||
title: Joi.string(),
|
title: Joi.string(),
|
||||||
|
@ -27,11 +31,14 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
sidebar_label: Joi.string(),
|
sidebar_label: Joi.string(),
|
||||||
sidebar_position: Joi.number(),
|
sidebar_position: Joi.number(),
|
||||||
custom_edit_url: Joi.string().allow(null),
|
custom_edit_url: Joi.string().allow(null),
|
||||||
strip_number_prefixes: Joi.boolean(),
|
parse_number_prefixes: Joi.boolean(),
|
||||||
}).unknown();
|
});
|
||||||
|
|
||||||
export function assertDocFrontMatter(
|
export function validateDocFrontMatter(
|
||||||
frontMatter: Record<string, unknown>,
|
frontMatter: Record<string, unknown>,
|
||||||
): asserts frontMatter is DocFrontMatter {
|
): DocFrontMatter {
|
||||||
Joi.attempt(frontMatter, DocFrontMatterSchema);
|
return Joi.attempt(frontMatter, DocFrontMatterSchema, {
|
||||||
|
convert: true,
|
||||||
|
allowUnknown: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ import getSlug from './slug';
|
||||||
import {CURRENT_VERSION_NAME} from './constants';
|
import {CURRENT_VERSION_NAME} from './constants';
|
||||||
import globby from 'globby';
|
import globby from 'globby';
|
||||||
import {getDocsDirPaths} from './versions';
|
import {getDocsDirPaths} from './versions';
|
||||||
import {extractNumberPrefix, stripPathNumberPrefixes} from './numberPrefix';
|
import {stripPathNumberPrefixes} from './numberPrefix';
|
||||||
import {assertDocFrontMatter} from './docFrontMatter';
|
import {validateDocFrontMatter} from './docFrontMatter';
|
||||||
|
|
||||||
type LastUpdateOptions = Pick<
|
type LastUpdateOptions = Pick<
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
|
@ -117,18 +117,22 @@ export function processDocMetadata({
|
||||||
const {homePageId} = options;
|
const {homePageId} = options;
|
||||||
const {siteDir, i18n} = context;
|
const {siteDir, i18n} = context;
|
||||||
|
|
||||||
const {frontMatter, contentTitle, excerpt} = parseMarkdownString(content, {
|
const {
|
||||||
|
frontMatter: unsafeFrontMatter,
|
||||||
|
contentTitle,
|
||||||
|
excerpt,
|
||||||
|
} = parseMarkdownString(content, {
|
||||||
source,
|
source,
|
||||||
});
|
});
|
||||||
assertDocFrontMatter(frontMatter);
|
const frontMatter = validateDocFrontMatter(unsafeFrontMatter);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sidebar_label: sidebarLabel,
|
sidebar_label: sidebarLabel,
|
||||||
custom_edit_url: customEditURL,
|
custom_edit_url: customEditURL,
|
||||||
|
|
||||||
// Strip number prefixes by default (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) by default,
|
// Strip number prefixes by default (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) by default,
|
||||||
// but ability to disable this behavior with frontmatterr
|
// but allow to disable this behavior with frontmatterr
|
||||||
strip_number_prefixes: stripNumberPrefixes = true,
|
parse_number_prefixes = true,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
|
|
||||||
// ex: api/plugins/myDoc -> myDoc
|
// ex: api/plugins/myDoc -> myDoc
|
||||||
|
@ -142,8 +146,8 @@ export function processDocMetadata({
|
||||||
// ex: myDoc -> .
|
// ex: myDoc -> .
|
||||||
const sourceDirName = path.dirname(source);
|
const sourceDirName = path.dirname(source);
|
||||||
|
|
||||||
const {filename: unprefixedFileName, numberPrefix} = stripNumberPrefixes
|
const {filename: unprefixedFileName, numberPrefix} = parse_number_prefixes
|
||||||
? extractNumberPrefix(sourceFileNameWithoutExtension)
|
? options.numberPrefixParser(sourceFileNameWithoutExtension)
|
||||||
: {filename: sourceFileNameWithoutExtension, numberPrefix: undefined};
|
: {filename: sourceFileNameWithoutExtension, numberPrefix: undefined};
|
||||||
|
|
||||||
const baseID: string = frontMatter.id ?? unprefixedFileName;
|
const baseID: string = frontMatter.id ?? unprefixedFileName;
|
||||||
|
@ -170,8 +174,8 @@ export function processDocMetadata({
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
// Eventually remove the number prefixes from intermediate directories
|
// Eventually remove the number prefixes from intermediate directories
|
||||||
return stripNumberPrefixes
|
return parse_number_prefixes
|
||||||
? stripPathNumberPrefixes(sourceDirName)
|
? stripPathNumberPrefixes(sourceDirName, options.numberPrefixParser)
|
||||||
: sourceDirName;
|
: sourceDirName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +201,8 @@ export function processDocMetadata({
|
||||||
baseID,
|
baseID,
|
||||||
dirName: sourceDirName,
|
dirName: sourceDirName,
|
||||||
frontmatterSlug: frontMatter.slug,
|
frontmatterSlug: frontMatter.slug,
|
||||||
stripDirNumberPrefixes: stripNumberPrefixes,
|
stripDirNumberPrefixes: parse_number_prefixes,
|
||||||
|
numberPrefixParser: options.numberPrefixParser,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default title is the id.
|
// Default title is the id.
|
||||||
|
|
|
@ -177,6 +177,7 @@ export default function pluginContentDocs(
|
||||||
|
|
||||||
const sidebars = await processSidebars({
|
const sidebars = await processSidebars({
|
||||||
sidebarItemsGenerator: options.sidebarItemsGenerator,
|
sidebarItemsGenerator: options.sidebarItemsGenerator,
|
||||||
|
numberPrefixParser: options.numberPrefixParser,
|
||||||
unprocessedSidebars,
|
unprocessedSidebars,
|
||||||
docs: docsBase,
|
docs: docsBase,
|
||||||
version: versionMetadata,
|
version: versionMetadata,
|
||||||
|
|
|
@ -5,23 +5,34 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {NumberPrefixParser} from './types';
|
||||||
|
|
||||||
|
// Best-effort to avoid parsing some patterns as number prefix
|
||||||
|
const IgnoredPrefixPatterns = (function () {
|
||||||
|
// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
|
||||||
|
const DateLikePrefixRegex = /^((\d{2}|\d{4})[-_.]\d{2}([-_.](\d{2}|\d{4}))?)(.*)$/;
|
||||||
|
|
||||||
|
// ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653
|
||||||
|
// note: we could try to parse float numbers in filenames but that is probably not worth it
|
||||||
|
// as a version such as "8.0" can be interpreted as both a version and a float
|
||||||
|
// User can configure his own NumberPrefixParser if he wants 8.0 to be interpreted as a float
|
||||||
|
const VersionLikePrefixRegex = /^(\d+[-_.]\d+)(.*)$/;
|
||||||
|
|
||||||
|
return new RegExp(
|
||||||
|
`${DateLikePrefixRegex.source}|${VersionLikePrefixRegex.source}`,
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
const NumberPrefixRegex = /^(?<numberPrefix>\d+)(?<separator>\s*[-_.]+\s*)(?<suffix>.*)$/;
|
const NumberPrefixRegex = /^(?<numberPrefix>\d+)(?<separator>\s*[-_.]+\s*)(?<suffix>.*)$/;
|
||||||
|
|
||||||
// 0-myDoc => myDoc
|
|
||||||
export function stripNumberPrefix(str: string) {
|
|
||||||
return NumberPrefixRegex.exec(str)?.groups?.suffix ?? str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0-myFolder/0-mySubfolder/0-myDoc => myFolder/mySubfolder/myDoc
|
|
||||||
export function stripPathNumberPrefixes(path: string) {
|
|
||||||
return path.split('/').map(stripNumberPrefix).join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0-myDoc => {filename: myDoc, numberPrefix: 0}
|
// 0-myDoc => {filename: myDoc, numberPrefix: 0}
|
||||||
// 003 - myDoc => {filename: myDoc, numberPrefix: 3}
|
// 003 - myDoc => {filename: myDoc, numberPrefix: 3}
|
||||||
export function extractNumberPrefix(
|
export const DefaultNumberPrefixParser: NumberPrefixParser = (
|
||||||
filename: string,
|
filename: string,
|
||||||
): {filename: string; numberPrefix?: number} {
|
) => {
|
||||||
|
if (IgnoredPrefixPatterns.exec(filename)) {
|
||||||
|
return {filename, numberPrefix: undefined};
|
||||||
|
}
|
||||||
const match = NumberPrefixRegex.exec(filename);
|
const match = NumberPrefixRegex.exec(filename);
|
||||||
const cleanFileName = match?.groups?.suffix ?? filename;
|
const cleanFileName = match?.groups?.suffix ?? filename;
|
||||||
const numberPrefixString = match?.groups?.numberPrefix;
|
const numberPrefixString = match?.groups?.numberPrefix;
|
||||||
|
@ -32,4 +43,27 @@ export function extractNumberPrefix(
|
||||||
filename: cleanFileName,
|
filename: cleanFileName,
|
||||||
numberPrefix,
|
numberPrefix,
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisabledNumberPrefixParser: NumberPrefixParser = (
|
||||||
|
filename: string,
|
||||||
|
) => ({filename, numberPrefix: undefined});
|
||||||
|
|
||||||
|
// 0-myDoc => myDoc
|
||||||
|
export function stripNumberPrefix(
|
||||||
|
str: string,
|
||||||
|
parser: NumberPrefixParser,
|
||||||
|
): string {
|
||||||
|
return parser(str).filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-myFolder/0-mySubfolder/0-myDoc => myFolder/mySubfolder/myDoc
|
||||||
|
export function stripPathNumberPrefixes(
|
||||||
|
path: string,
|
||||||
|
parser: NumberPrefixParser,
|
||||||
|
): string {
|
||||||
|
return path
|
||||||
|
.split('/')
|
||||||
|
.map((segment) => stripNumberPrefix(segment, parser))
|
||||||
|
.join('/');
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ import {OptionValidationContext, ValidationResult} from '@docusaurus/types';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import admonitions from 'remark-admonitions';
|
import admonitions from 'remark-admonitions';
|
||||||
import {DefaultSidebarItemsGenerator} from './sidebarItemsGenerator';
|
import {DefaultSidebarItemsGenerator} from './sidebarItemsGenerator';
|
||||||
|
import {
|
||||||
|
DefaultNumberPrefixParser,
|
||||||
|
DisabledNumberPrefixParser,
|
||||||
|
} from './numberPrefix';
|
||||||
|
|
||||||
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
||||||
path: 'docs', // Path to data on filesystem, relative to site dir.
|
path: 'docs', // Path to data on filesystem, relative to site dir.
|
||||||
|
@ -24,6 +28,7 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
||||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||||
sidebarPath: 'sidebars.json', // Path to the sidebars configuration file
|
sidebarPath: 'sidebars.json', // Path to the sidebars configuration file
|
||||||
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||||
|
numberPrefixParser: DefaultNumberPrefixParser,
|
||||||
docLayoutComponent: '@theme/DocPage',
|
docLayoutComponent: '@theme/DocPage',
|
||||||
docItemComponent: '@theme/DocItem',
|
docItemComponent: '@theme/DocItem',
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
|
@ -66,6 +71,17 @@ export const OptionsSchema = Joi.object({
|
||||||
sidebarItemsGenerator: Joi.function().default(
|
sidebarItemsGenerator: Joi.function().default(
|
||||||
() => DEFAULT_OPTIONS.sidebarItemsGenerator,
|
() => DEFAULT_OPTIONS.sidebarItemsGenerator,
|
||||||
),
|
),
|
||||||
|
numberPrefixParser: Joi.alternatives()
|
||||||
|
.try(
|
||||||
|
Joi.function(),
|
||||||
|
// Convert boolean values to functions
|
||||||
|
Joi.alternatives().conditional(Joi.boolean(), {
|
||||||
|
then: Joi.custom((val) =>
|
||||||
|
val ? DefaultNumberPrefixParser : DisabledNumberPrefixParser,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.default(() => DEFAULT_OPTIONS.numberPrefixParser),
|
||||||
docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent),
|
docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent),
|
||||||
docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent),
|
docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent),
|
||||||
remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins),
|
remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins),
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
import {sortBy, take, last, orderBy} from 'lodash';
|
import {sortBy, take, last, orderBy} from 'lodash';
|
||||||
import {addTrailingSlash, posixPath} from '@docusaurus/utils';
|
import {addTrailingSlash, posixPath} from '@docusaurus/utils';
|
||||||
import {Joi} from '@docusaurus/utils-validation';
|
import {Joi} from '@docusaurus/utils-validation';
|
||||||
import {extractNumberPrefix} from './numberPrefix';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
@ -41,20 +40,22 @@ type WithPosition = {position?: number};
|
||||||
type SidebarItemWithPosition = SidebarItem & WithPosition;
|
type SidebarItemWithPosition = SidebarItem & WithPosition;
|
||||||
|
|
||||||
const CategoryMetadatasFileSchema = Joi.object<CategoryMetadatasFile>({
|
const CategoryMetadatasFileSchema = Joi.object<CategoryMetadatasFile>({
|
||||||
label: Joi.string().optional(),
|
label: Joi.string(),
|
||||||
position: Joi.number().optional(),
|
position: Joi.number(),
|
||||||
collapsed: Joi.boolean().optional(),
|
collapsed: Joi.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata
|
||||||
|
// Example use-case being able to disable number prefix parsing at the folder level, or customize the default route path segment for an intermediate directory...
|
||||||
// TODO later if there is `CategoryFolder/index.md`, we may want to read the metadata as yaml on it
|
// TODO later if there is `CategoryFolder/index.md`, we may want to read the metadata as yaml on it
|
||||||
// see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
|
// see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
|
||||||
async function readCategoryMetadatasFile(
|
async function readCategoryMetadatasFile(
|
||||||
categoryDirPath: string,
|
categoryDirPath: string,
|
||||||
): Promise<CategoryMetadatasFile | null> {
|
): Promise<CategoryMetadatasFile | null> {
|
||||||
function assertCategoryMetadataFile(
|
function validateCategoryMetadataFile(
|
||||||
content: unknown,
|
content: unknown,
|
||||||
): asserts content is CategoryMetadatasFile {
|
): CategoryMetadatasFile {
|
||||||
Joi.attempt(content, CategoryMetadatasFileSchema);
|
return Joi.attempt(content, CategoryMetadatasFileSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryReadFile(
|
async function tryReadFile(
|
||||||
|
@ -69,8 +70,7 @@ async function readCategoryMetadatasFile(
|
||||||
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
|
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
|
||||||
const unsafeContent: unknown = parse(contentString);
|
const unsafeContent: unknown = parse(contentString);
|
||||||
try {
|
try {
|
||||||
assertCategoryMetadataFile(unsafeContent);
|
return validateCategoryMetadataFile(unsafeContent);
|
||||||
return unsafeContent;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
chalk.red(
|
chalk.red(
|
||||||
|
@ -106,6 +106,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio
|
||||||
item,
|
item,
|
||||||
docs: allDocs,
|
docs: allDocs,
|
||||||
version,
|
version,
|
||||||
|
numberPrefixParser,
|
||||||
}): Promise<SidebarItem[]> {
|
}): Promise<SidebarItem[]> {
|
||||||
// Doc at the root of the autogenerated sidebar dir
|
// Doc at the root of the autogenerated sidebar dir
|
||||||
function isRootDoc(doc: SidebarItemsGeneratorDoc) {
|
function isRootDoc(doc: SidebarItemsGeneratorDoc) {
|
||||||
|
@ -194,7 +195,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio
|
||||||
|
|
||||||
const {tail} = parseBreadcrumb(breadcrumb);
|
const {tail} = parseBreadcrumb(breadcrumb);
|
||||||
|
|
||||||
const {filename, numberPrefix} = extractNumberPrefix(tail);
|
const {filename, numberPrefix} = numberPrefixParser(tail);
|
||||||
|
|
||||||
const position = categoryMetadatas?.position ?? numberPrefix;
|
const position = categoryMetadatas?.position ?? numberPrefix;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
SidebarItemsGenerator,
|
SidebarItemsGenerator,
|
||||||
SidebarItemsGeneratorDoc,
|
SidebarItemsGeneratorDoc,
|
||||||
SidebarItemsGeneratorVersion,
|
SidebarItemsGeneratorVersion,
|
||||||
|
NumberPrefixParser,
|
||||||
} from './types';
|
} from './types';
|
||||||
import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash';
|
import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash';
|
||||||
import {getElementsAround} from '@docusaurus/utils';
|
import {getElementsAround} from '@docusaurus/utils';
|
||||||
|
@ -289,11 +290,13 @@ export function toSidebarItemsGeneratorVersion(
|
||||||
// Handle the generation of autogenerated sidebar items
|
// Handle the generation of autogenerated sidebar items
|
||||||
export async function processSidebar({
|
export async function processSidebar({
|
||||||
sidebarItemsGenerator,
|
sidebarItemsGenerator,
|
||||||
|
numberPrefixParser,
|
||||||
unprocessedSidebar,
|
unprocessedSidebar,
|
||||||
docs,
|
docs,
|
||||||
version,
|
version,
|
||||||
}: {
|
}: {
|
||||||
sidebarItemsGenerator: SidebarItemsGenerator;
|
sidebarItemsGenerator: SidebarItemsGenerator;
|
||||||
|
numberPrefixParser: NumberPrefixParser;
|
||||||
unprocessedSidebar: UnprocessedSidebar;
|
unprocessedSidebar: UnprocessedSidebar;
|
||||||
docs: DocMetadataBase[];
|
docs: DocMetadataBase[];
|
||||||
version: VersionMetadata;
|
version: VersionMetadata;
|
||||||
|
@ -318,6 +321,7 @@ export async function processSidebar({
|
||||||
if (item.type === 'autogenerated') {
|
if (item.type === 'autogenerated') {
|
||||||
return sidebarItemsGenerator({
|
return sidebarItemsGenerator({
|
||||||
item,
|
item,
|
||||||
|
numberPrefixParser,
|
||||||
...getSidebarItemsGeneratorDocsAndVersion(),
|
...getSidebarItemsGeneratorDocsAndVersion(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -329,11 +333,13 @@ export async function processSidebar({
|
||||||
|
|
||||||
export async function processSidebars({
|
export async function processSidebars({
|
||||||
sidebarItemsGenerator,
|
sidebarItemsGenerator,
|
||||||
|
numberPrefixParser,
|
||||||
unprocessedSidebars,
|
unprocessedSidebars,
|
||||||
docs,
|
docs,
|
||||||
version,
|
version,
|
||||||
}: {
|
}: {
|
||||||
sidebarItemsGenerator: SidebarItemsGenerator;
|
sidebarItemsGenerator: SidebarItemsGenerator;
|
||||||
|
numberPrefixParser: NumberPrefixParser;
|
||||||
unprocessedSidebars: UnprocessedSidebars;
|
unprocessedSidebars: UnprocessedSidebars;
|
||||||
docs: DocMetadataBase[];
|
docs: DocMetadataBase[];
|
||||||
version: VersionMetadata;
|
version: VersionMetadata;
|
||||||
|
@ -342,6 +348,7 @@ export async function processSidebars({
|
||||||
mapValues(unprocessedSidebars, (unprocessedSidebar) =>
|
mapValues(unprocessedSidebars, (unprocessedSidebar) =>
|
||||||
processSidebar({
|
processSidebar({
|
||||||
sidebarItemsGenerator,
|
sidebarItemsGenerator,
|
||||||
|
numberPrefixParser,
|
||||||
unprocessedSidebar,
|
unprocessedSidebar,
|
||||||
docs,
|
docs,
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -11,18 +11,24 @@ import {
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
resolvePathname,
|
resolvePathname,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {stripPathNumberPrefixes} from './numberPrefix';
|
import {
|
||||||
|
DefaultNumberPrefixParser,
|
||||||
|
stripPathNumberPrefixes,
|
||||||
|
} from './numberPrefix';
|
||||||
|
import {NumberPrefixParser} from './types';
|
||||||
|
|
||||||
export default function getSlug({
|
export default function getSlug({
|
||||||
baseID,
|
baseID,
|
||||||
frontmatterSlug,
|
frontmatterSlug,
|
||||||
dirName,
|
dirName,
|
||||||
stripDirNumberPrefixes = true,
|
stripDirNumberPrefixes = true,
|
||||||
|
numberPrefixParser = DefaultNumberPrefixParser,
|
||||||
}: {
|
}: {
|
||||||
baseID: string;
|
baseID: string;
|
||||||
frontmatterSlug?: string;
|
frontmatterSlug?: string;
|
||||||
dirName: string;
|
dirName: string;
|
||||||
stripDirNumberPrefixes?: boolean;
|
stripDirNumberPrefixes?: boolean;
|
||||||
|
numberPrefixParser?: NumberPrefixParser;
|
||||||
}): string {
|
}): string {
|
||||||
const baseSlug = frontmatterSlug || baseID;
|
const baseSlug = frontmatterSlug || baseID;
|
||||||
let slug: string;
|
let slug: string;
|
||||||
|
@ -30,7 +36,7 @@ export default function getSlug({
|
||||||
slug = baseSlug;
|
slug = baseSlug;
|
||||||
} else {
|
} else {
|
||||||
const dirNameStripped = stripDirNumberPrefixes
|
const dirNameStripped = stripDirNumberPrefixes
|
||||||
? stripPathNumberPrefixes(dirName)
|
? stripPathNumberPrefixes(dirName, numberPrefixParser)
|
||||||
: dirName;
|
: dirName;
|
||||||
const resolveDirname =
|
const resolveDirname =
|
||||||
dirName === '.'
|
dirName === '.'
|
||||||
|
|
|
@ -53,6 +53,7 @@ export type MetadataOptions = {
|
||||||
editLocalizedFiles: boolean;
|
editLocalizedFiles: boolean;
|
||||||
showLastUpdateTime?: boolean;
|
showLastUpdateTime?: boolean;
|
||||||
showLastUpdateAuthor?: boolean;
|
showLastUpdateAuthor?: boolean;
|
||||||
|
numberPrefixParser: NumberPrefixParser;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PathOptions = {
|
export type PathOptions = {
|
||||||
|
@ -154,6 +155,7 @@ export type SidebarItemsGenerator = (generatorArgs: {
|
||||||
item: UnprocessedSidebarItemAutogenerated;
|
item: UnprocessedSidebarItemAutogenerated;
|
||||||
version: SidebarItemsGeneratorVersion;
|
version: SidebarItemsGeneratorVersion;
|
||||||
docs: SidebarItemsGeneratorDoc[];
|
docs: SidebarItemsGeneratorDoc[];
|
||||||
|
numberPrefixParser: NumberPrefixParser;
|
||||||
}) => Promise<SidebarItem[]>;
|
}) => Promise<SidebarItem[]>;
|
||||||
|
|
||||||
export type OrderMetadata = {
|
export type OrderMetadata = {
|
||||||
|
@ -245,3 +247,7 @@ export type DocsMarkdownOption = {
|
||||||
sourceToPermalink: SourceToPermalink;
|
sourceToPermalink: SourceToPermalink;
|
||||||
onBrokenMarkdownLink: (brokenMarkdownLink: BrokenMarkdownLink) => void;
|
onBrokenMarkdownLink: (brokenMarkdownLink: BrokenMarkdownLink) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NumberPrefixParser = (
|
||||||
|
filename: string,
|
||||||
|
) => {filename: string; numberPrefix?: number};
|
||||||
|
|
|
@ -76,10 +76,35 @@ module.exports = {
|
||||||
* Function used to replace the sidebar items of type "autogenerated"
|
* Function used to replace the sidebar items of type "autogenerated"
|
||||||
* by real sidebar items (docs, categories, links...)
|
* by real sidebar items (docs, categories, links...)
|
||||||
*/
|
*/
|
||||||
sidebarItemsGenerator: function ({item, version, docs}) {
|
sidebarItemsGenerator: function ({
|
||||||
|
item,
|
||||||
|
version,
|
||||||
|
docs,
|
||||||
|
numberPrefixParser,
|
||||||
|
}) {
|
||||||
// Use the provided data to create a custom "sidebar slice"
|
// Use the provided data to create a custom "sidebar slice"
|
||||||
return [{type: 'doc', id: 'doc1'}];
|
return [{type: 'doc', id: 'doc1'}];
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* The Docs plugin supports number prefixes like "01-My Folder/02.My Doc.md".
|
||||||
|
* Number prefixes are extracted and used as position to order autogenerated sidebar items.
|
||||||
|
* For conveniency, number prefixes are automatically removed from the default doc id, name, title.
|
||||||
|
* This parsing logic is configurable to allow all possible usecases and filename patterns.
|
||||||
|
* Use "false" to disable this behavior and leave the docs untouched.
|
||||||
|
*/
|
||||||
|
numberPrefixParser: function (filename) {
|
||||||
|
// Implement your own logic to extract a potential number prefix
|
||||||
|
const numberPrefix = findNumberPrefix(filename);
|
||||||
|
// Prefix found: return it with the cleaned filename
|
||||||
|
if (numberPrefix) {
|
||||||
|
return {
|
||||||
|
numberPrefix,
|
||||||
|
filename: filename.replace(prefix, ''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// No number prefix found
|
||||||
|
return {numberPrefix: undefined, filename};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Theme components used by the docs pages
|
* Theme components used by the docs pages
|
||||||
*/
|
*/
|
||||||
|
@ -165,7 +190,7 @@ Markdown documents can use the following markdown frontmatter metadata fields, e
|
||||||
- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`
|
- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`
|
||||||
- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`
|
- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`
|
||||||
- `sidebar_position`: Permits to control the position of a doc inside the generated sidebar slice, when using `autogenerated` sidebar items. Can be Int or Float.
|
- `sidebar_position`: Permits to control the position of a doc inside the generated sidebar slice, when using `autogenerated` sidebar items. Can be Int or Float.
|
||||||
- `strip_number_prefixes`: When a document has a number prefix (`001 - My Doc.md`, `2. MyDoc.md`...), it is automatically removed, and the prefix is used as `sidebar_position`. Use `strip_number_prefixes: false` if you want to disable this behavior
|
- `parse_number_prefixes`: When a document has a number prefix (`001 - My Doc.md`, `2. MyDoc.md`...), it is automatically parsed and extracted by the plugin `numberPrefixParser`, and the number prefix is used as `sidebar_position`. Use `parse_number_prefixes: false` to disable number prefix parsing on this doc
|
||||||
- `custom_edit_url`: The URL for editing this document. If this field is not present, the document's edit URL will fall back to `editUrl` from options fields passed to `docusaurus-plugin-content-docs`
|
- `custom_edit_url`: The URL for editing this document. If this field is not present, the document's edit URL will fall back to `editUrl` from options fields passed to `docusaurus-plugin-content-docs`
|
||||||
- `keywords`: Keywords meta tag for the document page, for search engines
|
- `keywords`: Keywords meta tag for the document page, for search engines
|
||||||
- `description`: The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. If this field is not present, it will default to the first line of the contents
|
- `description`: The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. If this field is not present, it will default to the first line of the contents
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue