refactor: make MDX export a flat TOC list instead of tree (#6729)

This commit is contained in:
Joshua Chen 2022-02-23 22:12:04 +08:00 committed by GitHub
parent 2d93750caf
commit c3370be64d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 312 additions and 445 deletions

View file

@ -5,32 +5,26 @@ exports[`inline code should be escaped 1`] = `
{ {
value: '<code>&lt;Head /&gt;</code>', value: '<code>&lt;Head /&gt;</code>',
id: 'head-', id: 'head-',
children: [
{
value: '<code>&lt;Head&gt;Test&lt;/Head&gt;</code>',
id: 'headtesthead',
children: [],
level: 3
}
],
level: 2 level: 2
}, },
{
value: '<code>&lt;Head&gt;Test&lt;/Head&gt;</code>',
id: 'headtesthead',
level: 3
},
{ {
value: '<code>&lt;div /&gt;</code>', value: '<code>&lt;div /&gt;</code>',
id: 'div-', id: 'div-',
children: [],
level: 2 level: 2
}, },
{ {
value: '<code>&lt;div&gt; Test &lt;/div&gt;</code>', value: '<code>&lt;div&gt; Test &lt;/div&gt;</code>',
id: 'div-test-div', id: 'div-test-div',
children: [],
level: 2 level: 2
}, },
{ {
value: '<code>&lt;div&gt;&lt;i&gt;Test&lt;/i&gt;&lt;/div&gt;</code>', value: '<code>&lt;div&gt;&lt;i&gt;Test&lt;/i&gt;&lt;/div&gt;</code>',
id: 'divitestidiv', id: 'divitestidiv',
children: [],
level: 2 level: 2
} }
]; ];
@ -52,32 +46,26 @@ exports[`non text phrasing content 1`] = `
{ {
value: '<em>Emphasis</em>', value: '<em>Emphasis</em>',
id: 'emphasis', id: 'emphasis',
children: [
{
value: '<strong>Importance</strong>',
id: 'importance',
children: [],
level: 3
}
],
level: 2 level: 2
}, },
{
value: '<strong>Importance</strong>',
id: 'importance',
level: 3
},
{ {
value: '<del>Strikethrough</del>', value: '<del>Strikethrough</del>',
id: 'strikethrough', id: 'strikethrough',
children: [],
level: 2 level: 2
}, },
{ {
value: '<i>HTML</i>', value: '<i>HTML</i>',
id: 'html', id: 'html',
children: [],
level: 2 level: 2
}, },
{ {
value: '<code>inline.code()</code>', value: '<code>inline.code()</code>',
id: 'inlinecode', id: 'inlinecode',
children: [],
level: 2 level: 2
} }
]; ];

View file

@ -41,26 +41,21 @@ test('text content', async () => {
{ {
value: 'Endi', value: 'Endi',
id: 'endi', id: 'endi',
children: [],
level: 3 level: 3
}, },
{ {
value: 'Endi', value: 'Endi',
id: 'endi-1', id: 'endi-1',
children: [
{
value: 'Yangshun',
id: 'yangshun',
children: [],
level: 3
}
],
level: 2 level: 2
}, },
{
value: 'Yangshun',
id: 'yangshun',
level: 3
},
{ {
value: 'I ♥ unicode.', value: 'I ♥ unicode.',
id: 'i--unicode', id: 'i--unicode',
children: [],
level: 2 level: 2
} }
]; ];
@ -91,21 +86,17 @@ test('should export even with existing name', async () => {
{ {
value: 'Thanos', value: 'Thanos',
id: 'thanos', id: 'thanos',
children: [],
level: 2 level: 2
}, },
{ {
value: 'Tony Stark', value: 'Tony Stark',
id: 'tony-stark', id: 'tony-stark',
children: [
{
value: 'Avengers',
id: 'avengers',
children: [],
level: 3
}
],
level: 2 level: 2
},
{
value: 'Avengers',
id: 'avengers',
level: 3
} }
]; ];
@ -128,26 +119,21 @@ test('should export with custom name', async () => {
{ {
value: 'Endi', value: 'Endi',
id: 'endi', id: 'endi',
children: [],
level: 3 level: 3
}, },
{ {
value: 'Endi', value: 'Endi',
id: 'endi-1', id: 'endi-1',
children: [
{
value: 'Yangshun',
id: 'yangshun',
children: [],
level: 3
}
],
level: 2 level: 2
}, },
{
value: 'Yangshun',
id: 'yangshun',
level: 3
},
{ {
value: 'I ♥ unicode.', value: 'I ♥ unicode.',
id: 'i--unicode', id: 'i--unicode',
children: [],
level: 2 level: 2
} }
]; ];
@ -182,21 +168,17 @@ test('should insert below imports', async () => {
{ {
value: 'Title', value: 'Title',
id: 'title', id: 'title',
children: [],
level: 2 level: 2
}, },
{ {
value: 'Test', value: 'Test',
id: 'test', id: 'test',
children: [
{
value: 'Again',
id: 'again',
children: [],
level: 3
}
],
level: 2 level: 2
},
{
value: 'Again',
id: 'again',
level: 3
} }
]; ];

