docusaurus-plugin-openapi
Advanced tools
Comparing version 0.6.2 to 0.6.3
@@ -16,2 +16,4 @@ "use strict"; | ||
const chalk_1 = __importDefault(require("chalk")); | ||
const fs_extra_1 = __importDefault(require("fs-extra")); | ||
const docs_1 = require("./docs/docs"); | ||
const markdown_1 = require("./markdown"); | ||
@@ -23,3 +25,3 @@ const openapi_1 = require("./openapi"); | ||
var _a; | ||
const { baseUrl, generatedFilesDir } = context; | ||
const { baseUrl, generatedFilesDir, siteDir } = context; | ||
const pluginId = (_a = options.id) !== null && _a !== void 0 ? _a : utils_1.DEFAULT_PLUGIN_ID; | ||
@@ -41,2 +43,23 @@ const pluginDataDirRoot = path_1.default.join(generatedFilesDir, "docusaurus-plugin-openapi"); | ||
const { routeBasePath } = options; | ||
async function toMetadata( | ||
/** E.g. "api/plugins/myDoc.mdx" */ | ||
relativeSource) { | ||
const source = path_1.default.join(contentPath, relativeSource); | ||
const content = await fs_extra_1.default.readFile(source, "utf-8"); | ||
return { | ||
type: "mdx", | ||
...(await (0, docs_1.processDocMetadata)({ | ||
docFile: { | ||
contentPath: contentPath, | ||
filePath: source, | ||
source: relativeSource, | ||
content: content, | ||
}, | ||
relativeSource: relativeSource, | ||
context: context, | ||
options: options, | ||
env: process.env.NODE_ENV, | ||
})), | ||
}; | ||
} | ||
try { | ||
@@ -49,2 +72,12 @@ const openapiFiles = await (0, openapi_1.readOpenapiFiles)(contentPath, {}); | ||
}); | ||
const pagesFiles = []; | ||
if (!contentPath.endsWith(".json") && | ||
!contentPath.endsWith(".yaml") && | ||
!contentPath.endsWith(".yml")) { | ||
pagesFiles.push(...(await (0, utils_1.Globby)(["**/*.{md,mdx}"], { | ||
cwd: contentPath, | ||
// ignore: options.exclude, // TODO | ||
}))); | ||
} | ||
loadedApi.push(...(await Promise.all(pagesFiles.map(toMetadata)))); | ||
return { loadedApi }; | ||
@@ -69,14 +102,31 @@ } | ||
const pageId = `site-${routeBasePath}-${item.id}`; | ||
await createData(`${(0, utils_1.docuHash)(pageId)}.json`, JSON.stringify(item, null, 2)); | ||
// TODO: "-content" should be inside hash to prevent name too long errors. | ||
const markdown = await createData(`${(0, utils_1.docuHash)(pageId)}-content.mdx`, item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item)); | ||
return { | ||
path: item.permalink, | ||
component: apiItemComponent, | ||
exact: true, | ||
modules: { | ||
content: markdown, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
if (item.type === "api" || item.type === "info") { | ||
await createData(`${(0, utils_1.docuHash)(pageId)}.json`, JSON.stringify(item, null, 2)); | ||
// TODO: "-content" should be inside hash to prevent name too long errors. | ||
const markdown = await createData(`${(0, utils_1.docuHash)(pageId)}-content.mdx`, item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item)); | ||
return { | ||
path: item.permalink, | ||
component: apiItemComponent, | ||
exact: true, | ||
modules: { | ||
content: markdown, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
} | ||
else { | ||
await createData( | ||
// Note that this created data path must be in sync with | ||
// metadataPath provided to mdx-loader. | ||
`${(0, utils_1.docuHash)(item.source)}.json`, JSON.stringify(item, null, 2)); | ||
return { | ||
path: item.permalink, | ||
component: "@theme/MarkdownItem", | ||
exact: true, | ||
modules: { | ||
content: item.source, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
} | ||
}); | ||
@@ -88,2 +138,3 @@ // Important: the layout component should not end with /, | ||
const basePath = apiBaseRoute === "/" ? "" : apiBaseRoute; | ||
// Generates a new root route using the first api item. | ||
async function rootRoute() { | ||
@@ -103,5 +154,7 @@ const item = loadedApi[0]; | ||
} | ||
// Whether we already have a document whose permalink falls on the root route. | ||
const hasRootRoute = (await Promise.all(promises)).find((d) => (0, utils_1.normalizeUrl)([d.path, "/"]) === (0, utils_1.normalizeUrl)([basePath, "/"])); | ||
const routes = (await Promise.all([ | ||
...promises, | ||
rootRoute(), | ||
...(hasRootRoute ? [] : [rootRoute()]), | ||
])); | ||
@@ -136,3 +189,3 @@ const apiBaseMetadataPath = await createData(`${(0, utils_1.docuHash)(`api-metadata-prop`)}.json`, JSON.stringify({ | ||
test: /(\.mdx?)$/, | ||
include: [dataDir].map(utils_1.addTrailingPathSeparator), | ||
include: [dataDir, contentPath].map(utils_1.addTrailingPathSeparator), | ||
use: [ | ||
@@ -147,4 +200,14 @@ getJSLoader({ isServer }), | ||
beforeDefaultRemarkPlugins, | ||
// Docusaurus 2.2.0 has a regression that requires this option to be set. | ||
markdownConfig: {}, | ||
metadataPath: (mdxPath) => { | ||
return mdxPath.replace(/(-content\.mdx?)$/, ".json"); | ||
if (mdxPath.startsWith(dataDir)) { | ||
// The MDX file already lives in `dataDir`: this is an OpenAPI MDX | ||
return mdxPath.replace(/(-content\.mdx?)$/, ".json"); | ||
} | ||
else { | ||
// Standard resolution | ||
const aliasedSource = (0, utils_1.aliasedSitePath)(mdxPath, siteDir); | ||
return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedSource)}.json`); | ||
} | ||
}, | ||
@@ -151,0 +214,0 @@ }, |
@@ -151,3 +151,3 @@ "use strict"; | ||
const apiItem = items.find((item) => { | ||
if (item.type === "info") { | ||
if (item.type === "info" || item.type === "mdx") { | ||
return false; | ||
@@ -184,2 +184,6 @@ } | ||
}); | ||
// Explicitly look for _spec_ files, which are excluded by default since they start with _ | ||
allFiles.push(...(await (0, utils_1.Globby)(["**/_spec_.{json,yaml,yml}"], { | ||
cwd: openapiPath, | ||
}))); | ||
const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude? | ||
@@ -186,0 +190,0 @@ return Promise.all(sources.map(async (source) => { |
import type { DeepPartial } from "utility-types"; | ||
import type { InfoPageMetadata, PropSidebar } from "../types"; | ||
import type { InfoPageMetadata, MdxPageMetadata, PropSidebar } from "../types"; | ||
import { ApiPageMetadata } from "../types"; | ||
@@ -14,4 +14,5 @@ interface Options { | ||
}; | ||
declare type Item = InfoItem | ApiItem; | ||
declare type MdxItem = Pick<MdxPageMetadata, keys | "frontMatter">; | ||
declare type Item = InfoItem | ApiItem | MdxItem; | ||
export declare function generateSidebar(items: Item[], options: Options): Promise<PropSidebar>; | ||
export {}; |
@@ -21,2 +21,3 @@ "use strict"; | ||
const CategoryMetadataFilenameBase = "_category_"; | ||
const BottomSidebarPosition = 999999; | ||
function isApiItem(item) { | ||
@@ -28,2 +29,5 @@ return item.type === "api"; | ||
} | ||
function isMdxItem(item) { | ||
return item.type === "mdx"; | ||
} | ||
const Terminator = "."; // a file or folder can never be "." | ||
@@ -39,3 +43,3 @@ const BreadcrumbSeparator = "/"; | ||
async function generateSidebar(items, options) { | ||
var _a, _b, _c, _d, _e; | ||
var _a, _b, _c, _d, _e, _f; | ||
const sourceGroups = (0, lodash_1.groupBy)(items, (item) => item.source); | ||
@@ -56,13 +60,23 @@ let sidebar = []; | ||
if (crumb === Terminator) { | ||
const title = (_b = (_a = items.filter(isApiItem)[0]) === null || _a === void 0 ? void 0 : _a.api.info) === null || _b === void 0 ? void 0 : _b.title; | ||
const fileName = path_1.default.basename(source, path_1.default.extname(source)); | ||
// Title could be an empty string so `??` won't work here. | ||
const label = !title ? fileName : title; | ||
visiting.push({ | ||
type: "category", | ||
label, | ||
collapsible: options.sidebarCollapsible, | ||
collapsed: options.sidebarCollapsed, | ||
items: groupByTags(items, options), | ||
}); | ||
if (isMdxItem(items[0])) { | ||
visiting.push(...groupByTags(items, options)); | ||
} | ||
else if (["_spec_.json", "_spec_.yml", "_spec_.yaml"].includes(path_1.default.basename(items[0].source))) { | ||
// Don't create a category for this spec file | ||
visiting.push(...groupByTags(items, options)); | ||
} | ||
else { | ||
const title = (_b = (_a = items.filter(isApiItem)[0]) === null || _a === void 0 ? void 0 : _a.api.info) === null || _b === void 0 ? void 0 : _b.title; | ||
const fileName = path_1.default.basename(source, path_1.default.extname(source)); | ||
// Title could be an empty string so `??` won't work here. | ||
const label = !title ? fileName : title; | ||
visiting.push({ | ||
type: "category", | ||
label, | ||
position: BottomSidebarPosition, | ||
collapsible: options.sidebarCollapsible, | ||
collapsed: options.sidebarCollapsed, | ||
items: groupByTags(items, options), | ||
}); | ||
} | ||
visiting = sidebar; // reset | ||
@@ -90,6 +104,6 @@ break; | ||
customProps: meta === null || meta === void 0 ? void 0 : meta.customProps, | ||
position: meta === null || meta === void 0 ? void 0 : meta.position, | ||
position: (_d = meta === null || meta === void 0 ? void 0 : meta.position) !== null && _d !== void 0 ? _d : BottomSidebarPosition, | ||
label, | ||
collapsible: (_d = meta === null || meta === void 0 ? void 0 : meta.collapsible) !== null && _d !== void 0 ? _d : options.sidebarCollapsible, | ||
collapsed: (_e = meta === null || meta === void 0 ? void 0 : meta.collapsed) !== null && _e !== void 0 ? _e : options.sidebarCollapsed, | ||
collapsible: (_e = meta === null || meta === void 0 ? void 0 : meta.collapsible) !== null && _e !== void 0 ? _e : options.sidebarCollapsible, | ||
collapsed: (_f = meta === null || meta === void 0 ? void 0 : meta.collapsed) !== null && _f !== void 0 ? _f : options.sidebarCollapsed, | ||
items: [], | ||
@@ -115,2 +129,3 @@ }; | ||
} | ||
sidebar = recursiveSidebarSort(sidebar); | ||
return sidebar; | ||
@@ -120,11 +135,34 @@ } | ||
/** | ||
* Sort the sidebar recursively based on `position`. | ||
* @param sidebar | ||
* @returns | ||
*/ | ||
function recursiveSidebarSort(sidebar) { | ||
// Use lodash sortBy to ensure sorting stability | ||
sidebar = (0, lodash_1.sortBy)(sidebar, (item) => item.position); | ||
for (const item of sidebar) { | ||
if (item.type === "category") { | ||
item.items = recursiveSidebarSort(item.items); | ||
} | ||
} | ||
return sidebar; | ||
} | ||
/** | ||
* Takes a flat list of pages and groups them into categories based on there tags. | ||
*/ | ||
function groupByTags(items, options) { | ||
const intros = items.filter(isInfoItem).map((item) => { | ||
const intros = items | ||
.filter((m) => isInfoItem(m) || isMdxItem(m)) | ||
.map((item) => { | ||
var _a, _b; | ||
const fileName = path_1.default.basename(item.source, path_1.default.extname(item.source)); | ||
const label = !item.title ? fileName : item.title; | ||
return { | ||
type: "link", | ||
label: item.title, | ||
label: label, | ||
href: item.permalink, | ||
docId: item.id, | ||
position: isMdxItem(item) | ||
? (_b = (_a = item.frontMatter) === null || _a === void 0 ? void 0 : _a.sidebar_position) !== null && _b !== void 0 ? _b : BottomSidebarPosition | ||
: BottomSidebarPosition, | ||
}; | ||
@@ -146,2 +184,3 @@ }); | ||
}, item.api.method), | ||
position: BottomSidebarPosition, | ||
}; | ||
@@ -159,2 +198,3 @@ } | ||
.map(createLink), | ||
position: BottomSidebarPosition, | ||
}; | ||
@@ -172,2 +212,3 @@ }) | ||
.map(createLink), | ||
position: BottomSidebarPosition, | ||
}, | ||
@@ -174,0 +215,0 @@ ]; |
@@ -21,3 +21,3 @@ import type { MDXOptions } from "@docusaurus/mdx-loader"; | ||
} | ||
export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata; | ||
export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | MdxPageMetadata; | ||
export interface ApiMetadataBase { | ||
@@ -56,2 +56,5 @@ sidebar?: string; | ||
} | ||
export interface MdxPageMetadata extends ApiMetadataBase { | ||
type: "mdx"; | ||
} | ||
export declare type ApiInfo = InfoObject; | ||
@@ -58,0 +61,0 @@ export interface ApiNavLink { |
{ | ||
"name": "docusaurus-plugin-openapi", | ||
"description": "OpenAPI plugin for Docusaurus.", | ||
"version": "0.6.2", | ||
"version": "0.6.3", | ||
"license": "MIT", | ||
@@ -59,3 +59,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "30ba78a7390da009efc8db4321e532f23b95d380" | ||
"gitHead": "e37b579105a2e6dd34fdb87b5973172e32763e75" | ||
} |
128
src/index.ts
@@ -22,10 +22,14 @@ /* ============================================================================ | ||
posixPath, | ||
aliasedSitePath, | ||
Globby, | ||
} from "@docusaurus/utils"; | ||
import chalk from "chalk"; | ||
import fs from "fs-extra"; | ||
import { Configuration } from "webpack"; | ||
import { DocEnv, processDocMetadata } from "./docs/docs"; | ||
import { createApiPageMD, createInfoPageMD } from "./markdown"; | ||
import { readOpenapiFiles, processOpenapiFiles } from "./openapi"; | ||
import { generateSidebar } from "./sidebars"; | ||
import type { PluginOptions, LoadedContent } from "./types"; | ||
import type { PluginOptions, LoadedContent, MdxPageMetadata } from "./types"; | ||
import { isURL } from "./util"; | ||
@@ -37,3 +41,3 @@ | ||
): Plugin<LoadedContent> { | ||
const { baseUrl, generatedFilesDir } = context; | ||
const { baseUrl, generatedFilesDir, siteDir } = context; | ||
@@ -67,2 +71,26 @@ const pluginId = options.id ?? DEFAULT_PLUGIN_ID; | ||
async function toMetadata( | ||
/** E.g. "api/plugins/myDoc.mdx" */ | ||
relativeSource: string | ||
): Promise<MdxPageMetadata> { | ||
const source = path.join(contentPath, relativeSource); | ||
const content = await fs.readFile(source, "utf-8"); | ||
return { | ||
type: "mdx", | ||
...(await processDocMetadata({ | ||
docFile: { | ||
contentPath: contentPath, | ||
filePath: source, | ||
source: relativeSource, | ||
content: content, | ||
}, | ||
relativeSource: relativeSource, | ||
context: context, | ||
options: options, | ||
env: process.env.NODE_ENV as DocEnv, | ||
})), | ||
}; | ||
} | ||
try { | ||
@@ -75,2 +103,19 @@ const openapiFiles = await readOpenapiFiles(contentPath, {}); | ||
}); | ||
const pagesFiles: string[] = []; | ||
if ( | ||
!contentPath.endsWith(".json") && | ||
!contentPath.endsWith(".yaml") && | ||
!contentPath.endsWith(".yml") | ||
) { | ||
pagesFiles.push( | ||
...(await Globby(["**/*.{md,mdx}"], { | ||
cwd: contentPath, | ||
// ignore: options.exclude, // TODO | ||
})) | ||
); | ||
} | ||
loadedApi.push(...(await Promise.all(pagesFiles.map(toMetadata)))); | ||
return { loadedApi }; | ||
@@ -105,21 +150,40 @@ } catch (e) { | ||
await createData( | ||
`${docuHash(pageId)}.json`, | ||
JSON.stringify(item, null, 2) | ||
); | ||
if (item.type === "api" || item.type === "info") { | ||
await createData( | ||
`${docuHash(pageId)}.json`, | ||
JSON.stringify(item, null, 2) | ||
); | ||
// TODO: "-content" should be inside hash to prevent name too long errors. | ||
const markdown = await createData( | ||
`${docuHash(pageId)}-content.mdx`, | ||
item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item) | ||
); | ||
return { | ||
path: item.permalink, | ||
component: apiItemComponent, | ||
exact: true, | ||
modules: { | ||
content: markdown, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
// TODO: "-content" should be inside hash to prevent name too long errors. | ||
const markdown = await createData( | ||
`${docuHash(pageId)}-content.mdx`, | ||
item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item) | ||
); | ||
return { | ||
path: item.permalink, | ||
component: apiItemComponent, | ||
exact: true, | ||
modules: { | ||
content: markdown, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
} else { | ||
await createData( | ||
// Note that this created data path must be in sync with | ||
// metadataPath provided to mdx-loader. | ||
`${docuHash(item.source)}.json`, | ||
JSON.stringify(item, null, 2) | ||
); | ||
return { | ||
path: item.permalink, | ||
component: "@theme/MarkdownItem", // "@theme/DocItem" | ||
exact: true, | ||
modules: { | ||
content: item.source, | ||
}, | ||
sidebar: sidebarName, | ||
}; | ||
} | ||
}); | ||
@@ -133,2 +197,3 @@ | ||
// Generates a new root route using the first api item. | ||
async function rootRoute() { | ||
@@ -150,5 +215,10 @@ const item = loadedApi[0]; | ||
// Whether we already have a document whose permalink falls on the root route. | ||
const hasRootRoute = (await Promise.all(promises)).find( | ||
(d) => normalizeUrl([d.path, "/"]) === normalizeUrl([basePath, "/"]) | ||
); | ||
const routes = (await Promise.all([ | ||
...promises, | ||
rootRoute(), | ||
...(hasRootRoute ? [] : [rootRoute()]), | ||
])) as RouteConfig[]; | ||
@@ -204,3 +274,3 @@ | ||
test: /(\.mdx?)$/, | ||
include: [dataDir].map(addTrailingPathSeparator), | ||
include: [dataDir, contentPath].map(addTrailingPathSeparator), | ||
use: [ | ||
@@ -215,4 +285,16 @@ getJSLoader({ isServer }), | ||
beforeDefaultRemarkPlugins, | ||
// Docusaurus 2.2.0 has a regression that requires this option to be set. | ||
markdownConfig: {}, | ||
metadataPath: (mdxPath: string) => { | ||
return mdxPath.replace(/(-content\.mdx?)$/, ".json"); | ||
if (mdxPath.startsWith(dataDir)) { | ||
// The MDX file already lives in `dataDir`: this is an OpenAPI MDX | ||
return mdxPath.replace(/(-content\.mdx?)$/, ".json"); | ||
} else { | ||
// Standard resolution | ||
const aliasedSource = aliasedSitePath(mdxPath, siteDir); | ||
return path.join( | ||
dataDir, | ||
`${docuHash(aliasedSource)}.json` | ||
); | ||
} | ||
}, | ||
@@ -219,0 +301,0 @@ }, |
@@ -193,3 +193,3 @@ /* ============================================================================ | ||
const apiItem = items.find((item) => { | ||
if (item.type === "info") { | ||
if (item.type === "info" || item.type === "mdx") { | ||
return false; | ||
@@ -244,2 +244,10 @@ } | ||
}); | ||
// Explicitly look for _spec_ files, which are excluded by default since they start with _ | ||
allFiles.push( | ||
...(await Globby(["**/_spec_.{json,yaml,yml}"], { | ||
cwd: openapiPath, | ||
})) | ||
); | ||
const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude? | ||
@@ -246,0 +254,0 @@ return Promise.all( |
@@ -16,2 +16,3 @@ /* ============================================================================ | ||
customProps?: Record<string, unknown>; | ||
position?: number; | ||
}; | ||
@@ -123,1 +124,13 @@ | ||
} | ||
declare module "@theme/MarkdownItem" { | ||
export interface Props { | ||
readonly content: { | ||
readonly metadata: Metadata; | ||
(): JSX.Element; | ||
}; | ||
} | ||
const MarkdownItem: (props: Props) => JSX.Element; | ||
export default MarkdownItem; | ||
} |
@@ -16,3 +16,3 @@ /* ============================================================================ | ||
import Yaml from "js-yaml"; | ||
import { groupBy, uniq } from "lodash"; | ||
import { groupBy, sortBy, uniq } from "lodash"; | ||
import type { DeepPartial } from "utility-types"; | ||
@@ -22,3 +22,5 @@ | ||
InfoPageMetadata, | ||
MdxPageMetadata, | ||
PropSidebar, | ||
PropSidebarItem, | ||
PropSidebarItemCategory, | ||
@@ -40,7 +42,10 @@ } from "../types"; | ||
}; | ||
type MdxItem = Pick<MdxPageMetadata, keys | "frontMatter">; | ||
type Item = InfoItem | ApiItem; | ||
type Item = InfoItem | ApiItem | MdxItem; | ||
const CategoryMetadataFilenameBase = "_category_"; | ||
const BottomSidebarPosition = 999999; | ||
function isApiItem(item: Item): item is ApiItem { | ||
@@ -54,2 +59,6 @@ return item.type === "api"; | ||
function isMdxItem(item: Item): item is MdxItem { | ||
return item.type === "mdx"; | ||
} | ||
const Terminator = "."; // a file or folder can never be "." | ||
@@ -88,13 +97,26 @@ const BreadcrumbSeparator = "/"; | ||
if (crumb === Terminator) { | ||
const title = items.filter(isApiItem)[0]?.api.info?.title; | ||
const fileName = path.basename(source, path.extname(source)); | ||
// Title could be an empty string so `??` won't work here. | ||
const label = !title ? fileName : title; | ||
visiting.push({ | ||
type: "category" as const, | ||
label, | ||
collapsible: options.sidebarCollapsible, | ||
collapsed: options.sidebarCollapsed, | ||
items: groupByTags(items, options), | ||
}); | ||
if (isMdxItem(items[0])) { | ||
visiting.push(...groupByTags(items, options)); | ||
} else if ( | ||
["_spec_.json", "_spec_.yml", "_spec_.yaml"].includes( | ||
path.basename(items[0].source) | ||
) | ||
) { | ||
// Don't create a category for this spec file | ||
visiting.push(...groupByTags(items, options)); | ||
} else { | ||
const title = items.filter(isApiItem)[0]?.api.info?.title; | ||
const fileName = path.basename(source, path.extname(source)); | ||
// Title could be an empty string so `??` won't work here. | ||
const label = !title ? fileName : title; | ||
visiting.push({ | ||
type: "category" as const, | ||
label, | ||
position: BottomSidebarPosition, | ||
collapsible: options.sidebarCollapsible, | ||
collapsed: options.sidebarCollapsed, | ||
items: groupByTags(items, options), | ||
}); | ||
} | ||
visiting = sidebar; // reset | ||
@@ -126,3 +148,3 @@ break; | ||
customProps: meta?.customProps, | ||
position: meta?.position, | ||
position: meta?.position ?? BottomSidebarPosition, | ||
label, | ||
@@ -156,2 +178,4 @@ collapsible: meta?.collapsible ?? options.sidebarCollapsible, | ||
sidebar = recursiveSidebarSort(sidebar); | ||
return sidebar; | ||
@@ -161,14 +185,39 @@ } | ||
/** | ||
* Sort the sidebar recursively based on `position`. | ||
* @param sidebar | ||
* @returns | ||
*/ | ||
function recursiveSidebarSort(sidebar: PropSidebar | PropSidebarItem[]) { | ||
// Use lodash sortBy to ensure sorting stability | ||
sidebar = sortBy(sidebar, (item) => item.position); | ||
for (const item of sidebar) { | ||
if (item.type === "category") { | ||
item.items = recursiveSidebarSort(item.items); | ||
} | ||
} | ||
return sidebar; | ||
} | ||
/** | ||
* Takes a flat list of pages and groups them into categories based on there tags. | ||
*/ | ||
function groupByTags(items: Item[], options: Options): PropSidebar { | ||
const intros = items.filter(isInfoItem).map((item) => { | ||
return { | ||
type: "link" as const, | ||
label: item.title, | ||
href: item.permalink, | ||
docId: item.id, | ||
}; | ||
}); | ||
const intros = items | ||
.filter((m) => isInfoItem(m) || isMdxItem(m)) | ||
.map((item) => { | ||
const fileName = path.basename(item.source, path.extname(item.source)); | ||
const label = !item.title ? fileName : item.title; | ||
return { | ||
type: "link" as const, | ||
label: label, | ||
href: item.permalink, | ||
docId: item.id, | ||
position: isMdxItem(item) | ||
? (item.frontMatter?.sidebar_position as number) ?? | ||
BottomSidebarPosition | ||
: BottomSidebarPosition, | ||
}; | ||
}); | ||
const apiItems = items.filter(isApiItem); | ||
@@ -195,2 +244,3 @@ | ||
), | ||
position: BottomSidebarPosition, | ||
}; | ||
@@ -209,2 +259,3 @@ } | ||
.map(createLink), | ||
position: BottomSidebarPosition, | ||
}; | ||
@@ -223,2 +274,3 @@ }) | ||
.map(createLink), | ||
position: BottomSidebarPosition, | ||
}, | ||
@@ -225,0 +277,0 @@ ]; |
@@ -44,3 +44,3 @@ /* ============================================================================ | ||
export type ApiMetadata = ApiPageMetadata | InfoPageMetadata; | ||
export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | MdxPageMetadata; | ||
@@ -85,2 +85,6 @@ export interface ApiMetadataBase { | ||
export interface MdxPageMetadata extends ApiMetadataBase { | ||
type: "mdx"; | ||
} | ||
export type ApiInfo = InfoObject; | ||
@@ -87,0 +91,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
246476
95
6477
4