docs: redesign table of content (#12647)

* implement toc

* added to projects

* fixes and adapt for references

* added product frontmatter

* remove action menu from 404 pages
This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-05-30 16:55:36 +03:00
1 parent 490bd7647f
commit 009d00f27d
293 files changed
+1975 -506

No files matched your search

@@ -30,9 +30,11 @@
"dependencies": {
"@cloudinary/url-gen": "^1.17.0",
"docs-utils": "*",
"slugify": "^1.6.6",
"unified": "^11.0.4",
"unist-builder": "3.0.0",
"unist-util-visit": "4.1.2"
"unist-util-visit": "4.1.2",
"vfile-matter": "^5.0.1"
},
"devDependencies": {
"@types/node": "^20.11.20",
@@ -0,0 +1,9 @@
import { Transformer } from "unified"
export function remarkAttachFrontmatterDataPlugin(): Transformer {
return async (tree, file) => {
const { matter } = await import("vfile-matter")
matter(file)
}
}
@@ -3,6 +3,8 @@ export * from "./broken-link-checker.js"
export * from "./change-links-md.js"
export * from "./cloudinary-img.js"
export * from "./cross-project-links.js"
export * from "./attach-frontmatter-data.js"
export * from "./inject-mdx-data.js"
export * from "./local-links.js"
export * from "./page-number.js"
export * from "./prerequisites-link-fixer.js"
@@ -0,0 +1,345 @@
import {
FrontMatter,
ObjectExpression,
ToCItem,
UnistFunctionDeclarationNode,
UnistNode,
UnistProgram,
UnistReturnStatementNode,
} from "types"
import type { Transformer } from "unified"
type DataToc = Record<string, unknown>[]
type Options = {
isRemoteMdx?: boolean
mode?: "development" | "production"
}
export function recmaInjectMdxDataPlugin(options?: Options): Transformer {
const isRemoteMdx = options?.isRemoteMdx || false
const mode = options?.mode || "development"
return async (tree, file) => {
const frontmatter: FrontMatter = file.data.matter || {}
const tocMaxDepth = frontmatter.toc_max_depth || 4
let dataToc = (file.data.toc || []) as DataToc
if (dataToc.length > 0 && dataToc[0].depth === 1) {
dataToc = (dataToc[0].children || []) as DataToc
}
const toc: ToCItem[] = frontmatter.generate_toc
? []
: getToc(dataToc, tocMaxDepth)
const program = tree as UnistProgram
for (const node of program.body) {
if (node.type === "FunctionDeclaration") {
const fnNode = node as UnistFunctionDeclarationNode
const returnStatement = fnNode.body.body.find(
(n) => n.type === "ReturnStatement"
)
if (!returnStatement) {
continue
}
const returnChildren = getReturnChildren(
returnStatement as UnistReturnStatementNode
)
if (isRemoteMdx) {
handleRemoteMdx({
returnChildren,
frontmatter,
toc,
mode,
})
} else {
handleDefaultMdx({
program,
returnChildren,
frontmatter,
toc,
})
}
return
}
}
}
}
function handleDefaultMdx({
program,
returnChildren,
frontmatter,
toc,
}: {
program: UnistProgram
returnChildren: UnistNode[]
frontmatter: FrontMatter
toc: ToCItem[]
}) {
program.body.unshift({
type: "ImportDeclaration",
source: { type: "Literal", value: "docs-ui" },
specifiers: [
{
type: "ImportSpecifier",
imported: { type: "Identifier", name: "InjectedMDXData" },
local: { type: "Identifier", name: "InjectedMDXData" },
},
],
})
returnChildren.push({
type: "JSXElement",
openingElement: {
type: "JSXOpeningElement",
name: {
type: "JSXIdentifier",
name: "InjectedMDXData",
},
attributes: [
{
type: "JSXAttribute",
name: {
type: "JSXIdentifier",
name: "frontmatter",
},
value: {
type: "JSXExpressionContainer",
expression: {
type: "ObjectExpression",
properties: Object.entries(frontmatter).map(([key, value]) => ({
type: "Property",
key: {
type: "Identifier",
name: key,
},
value: {
type: "Literal",
value: value,
raw: JSON.stringify(value),
},
kind: "init",
computed: false,
method: false,
shorthand: false,
})),
},
},
},
{
type: "JSXAttribute",
name: {
type: "JSXIdentifier",
name: "toc",
},
value: {
type: "JSXExpressionContainer",
expression: {
type: "ArrayExpression",
elements: getTocJSX(toc),
},
},
},
],
selfClosing: true,
},
} as any)
}
function handleRemoteMdx({
returnChildren,
frontmatter,
toc,
mode,
}: {
returnChildren: UnistNode[]
frontmatter: FrontMatter
toc: ToCItem[]
mode: "development" | "production"
}) {
const functionName = mode === "development" ? "_jsxDEV" : "_jsx"
returnChildren.push({
type: "Literal",
value: "\n",
})
returnChildren.push({
type: "CallExpression",
callee: {
type: "Identifier",
name: functionName,
},
arguments: [
{
type: "MemberExpression",
object: {
type: "Identifier",
name: "_components",
},
property: {
type: "Identifier",
name: "InjectedMDXData",
},
computed: false,
optional: false,
shorthand: false,
},
{
type: "ObjectExpression",
properties: [
{
type: "Property",
key: { type: "Identifier", name: "frontmatter" },
value: {
type: "ObjectExpression",
properties: Object.entries(frontmatter).map(([key, value]) => ({
type: "Property",
key: {
type: "Identifier",
name: key,
},
value: {
type: "Literal",
value: value,
raw: JSON.stringify(value),
},
kind: "init",
computed: false,
optional: false,
shorthand: false,
})),
},
kind: "init",
computed: false,
optional: false,
shorthand: false,
},
{
type: "Property",
key: { type: "Identifier", name: "toc" },
value: {
type: "ArrayExpression",
elements: getTocJSX(toc),
},
kind: "init",
computed: false,
optional: false,
shorthand: false,
},
],
},
],
} as any)
}
function getReturnChildren(node: UnistReturnStatementNode): UnistNode[] {
const rootJSX = node.argument
if (rootJSX.type === "JSXFragment") {
return rootJSX.children
} else if (rootJSX.type === "JSXElement") {
return []
}
const props = rootJSX.arguments.find(
(arg) => arg.type === "ObjectExpression"
) as ObjectExpression | undefined
if (!props) {
return []
}
const childrenProp = props.properties.find(
(prop) => prop.key.name === "children"
)
if (!childrenProp || childrenProp.value.type !== "ArrayExpression") {
return []
}
return childrenProp.value.elements as UnistNode[]
}
function getToc(items: Record<string, unknown>[], maxDepth: number): ToCItem[] {
const toc: ToCItem[] = []
items.forEach((i) => {
const depth = i.depth as number
if (depth > maxDepth) {
return
}
const tocItem: ToCItem = {
title: i.value as string,
level: depth,
id: i.id as string,
children: [],
}
if (i.children && Array.isArray(i.children) && i.children.length > 0) {
tocItem.children = getToc(i.children, maxDepth)
}
toc.push(tocItem)
})
return toc
}
function getTocJSX(toc: ToCItem[]): any[] {
return toc.map((item) => {
const itemData = {
type: "ObjectExpression",
properties: [
{
type: "Property",
key: { type: "Identifier", name: "title" },
value: { type: "Literal", value: item.title },
kind: "init",
computed: false,
method: false,
shorthand: false,
},
{
type: "Property",
key: { type: "Identifier", name: "level" },
value: { type: "Literal", value: item.level },
kind: "init",
computed: false,
method: false,
shorthand: false,
},
{
type: "Property",
key: { type: "Identifier", name: "id" },
value: { type: "Literal", value: item.id },
kind: "init",
computed: false,
method: false,
shorthand: false,
},
],
}
if (item.children && item.children.length > 0) {
itemData.properties.push({
type: "Property",
key: { type: "Identifier", name: "children" },
value: {
type: "ArrayExpression",
elements: getTocJSX(item.children),
},
kind: "init",
computed: false,
method: false,
shorthand: false,
} as any)
}
return itemData
})
}