View file

@ -1,182 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import remark from 'remark';
import mdx from 'remark-mdx';
import search from '../search';
import headings from '../../headings/index';
const getHeadings = async (mdText: string) => {
const node = remark().parse(mdText);
const result = await remark().use(headings).use(mdx).run(node);
return search(result);
};
test('should process all heading levels', async () => {
const md = `
# Alpha
## Bravo
### Charlie
#### Delta
##### Echo
###### Foxtrot
`;
await expect(getHeadings(md)).resolves.toEqual([
{
children: [
{
children: [
{
children: [
{
children: [
{
children: [],
id: 'foxtrot',
level: 6,
value: 'Foxtrot',
},
],
id: 'echo',
level: 5,
value: 'Echo',
},
],
id: 'delta',
level: 4,
value: 'Delta',
},
],
id: 'charlie',
level: 3,
value: 'Charlie',
},
],
id: 'bravo',
level: 2,
value: 'Bravo',
},
]);
});
test('should process real-world well-formatted md', async () => {
const md = `
# title
some text
## section 1
some text
### subsection 1-1
some text
#### subsection 1-1-1
some text
#### subsection 1-1-2
some text
### subsection 1-2
some text
### subsection 1-3
some text
## section 2
some text
### subsection 2-1
some text
### subsection 2-1
some text
## section 3
some text
### subsection 3-1
some text
### subsection 3-2
some text
`;
await expect(getHeadings(md)).resolves.toEqual([
{
children: [
{
children: [
{
children: [],
id: 'subsection-1-1-1',
level: 4,
value: 'subsection 1-1-1',
},
{
children: [],
id: 'subsection-1-1-2',
level: 4,
value: 'subsection 1-1-2',
},
],
id: 'subsection-1-1',
level: 3,
value: 'subsection 1-1',
},
{children: [], id: 'subsection-1-2', level: 3, value: 'subsection 1-2'},
{children: [], id: 'subsection-1-3', level: 3, value: 'subsection 1-3'},
],
id: 'section-1',
level: 2,
value: 'section 1',
},
{
children: [
{children: [], id: 'subsection-2-1', level: 3, value: 'subsection 2-1'},
{
children: [],
id: 'subsection-2-1-1',
level: 3,
value: 'subsection 2-1',
},
],
id: 'section-2',
level: 2,
value: 'section 2',
},
{
children: [
{children: [], id: 'subsection-3-1', level: 3, value: 'subsection 3-1'},
{children: [], id: 'subsection-3-2', level: 3, value: 'subsection 3-2'},
],
id: 'section-3',
level: 2,
value: 'section 3',
},
]);
});

View file

@ -9,10 +9,14 @@ import {parse, type ParserOptions} from '@babel/parser';
import type {Identifier} from '@babel/types'; import type {Identifier} from '@babel/types';
import traverse from '@babel/traverse'; import traverse from '@babel/traverse';
import stringifyObject from 'stringify-object'; import stringifyObject from 'stringify-object';
import search from './search'; import toString from 'mdast-util-to-string';
import type {Plugin, Transformer} from 'unified'; import visit from 'unist-util-visit';
import {toValue} from '../utils';
import type {TOCItem} from '@docusaurus/types';
import type {Node, Parent} from 'unist'; import type {Node, Parent} from 'unist';
import type {Literal} from 'mdast'; import type {Heading, Literal} from 'mdast';
import type {Plugin, Transformer} from 'unified';
const parseOptions: ParserOptions = { const parseOptions: ParserOptions = {
plugins: ['jsx'], plugins: ['jsx'],
@ -70,7 +74,22 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const name = options.name || 'toc'; const name = options.name || 'toc';
const transformer: Transformer = (node) => { const transformer: Transformer = (node) => {
const headings = search(node); const headings: TOCItem[] = [];
visit(node, 'heading', (child: Heading, _index, parent) => {
const value = toString(child);
// depth:1 headings are titles and not included in the TOC
if (parent !== node || !value || child.depth < 2) {
return;
}
headings.push({
value: toValue(child),
id: child.data!.id as string,
level: child.depth,
});
});
const {children} = node as Parent<Literal>; const {children} = node as Parent<Literal>;
const targetIndex = getOrCreateExistingTargetIndex(children, name); const targetIndex = getOrCreateExistingTargetIndex(children, name);

View file

@ -1,80 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import {toValue} from '../utils';
import type {TOCItem} from '@docusaurus/types';
import type {Node} from 'unist';
import type {Heading} from 'mdast';
// Intermediate interface for TOC algorithm
interface SearchItem {
node: TOCItem;
level: number;
parentIndex: number;
}
/**
*
* Generate a TOC AST from the raw Markdown contents
*/
export default function search(node: Node): TOCItem[] {
const headings: SearchItem[] = [];
visit(node, 'heading', (child: Heading, _index, parent) => {
const value = toString(child);
// depth:1 headings are titles and not included in the TOC
if (parent !== node || !value || child.depth < 2) {
return;
}
headings.push({
node: {
value: toValue(child),
id: child.data!.id as string,
children: [],
level: child.depth,
},
level: child.depth,
parentIndex: -1,
});
});
// Keep track of which previous index would be the current heading's direct
// parent. Each entry <i> is the last index of the `headings` array at heading
// level <i>. We will modify these indices as we iterate through all headings.
// e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2
// indices 0 and 1 will remain unused.
const prevIndexForLevel = Array(7).fill(-1);
headings.forEach((curr, currIndex) => {
// take the last seen index for each ancestor level. the highest
// index will be the direct ancestor of the current heading.
const ancestorLevelIndexes = prevIndexForLevel.slice(2, curr.level);
curr.parentIndex = Math.max(...ancestorLevelIndexes);
// mark that curr.level was last seen at the current index
prevIndexForLevel[curr.level] = currIndex;
});
const rootNodeIndexes: number[] = [];
// For a given parentIndex, add each Node into that parent's `children` array
headings.forEach((heading, i) => {
if (heading.parentIndex >= 0) {
headings[heading.parentIndex].node.children.push(heading.node);
} else {
rootNodeIndexes.push(i);
}
});
const toc = headings
.filter((_, k) => rootNodeIndexes.includes(k)) // only return root nodes
.map((heading) => heading.node); // only return Node, no metadata
return toc;
}

View file

@ -7,12 +7,12 @@
import React, {useMemo} from 'react'; import React, {useMemo} from 'react';
import type {Props} from '@theme/TOCItems'; import type {Props} from '@theme/TOCItems';
import type {TOCItem} from '@docusaurus/types';
import { import {
type TOCHighlightConfig, type TOCHighlightConfig,
type TOCTreeNode,
useThemeConfig, useThemeConfig,
useTOCFilter,
useTOCHighlight, useTOCHighlight,
useFilteredAndTreeifiedTOC,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
// Recursive component rendering the toc tree // Recursive component rendering the toc tree
@ -23,7 +23,7 @@ function TOCItemList({
linkClassName, linkClassName,
isChild, isChild,
}: { }: {
readonly toc: readonly TOCItem[]; readonly toc: readonly TOCTreeNode[];
readonly className: string; readonly className: string;
readonly linkClassName: string | null; readonly linkClassName: string | null;
readonly isChild?: boolean; readonly isChild?: boolean;
@ -70,7 +70,11 @@ export default function TOCItems({
const maxHeadingLevel = const maxHeadingLevel =
maxHeadingLevelOption ?? themeConfig.tableOfContents.maxHeadingLevel; maxHeadingLevelOption ?? themeConfig.tableOfContents.maxHeadingLevel;
const tocFiltered = useTOCFilter({toc, minHeadingLevel, maxHeadingLevel}); const tocTree = useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel,
maxHeadingLevel,
});
const tocHighlightConfig: TOCHighlightConfig | undefined = useMemo(() => { const tocHighlightConfig: TOCHighlightConfig | undefined = useMemo(() => {
if (linkClassName && linkActiveClassName) { if (linkClassName && linkActiveClassName) {
@ -87,7 +91,7 @@ export default function TOCItems({
return ( return (
<TOCItemList <TOCItemList
toc={tocFiltered} toc={tocTree}
className={className} className={className}
linkClassName={linkClassName} linkClassName={linkClassName}
{...props} {...props}

View file

@ -104,7 +104,11 @@ export {useHistoryPopHandler} from './utils/historyUtils';
export {default as useTOCHighlight} from './utils/useTOCHighlight'; export {default as useTOCHighlight} from './utils/useTOCHighlight';
export type {TOCHighlightConfig} from './utils/useTOCHighlight'; export type {TOCHighlightConfig} from './utils/useTOCHighlight';
export {useTOCFilter} from './utils/tocUtils'; export {
useFilteredAndTreeifiedTOC,
useTreeifiedTOC,
type TOCTreeNode,
} from './utils/tocUtils';
export { export {
ScrollControllerProvider, ScrollControllerProvider,

View file

@ -6,55 +6,53 @@
*/ */
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/types';
import {filterTOC} from '../tocUtils'; import {renderHook} from '@testing-library/react-hooks';
import {useFilteredAndTreeifiedTOC} from '../tocUtils';
describe('filterTOC', () => { describe('useFilteredAndTreeifiedTOC', () => {
test('filter a toc with all heading levels', () => { test('filter a toc with all heading levels', () => {
const toc: TOCItem[] = [ const toc: TOCItem[] = [
{ {
id: 'alpha', id: 'alpha',
level: 1, level: 1,
value: 'alpha', value: 'alpha',
children: [ },
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
value: 'Bravo', value: 'Bravo',
children: [ },
{ {
id: 'charlie', id: 'charlie',
level: 3, level: 3,
value: 'Charlie', value: 'Charlie',
children: [ },
{ {
id: 'delta', id: 'delta',
level: 4, level: 4,
value: 'Delta', value: 'Delta',
children: [ },
{ {
id: 'echo', id: 'echo',
level: 5, level: 5,
value: 'Echo', value: 'Echo',
children: [ },
{ {
id: 'foxtrot', id: 'foxtrot',
level: 6, level: 6,
value: 'Foxtrot', value: 'Foxtrot',
children: [],
},
],
},
],
},
],
},
],
},
],
}, },
]; ];
expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 2})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 2,
maxHeadingLevel: 2,
}),
).result.current,
).toEqual([
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
@ -63,7 +61,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 3})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 3,
maxHeadingLevel: 3,
}),
).result.current,
).toEqual([
{ {
id: 'charlie', id: 'charlie',
level: 3, level: 3,
@ -72,7 +78,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 3})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 2,
maxHeadingLevel: 3,
}),
).result.current,
).toEqual([
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
@ -88,7 +102,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 4})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 2,
maxHeadingLevel: 4,
}),
).result.current,
).toEqual([
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
@ -121,24 +143,28 @@ describe('filterTOC', () => {
id: 'charlie', id: 'charlie',
level: 3, level: 3,
value: 'Charlie', value: 'Charlie',
children: [],
}, },
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
value: 'Bravo', value: 'Bravo',
children: [ },
{ {
id: 'delta', id: 'delta',
level: 4, level: 4,
value: 'Delta', value: 'Delta',
children: [],
},
],
}, },
]; ];
expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 2})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 2,
maxHeadingLevel: 2,
}),
).result.current,
).toEqual([
{ {
id: 'bravo', id: 'bravo',
level: 2, level: 2,
@ -147,7 +173,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 3})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 3,
maxHeadingLevel: 3,
}),
).result.current,
).toEqual([
{ {
id: 'charlie', id: 'charlie',
level: 3, level: 3,
@ -156,7 +190,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 4, maxHeadingLevel: 4})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 4,
maxHeadingLevel: 4,
}),
).result.current,
).toEqual([
{ {
id: 'delta', id: 'delta',
level: 4, level: 4,
@ -165,7 +207,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 3})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 2,
maxHeadingLevel: 3,
}),
).result.current,
).toEqual([
{ {
id: 'charlie', id: 'charlie',
level: 3, level: 3,
@ -180,7 +230,15 @@ describe('filterTOC', () => {
}, },
]); ]);
expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 4})).toEqual([ expect(
renderHook(() =>
useFilteredAndTreeifiedTOC({
toc,
minHeadingLevel: 3,
maxHeadingLevel: 4,
}),
).result.current,
).toEqual([
{ {
id: 'charlie', id: 'charlie',
level: 3, level: 3,

View file

@ -8,18 +8,64 @@
import {useMemo} from 'react'; import {useMemo} from 'react';
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/types';
type FilterTOCParam = { export type TOCTreeNode = {
toc: readonly TOCItem[]; readonly value: string;
minHeadingLevel: number; readonly id: string;
maxHeadingLevel: number; readonly level: number;
readonly children: readonly TOCTreeNode[];
}; };
export function filterTOC({ function treeifyTOC(flatTOC: readonly TOCItem[]): TOCTreeNode[] {
const headings = flatTOC.map((heading) => ({
...heading,
parentIndex: -1,
children: [] as TOCTreeNode[],
}));
// Keep track of which previous index would be the current heading's direct
// parent. Each entry <i> is the last index of the `headings` array at heading
// level <i>. We will modify these indices as we iterate through all headings.
// e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2
// indices 0 and 1 will remain unused.
const prevIndexForLevel = Array(7).fill(-1);
headings.forEach((curr, currIndex) => {
// take the last seen index for each ancestor level. the highest
// index will be the direct ancestor of the current heading.
const ancestorLevelIndexes = prevIndexForLevel.slice(2, curr.level);
curr.parentIndex = Math.max(...ancestorLevelIndexes);
// mark that curr.level was last seen at the current index
prevIndexForLevel[curr.level] = currIndex;
});
const rootNodes: TOCTreeNode[] = [];
// For a given parentIndex, add each Node into that parent's `children` array
headings.forEach((heading) => {
const {parentIndex, ...rest} = heading;
if (parentIndex >= 0) {
headings[parentIndex].children.push(rest);
} else {
rootNodes.push(rest);
}
});
return rootNodes;
}
export function useTreeifiedTOC(toc: TOCItem[]): readonly TOCTreeNode[] {
return useMemo(() => treeifyTOC(toc), [toc]);
}
function filterTOC({
toc, toc,
minHeadingLevel, minHeadingLevel,
maxHeadingLevel, maxHeadingLevel,
}: FilterTOCParam): TOCItem[] { }: {
function isValid(item: TOCItem) { toc: readonly TOCTreeNode[];
minHeadingLevel: number;
maxHeadingLevel: number;
}): TOCTreeNode[] {
function isValid(item: TOCTreeNode) {
return item.level >= minHeadingLevel && item.level <= maxHeadingLevel; return item.level >= minHeadingLevel && item.level <= maxHeadingLevel;
} }
@ -41,14 +87,22 @@ export function filterTOC({
}); });
} }
// Memoize potentially expensive filtering logic export function useFilteredAndTreeifiedTOC({
export function useTOCFilter({
toc, toc,
minHeadingLevel, minHeadingLevel,
maxHeadingLevel, maxHeadingLevel,
}: FilterTOCParam): readonly TOCItem[] { }: {
toc: readonly TOCItem[];
minHeadingLevel: number;
maxHeadingLevel: number;
}): readonly TOCTreeNode[] {
return useMemo( return useMemo(
() => filterTOC({toc, minHeadingLevel, maxHeadingLevel}), () =>
// Note: we have to filter the TOC after it has been treeified. This is
// mostly to ensure that weird TOC structures preserve their semantics.
// For example, an h3-h2-h4 sequence should not be treeified as an h3 > h4
// hierarchy with min=3, max=4, but should rather be [h3, h4]
filterTOC({toc: treeifyTOC(toc), minHeadingLevel, maxHeadingLevel}),
[toc, minHeadingLevel, maxHeadingLevel], [toc, minHeadingLevel, maxHeadingLevel],
); );
} }

View file

@ -429,12 +429,11 @@ export interface ThemeConfigValidationContext<T> {
themeConfig: Partial<T>; themeConfig: Partial<T>;
} }
export interface TOCItem { export type TOCItem = {
readonly value: string; readonly value: string;
readonly id: string; readonly id: string;
readonly children: TOCItem[];
readonly level: number; readonly level: number;
} };
export type RouteChunksTree = {[x: string | number]: string | RouteChunksTree}; export type RouteChunksTree = {[x: string | number]: string | RouteChunksTree};

View file

@ -246,6 +246,7 @@ stylelintrc
sublabel sublabel
sublicensable sublicensable
sublist sublist
subsubsection
subpage subpage
subroute subroute
subroutes subroutes
@ -260,6 +261,7 @@ toolset
toplevel toplevel
transifex transifex
transpiles transpiles
treeified
treeify treeify
treosh treosh
triaging triaging

View file

@ -25,12 +25,6 @@ Once your website is bootstrapped, the website source will contain the Docusauru
} }
``` ```
## Index {#index}
import TOCInline from "@theme/TOCInline"
<TOCInline toc={toc[1].children}/>
## Docusaurus CLI commands {#docusaurus-cli-commands} ## Docusaurus CLI commands {#docusaurus-cli-commands}
Below is a list of Docusaurus CLI commands and their usages: Below is a list of Docusaurus CLI commands and their usages:

View file

@ -17,7 +17,7 @@ However, it can be helpful if you have a high-level understanding of how the con
The high-level overview of Docusaurus configuration can be categorized into: The high-level overview of Docusaurus configuration can be categorized into:
<TOCInline toc={toc[0].children} /> <TOCInline toc={toc} minHeadingLevel={3} maxHeadingLevel={3} />
For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md). For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md).

View file

@ -35,33 +35,30 @@ import TOCInline from '@theme/TOCInline';
## Custom table of contents {#custom-table-of-contents} ## Custom table of contents {#custom-table-of-contents}
The `toc` props is just a list of table of contents items: The `toc` prop is just a list of heading items:
```ts ```ts
type TOCItem = { type TOCItem = {
value: string; value: string;
id: string; id: string;
children: TOCItem[];
level: number; level: number;
}; };
``` ```
You can create this TOC tree manually, or derive a new TOC tree from the `toc` variable: Note that the `toc` global is a flat array, so you can easily cut out unwanted nodes or insert extra nodes, and create a new TOC tree.
```jsx ```jsx
import TOCInline from '@theme/TOCInline'; import TOCInline from '@theme/TOCInline';
<TOCInline <TOCInline
toc={ // Only show h2 and h4 headings
// Only show 3th and 5th top-level heading toc={toc.filter((node) => node.level === 2 || node.level === 4)}
[toc[2], toc[4]]
}
/>; />;
``` ```
```mdx-code-block ```mdx-code-block
<BrowserWindow> <BrowserWindow>
<TOCInline toc={[toc[2], toc[4]]} /> <TOCInline toc={toc.filter((node) => node.level === 2 || node.level === 4)} />
</BrowserWindow> </BrowserWindow>
``` ```
@ -81,14 +78,32 @@ Lorem ipsum
Lorem ipsum Lorem ipsum
#### Example subsubsection 1 a I
#### Example subsubsection 1 a II
#### Example subsubsection 1 a III
### Example Subsection 1 b {#example-subsection-1-b} ### Example Subsection 1 b {#example-subsection-1-b}
Lorem ipsum Lorem ipsum
#### Example subsubsection 1 b I
#### Example subsubsection 1 b II
#### Example subsubsection 1 b III
### Example Subsection 1 c {#example-subsection-1-c} ### Example Subsection 1 c {#example-subsection-1-c}
Lorem ipsum Lorem ipsum
#### Example subsubsection 1 c I
#### Example subsubsection 1 c II
#### Example subsubsection 1 c III
## Example Section 2 {#example-section-2} ## Example Section 2 {#example-section-2}
Lorem ipsum Lorem ipsum
@ -97,14 +112,32 @@ Lorem ipsum
Lorem ipsum Lorem ipsum
#### Example subsubsection 2 a I
#### Example subsubsection 2 a II
#### Example subsubsection 2 a III
### Example Subsection 2 b {#example-subsection-2-b} ### Example Subsection 2 b {#example-subsection-2-b}
Lorem ipsum Lorem ipsum
#### Example subsubsection 2 b I
#### Example subsubsection 2 b II
#### Example subsubsection 2 b III
### Example Subsection 2 c {#example-subsection-2-c} ### Example Subsection 2 c {#example-subsection-2-c}
Lorem ipsum Lorem ipsum
#### Example subsubsection 2 c I
#### Example subsubsection 2 c II
#### Example subsubsection 2 c III
## Example Section 3 {#example-section-3} ## Example Section 3 {#example-section-3}
Lorem ipsum Lorem ipsum
@ -113,10 +146,28 @@ Lorem ipsum
Lorem ipsum Lorem ipsum
#### Example subsubsection 3 a I
#### Example subsubsection 3 a II
#### Example subsubsection 3 a III
### Example Subsection 3 b {#example-subsection-3-b} ### Example Subsection 3 b {#example-subsection-3-b}
Lorem ipsum Lorem ipsum
#### Example subsubsection 3 b I
#### Example subsubsection 3 b II
#### Example subsubsection 3 b III
### Example Subsection 3 c {#example-subsection-3-c} ### Example Subsection 3 c {#example-subsection-3-c}
Lorem ipsum Lorem ipsum
#### Example subsubsection 3 c I
#### Example subsubsection 3 c II
#### Example subsubsection 3 c III

View file

@ -25,12 +25,6 @@ Once your website is bootstrapped, the website source will contain the Docusauru
} }
``` ```
## Index {#index}
import TOCInline from "@theme/TOCInline"
<TOCInline toc={toc[1].children}/>
## Docusaurus CLI commands {#docusaurus-cli-commands} ## Docusaurus CLI commands {#docusaurus-cli-commands}
Below is a list of Docusaurus CLI commands and their usages: Below is a list of Docusaurus CLI commands and their usages:

View file

@ -17,7 +17,7 @@ However, it can be helpful if you have a high-level understanding of how the con
The high-level overview of Docusaurus configuration can be categorized into: The high-level overview of Docusaurus configuration can be categorized into:
<TOCInline toc={toc[0].children} /> <TOCInline toc={toc} minHeadingLevel={3} maxHeadingLevel={3} />
For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md). For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md).

View file

@ -59,14 +59,6 @@ import TOCInline from '@theme/TOCInline';
/>; />;
``` ```
```mdx-code-block
<BrowserWindow>
<TOCInline toc={[toc[2], toc[4]]} />
</BrowserWindow>
```
--- ---
:::caution :::caution

View file

@ -25,12 +25,6 @@ Once your website is bootstrapped, the website source will contain the Docusauru
} }
``` ```
## Index {#index}
import TOCInline from "@theme/TOCInline"
<TOCInline toc={toc[1].children}/>
## Docusaurus CLI commands {#docusaurus-cli-commands} ## Docusaurus CLI commands {#docusaurus-cli-commands}
Below is a list of Docusaurus CLI commands and their usages: Below is a list of Docusaurus CLI commands and their usages:

View file

@ -17,7 +17,7 @@ However, it can be helpful if you have a high-level understanding of how the con
The high-level overview of Docusaurus configuration can be categorized into: The high-level overview of Docusaurus configuration can be categorized into:
<TOCInline toc={toc[0].children} /> <TOCInline toc={toc} minHeadingLevel={3} maxHeadingLevel={3} />
For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md). For exact reference to each of the configurable fields, you may refer to [**`docusaurus.config.js` API reference**](api/docusaurus.config.js.md).

View file

@ -59,12 +59,6 @@ import TOCInline from '@theme/TOCInline';
/>; />;
``` ```
```mdx-code-block
<BrowserWindow>
<TOCInline toc={[toc[2], toc[4]]} />
</BrowserWindow>
```
--- ---
:::caution :::caution